5 Angular directives you can use in your project
Dmitry Pashkevich
Reading time: about 8 min
Topics:
#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 usingviewContainer.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: 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’sresize
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.
About Lucid
Lucid Software is a pioneer and leader in visual collaboration dedicated to helping teams build the future. With its products—Lucidchart, Lucidspark, and Lucidscale—teams are supported from ideation to execution and are empowered to align around a shared vision, clarify complexity, and collaborate visually, no matter where they are. Lucid is proud to serve top businesses around the world, including customers such as Google, GE, and NBC Universal, and 99% of the Fortune 500. Lucid partners with industry leaders, including Google, Atlassian, and Microsoft. Since its founding, Lucid has received numerous awards for its products, business, and workplace culture. For more information, visit lucid.co.