@tkssharma

github.com/tkssharma

Understanding ViewChildren, ContentChildren, and QueryList in Angular

 

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

 

 

@Component({
  selector: 'alert',
  template: `
    <h1 (click)="alert()">{{type}}</h1>
  `,
})
export class AlertComponent {
  @Input() type: string = "success";
  
  alert() {
    console.log("alert");
  }
}
@ViewChildren decorator to grab elements from the host view.

 

 

@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));
  }
}
ViewChildren decorator will return the component instance, but you can ask for other tokens:

 

 

@ViewChildren(AlertComponent, { read: ElementRef }) 
              alerts: QueryList<ElementRef>

@ViewChildren(AlertComponent, { read: ViewContainerRef }) 
              alerts: QueryList<ViewContainerRef>

   QueryList —

  •  
  • The return type of ViewChildren is QueryList. QueryList is just a fancy name for an object that stores a list of items
  •  
  • QueryList implements an iterable interface, therefore, it can be used in Angular templates with the ngFor directive.
  • QueryList is initialized only before the ngAfterViewInit lifecycle hook
  •  

 

ViewChildren vs ContentChildren —

 

ContentChildren includes only elements that exists within the ng-content tag.


 

ViewChildren don’t include elements that exist within the ng-content tag.


 

 

 

 

 

ViewChildren vs ContentChildren —

 

 

 

 

 

 

 

ContentChildren — All child tab Instance

 

 

 

 

 

 

 

 

 

@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 {}

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

If you want your component to notify his parent about something you can use the Output decorator with EventEmitter to create a custom event.

@Component({
  selector: 'add-todo',
  template: `
   <input type="text" placeholder="Add todo.." [formControl]="control">
   <button (click)="add.next(control.value)">Add</button>
`,
})
export class AddTodoComponent {
  control : FormControl = new FormControl("");
  @Output() add = new EventEmitter();
}








<add-todo (add)="addTodo($event)"></add-todo>

What is EventEmitter .. a subject ??

export declare class EventEmitter<T> extends Subject<T> {
    __isAsync: boolean;
    constructor(isAsync?: boolean);
    emit(value?: T): void;
    subscribe(generatorOrNext?: any, error?: any, complete?: any): any;
}

💪 You have the power of Rx 💪

@Output() add = new EventEmitter().filter(v => !!v);

@Output() add = new BehaviorSubject("Awesome").filter(v => !!v);

EventEmitters are not DOM events

they can't bubble to top of DOM

Event emitter !== DOM

You can't emit event from todo to todoPage component

Solution is

    Keep passing the event up the tree

 

export class TodoComponent {
  @Output() toggle = new EventEmitter<any>();
}

export class TodosComponent {
  @Output() toggle = new EventEmitter<any>();
}

export class TodosPageComponent {
  toggle($event) {}
}

Too much nesting here

Lets do it in native way !

@Component({
  selector: 'app-todo',
  template: `
     <p (click)="toggleTodo(todo)">
      {{todo.title}}
     </p>
   `
})
export class TodoComponent {
  @Input() todo;
  constructor(private el: ElementRef) {}

  toggleTodo(todo) {
    this.el.nativeElement
      .dispatchEvent(new CustomEvent('toggle-todo', {
        detail: todo,
        bubbles: true
      }));
  }
}

Native DOM not a good choice

Lets do using service

@Injectable()
export class TodosService {
  private _toggle = new Subject();
  toggle$ = this._toggle.asObservable();

  toggle(todo) {
    this._toggle.next(todo);
  }
}

export class TodoComponent {
  constructor(private todosService: TodosService) {}
  
  toggle(todo) {
    this.todosService.toggle(todo);
  }
}

export class TodosPageComponent {
  constructor(private todosService: TodosService) {
    todosService.toggle$.subscribe(..);
  }
}

View Encapsulation

import {Component, ViewEncapsulation} 
        from 'angular2/angular2';

@component({
    selector: 'app',
    template: `<p class="green">`,
    style: [` 
            .green { color: green}
        `],
    encapsulation: 'ViewEncapsulation.Native'
                            // .Emulated .None
})

export class App { 

       constructor() {}
}
  1. ViewEncapsulation.Emulated
    • ​angular2 view encapsulation enabled // default
  2. ViewEncapsulation.None
    • No view encapsulation​
  3. ViewEncapsulation.Native
    • ​view encapsulation using shadow dom

 

Directives


import {Directive, ElementRef} from 'angular2/angular2';

@Directive({
	selector: '[bold]'
})

export class BoldDirective {
	constructor(el: ElementRef) {
		el.nativeElement.style.fontWeight = 'bold';
	}
}
<div class="myapp">
    <p bold> Hello world </p>
</div>

Dependency Injection

class Vehicle {
    constructor() {
        var engine = new Engine();
            engine.build();
    }
}


Constructor injection

class Vehicle {
    constructor(engine: Engine) {
            engine.build();
    }
}



class Engine {
    constructor(nutsNBolts: NutsNBolts) {
            nutsNBolts.fit();
    }
}



class NutsNBolts {
    constructor() {
    .....
    }
    fit() {

    }
}



new Vehicle(new Engine(new NutsNBolts()));


// Parent component

import {Component} from 'angular2/angular2';
import {Bootstrap} from 'angular2/angular2';
import {UserService} from './userservice.js';

@Component({ template: `<div> My App </div>` ... })

export class App {
    constructor() {
     
    }
}
 
Bootstrap(App,[UserService]); //UserService available in entire app.



//Child component                                   

import {Component} from 'angular2/angular2';   
import {UserService} from './userservice.js';                          
...                                                                           
                                                                      
@Component({ ... })                                                      

export class SomeComponent {
    constructor(user: UserService) {  //Singleton 
        user.dosomething();    
    }
}

Global Injection

By default all objects injected are singleton

// parent.ts  -- parent component
import {RoutineService} from './componentservice';
import {Child} from './child';

@Component({
    ......
    template: `<div>Parent component  <child></child> </div>`,
    directives: [Child]
    providers: [RoutineService]
//    viewProviders: [RoutineService]
})

export class App {
    constructor(routineservice: RoutineService) {
         routineservice.addRoutine(["eat", "sleep", "code"]);
    }
}
//child.ts -- child component                                   

import {RoutineService} from './routineservice.js';
                                                                                                                                              
@Component({ 
    selector: 'child',
    template `<p> I am child component </p>`
})                                                      

export class SomeComponent {
    constructor(routineservice: RoutineService) {  //Singleton 
        routineservice.deleteRoutine(["sleep"]);    
    }
}

Component level injection

PIPES

<!-- Date PIPE --!>

<p>{{date | date:'mediumDate'}}</p>
    <!-- Sep 1, 2015 --!>
<p>{{date | date:'yMMMMd'}}</p>
    <!-- September 1, 2015 --!>
<p>{{date | date:'shortTime'}}</p>
    <!-- 3:50 pm --!>


<!-- CURRENCY PIPE --!>

<p>{{43 | currency: 'USD' : true}}</p>
    <!-- $43 --!>
<p>{{43 | currency: 'USD' : true : '2.2'}} </p>
    <!--$43.00 --!>
<p>{{43 | currency: 'USD' : true : '3.3'}} </p>
     <!--$043.000 --!>

Angular2 inbuilt pipes

  • currency
  • date
  • uppercase
  • json
  • limitTo
  • lowercase
  • orderBy
  • filter
  • async
  • decimal
  • percent
  • number

Async Pipes

@Component({
    template: 
    `
        <div>
             <h1>My Component </h1>
             This component is ... {{lazydata | async}}
        </div>
    `
});


export class App{
    lazyData: Promise<string> = null;

    constructor() {
        this.lazyData =  new Promise<string>((resolve: any, reject: any) => {
                                setTimeout(()=> resolve("lazy"), 3000);
                            })        
    }
}    

Async pipe also works with Observables.

Async calls

  • Http calls return observables.
  • Observable is of a love child of Promise and collection/set
  • Observable is alias to RxJs Observable
  • Rxjs is reactive programming libraray
import {Http} from 'angular2/angular2';
import {Earthquake} from '../models/earthquake';

class EarthquakeService() {
    .
    .
    .
    
    getEarthquakeData(callback:(data: any[]) => void) {
    
      this.http.get('http://earthquake-report.com/feeds/recent-eq?json')
    
    	    .map((response: any) => {
    	        return response.json();
    	    })
    
    	    .map((jsonData: any[]) => {
    		jsonData.forEach((item) => {
    	            this.earthquakeData.push(new Earthquake(item));
    		});
    		return this.earthquakeData;
    				
    	    })
    
    	    .subscribe(
    		(data:any) => callback(this.earthquakeData), //OnNext
    		(err:any)  => console.log(err),              //OnError
    		() => console.log("call completed")          //OnComplete
    	    );
    	}
    }
}

References

  • http://blog.thoughtram.io
  • http://angularconnect.com/sessions
  • https://angular.io
  • https://disqus.com/home/forum/victorsavkinsblog
  • https://github.com/abhishek-agashe/angular2-playground

 

?

Angular 5 Advance webinars 03

By Tarun Sharma

Angular 5 Advance webinars 03

  • 575