Directives

杨旭 bio photo By 杨旭

Directives

Component is the sub class of Directive, it can be treated as a directive with template.

There are three kinds of directive

  • Component - directives with template
  • Attribute Directives - change the behaver or appearance of an existing DOM element
  • Structural Directives - change the DOM layout

Lifefycle of directives

Some hooks exist in both component and directive

  • ngOnInit
  • ngOnChanges
  • ngDoCheck
  • ngOnDestroy

Some hooks only exist in component

  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked

Native Directives

There are directives offered by Angular including Common Directives, Router Directives and Form Directives.

Make a Attribute Directive

  • A class with a @Directuve defined.
  • Match DOM elements with selector.
  • Get the reference of the element with ElementRef injected in the constructor.

see Developing attribute directive for details.

Make a Structural Directive

Feature - an unless directive which will show the element when its value is false

  it('should show element when `myUnless` is false', () => {
    const fixture = createGenericTestComponent(`
      <div class="content" *myUnless="false">Content</div>
    `, TestComponent);

    const el = fixture.debugElement.query(By.css('.content'));
    expect(el).toBeTruthy();
  });

  it('should remove element when `myUnless` is true', () => {
    const fixture = createGenericTestComponent(`
      <div class="content" *myUnless="true">Content</div>
    `, TestComponent);

    const el = fixture.debugElement.query(By.css('.content'));
    expect(el).toBeFalsy();
  });

Error: No provider for TemplateRef! You missed the * in front of structural directive.

Create a directive with TemplateRef and ViewContainerRef injected

import { Directive, Input, ViewContainerRef, TemplateRef } from '@angular/core';

@Directive({
  selector: '[myUnless]'
})
export class MyUnlessDirective {

  constructor(
    private tempalteRef: TemplateRef<any>,
    private viewContainerRef: ViewContainerRef
  ) { }

  @Input() set myUnless(unless: boolean) {
    if (unless) {
      this.viewContainerRef.clear();
    } else {
      this.viewContainerRef.createEmbeddedView(this.tempalteRef);
    }
  }
}
  • tempalteRef is used to access the template
  • viewContainerRef is the renderer

Structural directives, like ngIf, do their magic by using the HTML 5 template tag.

Outside of an Angular app, the

The asterisk (*) effect

The asterisk is “syntactic sugar”

<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngIf paragraph -->
<p *ngIf="condition">
  Our heroes are true!
</p>

<!-- (B) [ngIf] with template -->
<template [ngIf]="condition">
  <p>
    Our heroes are true!
  </p>
</template>

Concepts

The HTML

We use

<script id="tpl-mock" type="text/template">
   <span>I am span in mock template</span>
</script>

tempalte tag in HTML5

<template id="tpl">
    <span>I am span in template</span>
</template>

is suggested in angular

  <ng-template>
    <span>I am span in template</span>
  </ng-template>

We fill a container with templte

<!-- Template Container -->
<div class="tpl-container"></div>
<!-- Template -->
<template id="tpl">
    <span>I am span in template</span>
</template>
<!-- Script -->
<script type="text/javascript">
    (function renderTpl() {
        if ('content' in document.createElement('template')) {
            var tpl = document.querySelector('#tpl'); // --> templateRef
            var tplContainer = document.querySelector('.tpl-container'); // --> viewContainerRef
            var tplNode = document.importNode(tpl.content, true); 
            tplContainer.appendChild(tplNode); // --> viewContainerRef.createEmbeddedView
        } else {
            throw  new Error("Current browser doesn't support template element");
        }
    })();
</script>

HostBinding

Bind the property of the host element. Using a get function to update the HostBinding value when Input value changed.

  @Input() myCollaspe = false;
  @HostBinding('class.show') get showClass() { return !this.myCollaspe; }

TemplateRef, ViewContainerRef and EmbeddedViewRef

Results in browser when using structural directive

The displayed element existed in nodes property.

ViewContainerRef

/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import { Injector } from '../di/injector';
import { ComponentFactory, ComponentRef } from './component_factory';
import { ElementRef } from './element_ref';
import { NgModuleRef } from './ng_module_factory';
import { TemplateRef } from './template_ref';
import { EmbeddedViewRef, ViewRef } from './view_ref';
/**
 * Represents a container where one or more Views can be attached.
 *
 * The container can contain two kinds of Views. Host Views, created by instantiating a
 * {@link Component} via {@link #createComponent}, and Embedded Views, created by instantiating an
 * {@link TemplateRef Embedded Template} via {@link #createEmbeddedView}.
 *
 * The location of the View Container within the containing View is specified by the Anchor
 * `element`. Each View Container can have only one Anchor Element and each Anchor Element can only
 * have a single View Container.
 *
 * Root elements of Views attached to this container become siblings of the Anchor Element in
 * the Rendered View.
 *
 * To access a `ViewContainerRef` of an Element, you can either place a {@link Directive} injected
 * with `ViewContainerRef` on the Element, or you obtain it via a {@link ViewChild} query.
 * @stable
 */
export declare abstract class ViewContainerRef {
    /**
     * Anchor element that specifies the location of this container in the containing View.
     * <!-- TODO: rename to anchorElement -->
     */
    readonly abstract element: ElementRef;
    readonly abstract injector: Injector;
    readonly abstract parentInjector: Injector;
    /**
     * Destroys all Views in this container.
     */
    abstract clear(): void;
    /**
     * Returns the {@link ViewRef} for the View located in this container at the specified index.
     */
    abstract get(index: number): ViewRef | null;
    /**
     * Returns the number of Views currently attached to this container.
     */
    readonly abstract length: number;
    /**
     * Instantiates an Embedded View based on the {@link TemplateRef `templateRef`} and inserts it
     * into this container at the specified `index`.
     *
     * If `index` is not specified, the new View will be inserted as the last View in the container.
     *
     * Returns the {@link ViewRef} for the newly created View.
     */
    abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
    /**
     * Instantiates a single {@link Component} and inserts its Host View into this container at the
     * specified `index`.
     *
     * The component is instantiated using its {@link ComponentFactory} which can be
     * obtained via {@link ComponentFactoryResolver#resolveComponentFactory}.
     *
     * If `index` is not specified, the new View will be inserted as the last View in the container.
     *
     * You can optionally specify the {@link Injector} that will be used as parent for the Component.
     *
     * Returns the {@link ComponentRef} of the Host View created for the newly instantiated Component.
     */
    abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
    /**
     * Inserts a View identified by a {@link ViewRef} into the container at the specified `index`.
     *
     * If `index` is not specified, the new View will be inserted as the last View in the container.
     *
     * Returns the inserted {@link ViewRef}.
     */
    abstract insert(viewRef: ViewRef, index?: number): ViewRef;
    /**
     * Moves a View identified by a {@link ViewRef} into the container at the specified `index`.
     *
     * Returns the inserted {@link ViewRef}.
     */
    abstract move(viewRef: ViewRef, currentIndex: number): ViewRef;
    /**
     * Returns the index of the View, specified via {@link ViewRef}, within the current container or
     * `-1` if this container doesn't contain the View.
     */
    abstract indexOf(viewRef: ViewRef): number;
    /**
     * Destroys a View attached to this container at the specified `index`.
     *
     * If `index` is not specified, the last View in the container will be removed.
     */
    abstract remove(index?: number): void;
    /**
     * Use along with {@link #insert} to move a View within the current container.
     *
     * If the `index` param is omitted, the last {@link ViewRef} is detached.
     */
    abstract detach(index?: number): ViewRef | null;
}