Sign In Start Free Trial
Account

Add to playlist

Create a Playlist

Modal Close icon
You need to login to use this feature.
  • Book Overview & Buying Angular Cookbook
  • Table Of Contents Toc
  • Feedback & Rating feedback
Angular Cookbook

Angular Cookbook

By : Muhammad Ahsan Ayaz
4 (13)
close
close
Angular Cookbook

Angular Cookbook

4 (13)
By: Muhammad Ahsan Ayaz

Overview of this book

Angular has long been the framework of choice for web development projects of various scales, offering much-needed stability and a rich tooling ecosystem for building production-ready web and mobile apps. This recipe-based guide ensures high performance apps with the latest version of Angular, helping you to build up your Angular expertise with a wide range of recipes across key tasks in web development. In this second edition, the recipes have been updated, added, and improved based on developer feedback, new challenges, and Angular 17. The first few chapters will show you how to utilize core Angular concepts such as components, directives, and services to get you ready for building frontend web apps. You’ll then develop web components with Angular and go on to learn about advanced concepts such as dynamic components loading and state management with NgRx for achieving real-time performance. Later chapters will focus on recipes for effectively testing your Angular apps to make them fail-safe, before progressing to techniques for optimizing your app’s performance. Finally, you’ll create Progressive Web Apps (PWA) with Angular to provide an intuitive experience for users. By the end of this book, you’ll be able to create full-fledged, professional-looking Angular apps and have the skills you need for frontend development.
Table of Contents (16 chapters)
close
close
14
Other Books You May Enjoy
15
Index

Writing your first custom structural directive

In this recipe, you’ll write your first custom structural directive named showFor (or *appShowFor with the prefix). A structural directive is one that can add or remove elements from the DOM. So, with this directive, we will add the particular element to the DOM if a provided Boolean is true, and we will remove it after the specified time (provided as a number representing milliseconds).

Getting ready

The app that we are going to work with resides in start/apps/chapter02/ng-show-for-directive 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-show-for-directive
    

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

    Figure 2.7: ng-show-for-directive app running on http://localhost:4200

How to do it…

  1. First of all, we’ll create a directive using the following command in the workspace root folder:
    cd start && nx g directive show-for --directory apps/chapter02/ng-show-for-directive/src/app/directives --standalone=false
    

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

  1. Now, instead of the *ngIf directive in the app.component.html file on the element with the class "dialog", we can use our *appShowFor directive:
    ...
    <main class="content" role="main">
      <button (click)="toggleDialog()">Toggle Dialog</button>
      <div class="dialog" *appShowFor="showDialog">
        <div class="dialog__heading">...</div>
        <div class="dialog__body">...</div>
      </div>
    </main>
    
  2. Now that we have set the condition, we need to create two @Input properties inside the directive’s TypeScript file, one being a boolean property and one being a number. We’ll use a setter to intercept the Boolean value’s changes and will log the value to the console for now:
    import { Directive, Input } from '@angular/core';
    @Directive({
      selector: '[appShowFor]',
    })
    export class ShowForDirective {
      @Input() duration = 1500;
      @Input() set appShowFor(value: boolean) {
        console.log({ showForValue: value });
      }
    }
    
  3. If you tap on the Toggle Dialog button now, you should see the values being changed and reflected on the console, as follows:

Figure 2.8: Console logs displaying changes for the appShowFor directive values

  1. Now, we’re moving toward the actual implementation of showing and hiding the content based on the value being false and true respectively. For that, we first need the TemplateRef service and the ViewContainerRef service injected into the constructor of the if-not.directive.ts file. Let’s add these, as follows:
    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    @Directive({
      selector: '[appShowFor]'
    })
    export class ShowForDirective{
      @Input() duration = 1500;
      @Input() set appShowFor(value: boolean) {
         console.log({ showForValue: value });
       }
       constructor(
         private templateRef: TemplateRef<any>,
         private viewContainerRef: ViewContainerRef
       ) {}
    }
    
  2. Now let’s show the element. We’re going to create a show method and we’ll call it when the value of the appShowFor property becomes true. The code should look as follows:
    ...
    export class ShowForDirective {
      @Input() duration = 1500;
      @Input() set appShowFor(value: boolean) {
        console.log({ showForValue: value });
        if (value) {
          this.show();
        }
      }
      show() {
        this.viewContainerRef.createEmbeddedView(
         this.templateRef
        );
      }
      constructor(...) {}
    }
    

    If you click the Toggle Dialog button now, you should be able to see the dialog as follows:

    Figure 2.9: Dialog being shown using the show method

  1. Let’s implement the logic of hiding the dialog. We’ll use an @Output() prop with an EventEmitter for this as we want the value of appShowFor that’s passed by the parent to be updated, instead of updating it within the directive. Modify the code as follows:
    import { ... , EventEmitter } from '@angular/core';
    ...
    export class ShowForDirective {
      @Input() duration = 1500;
      @Input() set appShowFor(value: boolean) {
        ...
      }
      @Output() elementHidden = new EventEmitter();
      show() {...}
      hide() {
        this.viewContainerRef.clear();
      }
      constructor(...) {}
    }
    
  2. Now that we have the hide method there, let’s call it after the duration time saved in the duration property of the directive. This is so the dialog hides after that duration. Modify the code of the show method as follows:
    show() {
      this.viewContainerRef.createEmbeddedView(
       this.templateRef
      );
      setTimeout(() => {
        this.elementHidden.emit();
      }, this.duration);
     }
    

    With this change, you’ll see that nothing happens if you click the Toggle Dialog button after the dialog is shown, i.e., it never gets hidden. For that, we need to listen to the elementHidden event emitter we just created.

  1. Let’s make the app.component.html listen to the elementHidden event listener to change the value of the showDialog property as follows:
    <div class="dialog" *appShowFor="showDialog"
      (elementHidden)="toggleDialog()">
        <div class="dialog__heading">
                I am a Dialog
        </div>
        <div class="dialog__body">
          And this is some random content
        </div>
      </div>
    

    With this change, you’ll notice that it still doesn’t work. Yep! Because we need to call the hide method when the value of showDialog passed as the appShowFor prop is set to false.

  1. Let’s call the hide method in the ShowForDirective (in the appShowFor property’s set method) when the value of appShowFor becomes false as follows:
    @Input() set appShowFor(value: boolean) {
        console.log({ showForValue: value });
        if (value) {
          this.show();
        } else {
          this.hide();
        }
      }
    

    The thing is… this still won’t work because a structural directive in Angular can’t emit values. Or even if it does, the parent element won’t be able to listen to it. The following Stack Overflow question discusses why and links to an open GitHub issue in the Angular repository as well: https://stackoverflow.com/q/44235638.

  1. To make our structural directive work, we need to get rid of the syntactic sugar it comes with. Let’s modify the app.component.html to use the directive in a different (expanded) way, as follows:
    <main class="content" role="main">
      <button (click)="toggleDialog()">Toggle Dialog</button>
      <ng-template [appShowFor]="showDialog"
        (elementHidden)="toggleDialog()">
        <div class="dialog">
          <div class="dialog__heading">
            I am a Dialog
          </div>
          <div class="dialog__body">
            And this is some random content
          </div>
        </div>
      </ng-template>
    </main>
    

    The dialog should be hidden now. Yay! But wait. Try clicking the Toggle Dialog button lots of times quickly. You’ll see that the app goes crazy. That’s because we end up having too many setTimeout functions registered.

  1. Let’s clear the setTimeout if we toggle the dialog to manually hide it. Update the code for the ShowForDirective class as follows:
    ...
    export class ShowForDirective {
      ...
      timer!: ReturnType<typeof setTimeout>;
      show() {
        this.viewContainerRef.createEmbeddedView(
          this.templateRef
        );
        this.timer = setTimeout(() => {
          this.elementHidden.emit();
        }, this.duration);
      }
      hide() {
        clearTimeout(this.timer);
        this.viewContainerRef.clear();
      }
      constructor(...) {}
    }
    

Awesome! You’ll notice that even if you click the Toggle Dialog button fast and too many times, the app behaves correctly.

How it works…

Structural directives in Angular are special for multiple reasons. First, they allow you to manipulate DOM elements—that is, not just showing and hiding but also adding and removing elements entirely from the DOM based on your needs. Moreover, they have the * prefix, which binds to all the magic Angular does behind the scenes. For example, Angular automatically provides the TemplateRef and ViewContainer for working with this directive. As an example, *ngIf and *ngFor are both structural directives that work behind the scenes with the <ng-template> directive containing the content you bind the directive to. They then create the required variables/properties for you in the scope of ng-template. In this recipe, we do the same. We use the TemplateRef service to access the <ng-template> directive that Angular creates for us behind the scenes, containing the host element to which our appShowFor directive is applied. We use the ViewContainerRef service to add the TemplateRef to the DOM via the createEmbeddedView method.

We do this when the value of the appShowFor property becomes true. Notice that we’re intercepting the property appShowFor using a setter. We learned about this in Chapter 1, Winning Components Communication. We then use a setTimeout to automatically notify the parent component that the value passed to the appShowFor property needs to be changed to false. We do this using an @Output() emitter named elementHidden. Notice that we’re not supposed to make it false within the directive. The parent component is supposed to do it and it will automatically reflect in the directive. Our directive is supposed to react to that change and hide (or remove) the TemplateRef from the ViewContainer. You can see that we do this in the hide method using the this.viewContainerRef.clear(); statement. One of the key things to learn from this recipe is that if we use syntactic sugar, i.e., *appShowFor, in the app.component.html, we can’t listen to the elementHidden event emitter. That’s because this is a quirk of Angular - there’s an open issue on GitHub about this (check the See also section). For this to work, we removed the syntactic sugar and expanded the syntax by using a <ng-template> to wrap our dialog’s HTML in step 11. Notice that we just used [appShowFor] to pass the showDialog variable instead of *appShowFor="showDialog". And we are also listening to the elementHidden event on the <ng-template> element itself.

See also

bookmark search playlist download font-size

Change the font size

margin-width

Change margin width

day-mode

Change background colour

Close icon Search
Country selected

Close icon Your notes and bookmarks

Delete Bookmark

Modal Close icon
Are you sure you want to delete it?
Cancel
Yes, Delete

Confirmation

Modal Close icon
claim successful

Buy this book with your credits?

Modal Close icon
Are you sure you want to buy this book with one of your credits?
Close
YES, BUY