{ "id": "guide/structural-directives", "title": "Writing structural directives", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n

Writing structural directiveslink

\n

This topic demonstrates how to create a structural directive and provides conceptual information on how directives work, how Angular interprets shorthand, and how to add template guard properties to catch template type errors.

\n
\n

For the example app that this page describes, see the .

\n
\n

For more information on Angular's built-in structural directives, such as NgIf, NgFor, and NgSwitch, see Built-in directives.

\n\n

Creating a structural directivelink

\n

This section guides you through creating an UnlessDirective and how to set condition values.\nThe UnlessDirective does the opposite of NgIf, and condition values can be set to true or false.\nNgIf displays the template content when the condition is true.\nUnlessDirective displays the content when the condition is false.

\n

Following is the UnlessDirective selector, appUnless, applied to the paragraph element.\nWhen condition is true, the browser displays the sentence.

\n\n<p *appUnless=\"condition\">Show this sentence unless the condition is true.</p>\n\n\n
    \n
  1. \n

    Using the Angular CLI, run the following command, where unless is the name of the directive:

    \n\nng generate directive unless\n\n

    Angular creates the directive class and specifies the CSS selector, appUnless, that identifies the directive in a template.

    \n
  2. \n
  3. \n

    Import Input, TemplateRef, and ViewContainerRef.

    \n\nimport { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';\n\n@Directive({ selector: '[appUnless]'})\nexport class UnlessDirective {\n}\n\n\n\n
  4. \n
  5. \n

    Inject TemplateRef and ViewContainerRef in the directive constructor as private variables.

    \n\nconstructor(\n private templateRef: TemplateRef<any>,\n private viewContainer: ViewContainerRef) { }\n\n\n

    The UnlessDirective creates an embedded view from the Angular-generated <ng-template> and inserts that view in a view container adjacent to the directive's original <p> host element.

    \n

    TemplateRef helps you get to the <ng-template> contents and ViewContainerRef accesses the view container.

    \n
  6. \n
  7. \n

    Add an appUnless @Input() property with a setter.

    \n\n@Input() set appUnless(condition: boolean) {\n if (!condition && !this.hasView) {\n this.viewContainer.createEmbeddedView(this.templateRef);\n this.hasView = true;\n } else if (condition && this.hasView) {\n this.viewContainer.clear();\n this.hasView = false;\n }\n}\n\n\n

    Angular sets the appUnless property whenever the value of the condition changes.

    \n
      \n
    • \n

      If the condition is falsy and Angular hasn't created the view previously, the setter causes the view container to create the embedded view from the template.

      \n
    • \n
    • \n

      If the condition is truthy and the view is currently displayed, the setter clears the container, which disposes of the view.

      \n
    • \n
    \n
  8. \n
\n

The complete directive is as follows:

\n\nimport { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';\n\n/**\n * Add the template content to the DOM unless the condition is true.\n */\n@Directive({ selector: '[appUnless]'})\nexport class UnlessDirective {\n private hasView = false;\n\n constructor(\n private templateRef: TemplateRef<any>,\n private viewContainer: ViewContainerRef) { }\n\n @Input() set appUnless(condition: boolean) {\n if (!condition && !this.hasView) {\n this.viewContainer.createEmbeddedView(this.templateRef);\n this.hasView = true;\n } else if (condition && this.hasView) {\n this.viewContainer.clear();\n this.hasView = false;\n }\n }\n}\n\n\n\n

Testing the directivelink

\n

In this section, you'll update your application to test the UnlessDirective.

\n
    \n
  1. \n

    Add a condition set to false in the AppComponent.

    \n\ncondition = false;\n\n\n
  2. \n
  3. \n

    Update the template to use the directive.\nHere, *appUnless is on two <p> tags with opposite condition values, one true and one false.

    \n\n<p *appUnless=\"condition\" class=\"unless a\">\n (A) This paragraph is displayed because the condition is false.\n</p>\n\n<p *appUnless=\"!condition\" class=\"unless b\">\n (B) Although the condition is true,\n this paragraph is displayed because appUnless is set to false.\n</p>\n\n\n

    The asterisk is shorthand that marks appUnless as a structural directive.\nWhen the condition is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears.\nWhen the condition is truthy, the top (A) paragraph disappears and the bottom (B) paragraph appears.

    \n
  4. \n
  5. \n

    To change and display the value of condition in the browser, add markup that displays the status and a button.

    \n\n<p>\n The condition is currently\n <span [ngClass]=\"{ 'a': !condition, 'b': condition, 'unless': true }\">{{condition}}</span>.\n <button\n (click)=\"condition = !condition\"\n [ngClass] = \"{ 'a': condition, 'b': !condition }\" >\n Toggle condition to {{condition ? 'false' : 'true'}}\n </button>\n</p>\n\n\n
  6. \n
\n

To verify that the directive works, click the button to change the value of condition.

\n
\n \"UnlessDirective\n
\n\n\n

Structural directive shorthandlink

\n

The asterisk, *, syntax on a structural directive, such as *ngIf, is shorthand that Angular interprets into a longer form.\nAngular transforms the asterisk in front of a structural directive into an <ng-template> that surrounds the host element and its descendants.

\n

The following is an example of *ngIf that displays the hero's name if hero exists:

\n\n<div *ngIf=\"hero\" class=\"name\">{{hero.name}}</div>\n\n\n

The *ngIf directive moves to the <ng-template> where it becomes a property binding in square brackets, [ngIf].\nThe rest of the <div>, including its class attribute, moves inside the <ng-template>.

\n\n<ng-template [ngIf]=\"hero\">\n <div class=\"name\">{{hero.name}}</div>\n</ng-template>\n\n\n

Angular does not create a real <ng-template> element, instead rendering only the <div> and a comment node placeholder to the DOM.

\n\n<!--bindings={\n \"ng-reflect-ng-if\": \"[object Object]\"\n}-->\n<div _ngcontent-c0>Mr. Nice</div>\n\n

The following example compares the shorthand use of the asterisk in *ngFor with the longhand <ng-template> form:

\n\n<div *ngFor=\"let hero of heroes; let i=index; let odd=odd; trackBy: trackById\" [class.odd]=\"odd\">\n ({{i}}) {{hero.name}}\n</div>\n\n<ng-template ngFor let-hero [ngForOf]=\"heroes\" let-i=\"index\" let-odd=\"odd\" [ngForTrackBy]=\"trackById\">\n <div [class.odd]=\"odd\">({{i}}) {{hero.name}}</div>\n</ng-template>\n\n\n

Here, everything related to the ngFor structural directive applies to the <ng-template>.\nAll other bindings and attributes on the element apply to the <div> element within the <ng-template>.\nOther modifiers on the host element, in addition to the ngFor string, remain in place as the element moves inside the <ng-template>.\nIn this example, the [class.odd]=\"odd\" stays on the <div>.

\n

The let keyword declares a template input variable that you can reference within the template.\nThe input variables in this example are hero, i, and odd.\nThe parser translates let hero, let i, and let odd into variables named let-hero, let-i, and let-odd.\nThe let-i and let-odd variables become let i=index and let odd=odd.\nAngular sets i and odd to the current value of the context's index and odd properties.

\n

The parser applies PascalCase to all directives and prefixes them with the directive's attribute name, such as ngFor.\nFor example, the ngFor input properties, of and trackBy, map to ngForOf and ngForTrackBy.\nAs the NgFor directive loops through the list, it sets and resets properties of its own context object.\nThese properties can include, but aren't limited to, index, odd, and a special property\nnamed $implicit.

\n

Angular sets let-hero to the value of the context's $implicit property, which NgFor has initialized with the hero for the current iteration.

\n

For more information, see the NgFor API and NgForOf API documentation.

\n

Creating template fragments with <ng-template>link

\n

Angular's <ng-template> element defines a template that doesn't render anything by default.\nWith <ng-template>, you can render the content manually for full control over how the content displays.

\n

If there is no structural directive and you wrap some elements in an <ng-template>, those elements disappear.\nIn the following example, Angular does not render the middle \"Hip!\" in the phrase \"Hip! Hip! Hooray!\" because of the surrounding <ng-template>.

\n\n<p>Hip!</p>\n<ng-template>\n <p>Hip!</p>\n</ng-template>\n<p>Hooray!</p>\n\n\n
\n \"template\n
\n

Structural directive syntax referencelink

\n

When you write your own structural directives, use the following syntax:

\n\n*:prefix=\"( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*\"\n\n

The following tables describe each portion of the structural directive grammar:

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
prefixHTML attribute key
keyHTML attribute key
locallocal variable name used in the template
exportvalue exported by the directive under a given name
expressionstandard Angular expression
\n\n \n \n \n \n \n \n \n \n \n \n \n \n
keyExp = :key \":\"? :expression (\"as\" :local)? \";\"?
let = \"let\" :local \"=\" :export \";\"?
as = :export \"as\" :local \";\"?
\n

How Angular translates shorthandlink

\n

Angular translates structural directive shorthand into the normal binding syntax as follows:

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ShorthandTranslation
prefix and naked expression[prefix]=\"expression\"
keyExp[prefixKey] \"expression\"\n (let-prefixKey=\"export\")\n
\n Notice that the prefix\n is added to the key\n
letlet-local=\"export\"
\n

Shorthand exampleslink

\n

The following table provides shorthand examples:

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ShorthandHow Angular interprets the syntax
*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\">\n
*ngIf=\"exp\"<ng-template [ngIf]=\"exp\">
*ngIf=\"exp as value\"<ng-template [ngIf]=\"exp\" let-value=\"ngIf\">
\n\n\n

Improving template type checking for custom directiveslink

\n

You can improve template type checking for custom directives by adding template guard properties to your directive definition.\nThese properties help the Angular template type checker find mistakes in the template at compile time, which can avoid runtime errors.\nThese properties are as follows:

\n\n

This section provides examples of both kinds of type-guard property.\nFor more information, see Template type checking.

\n\n

Making in-template type requirements more specific with template guardslink

\n

A structural directive in a template controls whether that template is rendered at run time, based on its input expression.\nTo help the compiler catch template type errors, you should specify as closely as possible the required type of a directive's input expression when it occurs inside the template.

\n

A type guard function narrows the expected type of an input expression to a subset of types that might be passed to the directive within the template at run time.\nYou can provide such a function to help the type-checker infer the proper type for the expression at compile time.

\n

For example, the NgIf implementation uses type-narrowing to ensure that the\ntemplate is only instantiated if the input expression to *ngIf is truthy.\nTo provide the specific type requirement, the NgIf directive defines a static property ngTemplateGuard_ngIf: 'binding'.\nThe binding value is a special case for a common kind of type-narrowing where the input expression is evaluated in order to satisfy the type requirement.

\n

To provide a more specific type for an input expression to a directive within the template, add an ngTemplateGuard_xx property to the directive, where the suffix to the static property name, xx, is the @Input() field name.\nThe value of the property can be either a general type-narrowing function based on its return type, or the string \"binding\", as in the case of NgIf.

\n

For example, consider the following structural directive that takes the result of a template expression as an input:

\n\nexport type Loaded = { type: 'loaded', data: T };\nexport type Loading = { type: 'loading' };\nexport type LoadingState = Loaded | Loading;\nexport class IfLoadedDirective {\n @Input('ifLoaded') set state(state: LoadingState) {}\n static ngTemplateGuard_state(dir: IfLoadedDirective, expr: LoadingState): expr is Loaded { return true; };\n}\n\nexport interface Person {\n name: string;\n}\n\n@Component({\n template: `<div *ifLoaded=\"state\">{{ state.data }}</div>`,\n})\nexport class AppComponent {\n state: LoadingState;\n}\n\n

In this example, the LoadingState<T> type permits either of two states, Loaded<T> or Loading. The expression used as the directive’s state input is of the umbrella type LoadingState, as it’s unknown what the loading state is at that point.

\n

The IfLoadedDirective definition declares the static field ngTemplateGuard_state, which expresses the narrowing behavior.\nWithin the AppComponent template, the *ifLoaded structural directive should render this template only when state is actually Loaded<Person>.\nThe type guard allows the type checker to infer that the acceptable type of state within the template is a Loaded<T>, and further infer that T must be an instance of Person.

\n\n

Typing the directive's contextlink

\n

If your structural directive provides a context to the instantiated template, you can properly type it inside the template by providing a static ngTemplateContextGuard function.\nThe following snippet shows an example of such a function.

\n\n@Directive({…})\nexport class ExampleDirective {\n // Make sure the template checker knows the type of the context with which the\n // template of this directive will be rendered\n static ngTemplateContextGuard(dir: ExampleDirective, ctx: unknown): ctx is ExampleContext { return true; };\n\n // …\n}\n\n\n \n
\n\n\n" }