Structural Directive in Angular

Learn via video courses
Topics Covered

Overview

AngularJS comes with a unique feature of directives. Directives do help to extend the HTML vocabulary. Similar thing is carried forward to Angular as well. The only difference between AngularJS vs Angular directive is, Angular directive does not contain a template. Essentially directives attach behavior on the DOM node. Structural Directive is a special kind of directive, it helps to add/remove/replace/ repeat templates on DOM based on the expression supplied to it.

Introduction to Structural Directives

Structural directives help to add or remove templates on DOM conditionally. Angular Framework provides three built-in structural directives like ngFor, ngIf, and ngSwitch. When we use structural directives prefixed with * like *ngFor, *ngIf, or *ngSwitch, angular internally transforms this * into ng-template. And this template is used by the respective directive to add or remove a template inside a DOM tree.

When do We Use Structural Directives?

The structural directive can be used in a situation

  1. Add / Remove DOM template based on expression
  2. Repeat DOM template based on the expression

Structural Directive Shorthand

Shorthandtransformed to
*ngFor="let item of [1,2,3]"<ng-template
    ngFor
    let-item
    [ngForOf]="[1,2,3]">
*ngFor="let item of [1,2,3] as items;
     trackBy: myTrack; index as i"
<ng-template ngFor
    let-item
    [ngForOf]="[1,2,3]"
    let-items="ngForOf"
    [ngForTrackBy]="myTrack"
    let-i="index">
*ngIf="exp"<ng-template [ngIf]="exp">
*ngIf="exp as value"<ng-template [ngIf]="exp"
    let-value="ngIf">

Commonly Used Structural Directives

There are 3 built-in structural directives provided by Angular provides

NgIf

ngIf is the most frequently used angular structural directive. It helps to conditionally add or remove templates.

Syntax

Usage

Above html conditionally displays Logged in as {{ user.name }} when isLoggedIn flag, and not displayed when isLoggedIn is false.

NgFor

ngFor is used for repeat template multiple times as like a for loop we use in ES6. The syntax is quite similar to for of, like for (let item of items)

Syntax

Usage

Given the template on which ngFor directive is applied is repeated the names.length times.

Syntaxdescription
let item of itemsLoop over items
indexValue from context, index of current iteration
trackByCallback returns a value, that should be compared , if mismatch render specific view again.

Output

NGFOR OUTPUT

NgSwitch

ngSwitch directive is the same as switch statement of Angular. Multiple templates can be specified inside a condition with a combination of *ngSwitchWhen (for cases) and *ngDefaultSwitch (for default templates).

Syntax

Usage

Above example demonstrate the usage of ngSwitch directive, like JS switch case switch(type) { case 1: break; default: break; }.

One Structural Directive Per Element

While using a structural directives,

You can not use two structural directives on a single element, that's a limitation. Technically if you think of doing it, it won't possible to achieve.

If you want to apply two structural directives like

You have to move a structural directive on a different level. Perhaps using the ng-container element instead of an extra div makes sense here, as it won't create any element.

Creating Custom Structural Directives

In a big enterprise-grade application, feature flag-driven development is followed. Feature flag development enables us to control features on the distribution level. We can easily enable or disable features by toggling flags on different environments. We've to build a custom structural directive that would display the content only when the specified feature flag is enabled.

Usage

We have defined a FeatureEnabledDirective class

  1. with selector [isFeatureEnabled]
  2. constructor has TemplateRef and ElementRef
  3. console log templateRef and el

When directive code executes it prints below-console log. CUSTOM STRUCTURAL DIRECTIVES OUTPUT

One thing to note here is el is converted to comment. Ideally we would expect to see <div>Test is enabled</div> element. But no it consoled comment. This is the magic of * in front of the directive name. Basically, when we add * (star) in front of the directive, it started working as a structural directive. And complete HTML is wrapped inside ng-template.

The above template would be converted as

Hence if we do TemplateRef inside the constructor, we would receive the ng-template which we can use later to add or remove the directive element.

Thus, we need ViewContainerRef to get hold of the directive element. And then we would define the Input property on the same name as the directive selector isFeatureEnabled.

The isFeatureEnabled setter accepts the input value and clears the viewContainerRef every time at the beginning. Afterward, add the content only if the feature exists in enabledFeatures variable.

Testing the Directive

To locally test the directive behavior we can try testing both +ve and -ve scenarios.

  1. Pass valid value, it should display on view.

  2. Pass invalid value, it should not display on view.

Structural Directive Syntax Reference

Structural directive syntax can be written in multiple formats, below is syntax expression, and how it looks in general.

Syntax

Shorthandtransformed to
prefixHTML attribute
keyHTML attribute
localLocal variable name used on a template
exportThe name with directive instance should be exported
expressionan ordinary expression

Example

How Angular Translates Shorthand

Translating shorthand work has been done by Angular Compiler. Though below are the template translation you can see.

Shorthandtranslates to
prefix and naked expression[prefix]="expression"
keyExp[prefixKey] "expression" (let-prefixKey="export")
letlet-local="export"

Improving Template Type Checking for Custom Directives

It will be an insult to typescript if we declare or use any type. We can make our directive use a type-checking mechanism in an efficient manner. To enforce this typing check, we have to do following things

  • For narrow downing type we can use ngTemplateGuard_(someInputProperty) static property
  • static property ngTemplateContextGuard helps to guard directive template context

Making in-template Type Requirements More Specific with Template Guards

We can narrow down the input property type if union type is used. One out of the union type should be strictly set on the template. Right now for the isFeatureEnabled directive, the Input property is of a type string. If we would use union type, we could strictly narrow down the type on the template

So on template, it would expect the object to be of type B. A, C and D Skelton won't work on UI. It will throw an error.

Typing the Directive’s Context

To make directive context available outside and type-safe, we can use ngTemplateContextGuard static function to achieve it. But before that, we have to create a class that holds the schema of context

The context contains a roles string collection, and isSuperUser getter that checks whether input role is super_user or not.

So far we have our TemplateRef of type any, we would like to make it type safe, right? Let's create a directive context and typesafe the directive. Next task is to set FeatureEnabledDirective context using ngTemplateContextGuard static method.

Let's look at the details of ngTemplateContextGuard method.

  1. directive (FeatureEnabledDirective) as a first parameter
  2. context (unknown) as a second parameter
  3. Return type is set to FeatureEnabledContext (the directive context) using is operator.
  4. return true from the function, that makes sure the directive is typesafe.

From lines 16-23, filter logic is applied to find a matching feature from a collection. If feature is found, roles are filled inside context.

Let's understand what changes are applied.

  1. 'super_user' is a value passed to isFeatureEnabled
  2. roles are taken out of context, and filled in inside let roles
  3. roles have been used on HTML.

If we try *isFeatureEnabled="'super_user'; let roles = role", a typo for role, TS will complain about the property name.

Conclusion

  • How to use built-in directives.
  • Long hand and syntax shorthand.
  • How one can create narrow down types of directive
  • How to expose typed directive context