
Directives in Angular are fundamental building blocks that allow you to add special features to elements in your Angular application. There are two types: structural and attribute directives, which I will elaborate on in this blog post.
Understanding the aspects of these directives is crucial for developers to make more informed decisions when architecting and optimizing their applications. Knowing when and how to use each type of directive can significantly increase the functionality and efficiency of your Angular applications.
Structural Directive: Dom Manipulation
These types of directives are all about changing the structure of the DOM, and the key aspect to understand here is that these directives actually modify the DOM layout, like adding or removing elements (not hiding them using CSS). Here you can see a few examples of built-in directives:
*ngIf– based on a condition, generates or removes part of the DOM.*ngSwitchCase,*ngSwitchDefault– displays DOM elements that adjust based on the value provided byngSwitch.
// Code example of ngSwitch
<container-element [ngSwitch]="switch_expression">
<!-- the same view can be shown in more than one case -->
<some-element *ngSwitchCase="match_expression_1">...</some-element>
<some-element *ngSwitchCase="match_expression_2">...</some-element>
<some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
<!--default case when there are no matches -->
<some-element *ngSwitchDefault>...</some-element>
</container-element>
// https://angular.io/api/common/NgSwitch
Side note: Starting with Angular v17, directives such as *ngIf, *ngFor, and *ngSwitch, are going to be deprecated in favor of new control flow, such as @if, @for, and @switch blocks.
1. Microsyntax and templates
Structural directives can be used in two ways: a shorthand and a longhand. Most of the time, we can see structural directives with an asterisk (*) in front, representing the shorthand form. This shorthand is syntactic sugar for the longer notation, in which Angular wraps the element and its children in an <ng-template> and applies the directive to this template element.
// Code example shorthand
<div *ngIf="hero" class="name">{{hero.name}}</div>
// Code example longhand
<ng-template [ngIf]="hero">
<div class="name">{{hero.name}}</div>
</ng-template>
2. Limitations
If you’re considering using both *ngIf and *ngFor directives on the same HTML element, it might seem like a practical approach. However, this combination is not supported. You cannot apply both *ngIf and *ngFor directives to a single element.
This is because Angular cannot determine which directive should go first. If *ngFor is first, should it cancel the effect of the *ngIf, or should it render that and then hide the result of *ngFor? It’s not possible for Angular to determine this case, and the only way to avoid this issue is to create an additional ng-container, which is an invisible element for the DOM.
3. Implementation
In this section, we are going to create our own structural directive named showWithDelay. As the name suggests, this directive will display its content after a specified delay. Structural directives typically involve two key concepts:
ViewContainerRef.createEmbeddedView(...)– method is used to instantiate the content that needs to be displayed. Essentially, it takes a template reference and renders it as a DOM element.TemplateRef– represents an embedded template that can be used to instantiate embedded views. It holds the template and the context for the embedded view, enabling the directive to control when and how the content is displayed.
@Directive({
selector: '[showWithDelay]',
})
export class ShowWithDelayDirective {
viewContainerRef = inject(ViewContainerRef);
tpl = inject(TemplateRef);
@Input({required: true}) set showWithDelay(val: number) {
setTimeout(() => {
this.viewContainerRef.createEmbeddedView(this.tpl);
}, val)
}
}
Attribute Directive: Changing Appearance and Behavior
In contrast to structural directives, attribute directives do not change the structure of the DOM. Instead, they change the appearance or behavior of an element, typically using the properties and events of the DOM elements. In other words, attribute directives:
- Directly manipulate the DOM’s attributes, properties, and styles. For example,
ngStyleandngClassdirectives change the style and class of DOM elements. - Define behavior in an attribute directive and then apply it to any element in the template.
- Encapsulate complex DOM behavior and reuse it across multiple components.
1. Limitations
Attribute directives have a limited scope, meaning they can only affect the element they are applied to and cannot directly manipulate parent or sibling elements.
2. Implementation
The way we use attribute directives is a bit different from structural ones. With attribute directives, we don’t use ViewContainerRef or TemplateRef. Instead, we work directly with an existing element whose behavior or look we want to change, like its background color, as shown in the example below. Often, we use ElementRef and Renderer2 for attribute directives, but they are not always necessary.
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective implements OnInit {
el = inject(ElementRef);
ngOnInit() {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
}
// https://angular.io/guide/attribute-directives
Summarizing Key Differences and Their Impacts
- Use Cases:
- Structural directives are best suited for managing dynamic content, like creating or destroying elements conditionally.
- Attribute directives are a good fit for adding behavior to existing elements, like a response to user interaction.
- Complexity and Performance:
- Due to their dynamic DOM nature, structural directives can be more complex and impact performance.
- Attribute directives are typically simpler as they only interact with existing elements.
- Custom structural directives are usually harder to implement than attribute directives and can be more complex as they involve understanding APIs like
TemplateRefandViewContainerRefand therefore, their context.
- Syntax:
- Attribute directives are called either with brackets as a property binding or without them as a static element attribute.
- Structural directives can be called in shorthand with an asterisk
(*)prefix or in longhand using brackets within an<ng-template>.
Popular 3rd party directives
Structural Directives:
*ngrxLet– a handy way of binding observables to a view context from the NgRx library.*ngxPermissionsOnly– show elements based on user permissions from the NGX-Permissions library.*cdkVirtualFor– the directive from the Angular CDK library that enables virtual scrolling.
Attribute Directives:
[nzTooltip]– attach a tooltip to an HTML element from NgZorro.[cdkAutoFocus]– automatically focuses the element when it becomes visible from Angular CDK.[ngClass]– the built-in attribute directive that applies or removes classes to the DOM elements.
Conclusion
In advanced Angular development, a deep understanding of the differences between structural and attribute directives is essential. Structural directives are powerful tools for dynamically manipulating the DOM structure, while attribute directives offer a way to increase element behavior and appearance.
By mastering these concepts and their subtle differences, Angular developers can greatly increase the efficiency, performance, and responsiveness of their applications, which, importantly, this knowledge is also beneficial during the interview processes. 🥸
