Angular, Exploring the Various Decorators

Angular, Exploring the Various Decorators

  • 2018-11-24 03:18 AM
  • 84

QueryList is just a fancy name for an object that stores a list of items. What is special about this object is when the state of the application

@Attribute —

attribute.di.ts

@Directive({
  selector: '[test]'
})
export class TestDirective {
  constructor(@Attribute('type') type ) {
    console.log(type); // text
  }
}
  
@Component({
  selector: 'my-app',
  template: `
    <input type="text" test>
  `,
})
export class App {}

Returns the value of the specified attribute from the host

@ViewChildren—

ViewChildern.ts

@Component({
  selector: 'alert',
  template: `
    {{type}}
  `,
})
export class AlertComponent {
  @Input() type: string = "success";
}

@Component({
  selector: 'my-app',
  template: `
    <alert></alert>
    <alert type="danger"></alert>
    <alert type="info"></alert>
  `,
})
export class App {
  @ViewChildren(AlertComponent) alerts: QueryList<AlertComponent>
  
  ngAfterViewInit() {
    this.alerts.forEach(alertInstance => console.log(alertInstance));
  }
}

Returns the specified elements or directives from the view DOM as QueryList

QueryList is just a fancy name for an object that stores a list of items. What is special about this object is when the state of the application changes Angular will automatically update the object items for you.

QueryList implements an iterable interface, therefore, it can be used in Angular templates with the ngFor directive. ( you can read more about this topic here )

QueryList API —

Getters —

  • first — get the first item
  • last — get the last item
  • length — get the length

Methods —

map(), filter() , find(), reduce(), forEach(), some().

  • toArray() — returns the items as javascript array
  • changes() — Changes can be observed by subscribing to the changes Observable. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value.

Things to Remember —

  1. The QueryList is initialized only before the ngAfterViewInit lifecycle hook, therefore, is available only from this point.
  2. ViewChildren don’t include elements that exist within the ng-content tag.

The read parameter —

By default, the ViewChildren decorator will return the component instance, but you can ask for other tokens:

  • The native DOM element —
@ViewChildren(AlertComponent, { read: ElementRef }) alerts: QueryList<AlertComponent>

  • ViewContainerRef — You need this token when you need to create templates or components dynamically
@ViewChildren(AlertComponent, { read: ViewContainerRef }) alerts: QueryList<AlertComponent>

@ViewChild—

@Component({
  selector: 'alert',
  template: `
    {{type}}
  `,
})
export class AlertComponent {
  @Input() type: string = "success";
}

@Component({
  selector: 'my-app',
  template: `
    <alert></alert>
    <div #divElement>Tada!</div>
  `,
})
export class App {
  // This will return the native element
  @ViewChild("divElement") div: any;
  // This will return the component instance
  @ViewChild(AlertComponent) alert: AlertComponent;
  
  ngAfterViewInit() {
    console.log(this.div);
    console.log(this.alert);
  }
}

ViewChild.ts

Like ViewChildren but returns only the first element or the directive matching the selector from the view DOM

You can see that you also can use a local variable from your template to get a reference to the element in your parent component.

@ContentChildren —

@Component({
  selector: 'tab',
  template: `
    <p>{{title}}</p>
  `,
})
export class TabComponent {
  @Input() title;
}

@Component({
  selector: 'tabs',
  template: `
    <ng-content></ng-content>
  `,
})
export class TabsComponent {
 @ContentChildren(TabComponent) tabs: QueryList<TabComponent>
 
 ngAfterContentInit() {
   this.tabs.forEach(tabInstance => console.log(tabInstance))
 }
}

@Component({
  selector: 'my-app',
  template: `
    <tabs>
     <tab title="One"></tab>
     <tab title="Two"></tab>
    </tabs>
  `,
})
export class App {}

ContentChildren.ts

Returns the specified elements or directives from the content DOM as QueryList

Things to Remember —

  1. The QueryList is initialized only before the ngAfterContentInit lifecycle hook, therefore, is available only from this point.
  2. ContentChildren includes only elements that exists within the ng-content tag.
  3. Returns QueryList like ViewChildren .

@ContentChild —

Like ContentChildren but returns only the first element or the directive matching the selector from the content DOM

@Component({
  selector: 'tabs',
  template: `
    <ng-content></ng-content>
  `,
})
export class TabsComponent {
 @ContentChild("divElement") div: any;
 
 ngAfterContentInit() {
   console.log(this.div);
 }
}

@Component({
  selector: 'my-app',
  template: `
    <tabs>
     <div #divElement>Tada!</div>
    </tabs>
  `,
})
export class App {}

@HostBinding —

Declares a host property binding

@Directive({
  selector: '[host-binding]'
})
export class HostBindingDirective {
  @HostBinding("class.tooltip") tooltip = true;
  
  @HostBinding("class.tooltip") 
  get tooltipAsGetter() {
    // your logic
    return true;
  };
   
  @HostBinding() type = "text";
}

@Component({
  selector: 'my-app',
  template: `
    <input type="text" host-binding> // this will add the "tooltip" class to the host
  `,
})
export class App {}

hostBinding.ts
In the third example Angular will add type=”text” to our host because when you don’t provide a parameter the class property name is used.

@HostListener —

Declares a host listener. Angular will invoke the decorated method when the host element emits the specified event

@Directive({
  selector: '[count]'
})
export class HostListenerDirective {
  numClicks = 0;
  numClicksWindow = 0;
  @HostListener("click", ["$event"])
  onClick(event) {
    console.log(this.numClicks++);
  }
  
  @HostListener("window:click", ["$event"])
  onClick(event) {
    console.log("Num clicks on the window:", this.numClicksWindow++);
  }
}

@Component({
  selector: 'my-app',
  template: `
    <input type="button" count value="+">
  `,
})
export class App {}

hostListener.ts

This decorator also support adding events to the globals window, document, and body with this syntax — global:event.

@Self —

The @Self decorator tells DI to look for a dependency only from itself, so it will not walk up the tree

class Dependency {}

class ChildDependency {
 constructor() {
   console.log("ChildDependency");
 }
}

class ParentDependency {
 constructor() {
   console.log("ParentDependency");
 }
}

@Component({
  selector: 'cmp',
  template: `
    cmp
  `,
  providers: [{ provide: Dependency, useClass: ChildDependency }]
})
export class DIComponent {
  constructor(@Self() public dependency: Dependency) {}
}

@Component({
  selector: 'my-app',
  template: `
    <cmp></cmp>
  `,
})
export class App {}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App,  DIComponent],
  providers: [{ provide: Dependency, useClass: ParentDependency }],
  bootstrap: [ App ]
})
export class AppModule {}

self.di.ts

If the injector does not find the dependency, it will throw.

@SkipSelf —

The @SkipSelf decorator tells DI to look for a dependency in the whole tree starting from the parent injector

class Dependency {}

class ChildDependency {
 constructor() {
   console.log("ChildDependency");
 }
}

class ParentDependency {
 constructor() {
   console.log("ParentDependency");
 }
}

@Component({
  selector: 'cmp',
  template: `
    cmp
  `,
  providers: [{ provide: Dependency, useClass: ChildDependency }]
})
export class DIComponent {
  constructor(@SkipSelf() public dependency: Dependency) {}
}

@Component({
  selector: 'my-app',
  template: `
    <cmp></cmp>
  `,
})
export class App {}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App,  DIComponent],
  providers: [{ provide: Dependency, useClass: ParentDependency }],
  bootstrap: [ App ]
})
export class AppModule {}

skipSelf.di.ts

If the injector does not find the dependency, it will throw.

@Host —

The @Host decorator tells DI to look for a dependency in any injector until it reaches the host

@Component({
  selector: 'cmp',
  template: `
    cmp
  `,
})
export class DIComponent {}

@Directive({
  selector: "[host-di]"
})
export class HostDI {
 constructor(@Host() cmp: DIComponent) {
   console.log(cmp);
 }
}

@Component({
  selector: 'my-app',
  template: `
    <cmp host-di></cmp>
  `,
})
export class App {}

For a more complicated use case you can read my article — Angular — Clean Up your Form Template.

@Optional —

A parameter metadata that marks a dependency as optional. The injector provides null if the dependency is not found.

class OptionalDependency {}

@Component({
  selector: 'cmp',
  template: `
    cmp
  `,
})
export class DIComponent {
  constructor(@Optional() public dependency: OptionalDependency) {}
}

optional.di.ts

In our example, the dependency property will be null because we don’t register the OptionalDependency provider in any injector.

@Inject —

A parameter decorator that specifies a dependency

@Component({
  selector: 'cmp',
  template: `
    cmp
  `
})
export class DIComponent {
  constructor(@Inject(Dependency) public dependency) {}
}

inject.di.ts

@Injectable —

Let’s Angular know that a class can be used with the DI

@Injectable()
export class WidgetService {
  constructor(
    public authService: AuthService) { }
}

Injectable.di.ts

Remember that this decorator is not required if the class does not have any dependencies or you don’t need the class to be injectable.

I skipped on the most common decorators: Input, Output, Component, Directive, Pipe.

30s ad

Angular 2 Firebase - Build a Web App with Typescript

Angular 2 Demystified

Master Angular 2 - The No Nonsense Course

Angular 2 - The Complete Guide

Angular 2 & TypeScript Beginner Web Development