@tkssharma
github.com/tkssharma
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 ?
@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
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++;
}
}
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>
ContentChildren includes only elements that exists within the ng-content tag.
ViewChildren don’t include elements that exist within the ng-content tag.
@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 {}
@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>
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;
}
@Output() add = new EventEmitter().filter(v => !!v);
@Output() add = new BehaviorSubject("Awesome").filter(v => !!v);
Event emitter !== DOM
export class TodoComponent {
@Output() toggle = new EventEmitter<any>();
}
export class TodosComponent {
@Output() toggle = new EventEmitter<any>();
}
export class TodosPageComponent {
toggle($event) {}
}
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
}));
}
}
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() {}
}
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>
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
<!-- 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
@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.
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
);
}
}
}