Last updated: November 15, 2020 8:44 PM

Angular 8 Demo Project Log and Experience Part 3.

Decorator(Annotation)

Annotation or Decorator is a function, which returns a function.

Create a customer decorator @Emoji

export function Emoji() {
  return (target: object, key: string) => {
    let val = target[key]; // let val get the target's value. key is the index.
    const getter = () => {
      return val;
    };

    // user setter to change the value
    const setter = (value: string) => {
      val = `😂 ${value} 😂`;
    };

    // append the property to the target
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

Thus, this decorator @Emoji can be import to other component and use it.

@Emoji() result = 'Hello';

The result output is 😂 Hello 😂;

Create a customer decorator @Confirmable(’…’)

This decorator contains parameter.

export function Confirmable(message: string) {
  return (target: object, key: string, descriptor: PropertyDescriptor) => {
    const original = descriptor.value;
    descriptor.value = function(...args: any) {
      const allow = window.confirm(message);
      if (allow) {
        const result = original.apply(args);
        return result;
      }
      return null;
    };
    return descriptor;
  };
}

To use this decorator.

@Confirmable('Are you sure to do something?')
handleClick() {
  console.log('handle click works');
}

When clicking the button, we want to pop up a window to warn our user.

Directive

Kinds

There are three kinds of directives in Angular:

  • Components: directives with a template.
  • Structural directives: change the DOM layout by adding and removing DOM elements. Change the structure of the view, ngFor, ngIf, ngSwitch.
  • Attribute directives: change the appearance or behavior of an element, component, or another directive.Used as attributes of elements, ngClass, ngStyle, ngModel.

Create Custom Directives

Create a file gird-item.directive.ts, in this file use a shortcut ng-directive to generate a directive. Then a template can be generated as below.

import { Directive } from '@angular/core';

@Directive({
  selector: 'appGridItem'
})
export class GridItemDirective {}

The selector name is wrong, it need to be an Attribute. Fix the problem to change the name to attribute:

@Directive({
  selector: '[appGridItem]',
})

Also, we need to add its deceleration in shared.component.ts and also export it.

The code to implement the directive:

import { Directive } from '@angular/core';

@Directive({
  selector: '[appGridItem]'
})
export class GridItemDirective {
  constructor(private elr: ElementRef, private rd2: Renderer2) {
    this.rd2.setStyle(this.elr.nativeElement, 'display', 'grid');
    this.rd2.setStyle(
      this.elr.nativeElement,
      'grid-template',
      `'image' 'title'`
    );
    this.rd2.setStyle(this.elr.nativeElement, 'place-itmes', 'center');
    this.rd2.setStyle(this.elr.nativeElement, 'width', '4rem');
  }
}

The best practice is to set styles in the ngOnInit, not in the constructor.

constructor (
    private elr: ElementRef,
    private rd2: Renderer2
  ) {}
ngOnInit() {
    this.rd2.setStyle(this.elr.nativeElement, 'display', 'grid');
    this.rd2.setStyle(this.elr.nativeElement, 'grid-template-areas', `'image' 'title'`);
    this.rd2.setStyle(this.elr.nativeElement, 'place-items', 'center');
    this.rd2.setStyle(this.elr.nativeElement, 'width', '4rem');
  }

Use Directives

We can create appGridItemImage and appGridItemTitle directives.

Directives are like properties. Now we can use these directives in the template of HorizontalGridComponent.

<div appGridItem *ngFor="let item of channels">
  <img [src]="item.imgUrl" alt="" appGridItemImage />
  <span appGridItemTitle></span>
</div>

Summary

Directives like properties, which can be applied to HTML tags.

Style Binding / Event Binding use Directive

There is no template in directive. Directive needs a host(an element). In Angular, @HostBinding can be used to bind attributes and styles to host, @HostListener can be used to bind events to host.

@HostBinding

We use rd2.setStyle to set styles of host, now we can use @HostBinding to refactor.

export class GridItemDirective { 
  @HostBinding('style.display') display = 'grid';
  @HostBinding('style.grid-template-areas') template = `'image' 'title'`;
  @HostBinding('style.place-items') align = 'center';
  @HostBinding('style.width') width = '4rem';
}

@HostListener

It accepts two params, one is eventName(The CSS event to listen for), another is event data (args, A set of arguments to pass to the handler method when the event occurs).

@HostListener('click', ['$event.target'])
 onClick(ev) {
   console.log('event: ', ev);
}

We don’t need to add additional event binding in template. What we do is just click the image, the @HostListener is listing to the click event.

 <div appGridItem *ngFor="let item of channels">
  <img [src]="item.icon" alt="" 
       [appGridItemImage]="'4rem'" 
       [fitMode]="'cover'">
  <span appGridItemTitle="0.6rem" class="title"></span>
</div>

:host

:host is used to design component styles in .component.css. It is a pseudo-classes selector. For example,

:host {
    display: flex;
    justify-content: center;
}

The pattern defined by :host is applied to the component itself.