Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Angular Cookbook

You're reading from   Angular Cookbook Over 80 actionable recipes every Angular developer should know

Arrow left icon
Product type Paperback
Published in Dec 2023
Publisher Packt
ISBN-13 9781803233444
Length 536 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Muhammad Ahsan Ayaz Muhammad Ahsan Ayaz
Author Profile Icon Muhammad Ahsan Ayaz
Muhammad Ahsan Ayaz
Arrow right icon
View More author details
Toc

Table of Contents (16) Chapters Close

Preface 1. Winning Component Communication 2. Working with Angular Directives and Built-In Control Flow FREE CHAPTER 3. The Magic of Dependency Injection in Angular 4. Understanding Angular Animations 5. Angular and RxJS – Awesomeness Combined 6. Reactive State Management with NgRx 7. Understanding Angular Navigation and Routing 8. Mastering Angular Forms 9. Angular and the Angular CDK 10. Writing Unit Tests in Angular with Jest 11. E2E Tests in Angular with Cypress 12. Performance Optimization in Angular 13. Building PWAs with Angular 14. Other Books You May Enjoy
15. Index

Applying multiple directives to the same element using the Directive Composition API

In this recipe, you’ll use the Directive Composition API to create multiple components and apply directives to them directly for reusability instead of having to apply the directives to each component or create additional elements inside the template of the component to apply the directives.

Getting ready

The app that we are going to work with resides in start/apps/chapter02/ng-directive-comp-api inside the cloned repository:

  1. Open the code repository in your code editor.
  2. Open the terminal, navigate to the code repository directory, and run the following command to serve the project:
    npm run serve ng-directive-comp-api
    

    This should open the app in a new browser tab, and you should see the following:

    Figure 2.13: ng-directive-comp-api app running on http://localhost:4200

How to do it…

  1. First, we’ll create a couple of components for our application. We’ll create one directive for the filled button, one for the outline button, and one for a button with a tooltip. Run the following command from the start folder within the workspace:
    nx g directive button-filled --directory apps/chapter02/ng-directive-comp-api/src/app/directives --standalone=false
    nx g directive button-outlined --directory apps/chapter02/ng-directive-comp-api/src/app/directives --standalone=false
    nx g directive button-with-tooltip --directory apps/chapter02/ng-directive-comp-api/src/app/directives --standalone=false
    

    If asked, choose the @nx/angular:component schematics and choose the “As provided” action.

    Note that all the directives we have created are non-standalone directives. That is because the application is bootstrapped with an NgModule and the AppComponent is not a standalone component. Therefore, we these directives to be imported in the app.module.ts for this recipe to work.

  1. Let’s make the ButtonDirective a standalone directive, which means this isn’t going to be a part of any NgModule. Update the button.directive.ts as follows:
    ...
    @Directive({
      selector: '[appButton]',
      standalone: true,
    })
    export class ButtonDirective {
      ...
    }
    
  2. Let’s also remove it from the app.module.ts file as it is now a standalone directive. Update the app.module.ts file as follows:
    ...
    import { ButtonDirective } from './directives/button.directive'; // <-- remove the import
    ...
    @NgModule({
      declarations: [
        ...,
        ButtonDirective, // <-- remove this
        ...
      ],
      ...
    })
    export class AppModule {}
    

    You’ll notice that none of the buttons have the required styles anymore as follows:

    Figure 2.14: Styles from the button directive are gone

  1. Let’s update the ButtonFilledDirective to use the ButtonDirective using the Directive Composition API. Update the button-filled.directive.ts file as follows:
    import { Directive, HostBinding } from '@angular/core';
    import { ButtonDirective } from './button.directive';
    @Directive({
      selector: '[appButtonFilled]',
      hostDirectives: [
        {
          directive: ButtonDirective,
          inputs: ['color'],
        },
      ],
    })
    export class ButtonFilledDirective {
      @HostBinding('attr.fill')
      fill = 'filled';
    }
    
  2. We can use the appButtonFilled directive in the app.component.html file as follows:
    ...
    <main class="content" role="main">
      <ul class="flex flex-col">
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">...</li>
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">
          <h4 class="text-lg">Filled Button:</h4>
          <button appButtonFilled color="yellow">Click
            Me</button>
        </li>
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">...</li>
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">...</li>
      </ul>
    </main>
    

Notice that we’ve removed the fill attribute from the element.

  1. Let’s update the ButtonOutlined directive as well. We’ll modify the button-outlined.directive.ts as follows:
    import { Directive, HostBinding } from '@angular/core';
    import { ButtonDirective } from './button.directive';
    @Directive({
      selector: '[appButtonOutlined]',
      hostDirectives: [
        {
          directive: ButtonDirective,
          inputs: ['color'],
        },
      ],
    })
    export class ButtonOutlinedDirective {
      @HostBinding('attr.fill')
      fill = 'outlined';
    }
    
  2. Let’s also modify the ButtonWithTooltipDirective class. We’ll update the button-with-tooltip.directive.ts as follows:
    import { Directive } from '@angular/core';
    import { ButtonDirective } from './button.directive';
    import { TooltipDirective } from './tooltip.directive';
    @Directive({
      selector: '[appButtonWithTooltip]',
      hostDirectives: [
        {
          directive: ButtonDirective,
          inputs: ['color', 'fill'],
        },
        {
          directive: TooltipDirective,
          inputs: ['appTooltip: tooltip'],
        },
      ],
    })
    export class ButtonWithTooltipDirective {}
    

    You will notice that the app starts throwing an error that TooltipDirective is not a standalone component. That’s true. We need to do the same thing we did for the ButtonDirective in step 2 and step 3 for the TooltipDirective as well. Move on to the next step once you’ve done that.

  1. Now, update the app.component.html file to use both the appButtonOutlined and appButtonTooltip directives as follows:
    ...
    <main class="content" role="main">
      <ul class="flex flex-col">
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">...</li>
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">...</li>
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">
          <h4 class="text-lg">Outlined Button:</h4>
          <button appButtonOutlined>Click Me</button>
        </li>
        <li class="flex gap-4 items-center border-b justify-
          between border-slate-300 py-3">
          <h4 class="text-lg">Button with Tooltip:</h4>
          <div class="flex flex-col gap-4">
            <button appButtonWithTooltip tooltip="code with
              ahsan" fill="outlined" color="blue">
              Click Me
            </button>
            <button appButtonWithTooltip tooltip="code with
              ahsan" fill="filled" color="blue">
              Click Me
            </button>
          </div>
        </li>
      </ul>
    </main>
    

    If you’ve followed all the steps correctly, you should be able to see the final result as follows:

    Figure 2.15: Final result containing buttons with different directives applied

How it works…

The Directive Composition API was introduced in Angular v15 and has been one of the most requested features from the Angular community. In this recipe, we tried to create some components that bind the directives to the component directly in the component’s TypeScript classes rather than in the template. This eliminates the need to create a wrapper element within the components to then apply the directives or to map the inputs of the components to the inputs of the directives. This also allows multiple directives to be bound to the same component – even if they may have inputs with the same names, we can alias them differently.

The flow of the directives in our application works in the following way:

  • The AppComponent uses the ButtonFilledDirective, ButtonOutlinedDirective, and ButtonWithTooltipDirective directives. For this, these directive need to be non-standalone since the application is bootstrapped with an NgModule
  • ButtonFilledDirective, ButtonOutlinedDirective, and ButtonWithTooltipDirective directives use the directive composition API to use the ButtonDirective and the TooltipDirective. These need to be standalone directives to be used as ‘hostDirectives'

The key to using the Directive Composition API is to construct your base-directives with the standalone: true flag. This means your directives aren’t part of any NgModule and can be imported directly into the imports array of any component they’re being used in. This is why we make both the ButtonDirective and the TooltipDirective standalone in steps 2, 3, and 7. Then, we use those directives in ButtonFilledDirective, ButtonOutlinedDirective, and ButtonWithTooltipDirective to be able to reuse the logic without having to create any wrapper component or additional HTML. We do it using the hostDirectives property in the directive metadata. Notice that we pass an array of objects to this property and each object can contain the directive property, which takes the class of the directive to be applied. And we can also provide inputs and outputs for the host bindings. As you saw for the ButtonWithTooltipDirective, we also aliased the appTooltip input of the TooltipDirective with the tooltip input of the ButtonWithTooltipDirective. One thing to notice is that if you don’t want to map any inputs or outputs and just want to bind a directive in the hostDirectives, you can just provide an array of the classes of the directives to be applied as follows:

hostDirectives: [
  ButtonDirective,
  TooltipDirective
],

See also

Learn more on Discord

To join the Discord community for this book – where you can share feedback, ask questions to the author, and learn about new releases – follow the QR code below:

https://packt.link/AngularCookbook2e

You have been reading a chapter from
Angular Cookbook - Second Edition
Published in: Dec 2023
Publisher: Packt
ISBN-13: 9781803233444
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime