Tuesday, 26 June 2018

Angular – Structural directive - The ng-template

The <ng-template> is an Angular element for rendering HTML. It is never displayed directly. In fact, before rendering the view, Angular replaces the <ng-template> and its contents with a comment.
If there is no structural directive and you merely wrap some elements in a <ng-template>, those elements disappear. That's the fate of the middle "Hip!" in the phrase "Hip! Hip! Hooray!".
src/app/app.component.html (template-tag)
<p>Hip!</p>
<ng-template>
  <p>Hip!</p>
</ng-template>
<p>Hooray!</p>
Angular erases the middle "Hip!", leaving the cheer a bit less enthusiastic.



Write a structural directive:

In this section, you write an UnlessDirective structural directive that does the opposite of NgIfNgIf displays the template content when the condition is trueUnlessDirective displays the content when the condition is false.
src/app/app.component.html (appUnless-1)
<p *appUnless="condition">Show this sentence unless the condition is true.</p>
Creating a directive is similar to creating a component.
·         Import the Directive decorator (instead of the Component decorator).
·         Import the InputTemplateRef, and ViewContainerRef symbols; you'll need them for any structural directive.
·         Apply the decorator to the directive class.
·         Set the CSS attribute selector that identifies the directive when applied to an element in a template.
Here's how you might begin:
src/app/unless.directive.ts (skeleton)
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
 
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
}
The directive's selector is typically the directive's attribute name in square brackets, [appUnless]. The brackets define a CSS attribute selector.
The directive attribute name should be spelled in lowerCamelCase and begin with a prefix. Don't use ng. That prefix belongs to Angular. Pick something short that fits you or your company. In this example, the prefix is app.
The directive class name ends in Directive per the style guide. Angular's own directives do not.

 

TemplateRef and ViewContainerRef:

A simple structural directive like this one 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.
You'll acquire the <ng-template> contents with a TemplateRef and access the view container through a ViewContainerRef.
You inject both in the directive constructor as private variables of the class.
src/app/unless.directive.ts (ctor)
constructor(
  private templateRef: TemplateRef<any>,
  private viewContainer: ViewContainerRef) { }

 

The appUnless property:

The directive consumer expects to bind a true/false condition to [appUnless]. That means the directive needs an appUnless property, decorated with @Input
src/app/unless.directive.ts (set)
@Input() set appUnless(condition: boolean) {
  if (!condition && !this.hasView) {
    this.viewContainer.createEmbeddedView(this.templateRef);
    this.hasView = true;
  } else if (condition && this.hasView) {
    this.viewContainer.clear();
    this.hasView = false;
  }
}
Angular sets the appUnless property whenever the value of the condition changes. Because the appUnless property does work, it needs a setter.
·         If the condition is falsy and the view hasn't been created previously, tell the view container to create the embedded view from the template.
·         If the condition is truthy and the view is currently displayed, clear the container which also destroys the view.
Nobody reads the appUnless property so it doesn't need a getter.
The completed directive code looks like this:
src/app/unless.directive.ts (excerpt)
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
 
/**
 * Add the template content to the DOM unless the condition is true.
 */
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
  private hasView = false;
 
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }
 
  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}
Add this directive to the declarations array of the AppModule.
Then create some HTML to try it.
src/app/app.component.html (appUnless)
<p *appUnless="condition" class="unless a">
  (A) This paragraph is displayed because the condition is false.
</p>
 
<p *appUnless="!condition" class="unless b">
  (B) Although the condition is true,
  this paragraph is displayed because appUnless is set to false.
</p>
When the condition is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears. When the condition is truthy, the top (A) paragraph is removed and the bottom (B) paragraph appears.




No comments:

Post a Comment