Wednesday, 20 December, 2017 UTC


Summary

In a recent blog, we showed off some examples of Angular pipes we use in our code base. Now, we’d like to talk about directives. Angular directives allow you to attach behavior to elements in the DOM and reuse it across your project. The framework already comes with a number of convenient directives like NgStyle, NgIf, NgFor, and NgSwitch.
We’ve written over 65 custom directives so far and would like to share the top five that we think you’ll find useful in your project. Just note that the directives in the demos below were intentionally distilled to bare minimum for clarity and don’t have extensive customization options or destructors/cleanup code. Use them as a starting point.
#1 Callout
Virtually no application goes without callouts or tooltips, which are small popup elements providing more detail about their owner.
Here is a minimal example of a callout directive that dynamically creates a callout component with specified text when you hover over an element:
The CalloutComponent used in this demo is created on the fly using viewContainer.createComponent() and destroyed when the mouse leaves the element. Here we insert the callout immediately after the element that the directive is applied to (this is where the injected ViewContainerRef points to). For more elaborate configuration options, see our earlier article on building Angular components on the fly.
#2 Deep disabled
In larger applications, it’s not uncommon to disable entire groups of UI elements as the state changes. For example, the Lucidchart editor dynamically enables and disables certain tool groups depending on what shapes are currently selected:
Fragment of the Lucidchart editor. Line options are disabled because a block is selected.
Instead of repeating the expression for disabled state on each individual component, we can make a special disabled directive that will cascade down the disabled state from one of the parent components.
Let’s prototype this:
Most of the “magic” here is actually done by Angular’s dependency injection framework:
class DisabledDirective {
    constructor(
        @SkipSelf() @Optional() private optParent: DisabledDirective
    ) {...}
}
This line in the constructor of the directive means “inject the nearest parent DisabledDirective, skipping myself, if there is one.” Now, during the change detection cycle, we will check not only the boolean value of the current directive but the parent too:
newValue = !!this.appDisabled || this.optParent.disabled;
After implementing the directive, we update the code of our components like form fields and buttons to be aware of it (again using the dependency injection mechanism).
@Component({
  selector: 'app-button',
  template: `
        <button [disabled]="disabled"><ng-content></ng-content></button>
  `
})
export class ButtonComponent {
    private disabledDirective: DisabledDirective;
    
    constructor(changeDetector: ChangeDetectorRef, @Optional() optDisabled: DisabledDirective) {
        this.disabledDirective = resolve(optDisabled);
        this.disabledDirective.onChange(this, (newValue) => {
            changeDetector.markForCheck();
        });
    }
    
    get disabled(): boolean {
        return this.disabledDirective.disabled;
    }
}
While this directive is quite simple, it reduces code duplication and allows us to toggle areas of the UI on multiple levels without any explicit communication between components.
We also added the ability to disable the cascading of the disabled state on select elements (like the Fullscreen button in the demo above) via the disabledStopPropagation attribute.
#3 animatedIf
*ngIf is perhaps one of the most widely used built-in Angular directives. But what if we want to use animation to toggle components in our app? The following directive extends ngIf to support
The directive simply toggles the showing and hiding classes on the container element and  assumes that the animation is done via CSS which provides great flexibility—you don’t have to touch the directive code to change animation.
private hide() {
    let container = this.getContainer();
    if (!!container) {
        container.classList.remove('showing');
        container.classList.add('hiding');

        animationEndSafe(container, 1000).then(() => {
            this.ngIf = this.visible;
            container.classList.remove('hiding');
        });

        this.animatedIfOnHide.emit();
    }
}
The demo implements a simple fade animation, but it can easily be tweaked to be slide or grow/shrink or any combination of them. The animationEndSafe function is simply a wrapper around a listener to the animationend event that calls the callback after a specified timeout if the event hasn’t fired. This is to ensure that the code doesn’t get “stuck” in case the container element doesn’t have any animation defined on it.
#4 Window resize thresholds
CSS3 Media Queries (technically called media features) greatly simplified responsive design for web developers, allowing us to alter page layout based on features like screen size, orientation, and pixel density. In Angular world, however, a significant part of the app’s UI rendering is taken over by the framework.
The following directive lets you define a series of window width “breakpoints” and alter the template when transitions between the thresholds happen.
The directive listens to Window’s resize event through the convenient host binding:
@Directive({
    selector: '[appWindowResize]',
    host: {
        '(window:resize)': 'onResize()',
    }
})
The only other technicality worth mentioning is that whenever you’re listening to DOM events that may fire frequently like resize or mouse movement, make sure to debounce your event handler so that it doesn’t execute an excessive number of times, creating unnecessary CPU load. Many third party libraries contain a debounce function, but we included our implementation in the demo:
// Callback debounce utility
function debounce<F extends(...args: any[]) => void>(f: F, timeout: number, target?: any): F {
    let timer: number|undefined = undefined;
    return (function(this: any, ...args: any[]) {
               target = target || this;

               timer && clearTimeout(timer);
               timer = setTimeout(() => {
                   f.apply(target, args);
               }, timeout);
           }) as F;
}

private onResize = debounce(() => {
    const offsetWidth = this.getWidth();
    ...
}, 200);
#5 focus
There is a native autofocus attribute in HTML5 spec for automatically focusing form fields upon page load. For example, this provides developers with an easy way to focus the login form on a page, saving the visitor the time to click on an input field before they can start typing in their credentials. However, the attribute won’t work in an Angular app where the framework builds the DOM dynamically. This directive is the Angular equivalent of the autofocus attribute.

What are the directives you use often in your projects? Share in the comments.
The post 5 Angular directives you can use in your project appeared first on Lucidchart.