@tkssharma

github.com/tkssharma

Angular 6 Webinar series

Change Detection

Strategy

By default Angular uses the ChangeDetectionStrategy.Default change detection strategy.

 

change detection will run on all components

once event happened !! is this good ?

change detection


@Component({
  template: `
    <h1>Hello {{name}}!</h1>
    {{runChangeDetection}}
  `
})
export class HelloComponent {
  @Input() name: string;

  get runChangeDetection() {
    console.log('Checking the view');
    return true;
  }
}

@Component({
  template: `
    <hello></hello>
    <button (click)="onClick()">Trigger change detection</button>
  `
})
export class AppComponent  {
  onClick() {}
}
This technique is called dirty checking.
It should not be there for Huge/complex apps

🔥 OnPush Change Detection Strategy

OnPush Change Detection

 

  • This tells Angular that the component only depends on its @inputs() ( aka pure ) and needs to be checked only in the following cases:

 

 

  1. 1️⃣ The Input reference changes.
  2. An event originated from the component or one of its children.

  3. 3️⃣ We run change detection explicitly.

 
Running Change detection manually ...
@Component({
  selector: 'counter',
  template: `{{count}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent { 
  count = 0;

  constructor(private cdr: ChangeDetectorRef) {

    setTimeout(() => {
      this.count = 5;
      this.cdr.detectChanges();
    }, 1000);

  }

}
@input reference  changed  ...
@Component({
  selector: 'tooltip',
  template: `
    <h1>{{config.position}}</h1>
    {{runChangeDetection}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipComponent  {

  @Input() config;

  get runChangeDetection() {
    console.log('Checking the view');
    return true;
  }
}
@Component({
  template: `
    <tooltip [config]="config"></tooltip>
  `
})
export class AppComponent  {
  config = {
    position: 'top'
  };

  onClick() {
    this.config.position = 'bottom';
  }
}
@Change detection in component simple button click ...
@Component({
  template: `
    <button (click)="add()">Add</button>
    {{count}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  count = 0;

  add() {
    this.count++;
  }

}
@Change detection works for DOM events not any XHR call, value of count will be updated only on click event
@Component({
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  count = 0;

  constructor() {
    setTimeout(() => this.count = 5, 0);
    setInterval(() => this.count = 5, 100);
    Promise.resolve().then(() => this.count = 5); 
    this.http.get('https://count.com').subscribe(res => {
      this.count = res;
    });
  }

  add() {
    this.count++;
  }

}

Welcome to Angular 6.0

Finite Obserables ...

export class TestComponent {

  constructor(private http: Http) { }

  ngOnInit() {
    Observable.timer(1000).subscribe(console.log);
    this.http.get('http://api.com').subscribe(console.log);
  }


}

This is How you can use it ...

<div *ifRole="'admin'">
  Only for Admin
</div>

<div *ifRole="'client'">
  Only for Client
</div>

<div *ifRole="'editor'">
  Only for Editor
</div>

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

 

?

Made with Slides.com