This repository has been archived by the owner on Dec 4, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 879
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(change-detection): add change detection dev guide
- Loading branch information
Showing
31 changed files
with
1,243 additions
and
0 deletions.
There are no files selected for viewing
112 changes: 112 additions & 0 deletions
112
public/docs/_examples/change-detection/ts/app/app.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { Component } from '@angular/core'; | ||
import { Hero } from './hero.model'; | ||
import { HeroModel } from './onpush/hero-evented.model'; | ||
|
||
@Component({ | ||
moduleId: module.id, | ||
selector: 'hero-app', | ||
template: ` | ||
<h1>Angular Change Detection</h1> | ||
<h2>Basic Example</h2> | ||
<hero-counter> | ||
</hero-counter> | ||
<h2>Single-Pass</h2> | ||
<h3>Broken Example</h3> | ||
<hero-name-badge-broken [hero]="anonymousHero"> | ||
</hero-name-badge-broken> | ||
<h3>Fixed Example</h3> | ||
<hero-name-badge [hero]="secondAnonymousHero"> | ||
</hero-name-badge> | ||
<h2>OnPush</h2> | ||
<h3>Immutable Primitive Values</h3> | ||
<p>OnPush only runs detection when inputs change.</p> | ||
<hero-search-result [searchResult]="'Windstorm'" [searchTerm]="'indsto'"> | ||
</hero-search-result> | ||
<h3>Mutable Collection, Broken Example</h3> | ||
<p>OnPush does not detect changes inside array inputs.</p> | ||
<hero-manager-mutable> | ||
</hero-manager-mutable> | ||
<h3>Immutable Collection, Fixed Example</h3> | ||
<p>OnPush detects changes for array inputs as longs as they're treated as immutable values.</p> | ||
<hero-manager-immutable> | ||
</hero-manager-immutable> | ||
<h3>Events</h3> | ||
<p>OnPush detects changes when they originate in an event handler.</p> | ||
<hero-counter-onpush> | ||
</hero-counter-onpush> | ||
<h3>Explicit Change Marking, Broken Without</h3> | ||
<p>A counter incrementing with setTimeout() inside an OnPush component does not update.</p> | ||
<hero-counter-auto-broken> | ||
</hero-counter-auto-broken> | ||
<h3>Explicit Change Marking</h3> | ||
<p>This is fixed using markForCheck()</p> | ||
<hero-counter-auto> | ||
</hero-counter-auto> | ||
<h3>Explicit Change Marking with Library Callback</h3> | ||
<hero-name-badge-evented [hero]="heroModel"> | ||
</hero-name-badge-evented> | ||
<button (click)="renameHeroModel()">Rename</button> | ||
<h2>Detaching</h2> | ||
<h3>Permanently, "One-Time Binding"</h3> | ||
<p>By detaching a component's change detector at ngOnInit() we can do "one-time binding".</p> | ||
<hero-name-badge-detached [hero]="hero"> | ||
</hero-name-badge-detached> | ||
<button (click)="renameHero()">Rename</button> | ||
<h3>Temporarily, reattach</h3> | ||
<p>By detaching/reattaching a change detector we can toggle whether a component has "live updates".</p> | ||
<hero-counter-live> | ||
</hero-counter-live> | ||
<h3>Throttling with Internal detectChanges</h3> | ||
<p> | ||
By calling detectChanges() on a detached change detector we can choose when change detection is done. | ||
This can be used to update the view at a lower frequency than data changes. | ||
</p> | ||
<hero-counter-throttled> | ||
</hero-counter-throttled> | ||
<h3>Flushing to DOM with Internal detectChanges</h3> | ||
<p>We can use detectChanges() to flush changes to the view immediately if we can't wait for the next turn of the zone.</p> | ||
<hero-signature-form> | ||
</hero-signature-form> | ||
<h2>Escaping NgZone For Async Work</h2> | ||
<h3>Without</h3> | ||
<p>Many unnecessary change detections will be performed for this workflow because it is all inside NgZone.</p> | ||
<hero-async-workflow></hero-async-workflow> | ||
` | ||
}) | ||
export class AppComponent { | ||
hero: Hero = {name: 'Windstorm', onDuty: true}; | ||
anonymousHero: Hero = {name: '', onDuty: false}; | ||
secondAnonymousHero: Hero = {name: '', onDuty: false}; | ||
|
||
heroModel = new HeroModel('Windstorm'); | ||
|
||
renameHero() { | ||
this.hero.name = 'Magneta'; | ||
} | ||
|
||
renameHeroModel() { | ||
this.heroModel.setName('Magneta'); | ||
} | ||
|
||
} |
52 changes: 52 additions & 0 deletions
52
public/docs/_examples/change-detection/ts/app/app.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { BrowserModule } from '@angular/platform-browser'; | ||
import { FormsModule } from '@angular/forms'; | ||
|
||
import { AppComponent } from './app.component'; | ||
|
||
import { HeroCounterComponent } from './hero-counter.component'; | ||
import { HeroNameBadgeBrokenComponent } from './hero-name-badge.broken.component'; | ||
import { HeroNameBadgeComponent } from './hero-name-badge.component'; | ||
import { SearchResultComponent } from './onpush/search-result.component'; | ||
import { HeroListComponent as HeroListOnpushComponent } from './onpush/hero-list.onpush.component'; | ||
import { HeroManagerMutableComponent } from './onpush/hero-manager.mutable.component'; | ||
import { HeroManagerImmutableComponent } from './onpush/hero-manager.immutable.component'; | ||
import { HeroCounterComponent as HeroCounterOnPushComponent } from './onpush/hero-counter.onpush.component'; | ||
import { HeroCounterAutoComponent as HeroCounterAutoBrokenComponent } from './onpush/hero-counter-auto.broken.component'; | ||
import { HeroCounterAutoComponent } from './onpush/hero-counter-auto.component'; | ||
import { HeroNameBadgeComponent as HeroNameBadgeEventedComponent } from './onpush/hero-name-badge-evented.component'; | ||
import { HeroNameBadgeComponent as HeroNameBadgeDetachedComponent } from './detach/hero-name-badge-detached.component'; | ||
import { HeroCounterComponent as HeroCounterLiveComponent } from './detach/hero-counter-live.component'; | ||
import { HeroCounterComponent as HeroCounterThrottledComponent } from './detach/hero-counter-throttled.component'; | ||
import { HeroSignatureFormComponent } from './detach/hero-signature-form.component'; | ||
import { AsyncWorkflowComponent } from './async-workflow.component'; | ||
|
||
@NgModule({ | ||
imports: [ | ||
BrowserModule, | ||
FormsModule | ||
], | ||
declarations: [ | ||
AppComponent, | ||
HeroCounterComponent, | ||
HeroNameBadgeBrokenComponent, | ||
HeroNameBadgeComponent, | ||
SearchResultComponent, | ||
HeroListOnpushComponent, | ||
HeroManagerMutableComponent, | ||
HeroManagerImmutableComponent, | ||
HeroCounterOnPushComponent, | ||
HeroCounterAutoBrokenComponent, | ||
HeroCounterAutoComponent, | ||
HeroNameBadgeEventedComponent, | ||
HeroNameBadgeDetachedComponent, | ||
HeroCounterLiveComponent, | ||
HeroCounterThrottledComponent, | ||
HeroSignatureFormComponent, | ||
AsyncWorkflowComponent | ||
], | ||
bootstrap: [ | ||
AppComponent | ||
] | ||
}) | ||
export class AppModule { } |
80 changes: 80 additions & 0 deletions
80
public/docs/_examples/change-detection/ts/app/async-workflow.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Component, DoCheck, NgZone } from '@angular/core'; | ||
import { Observable } from 'rxjs/Observable'; | ||
|
||
import 'rxjs/add/observable/interval'; | ||
import 'rxjs/add/observable/merge'; | ||
import 'rxjs/add/operator/do'; | ||
import 'rxjs/add/operator/map'; | ||
import 'rxjs/add/operator/reduce'; | ||
import 'rxjs/add/operator/take'; | ||
|
||
@Component({ | ||
selector: 'hero-async-workflow', | ||
template: ` | ||
<button (click)="loadInsideZone()">Start loading inside NgZone</button> | ||
<button (click)="loadOutsideZone()">Start loading outside NgZone</button> | ||
Results: | ||
<ul> | ||
<li *ngFor="let itm of results"> | ||
{{ itm }} | ||
</li> | ||
</ul> | ||
` | ||
}) | ||
export class AsyncWorkflowComponent implements DoCheck { | ||
results: string[]; | ||
|
||
// #docregion outside-zone | ||
constructor(private ngZone: NgZone) { } | ||
// #enddocregion outside-zone | ||
|
||
// #docregion inside-zone | ||
loadInsideZone() { | ||
Observable.merge(loadHeroes(), loadMoreHeroes(), loadEvenMoreHeroes()) | ||
.reduce((heroes, hero) => [...heroes, hero], []) | ||
.subscribe(heroes => this.results = heroes); | ||
} | ||
// #enddocregion inside-zone | ||
|
||
// #docregion outside-zone | ||
loadOutsideZone() { | ||
// Escape NgZone before starting work. | ||
// No change detection will be performed during this work. | ||
this.ngZone.runOutsideAngular(() => { | ||
Observable.merge(loadHeroes(), loadMoreHeroes(), loadEvenMoreHeroes()) | ||
.reduce((heroes, hero) => [...heroes, hero], []) | ||
.subscribe(heroes => { | ||
// Re-enter zone to process final result. | ||
// Change detection will be performed. | ||
this.ngZone.run(() => this.results = heroes); | ||
}); | ||
}); | ||
} | ||
// #enddocregion outside-zone | ||
|
||
ngDoCheck() { | ||
console.log('cd'); | ||
} | ||
|
||
} | ||
|
||
function loadHeroes() { | ||
return Observable.interval(100) | ||
.map(n => `hero a${n}`) | ||
.do(v => console.log(v)) | ||
.take(3); | ||
} | ||
|
||
function loadMoreHeroes() { | ||
return Observable.interval(150) | ||
.map(n => `hero b${n}`) | ||
.do(v => console.log(v)) | ||
.take(3) | ||
} | ||
|
||
function loadEvenMoreHeroes() { | ||
return Observable.interval(200) | ||
.map(n => `hero c${n}`) | ||
.do(v => console.log(v)) | ||
.take(3) | ||
} |
35 changes: 35 additions & 0 deletions
35
public/docs/_examples/change-detection/ts/app/detach/hero-counter-live.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; | ||
|
||
// #docregion | ||
@Component({ | ||
selector: 'hero-counter-live', | ||
template: ` | ||
Number of heroes: {{ heroCount }} | ||
<button (click)="toggleLive()">Toggle live update</button> | ||
` | ||
}) | ||
export class HeroCounterComponent implements OnInit, OnDestroy { | ||
heroCount = 5; | ||
private live = true; | ||
private updateIntervalId: any; | ||
|
||
constructor(private changeDetector: ChangeDetectorRef) { } | ||
|
||
ngOnInit() { | ||
// Increment counter ten times per second | ||
this.updateIntervalId = setInterval(() => this.heroCount++, 100); | ||
} | ||
|
||
ngOnDestroy() { | ||
clearInterval(this.updateIntervalId); | ||
} | ||
|
||
toggleLive() { | ||
this.live = !this.live; | ||
if (this.live) { | ||
this.changeDetector.reattach(); | ||
} else { | ||
this.changeDetector.detach(); | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
public/docs/_examples/change-detection/ts/app/detach/hero-counter-throttled.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; | ||
|
||
// #docregion | ||
@Component({ | ||
selector: 'hero-counter-throttled', | ||
template: ` | ||
Number of heroes: {{ heroCount }} | ||
` | ||
}) | ||
export class HeroCounterComponent implements OnInit, OnDestroy { | ||
heroCount = 5; | ||
|
||
private dataUpdateIntervalId: any; | ||
private viewUpdateIntervalId: any; | ||
|
||
constructor(private changeDetector: ChangeDetectorRef) { } | ||
|
||
ngOnInit() { | ||
// Detach the change detector so it never runs unless we do it manually. | ||
this.changeDetector.detach(); | ||
// Change data a hundred times per second... | ||
this.dataUpdateIntervalId = setInterval(() => this.heroCount++, 10); | ||
// ...but detect changes only once per second | ||
this.viewUpdateIntervalId = setInterval(() => this.changeDetector.detectChanges(), 1000); | ||
} | ||
|
||
ngOnDestroy() { | ||
clearInterval(this.dataUpdateIntervalId); | ||
clearInterval(this.viewUpdateIntervalId); | ||
} | ||
|
||
} |
21 changes: 21 additions & 0 deletions
21
public/docs/_examples/change-detection/ts/app/detach/hero-name-badge-detached.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { ChangeDetectorRef, Component, Input, AfterViewInit } from '@angular/core'; | ||
import { Hero } from '../hero.model'; | ||
|
||
// #docregion | ||
@Component({ | ||
selector: 'hero-name-badge-detached', | ||
template: ` | ||
<h4>{{ hero.name }} details</h4> | ||
<p>Name: {{ hero.name }}</p> | ||
` | ||
}) | ||
export class HeroNameBadgeComponent implements AfterViewInit { | ||
@Input() hero: Hero; | ||
|
||
constructor(private changeDetector: ChangeDetectorRef) { } | ||
|
||
ngAfterViewInit() { | ||
this.changeDetector.detach(); | ||
} | ||
|
||
} |
34 changes: 34 additions & 0 deletions
34
public/docs/_examples/change-detection/ts/app/detach/hero-signature-form.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/core'; | ||
|
||
// #docregion | ||
@Component({ | ||
selector: 'hero-signature-form', | ||
template: ` | ||
<form #signatureForm method="POST" action="/sign" > | ||
<input type="text" name="username" [(ngModel)]="username" /> | ||
<input type="hidden" name="secret" [value]="secret" /> | ||
<button (click)="sendForm()">Submit</button> | ||
</form> | ||
` | ||
}) | ||
export class HeroSignatureFormComponent { | ||
@ViewChild('signatureForm') signatureForm: ElementRef; | ||
|
||
username: string; | ||
secret: string; | ||
|
||
constructor(private changeDetector: ChangeDetectorRef) { } | ||
|
||
sendForm() { | ||
this.secret = calculateSecret(this.username); | ||
// Ensure the secret is flushed into the form field before we submit. | ||
this.changeDetector.detectChanges(); | ||
this.signatureForm.nativeElement.submit(); | ||
} | ||
|
||
} | ||
// #enddocregion | ||
|
||
function calculateSecret(username: string) { | ||
return `SECRET FOR ${username}`; | ||
} |
20 changes: 20 additions & 0 deletions
20
public/docs/_examples/change-detection/ts/app/hero-counter.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// #docregion | ||
import { Component } from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'hero-counter', | ||
template: ` | ||
Number of heroes: | ||
<button (click)="decrease()">-</button> | ||
{{ heroCount }} <!-- This expression always evaluates to the latest value --> | ||
<button (click)="increase()">+</button> | ||
` | ||
}) | ||
export class HeroCounterComponent { | ||
heroCount = 5; | ||
|
||
// When we change data, we don't need to do anything to update the view. | ||
increase() { this.heroCount = this.heroCount + 1; } | ||
decrease() { this.heroCount = Math.max(this.heroCount - 1, 0); } | ||
|
||
} |
Oops, something went wrong.