1097 lines
30 KiB
HTML
1097 lines
30 KiB
HTML
<html lang="en"><head></head><body><form id="mainForm" method="post" action="http://plnkr.co/edit/?p=preview" target="_self"><input type="hidden" name="files[app/after-content.component.ts]" value="import { AfterContentChecked, AfterContentInit, Component, ContentChild } from '@angular/core';
|
|
|
|
import { LoggerService } from './logger.service';
|
|
|
|
//////////////////
|
|
@Component({
|
|
selector: 'my-child',
|
|
template: '<input [(ngModel)]="hero">'
|
|
})
|
|
export class ChildComponent {
|
|
hero = 'Magneta';
|
|
}
|
|
|
|
//////////////////////
|
|
@Component({
|
|
selector: 'after-content',
|
|
template: `
|
|
<div>-- projected content begins --</div>
|
|
<ng-content></ng-content>
|
|
<div>-- projected content ends --</div>`
|
|
+ `
|
|
<p *ngIf="comment" class="comment">
|
|
{{comment}}
|
|
</p>
|
|
`
|
|
})
|
|
export class AfterContentComponent implements AfterContentChecked, AfterContentInit {
|
|
private prevHero = '';
|
|
comment = '';
|
|
|
|
// Query for a CONTENT child of type `ChildComponent`
|
|
@ContentChild(ChildComponent) contentChild: ChildComponent;
|
|
|
|
constructor(private logger: LoggerService) {
|
|
this.logIt('AfterContent constructor');
|
|
}
|
|
|
|
ngAfterContentInit() {
|
|
// contentChild is set after the content has been initialized
|
|
this.logIt('AfterContentInit');
|
|
this.doSomething();
|
|
}
|
|
|
|
ngAfterContentChecked() {
|
|
// contentChild is updated after the content has been checked
|
|
if (this.prevHero === this.contentChild.hero) {
|
|
this.logIt('AfterContentChecked (no change)');
|
|
} else {
|
|
this.prevHero = this.contentChild.hero;
|
|
this.logIt('AfterContentChecked');
|
|
this.doSomething();
|
|
}
|
|
}
|
|
|
|
// This surrogate for real business logic sets the `comment`
|
|
private doSomething() {
|
|
this.comment = this.contentChild.hero.length > 10 ? `That's a long name` : '';
|
|
}
|
|
|
|
private logIt(method: string) {
|
|
let child = this.contentChild;
|
|
let message = `${method}: ${child ? child.hero : 'no'} child content`;
|
|
this.logger.log(message);
|
|
}
|
|
// ...
|
|
}
|
|
|
|
//////////////
|
|
@Component({
|
|
selector: 'after-content-parent',
|
|
template: `
|
|
<div class="parent">
|
|
<h2>AfterContent</h2>
|
|
|
|
<div *ngIf="show">` +
|
|
`<after-content>
|
|
<my-child></my-child>
|
|
</after-content>`
|
|
+ `</div>
|
|
|
|
<h4>-- AfterContent Logs --</h4>
|
|
<p><button (click)="reset()">Reset</button></p>
|
|
<div *ngFor="let msg of logs">{{msg}}</div>
|
|
</div>
|
|
`,
|
|
styles: ['.parent {background: burlywood}'],
|
|
providers: [LoggerService]
|
|
})
|
|
export class AfterContentParentComponent {
|
|
logs: string[];
|
|
show = true;
|
|
|
|
constructor(private logger: LoggerService) {
|
|
this.logs = logger.logs;
|
|
}
|
|
|
|
reset() {
|
|
this.logs.length = 0;
|
|
// quickly remove and reload AfterContentComponent which recreates it
|
|
this.show = false;
|
|
this.logger.tick_then(() => this.show = true);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/after-view.component.ts]" value="import { AfterViewChecked, AfterViewInit, Component, ViewChild } from '@angular/core';
|
|
|
|
import { LoggerService } from './logger.service';
|
|
|
|
//////////////////
|
|
@Component({
|
|
selector: 'my-child-view',
|
|
template: '<input [(ngModel)]="hero">'
|
|
})
|
|
export class ChildViewComponent {
|
|
hero = 'Magneta';
|
|
}
|
|
|
|
//////////////////////
|
|
@Component({
|
|
selector: 'after-view',
|
|
template: `
|
|
<div>-- child view begins --</div>
|
|
<my-child-view></my-child-view>
|
|
<div>-- child view ends --</div>`
|
|
+ `
|
|
<p *ngIf="comment" class="comment">
|
|
{{comment}}
|
|
</p>
|
|
`
|
|
})
|
|
export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
|
|
private prevHero = '';
|
|
|
|
// Query for a VIEW child of type `ChildViewComponent`
|
|
@ViewChild(ChildViewComponent) viewChild: ChildViewComponent;
|
|
|
|
constructor(private logger: LoggerService) {
|
|
this.logIt('AfterView constructor');
|
|
}
|
|
|
|
ngAfterViewInit() {
|
|
// viewChild is set after the view has been initialized
|
|
this.logIt('AfterViewInit');
|
|
this.doSomething();
|
|
}
|
|
|
|
ngAfterViewChecked() {
|
|
// viewChild is updated after the view has been checked
|
|
if (this.prevHero === this.viewChild.hero) {
|
|
this.logIt('AfterViewChecked (no change)');
|
|
} else {
|
|
this.prevHero = this.viewChild.hero;
|
|
this.logIt('AfterViewChecked');
|
|
this.doSomething();
|
|
}
|
|
}
|
|
|
|
comment = '';
|
|
|
|
// This surrogate for real business logic sets the `comment`
|
|
private doSomething() {
|
|
let c = this.viewChild.hero.length > 10 ? `That's a long name` : '';
|
|
if (c !== this.comment) {
|
|
// Wait a tick because the component's view has already been checked
|
|
this.logger.tick_then(() => this.comment = c);
|
|
}
|
|
}
|
|
|
|
private logIt(method: string) {
|
|
let child = this.viewChild;
|
|
let message = `${method}: ${child ? child.hero : 'no'} child view`;
|
|
this.logger.log(message);
|
|
}
|
|
// ...
|
|
}
|
|
|
|
//////////////
|
|
@Component({
|
|
selector: 'after-view-parent',
|
|
template: `
|
|
<div class="parent">
|
|
<h2>AfterView</h2>
|
|
|
|
<after-view *ngIf="show"></after-view>
|
|
|
|
<h4>-- AfterView Logs --</h4>
|
|
<p><button (click)="reset()">Reset</button></p>
|
|
<div *ngFor="let msg of logs">{{msg}}</div>
|
|
</div>
|
|
`,
|
|
styles: ['.parent {background: burlywood}'],
|
|
providers: [LoggerService]
|
|
})
|
|
export class AfterViewParentComponent {
|
|
logs: string[];
|
|
show = true;
|
|
|
|
constructor(private logger: LoggerService) {
|
|
this.logs = logger.logs;
|
|
}
|
|
|
|
reset() {
|
|
this.logs.length = 0;
|
|
// quickly remove and reload AfterViewComponent which recreates it
|
|
this.show = false;
|
|
this.logger.tick_then(() => this.show = true);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/app.component.ts]" value="import { Component } from '@angular/core';
|
|
@Component({
|
|
moduleId: module.id,
|
|
selector: 'my-app',
|
|
templateUrl: './app.component.html'
|
|
})
|
|
export class AppComponent { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/app.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { BrowserModule } from '@angular/platform-browser';
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
import { AppComponent } from './app.component';
|
|
|
|
import {
|
|
AfterContentParentComponent,
|
|
AfterContentComponent,
|
|
ChildComponent
|
|
} from './after-content.component';
|
|
|
|
import {
|
|
AfterViewParentComponent,
|
|
AfterViewComponent,
|
|
ChildViewComponent
|
|
} from './after-view.component';
|
|
|
|
import {
|
|
CounterParentComponent,
|
|
MyCounterComponent
|
|
} from './counter.component';
|
|
|
|
import {
|
|
DoCheckParentComponent,
|
|
DoCheckComponent
|
|
} from './do-check.component';
|
|
|
|
import {
|
|
OnChangesParentComponent,
|
|
OnChangesComponent
|
|
} from './on-changes.component';
|
|
|
|
import { PeekABooParentComponent } from './peek-a-boo-parent.component';
|
|
import { PeekABooComponent } from './peek-a-boo.component';
|
|
|
|
import { SpyParentComponent } from './spy.component';
|
|
import { SpyDirective } from './spy.directive';
|
|
|
|
@NgModule({
|
|
imports: [
|
|
BrowserModule,
|
|
FormsModule
|
|
],
|
|
declarations: [
|
|
AppComponent,
|
|
AfterContentParentComponent,
|
|
AfterContentComponent,
|
|
ChildComponent,
|
|
AfterViewParentComponent,
|
|
AfterViewComponent,
|
|
ChildViewComponent,
|
|
CounterParentComponent,
|
|
MyCounterComponent,
|
|
DoCheckParentComponent,
|
|
DoCheckComponent,
|
|
OnChangesParentComponent,
|
|
OnChangesComponent,
|
|
PeekABooParentComponent,
|
|
PeekABooComponent,
|
|
SpyParentComponent,
|
|
SpyDirective
|
|
],
|
|
bootstrap: [ AppComponent ]
|
|
})
|
|
export class AppModule { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/counter.component.ts]" value="import {
|
|
Component, Input,
|
|
OnChanges, SimpleChanges,
|
|
} from '@angular/core';
|
|
|
|
import { LoggerService } from './logger.service';
|
|
|
|
@Component({
|
|
selector: 'my-counter',
|
|
template: `
|
|
<div class="counter">
|
|
Counter = {{counter}}
|
|
|
|
<h5>-- Counter Change Log --</h5>
|
|
<div *ngFor="let chg of changeLog" mySpy>{{chg}}</div>
|
|
</div>
|
|
`,
|
|
styles: ['.counter {background: LightYellow; padding: 8px; margin-top: 8px}']
|
|
})
|
|
export class MyCounterComponent implements OnChanges {
|
|
@Input() counter: number;
|
|
changeLog: string[] = [];
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
|
|
// Empty the changeLog whenever counter goes to zero
|
|
// hint: this is a way to respond programmatically to external value changes.
|
|
if (this.counter === 0) {
|
|
this.changeLog.length = 0;
|
|
}
|
|
|
|
// A change to `counter` is the only change we care about
|
|
let chng = changes['counter'];
|
|
let cur = chng.currentValue;
|
|
let prev = JSON.stringify(chng.previousValue); // first time is {}; after is integer
|
|
this.changeLog.push(`counter: currentValue = ${cur}, previousValue = ${prev}`);
|
|
}
|
|
}
|
|
|
|
/***************************************/
|
|
|
|
@Component({
|
|
selector: 'counter-parent',
|
|
template: `
|
|
<div class="parent">
|
|
<h2>Counter Spy</h2>
|
|
|
|
<button (click)="updateCounter()">Update counter</button>
|
|
<button (click)="reset()">Reset Counter</button>
|
|
|
|
<my-counter [counter]="value"></my-counter>
|
|
|
|
<h4>-- Spy Lifecycle Hook Log --</h4>
|
|
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
|
</div>
|
|
`,
|
|
styles: ['.parent {background: gold;}'],
|
|
providers: [LoggerService]
|
|
})
|
|
export class CounterParentComponent {
|
|
value: number;
|
|
spyLog: string[] = [];
|
|
|
|
private logger: LoggerService;
|
|
|
|
constructor(logger: LoggerService) {
|
|
this.logger = logger;
|
|
this.spyLog = logger.logs;
|
|
this.reset();
|
|
}
|
|
|
|
updateCounter() {
|
|
this.value += 1;
|
|
this.logger.tick();
|
|
}
|
|
|
|
reset() {
|
|
this.logger.log('-- reset --');
|
|
this.value = 0;
|
|
this.logger.tick();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/do-check.component.ts]" value="/* tslint:disable:forin */
|
|
import { Component, DoCheck, Input, ViewChild } from '@angular/core';
|
|
|
|
class Hero {
|
|
constructor(public name: string) {}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'do-check',
|
|
template: `
|
|
<div class="hero">
|
|
<p>{{hero.name}} can {{power}}</p>
|
|
|
|
<h4>-- Change Log --</h4>
|
|
<div *ngFor="let chg of changeLog">{{chg}}</div>
|
|
</div>
|
|
`,
|
|
styles: [
|
|
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
|
|
'p {background: Yellow; padding: 8px; margin-top: 8px}'
|
|
]
|
|
})
|
|
export class DoCheckComponent implements DoCheck {
|
|
@Input() hero: Hero;
|
|
@Input() power: string;
|
|
|
|
changeDetected = false;
|
|
changeLog: string[] = [];
|
|
oldHeroName = '';
|
|
oldPower = '';
|
|
oldLogLength = 0;
|
|
noChangeCount = 0;
|
|
|
|
ngDoCheck() {
|
|
|
|
if (this.hero.name !== this.oldHeroName) {
|
|
this.changeDetected = true;
|
|
this.changeLog.push(`DoCheck: Hero name changed to "${this.hero.name}" from "${this.oldHeroName}"`);
|
|
this.oldHeroName = this.hero.name;
|
|
}
|
|
|
|
if (this.power !== this.oldPower) {
|
|
this.changeDetected = true;
|
|
this.changeLog.push(`DoCheck: Power changed to "${this.power}" from "${this.oldPower}"`);
|
|
this.oldPower = this.power;
|
|
}
|
|
|
|
if (this.changeDetected) {
|
|
this.noChangeCount = 0;
|
|
} else {
|
|
// log that hook was called when there was no relevant change.
|
|
let count = this.noChangeCount += 1;
|
|
let noChangeMsg = `DoCheck called ${count}x when no change to hero or power`;
|
|
if (count === 1) {
|
|
// add new "no change" message
|
|
this.changeLog.push(noChangeMsg);
|
|
} else {
|
|
// update last "no change" message
|
|
this.changeLog[this.changeLog.length - 1] = noChangeMsg;
|
|
}
|
|
}
|
|
|
|
this.changeDetected = false;
|
|
}
|
|
|
|
reset() {
|
|
this.changeDetected = true;
|
|
this.changeLog.length = 0;
|
|
}
|
|
}
|
|
|
|
/***************************************/
|
|
|
|
@Component({
|
|
moduleId: module.id,
|
|
selector: 'do-check-parent',
|
|
templateUrl: './do-check-parent.component.html',
|
|
styles: ['.parent {background: Lavender}']
|
|
})
|
|
export class DoCheckParentComponent {
|
|
hero: Hero;
|
|
power: string;
|
|
title = 'DoCheck';
|
|
@ViewChild(DoCheckComponent) childView: DoCheckComponent;
|
|
|
|
constructor() { this.reset(); }
|
|
|
|
reset() {
|
|
this.hero = new Hero('Windstorm');
|
|
this.power = 'sing';
|
|
if (this.childView) { this.childView.reset(); }
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/logger.service.ts]" value="import { Injectable } from '@angular/core';
|
|
|
|
@Injectable()
|
|
export class LoggerService {
|
|
logs: string[] = [];
|
|
prevMsg = '';
|
|
prevMsgCount = 1;
|
|
|
|
log(msg: string) {
|
|
if (msg === this.prevMsg) {
|
|
// Repeat message; update last log entry with count.
|
|
this.logs[this.logs.length - 1] = msg + ` (${this.prevMsgCount += 1}x)`;
|
|
} else {
|
|
// New message; log it.
|
|
this.prevMsg = msg;
|
|
this.prevMsgCount = 1;
|
|
this.logs.push(msg);
|
|
}
|
|
}
|
|
|
|
clear() { this.logs.length = 0; }
|
|
|
|
// schedules a view refresh to ensure display catches up
|
|
tick() { this.tick_then(() => { }); }
|
|
tick_then(fn: () => any) { setTimeout(fn, 0); }
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/on-changes.component.ts]" value="/* tslint:disable:forin */
|
|
import {
|
|
Component, Input, OnChanges,
|
|
SimpleChanges, ViewChild
|
|
} from '@angular/core';
|
|
|
|
class Hero {
|
|
constructor(public name: string) {}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'on-changes',
|
|
template: `
|
|
<div class="hero">
|
|
<p>{{hero.name}} can {{power}}</p>
|
|
|
|
<h4>-- Change Log --</h4>
|
|
<div *ngFor="let chg of changeLog">{{chg}}</div>
|
|
</div>
|
|
`,
|
|
styles: [
|
|
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
|
|
'p {background: Yellow; padding: 8px; margin-top: 8px}'
|
|
]
|
|
})
|
|
export class OnChangesComponent implements OnChanges {
|
|
@Input() hero: Hero;
|
|
@Input() power: string;
|
|
|
|
changeLog: string[] = [];
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
for (let propName in changes) {
|
|
let chng = changes[propName];
|
|
let cur = JSON.stringify(chng.currentValue);
|
|
let prev = JSON.stringify(chng.previousValue);
|
|
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
|
}
|
|
}
|
|
|
|
reset() { this.changeLog.length = 0; }
|
|
}
|
|
|
|
/***************************************/
|
|
|
|
@Component({
|
|
moduleId: module.id,
|
|
selector: 'on-changes-parent',
|
|
templateUrl: './on-changes-parent.component.html',
|
|
styles: ['.parent {background: Lavender;}']
|
|
})
|
|
export class OnChangesParentComponent {
|
|
hero: Hero;
|
|
power: string;
|
|
title = 'OnChanges';
|
|
@ViewChild(OnChangesComponent) childView: OnChangesComponent;
|
|
|
|
constructor() {
|
|
this.reset();
|
|
}
|
|
|
|
reset() {
|
|
// new Hero object every time; triggers onChanges
|
|
this.hero = new Hero('Windstorm');
|
|
// setting power only triggers onChanges if this value is different
|
|
this.power = 'sing';
|
|
if (this.childView) { this.childView.reset(); }
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/peek-a-boo-parent.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
import { LoggerService } from './logger.service';
|
|
|
|
@Component({
|
|
selector: 'peek-a-boo-parent',
|
|
template: `
|
|
<div class="parent">
|
|
<h2>Peek-A-Boo</h2>
|
|
|
|
<button (click)="toggleChild()">
|
|
{{hasChild ? 'Destroy' : 'Create'}} PeekABooComponent
|
|
</button>
|
|
<button (click)="updateHero()" [hidden]="!hasChild">Update Hero</button>
|
|
|
|
<peek-a-boo *ngIf="hasChild" [name]="heroName">
|
|
</peek-a-boo>
|
|
|
|
<h4>-- Lifecycle Hook Log --</h4>
|
|
<div *ngFor="let msg of hookLog">{{msg}}</div>
|
|
</div>
|
|
`,
|
|
styles: ['.parent {background: moccasin}'],
|
|
providers: [ LoggerService ]
|
|
})
|
|
export class PeekABooParentComponent {
|
|
|
|
hasChild = false;
|
|
hookLog: string[];
|
|
|
|
heroName = 'Windstorm';
|
|
private logger: LoggerService;
|
|
|
|
constructor(logger: LoggerService) {
|
|
this.logger = logger;
|
|
this.hookLog = logger.logs;
|
|
}
|
|
|
|
toggleChild() {
|
|
this.hasChild = !this.hasChild;
|
|
if (this.hasChild) {
|
|
this.heroName = 'Windstorm';
|
|
this.logger.clear(); // clear log on create
|
|
}
|
|
this.logger.tick();
|
|
}
|
|
|
|
updateHero() {
|
|
this.heroName += '!';
|
|
this.logger.tick();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/peek-a-boo.component.ts]" value="import {
|
|
AfterContentChecked,
|
|
AfterContentInit,
|
|
AfterViewChecked,
|
|
AfterViewInit,
|
|
DoCheck,
|
|
OnChanges,
|
|
OnDestroy,
|
|
OnInit,
|
|
SimpleChanges
|
|
} from '@angular/core';
|
|
import { Component, Input } from '@angular/core';
|
|
import { LoggerService } from './logger.service';
|
|
|
|
let nextId = 1;
|
|
|
|
export class PeekABoo implements OnInit {
|
|
constructor(private logger: LoggerService) { }
|
|
|
|
// implement OnInit's `ngOnInit` method
|
|
ngOnInit() { this.logIt(`OnInit`); }
|
|
|
|
logIt(msg: string) {
|
|
this.logger.log(`#${nextId++} ${msg}`);
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'peek-a-boo',
|
|
template: '<p>Now you see my hero, {{name}}</p>',
|
|
styles: ['p {background: LightYellow; padding: 8px}']
|
|
})
|
|
// Don't HAVE to mention the Lifecycle Hook interfaces
|
|
// unless we want typing and tool support.
|
|
export class PeekABooComponent extends PeekABoo implements
|
|
OnChanges, OnInit, DoCheck,
|
|
AfterContentInit, AfterContentChecked,
|
|
AfterViewInit, AfterViewChecked,
|
|
OnDestroy {
|
|
@Input() name: string;
|
|
|
|
private verb = 'initialized';
|
|
|
|
constructor(logger: LoggerService) {
|
|
super(logger);
|
|
|
|
let is = this.name ? 'is' : 'is not';
|
|
this.logIt(`name ${is} known at construction`);
|
|
}
|
|
|
|
// only called for/if there is an @input variable set by parent.
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
let changesMsgs: string[] = [];
|
|
for (let propName in changes) {
|
|
if (propName === 'name') {
|
|
let name = changes['name'].currentValue;
|
|
changesMsgs.push(`name ${this.verb} to "${name}"`);
|
|
} else {
|
|
changesMsgs.push(propName + ' ' + this.verb);
|
|
}
|
|
}
|
|
this.logIt(`OnChanges: ${changesMsgs.join('; ')}`);
|
|
this.verb = 'changed'; // next time it will be a change
|
|
}
|
|
|
|
// Beware! Called frequently!
|
|
// Called in every change detection cycle anywhere on the page
|
|
ngDoCheck() { this.logIt(`DoCheck`); }
|
|
|
|
ngAfterContentInit() { this.logIt(`AfterContentInit`); }
|
|
|
|
// Beware! Called frequently!
|
|
// Called in every change detection cycle anywhere on the page
|
|
ngAfterContentChecked() { this.logIt(`AfterContentChecked`); }
|
|
|
|
ngAfterViewInit() { this.logIt(`AfterViewInit`); }
|
|
|
|
// Beware! Called frequently!
|
|
// Called in every change detection cycle anywhere on the page
|
|
ngAfterViewChecked() { this.logIt(`AfterViewChecked`); }
|
|
|
|
ngOnDestroy() { this.logIt(`OnDestroy`); }
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/spy.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
import { LoggerService } from './logger.service';
|
|
|
|
@Component({
|
|
moduleId: module.id,
|
|
selector: 'spy-parent',
|
|
templateUrl: './spy.component.html',
|
|
styles: [
|
|
'.parent {background: khaki;}',
|
|
'.heroes {background: LightYellow; padding: 0 8px}'
|
|
],
|
|
providers: [LoggerService]
|
|
})
|
|
export class SpyParentComponent {
|
|
newName = 'Herbie';
|
|
heroes: string[] = ['Windstorm', 'Magneta'];
|
|
spyLog: string[];
|
|
|
|
constructor(private logger: LoggerService) {
|
|
this.spyLog = logger.logs;
|
|
}
|
|
|
|
addHero() {
|
|
if (this.newName.trim()) {
|
|
this.heroes.push(this.newName.trim());
|
|
this.newName = '';
|
|
this.logger.tick();
|
|
}
|
|
}
|
|
removeHero(hero: string) {
|
|
this.heroes.splice(this.heroes.indexOf(hero), 1);
|
|
this.logger.tick();
|
|
}
|
|
reset() {
|
|
this.logger.log('-- reset --');
|
|
this.heroes.length = 0;
|
|
this.logger.tick();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/spy.directive.ts]" value="import { Directive, OnInit, OnDestroy } from '@angular/core';
|
|
|
|
import { LoggerService } from './logger.service';
|
|
|
|
let nextId = 1;
|
|
|
|
// Spy on any element to which it is applied.
|
|
// Usage: <div mySpy>...</div>
|
|
@Directive({selector: '[mySpy]'})
|
|
export class SpyDirective implements OnInit, OnDestroy {
|
|
|
|
constructor(private logger: LoggerService) { }
|
|
|
|
ngOnInit() { this.logIt(`onInit`); }
|
|
|
|
ngOnDestroy() { this.logIt(`onDestroy`); }
|
|
|
|
private logIt(msg: string) {
|
|
this.logger.log(`Spy #${nextId++} ${msg}`);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[main.ts]" value="import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|
import { AppModule } from './app/app.module';
|
|
|
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[sample.css]" value=".parent {
|
|
color: #666;
|
|
margin: 14px 0;
|
|
padding: 8px;
|
|
}
|
|
input {
|
|
margin: 4px;
|
|
padding: 4px;
|
|
}
|
|
.comment {
|
|
color: red;
|
|
font-style: italic;
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[styles.css]" value="/* Master Styles */
|
|
h1 {
|
|
color: #369;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 250%;
|
|
}
|
|
h2, h3 {
|
|
color: #444;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-weight: lighter;
|
|
}
|
|
body {
|
|
margin: 2em;
|
|
}
|
|
body, input[text], button {
|
|
color: #888;
|
|
font-family: Cambria, Georgia;
|
|
}
|
|
a {
|
|
cursor: pointer;
|
|
cursor: hand;
|
|
}
|
|
button {
|
|
font-family: Arial;
|
|
background-color: #eee;
|
|
border: none;
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
cursor: hand;
|
|
}
|
|
button:hover {
|
|
background-color: #cfd8dc;
|
|
}
|
|
button:disabled {
|
|
background-color: #eee;
|
|
color: #aaa;
|
|
cursor: auto;
|
|
}
|
|
|
|
/* Navigation link styles */
|
|
nav a {
|
|
padding: 5px 10px;
|
|
text-decoration: none;
|
|
margin-right: 10px;
|
|
margin-top: 10px;
|
|
display: inline-block;
|
|
background-color: #eee;
|
|
border-radius: 4px;
|
|
}
|
|
nav a:visited, a:link {
|
|
color: #607D8B;
|
|
}
|
|
nav a:hover {
|
|
color: #039be5;
|
|
background-color: #CFD8DC;
|
|
}
|
|
nav a.active {
|
|
color: #039be5;
|
|
}
|
|
|
|
/* items class */
|
|
.items {
|
|
margin: 0 0 2em 0;
|
|
list-style-type: none;
|
|
padding: 0;
|
|
width: 24em;
|
|
}
|
|
.items li {
|
|
cursor: pointer;
|
|
position: relative;
|
|
left: 0;
|
|
background-color: #EEE;
|
|
margin: .5em;
|
|
padding: .3em 0;
|
|
height: 1.6em;
|
|
border-radius: 4px;
|
|
}
|
|
.items li:hover {
|
|
color: #607D8B;
|
|
background-color: #DDD;
|
|
left: .1em;
|
|
}
|
|
.items li.selected {
|
|
background-color: #CFD8DC;
|
|
color: white;
|
|
}
|
|
.items li.selected:hover {
|
|
background-color: #BBD8DC;
|
|
}
|
|
.items .text {
|
|
position: relative;
|
|
top: -3px;
|
|
}
|
|
.items .badge {
|
|
display: inline-block;
|
|
font-size: small;
|
|
color: white;
|
|
padding: 0.8em 0.7em 0 0.7em;
|
|
background-color: #607D8B;
|
|
line-height: 1em;
|
|
position: relative;
|
|
left: -1px;
|
|
top: -4px;
|
|
height: 1.8em;
|
|
margin-right: .8em;
|
|
border-radius: 4px 0 0 4px;
|
|
}
|
|
/* everywhere else */
|
|
* {
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/app.component.html]" value="<a id="top"></a>
|
|
<h1>Component Lifecycle Hooks</h1>
|
|
<a href="#hooks">Peek-a-boo: (most) lifecycle hooks</a><br>
|
|
<a href="#onchanges">OnChanges</a><br>
|
|
<a href="#docheck">DoCheck</a><br>
|
|
<a href="#after-view">AfterViewInit & AfterViewChecked</a><br>
|
|
<a href="#after-content">AfterContentInit & AfterContentChecked</a><br>
|
|
<a href="#spy">Spy: directive with OnInit & OnDestroy</a><br>
|
|
<a href="#counter">Counter: OnChanges + Spy directive</a><br>
|
|
|
|
<a id="hooks"></a>
|
|
<peek-a-boo-parent></peek-a-boo-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
<a id="spy"></a>
|
|
<spy-parent></spy-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
<a id="onchanges"></a>
|
|
<on-changes-parent></on-changes-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
<a id="docheck"></a>
|
|
<do-check-parent></do-check-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
<a id="after-view"></a>
|
|
<after-view-parent></after-view-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
<a id="after-content"></a>
|
|
<after-content-parent></after-content-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
<a id="counter"></a>
|
|
<counter-parent></counter-parent>
|
|
<a href="#top">back to top</a>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="files[app/do-check-parent.component.html]" value="<div class="parent">
|
|
<h2>{{title}}</h2>
|
|
|
|
<table>
|
|
<tr><td>Power: </td><td><input [(ngModel)]="power"></td></tr>
|
|
<tr><td>Hero.name: </td><td><input [(ngModel)]="hero.name"></td></tr>
|
|
</table>
|
|
<p><button (click)="reset()">Reset Log</button></p>
|
|
|
|
<do-check [hero]="hero" [power]="power"></do-check>
|
|
</div>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="files[app/on-changes-parent.component.html]" value="<div class="parent">
|
|
<h2>{{title}}</h2>
|
|
|
|
<table>
|
|
<tr><td>Power: </td><td><input [(ngModel)]="power"></td></tr>
|
|
<tr><td>Hero.name: </td><td><input [(ngModel)]="hero.name"></td></tr>
|
|
</table>
|
|
<p><button (click)="reset()">Reset Log</button></p>
|
|
|
|
<on-changes [hero]="hero" [power]="power"></on-changes>
|
|
</div>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="files[app/spy.component.html]" value="<div class="parent">
|
|
<h2>Spy Directive</h2>
|
|
|
|
<input [(ngModel)]="newName" (keyup.enter)="addHero()">
|
|
<button (click)="addHero()">Add Hero</button>
|
|
<button (click)="reset()">Reset Heroes</button>
|
|
|
|
<p></p>
|
|
<div *ngFor="let hero of heroes" mySpy class="heroes">
|
|
{{hero}}
|
|
</div>
|
|
<h4>-- Spy Lifecycle Hook Log --</h4>
|
|
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
|
</div>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="files[index.html]" value="<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Angular Lifecycle Hooks</title>
|
|
<script>document.write('<base href="' + document.location + '" />');</script>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="styles.css">
|
|
<link rel="stylesheet" href="sample.css">
|
|
|
|
<!-- Polyfills -->
|
|
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
|
|
|
|
<script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
|
|
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
|
|
|
|
<script src="https://cdn.rawgit.com/angular/angular.io/b3c65a9/public/docs/_examples/_boilerplate/systemjs.config.web.js"></script>
|
|
<script>
|
|
System.import('main.js').catch(function(err){ console.error(err); });
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<my-app>Loading...</my-app>
|
|
</body>
|
|
|
|
</html>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="tags[0]" value="angular"><input type="hidden" name="tags[1]" value="example"><input type="hidden" name="tags[2]" value="lifecycle"><input type="hidden" name="tags[3]" value="hooks"><input type="hidden" name="tags[4]" value="OnInit"><input type="hidden" name="tags[5]" value="OnDestroy"><input type="hidden" name="tags[6]" value="OnChange"><input type="hidden" name="tags[7]" value="DoCheck"><input type="hidden" name="tags[8]" value="AfterContentInit"><input type="hidden" name="tags[9]" value="AfterContentChecked"><input type="hidden" name="tags[10]" value="AfterViewInit"><input type="hidden" name="tags[11]" value="AfterViewChecked"><input type="hidden" name="private" value="true"><input type="hidden" name="description" value="Angular Example - Lifecycle Hooks"></form><script>document.getElementById("mainForm").submit();</script></body></html> |