From b5ea1d8f68c1306567453805b95dea6e226614f5 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Sat, 23 Jan 2016 18:21:09 +0000 Subject: [PATCH] docs(lifecycle-hooks): add hook sections, revise sample, add DoCheck & tests --- .../_examples/lifecycle-hooks/e2e-spec.js | 116 ++-- .../ts/app/after-content.component.ts | 149 +++-- .../ts/app/after-view.component.ts | 161 +++-- .../lifecycle-hooks/ts/app/app.component.html | 37 ++ .../lifecycle-hooks/ts/app/app.component.ts | 25 +- .../lifecycle-hooks/ts/app/child.component.ts | 20 - .../ts/app/counter.component.ts | 4 +- .../ts/app/do-check.component.ts | 105 +++ .../lifecycle-hooks/ts/app/logger.service.ts | 16 +- .../ts/app/on-changes-parent.component.html | 14 + .../ts/app/on-changes.component.ts | 52 +- .../ts/app/peek-a-boo-parent.component.ts | 2 +- .../ts/app/peek-a-boo.component.ts | 88 ++- .../lifecycle-hooks/ts/app/spy.component.ts | 42 +- .../lifecycle-hooks/ts/app/spy.directive.ts | 24 +- .../_examples/lifecycle-hooks/ts/index.html | 5 +- .../_examples/lifecycle-hooks/ts/plnkr.json | 7 +- .../_examples/lifecycle-hooks/ts/sample.css | 13 + .../docs/ts/latest/guide/lifecycle-hooks.jade | 599 ++++++++++++++++-- .../ts/latest/guide/server-communication.jade | 7 +- public/docs/ts/latest/tutorial/toh-pt4.jade | 2 + .../lifecycle-hooks/after-view-anim.gif | Bin 0 -> 220724 bytes .../lifecycle-hooks/do-check-anim.gif | Bin 0 -> 195114 bytes .../lifecycle-hooks/on-changes-anim.gif | Bin 0 -> 98813 bytes .../devguide/lifecycle-hooks/peek-a-boo.gif | Bin 0 -> 84694 bytes .../devguide/lifecycle-hooks/peek-a-boo.png | Bin 0 -> 56374 bytes .../lifecycle-hooks/projected-child-view.png | Bin 0 -> 11079 bytes .../lifecycle-hooks/spy-directive.gif | Bin 0 -> 90898 bytes 28 files changed, 1095 insertions(+), 393 deletions(-) create mode 100644 public/docs/_examples/lifecycle-hooks/ts/app/app.component.html delete mode 100644 public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts create mode 100644 public/docs/_examples/lifecycle-hooks/ts/app/do-check.component.ts create mode 100644 public/docs/_examples/lifecycle-hooks/ts/app/on-changes-parent.component.html create mode 100644 public/docs/_examples/lifecycle-hooks/ts/sample.css create mode 100644 public/resources/images/devguide/lifecycle-hooks/after-view-anim.gif create mode 100644 public/resources/images/devguide/lifecycle-hooks/do-check-anim.gif create mode 100644 public/resources/images/devguide/lifecycle-hooks/on-changes-anim.gif create mode 100644 public/resources/images/devguide/lifecycle-hooks/peek-a-boo.gif create mode 100644 public/resources/images/devguide/lifecycle-hooks/peek-a-boo.png create mode 100644 public/resources/images/devguide/lifecycle-hooks/projected-child-view.png create mode 100644 public/resources/images/devguide/lifecycle-hooks/spy-directive.gif diff --git a/public/docs/_examples/lifecycle-hooks/e2e-spec.js b/public/docs/_examples/lifecycle-hooks/e2e-spec.js index d201fd7bfb..2ae33f9d53 100644 --- a/public/docs/_examples/lifecycle-hooks/e2e-spec.js +++ b/public/docs/_examples/lifecycle-hooks/e2e-spec.js @@ -8,7 +8,7 @@ describe('Lifecycle hooks', function () { expect(element.all(by.css('h2')).get(0).getText()).toEqual('Peek-A-Boo'); }); - it('should be able to drive peek-a-boo button', function () { + it('should support peek-a-boo', function () { var pabComp = element(by.css('peek-a-boo-parent peek-a-boo')); expect(pabComp.isPresent()).toBe(false, "should not be able to find the 'peek-a-boo' component"); var pabButton = element.all(by.css('peek-a-boo-parent button')).get(0); @@ -29,80 +29,118 @@ describe('Lifecycle hooks', function () { }); }); - it('should be able to trigger onChanges', function () { - var onChangesViewEle = element.all(by.css('on-changes-parent my-hero div')).get(0); + it('should support OnChanges hook', function () { + var onChangesViewEle = element.all(by.css('on-changes div')).get(0); var inputEles = element.all(by.css('on-changes-parent input')); - var heroNameInputEle = inputEles.get(0); - var powerInputEle = inputEles.get(1); + var heroNameInputEle = inputEles.get(1); + var powerInputEle = inputEles.get(0); var titleEle = onChangesViewEle.element(by.css('p')); - expect(titleEle.getText()).toContain('Windstorm can sing'); var changeLogEles = onChangesViewEle.all(by.css('div')); - expect(changeLogEles.count()).toEqual(3, "should start with 3 messages"); + + expect(titleEle.getText()).toContain('Windstorm can sing'); + expect(changeLogEles.count()).toEqual(2, "should start with 2 messages"); // heroNameInputEle.sendKeys('-foo-').then(function () { sendKeys(heroNameInputEle, '-foo-').then(function () { expect(titleEle.getText()).toContain('Windstorm-foo- can sing'); - expect(changeLogEles.count()).toEqual(3, "should still have 3 messages"); + expect(changeLogEles.count()).toEqual(2, "should still have 2 messages"); // protractor bug with sendKeys means that line below does not work. // return powerInputEle.sendKeys('-bar-'); return sendKeys(powerInputEle, '-bar-'); }).then(function () { expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-'); - // 8 == 3 previously + length of '-bar-' - expect(changeLogEles.count()).toEqual(8, "should have 8 messages now"); + // 7 == 2 previously + length of '-bar-' + expect(changeLogEles.count()).toEqual(7, "should have 7 messages now"); }); }); - - it('should support after-view hooks', function () { - var inputEle = element(by.css('after-view-parent input')); - var buttonEle = element(by.css('after-view-parent button')); - var logEles = element.all(by.css('after-view-parent h4 ~ div')); - var childViewTextEle = element(by.css('after-view-parent my-child .child')); - expect(childViewTextEle.getText()).toContain('Magneta is my hero'); - expect(logEles.count()).toBeGreaterThan(2); + + it('should support DoCheck hook', function () { + var doCheckViewEle = element.all(by.css('do-check div')).get(0); + var inputEles = element.all(by.css('do-check-parent input')); + var heroNameInputEle = inputEles.get(1); + var powerInputEle = inputEles.get(0); + var titleEle = doCheckViewEle.element(by.css('p')); + var changeLogEles = doCheckViewEle.all(by.css('div')); var logCount; + + expect(titleEle.getText()).toContain('Windstorm can sing'); + changeLogEles.count().then(function(count) { + expect(count).toBeGreaterThan(3, "should start with at least 4 messages"); + logCount = count; + // heroNameInputEle.sendKeys('-foo-').then(function () { + return sendKeys(heroNameInputEle, '-foo-') + }).then(function () { + expect(titleEle.getText()).toContain('Windstorm-foo- can sing'); + return changeLogEles.count() + }).then(function (count) { + expect(count).toEqual(logCount + 10, 'should add 10 more messages') + logCount = count; + // return powerInputEle.sendKeys('-bar-'); + return sendKeys(powerInputEle, '-bar-'); + }).then(function () { + expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-'); + // 7 == 2 previously + length of '-bar-' + expect(changeLogEles.count()).toEqual(logCount + 15, 'should add 15 more messages'); + }); + }); + + it('should support AfterView hooks', function () { + var parentEle = element(by.tagName('after-view-parent')); + var buttonEle = parentEle.element(by.tagName('button')); // Reset + var commentEle = parentEle.element(by.className('comment')); + var logEles = parentEle.all(by.css('h4 ~ div')); + var childViewInputEle = parentEle.element(by.css('my-child input')); + var logCount; + + expect(childViewInputEle.getAttribute('value')).toContain('Magneta'); + expect(commentEle.isPresent()).toBe(false, 'comment should not be in DOM'); + logEles.count().then(function(count) { logCount = count; - return sendKeys(inputEle, "-test-"); + return sendKeys(childViewInputEle, "-test-"); }).then(function() { - expect(childViewTextEle.getText()).toContain('-test-'); + expect(childViewInputEle.getAttribute('value')).toContain('-test-'); + expect(commentEle.isPresent()).toBe(true,'should have comment because >10 chars'); + expect(commentEle.getText()).toContain('long name'); return logEles.count(); }).then(function(count) { - expect(logCount + 6).toEqual(count, "6 additional log messages should have been added"); + expect(logCount + 11).toEqual(count, "11 additional log messages should have been added"); logCount = count; return buttonEle.click(); }).then(function() { - expect(childViewTextEle.isPresent()).toBe(false,"child view should no longer be part of the DOM"); - sendKeys(inputEle, "-foo-"); - expect(logEles.count()).toEqual(logCount, "no additional log messages should have been added"); + expect(logEles.count()).toBeLessThan(logCount, "log should shrink after reset"); }); }); - it('should support after-content hooks', function () { - var inputEle = element(by.css('after-content-parent input')); - var buttonEle = element(by.css('after-content-parent button')); - var logEles = element.all(by.css('after-content-parent h4 ~ div')); - var childViewTextEle = element(by.css('after-content-parent my-child .child')); - expect(childViewTextEle.getText()).toContain('Magneta is my hero'); - expect(logEles.count()).toBeGreaterThan(2); + + it('should support AfterContent hooks', function () { + var parentEle = element(by.tagName('after-content-parent')); + var buttonEle = parentEle.element(by.tagName('button')); // Reset + var commentEle = parentEle.element(by.className('comment')); + var logEles = parentEle.all(by.css('h4 ~ div')); + var childViewInputEle = parentEle.element(by.css('my-child input')); var logCount; + + expect(childViewInputEle.getAttribute('value')).toContain('Magneta'); + expect(commentEle.isPresent()).toBe(false, 'comment should not be in DOM'); + logEles.count().then(function(count) { logCount = count; - return sendKeys(inputEle, "-test-"); + return sendKeys(childViewInputEle, "-test-"); }).then(function() { - expect(childViewTextEle.getText()).toContain('-test-'); + expect(childViewInputEle.getAttribute('value')).toContain('-test-'); + expect(commentEle.isPresent()).toBe(true,'should have comment because >10 chars'); + expect(commentEle.getText()).toContain('long name'); return logEles.count(); }).then(function(count) { - expect(logCount + 6).toEqual(count, "6 additional log messages should have been added"); + expect(logCount + 11).toEqual(count, "11 additional log messages should have been added"); logCount = count; return buttonEle.click(); }).then(function() { - expect(childViewTextEle.isPresent()).toBe(false,"child view should no longer be part of the DOM"); - sendKeys(inputEle, "-foo-"); - expect(logEles.count()).toEqual(logCount, "no additional log messages should have been added"); + expect(logEles.count()).toBeLessThan(logCount, "log should shrink after reset"); }); }); - it('should support "spy" hooks', function () { + it('should support spy\'s OnInit & OnDestroy hooks', function () { var inputEle = element(by.css('spy-parent input')); var addHeroButtonEle = element(by.cssContainingText('spy-parent button','Add Hero')); var resetHeroesButtonEle = element(by.cssContainingText('spy-parent button','Reset Heroes')); @@ -123,7 +161,7 @@ describe('Lifecycle hooks', function () { }) }); - it('should support "spy counter" hooks', function () { + it('should support "spy counter"', function () { var updateCounterButtonEle = element(by.cssContainingText('counter-parent button','Update')); var resetCounterButtonEle = element(by.cssContainingText('counter-parent button','Reset')); var textEle = element(by.css('counter-parent my-counter > div')); diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/after-content.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/after-content.component.ts index 1774470535..bfc2bd2e4f 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/after-content.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/after-content.component.ts @@ -1,102 +1,119 @@ +// #docplaster // #docregion -import { - Component, Input, Output, - AfterContentChecked, AfterContentInit, ContentChild, - AfterViewInit, ViewChild -} from 'angular2/core'; +import {Component, AfterContentChecked, AfterContentInit, ContentChild} from 'angular2/core'; -import {ChildComponent} from './child.component'; import {LoggerService} from './logger.service'; +////////////////// +@Component({ + selector: 'my-child', + template: '' +}) +export class ChildComponent { + hero = 'Magneta'; +} + +////////////////////// @Component({ selector: 'after-content', - template: ` -
-
-- child content begins --
- - - -
-- child content ends --
-
- `, - styles: ['.after-content {background: LightCyan; padding: 8px;}'], - +// #docregion template + template:` +
-- projected content begins --
+ +
-- projected content ends --
` +// #enddocregion template + + ` +

+ {{comment}} +

+ ` }) -export class AfterContentComponent - implements AfterContentChecked, AfterContentInit, AfterViewInit { - - private _logger:LoggerService; - - constructor(logger:LoggerService){ - this._logger = logger; - logger.log('AfterContent ctor: ' + this._getMessage()); - } +// #docregion hooks +export class AfterContentComponent implements AfterContentChecked, AfterContentInit { + private _prevHero = ''; // Query for a CONTENT child of type `ChildComponent` @ContentChild(ChildComponent) contentChild: ChildComponent; - // Query for a VIEW child of type`ChildComponent` - // No such VIEW child exists! - // This component holds content but no view of that type. - @ViewChild(ChildComponent) viewChild: ChildComponent; +// #enddocregion hooks + constructor(private _logger:LoggerService){ + this._logIt('AfterContent constructor'); + } - - ///// Hooks +// #docregion hooks ngAfterContentInit() { - // contentChild is set after the content has been initialized - this._logger.log('AfterContentInit: ' + this._getMessage()); + // viewChild is set after the view has been initialized + this._logIt('AfterContentInit'); + this._doSomething(); } - ngAfterViewInit() { - this._logger.log(`AfterViewInit: There is ${this.viewChild ? 'a' : 'no'} view child`); - } - - private _prevHero:string; ngAfterContentChecked() { - // contentChild is updated after the content has been checked - // Called frequently; only report when the hero changes - if (!this.contentChild || this._prevHero === this.contentChild.hero) {return;} - this._prevHero = this.contentChild.hero; - this._logger.log('AfterContentChecked: ' + this._getMessage()); + // viewChild is updated after the view 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(); + } + } +// #enddocregion hooks + + comment = ''; + +// #docregion do-something + + // This surrogate for real business logic sets the `comment` + private _doSomething() { + this.comment = this.contentChild.hero.length > 10 ? "That's a long name" : ''; } - private _getMessage(): string { - let cmp = this.contentChild; - return cmp ? `"${cmp.hero}" child content` : 'no child content'; + private _logIt(method:string){ + let vc = this.contentChild; + let message = `${method}: ${vc ? vc.hero:'no'} child view` + this._logger.log(message); } - +// #docregion hooks + // ... } +// #enddocregion hooks -/***************************************/ - +////////////// @Component({ selector: 'after-content-parent', template: `

AfterContent

- - - +
` + +// #docregion parent-template + ` + + ` +// #enddocregion parent-template ++ `
- -
- -

-- Lifecycle Hook Log --

-
{{msg}}
+

-- AfterContent Logs --

+

+
{{msg}}
`, - styles: ['.parent {background: powderblue; padding: 8px; margin:100px 8px;}'], - directives: [AfterContentComponent, ChildComponent], - providers:[LoggerService] + styles: ['.parent {background: burlywood}'], + providers:[LoggerService], + directives: [AfterContentComponent, ChildComponent] }) export class AfterContentParentComponent { - - hookLog:string[]; - hero = 'Magneta'; - showChild = true; + logs:string[]; + show = true; constructor(logger:LoggerService){ - this.hookLog = logger.logs; + this.logs = logger.logs; + } + + reset() { + this.logs.length=0; + // quickly remove and reload AfterContentComponent which recreates it + this.show = false; + setTimeout(() => this.show = true, 0) } } diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/after-view.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/after-view.component.ts index 47f12b53ae..0b516ee029 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/after-view.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/after-view.component.ts @@ -1,80 +1,121 @@ +// #docplaster // #docregion -import { - Component, Input, Output, - AfterContentInit, ContentChild, - AfterViewChecked, AfterViewInit, ViewChild -} from 'angular2/core'; +import {Component, AfterViewChecked, AfterViewInit, ViewChild} from 'angular2/core'; -import {ChildComponent} from './child.component'; import {LoggerService} from './logger.service'; +////////////////// +// #docregion child-view +@Component({ + selector: 'my-child', + template: '' +}) +export class ChildViewComponent { + hero = 'Magneta'; +} +// #enddocregion child-view + +////////////////////// +@Component({ + selector: 'after-view', +// #docregion template + template: ` +
-- child view begins --
+ +
-- child view ends --
` +// #enddocregion template + + ` +

+ {{comment}} +

+ `, + + directives: [ChildViewComponent] +}) +// #docregion hooks +export class AfterViewComponent implements AfterViewChecked, AfterViewInit { + private _prevHero = ''; + + // Query for a VIEW child of type `ChildViewComponent` + @ViewChild(ChildViewComponent) viewChild: ChildViewComponent; + +// #enddocregion hooks + constructor(private _logger:LoggerService){ + this._logIt('AfterView constructor'); + } + +// #docregion hooks + 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(); + } + } +// #enddocregion hooks + + comment = ''; + +// #docregion do-something + // 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 + setTimeout(() => this.comment = c, 0); + } + } +// #enddocregion do-something + + private _logIt(method:string){ + let vc = this.viewChild; + let message = `${method}: ${vc ? vc.hero:'no'} child view` + this._logger.log(message); + } +// #docregion hooks + // ... +} +// #enddocregion hooks + +////////////// @Component({ selector: 'after-view-parent', template: `

AfterView

-
- - + - -
- -

-- Lifecycle Hook Log --

-
{{msg}}
+

-- AfterView Logs --

+

+
{{msg}}
`, - styles: ['.parent {background: burlywood; padding: 8px; margin:100px 8px;}'], - directives: [ChildComponent], - providers:[LoggerService] + styles: ['.parent {background: burlywood}'], + providers:[LoggerService], + directives: [AfterViewComponent] }) -export class AfterViewParentComponent - implements AfterContentInit, AfterViewChecked, AfterViewInit { - - private _logger:LoggerService; +export class AfterViewParentComponent { + logs:string[]; + show = true; constructor(logger:LoggerService){ - this._logger = logger; - this.hookLog = logger.logs; - logger.log('AfterView ctor: ' + this._getMessage()); + this.logs = logger.logs; } - hookLog:string[]; - hero = 'Magneta'; - showChild = true; - - // Query for a CONTENT child of type `ChildComponent` - // No such CONTENT child exists! - // This component holds a view but no content of that type. - @ContentChild(ChildComponent) contentChild: ChildComponent; - - // Query for a VIEW child of type `ChildComponent` - @ViewChild(ChildComponent) viewChild: ChildComponent; - - - ///// Hooks - ngAfterContentInit() { - this._logger.log(`AfterContentInit: There is ${this.contentChild ? 'a' : 'no'} content child`); + reset() { + this.logs.length=0; + // quickly remove and reload AfterViewComponent which recreates it + this.show = false; + setTimeout(() => this.show = true, 0) } - - ngAfterViewInit() { - // viewChild is set after the view has been initialized - this._logger.log('AfterViewInit: ' + this._getMessage()); - } - - private _prevHero:string; - ngAfterViewChecked() { - // viewChild is updated after the view has been checked - // Called frequently; only report when the hero changes - if (!this.viewChild || this._prevHero === this.viewChild.hero) {return;} - this._prevHero = this.viewChild.hero; - this._logger.log('AfterViewChecked: ' + this._getMessage()); - } - - private _getMessage(): string { - let cmp = this.viewChild; - return cmp ? `"${cmp.hero}" child view` : 'no child view'; - } - } diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/app.component.html b/public/docs/_examples/lifecycle-hooks/ts/app/app.component.html new file mode 100644 index 0000000000..d0692e28ac --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/app.component.html @@ -0,0 +1,37 @@ + +

Component Lifecycle Hooks

+Peek-a-boo: (most) lifecycle hooks
+OnChanges
+DoCheck
+AfterViewInit & AfterViewChecked
+AfterContentInit & AfterContentChecked
+Spy: directive with OnInit & OnDestroy
+Counter: OnChanges + Spy directive
+ + + +back to top + + + +back to top + + + +back to top + + + +back to top + + + +back to top + + + +back to top + + + +back to top diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/app.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/app.component.ts index 14f1bdab24..7d1f362428 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/app.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/app.component.ts @@ -4,39 +4,22 @@ import {Component} from 'angular2/core'; import {AfterContentParentComponent} from './after-content.component'; import {AfterViewParentComponent} from './after-view.component'; import {CounterParentComponent} from './counter.component'; +import {DoCheckParentComponent} from './do-check.component'; import {OnChangesParentComponent} from './on-changes.component'; import {PeekABooParentComponent} from './peek-a-boo-parent.component'; import {SpyParentComponent} from './spy.component'; -/***************************************/ -/* - template: ` - - - - - - - `, - */ - @Component({ selector: 'my-app', - template: ` - - - - - - - `, + templateUrl: 'app/app.component.html', directives: [ AfterContentParentComponent, AfterViewParentComponent, + CounterParentComponent, + DoCheckParentComponent, OnChangesParentComponent, PeekABooParentComponent, SpyParentComponent, - CounterParentComponent ] }) export class AppComponent { diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts deleted file mode 100644 index a027d9180c..0000000000 --- a/public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -// #docregion -import {Component, Input} from 'angular2/core'; - -@Component({ - selector: 'my-child', - template: ` -
-
-- child view begins --
-
{{hero}} is my hero.
-
-- child view ends --
-
- `, - styles: [ - '.child {background: Yellow; padding: 8px; }', - '.my-child {background: LightYellow; padding: 8px; margin-top: 8px}' - ] -}) -export class ChildComponent { - @Input() hero: string; -} \ No newline at end of file diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/counter.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/counter.component.ts index 7a502a25b0..b17557f091 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/counter.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/counter.component.ts @@ -58,7 +58,7 @@ export class MyCounter implements OnChanges {
{{msg}}
`, - styles: ['.parent {background: gold; padding: 10px; margin:100px 8px;}'], + styles: ['.parent {background: gold;}'], directives: [MyCounter], providers: [LoggerService] }) @@ -76,11 +76,13 @@ export class CounterParentComponent { updateCounter() { this.value += 1; + this._logger.tick(); } reset(){ this._logger.log('-- reset --'); this.value=0; + this._logger.tick(); } } diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/do-check.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/do-check.component.ts new file mode 100644 index 0000000000..ba458ce2e5 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/do-check.component.ts @@ -0,0 +1,105 @@ +// #docregion +import {Component, DoCheck, OnChanges, Input, SimpleChange, ViewChild} from 'angular2/core'; + +class Hero { + constructor(public name:string){} +} + +@Component({ + selector: 'do-check', + template: ` +
+

{{hero.name}} can {{power}}

+ +

-- Change Log --

+
{{chg}}
+
+ `, + styles: [ + '.hero {background: LightYellow; padding: 8px; margin-top: 8px}', + 'p {background: Yellow; padding: 8px; margin-top: 8px}' + ] +}) +export class DoCheckComponent implements DoCheck, OnChanges { + @Input() hero: Hero; + @Input() power: string; + + changeDetected = false; + changeLog:string[] = []; + oldHeroName = ''; + oldPower = ''; + oldLogLength = 0; + noChangeCount = 0; + + // #docregion ng-do-check + 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; + } + // #enddocregion ng-do-check + + // Copied from OnChangesComponent + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + for (let propName in changes) { + let prop = changes[propName]; + let cur = JSON.stringify(prop.currentValue) + let prev = JSON.stringify(prop.previousValue); + this.changeLog.push(`OnChanges: ${propName}: currentValue = ${cur}, previousValue = ${prev}`); + } + } + + reset() { + this.changeDetected = true; + this.changeLog.length = 0; + } +} + +/***************************************/ + +@Component({ + selector: 'do-check-parent', + templateUrl:'app/on-changes-parent.component.html', + styles: ['.parent {background: Lavender}'], + directives: [DoCheckComponent] +}) +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'; + this.childView && this.childView.reset(); + } +} diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts b/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts index 209bd6a2a5..6f8f76aaba 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts @@ -3,14 +3,24 @@ import {Injectable} from 'angular2/core'; @Injectable() export class LoggerService { logs:string[] = []; + prevMsg = ''; + prevMsgCount = 1; - log(msg:string, noTick:boolean = false) { - if (!noTick) { this.tick(); } - this.logs.push(msg); + 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() { setTimeout(() => { // console.log('tick') diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/on-changes-parent.component.html b/public/docs/_examples/lifecycle-hooks/ts/app/on-changes-parent.component.html new file mode 100644 index 0000000000..7889ce8e91 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/on-changes-parent.component.html @@ -0,0 +1,14 @@ +
+

{{title}}

+ + + + +
Power:
Hero.name:
+

+ + + + + +
diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/on-changes.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/on-changes.component.ts index 32a2185aba..dd6c7bddbe 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/on-changes.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/on-changes.component.ts @@ -1,16 +1,16 @@ // #docregion import { - Component, Input, Output, - OnChanges, SimpleChange, + Component, Input, ViewChild, + OnChanges, SimpleChange } from 'angular2/core'; -export class Hero { +class Hero { constructor(public name:string){} } @Component({ - selector: 'my-hero', + selector: 'on-changes', template: `

{{hero.name}} can {{power}}

@@ -24,61 +24,51 @@ export class Hero { 'p {background: Yellow; padding: 8px; margin-top: 8px}' ] }) -export class MyHeroComponent implements OnChanges { +export class OnChangesComponent implements OnChanges { +// #docregion inputs @Input() hero: Hero; @Input() power: string; - @Input() reset: {}; +// #enddocregion inputs changeLog:string[] = []; + // #docregion ng-on-changes ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { - - // Empty the changeLog whenever 'reset' property changes - // hint: this is a way to respond programmatically to external value changes. - if (changes['reset']) { this.changeLog.length = 0; } - for (let propName in changes) { let prop = changes[propName]; - let cur = JSON.stringify(prop.currentValue) - let prev = JSON.stringify(prop.previousValue); // first time is {}; after is integer + let cur = JSON.stringify(prop.currentValue) + let prev = JSON.stringify(prop.previousValue); this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); } } + // #enddocregion ng-on-changes + + reset() { this.changeLog.length = 0; } } /***************************************/ @Component({ selector: 'on-changes-parent', - template: ` -
-

OnChanges

- -
Hero.name: does NOT trigger onChanges
-
Power: DOES trigger onChanges
-
triggers onChanges and clears the change log
- - -
- `, - styles: ['.parent {background: Lavender; padding: 10px; margin:100px 8px;}'], - directives: [MyHeroComponent] + templateUrl:'app/on-changes-parent.component.html', + styles: ['.parent {background: Lavender;}'], + directives: [OnChangesComponent] }) export class OnChangesParentComponent { hero:Hero; power:string; - resetTrigger = false; + title = 'OnChanges'; + @ViewChild(OnChangesComponent) childView:OnChangesComponent; constructor() { this.reset(); } reset(){ - // new Hero object every time; triggers onChange + // new Hero object every time; triggers onChanges this.hero = new Hero('Windstorm'); - // setting power only triggers onChange if this value is different + // setting power only triggers onChanges if this value is different this.power = 'sing'; - // always triggers onChange ... which is interpreted as a reset - this.resetTrigger = !this.resetTrigger; + this.childView && this.childView.reset(); } } diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo-parent.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo-parent.component.ts index 57e4754866..7362199eb6 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo-parent.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo-parent.component.ts @@ -21,7 +21,7 @@ import {LoggerService} from './logger.service';
{{msg}}
`, - styles: ['.parent {background: moccasin; padding: 10px; margin:100px 8px}'], + styles: ['.parent {background: moccasin}'], directives: [PeekABooComponent], providers: [LoggerService] }) diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo.component.ts index 5800554cf4..1a594d5fab 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo.component.ts @@ -1,21 +1,31 @@ -// #docregion -// #docregion lc-imports import { OnChanges, SimpleChange, OnInit, - // DoCheck, // not demonstrated + DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy } from 'angular2/core'; -// #docregion lc-imports import {Component, Input, Output} from 'angular2/core'; import {LoggerService} from './logger.service'; let nextId = 1; +// #docregion ngOnInit +export class PeekABoo implements OnInit { + constructor(private _logger:LoggerService) { } + + // implement OnInit's `ngOnInit` method + ngOnInit() { this._logIt(`OnInit`); } + + protected _logIt(msg:string){ + this._logger.log(`#${nextId++} ${msg}`); + } +} +// #enddocregion ngOnInit + @Component({ selector: 'peek-a-boo', template: '

Now you see my hero, {{name}}

', @@ -23,23 +33,23 @@ let nextId = 1; }) // Don't HAVE to mention the Lifecycle Hook interfaces // unless we want typing and tool support. -export class PeekABooComponent - implements OnChanges, OnInit,AfterContentInit,AfterContentChecked, - AfterViewInit, AfterViewChecked, OnDestroy { +export class PeekABooComponent extends PeekABoo implements + OnChanges, OnInit, DoCheck, + AfterContentInit,AfterContentChecked, + AfterViewInit, AfterViewChecked, + OnDestroy { @Input() name:string; - private _afterContentCheckedCounter = 1; - private _afterViewCheckedCounter = 1; - private _id = nextId++; - private _logger:LoggerService; - private _onChangesCounter = 1; private _verb = 'initialized'; - constructor(logger:LoggerService){ - this._logger = logger; + constructor(logger:LoggerService) { + super(logger); + + let is = this.name ? 'is' : 'is not'; + this._logIt(`name ${is} known at construction`); } - // only called if there is an @input variable set by parent. + // only called for/if there is an @input variable set by parent. ngOnChanges(changes: {[propertyName: string]: SimpleChange}){ let changesMsgs:string[] = [] for (let propName in changes) { @@ -50,49 +60,25 @@ export class PeekABooComponent changesMsgs.push(propName + ' ' + this._verb); } } - this._logIt(`onChanges (${this._onChangesCounter++}): ${changesMsgs.join('; ')}`); + this._logIt(`OnChanges: ${changesMsgs.join('; ')}`); this._verb = 'changed'; // next time it will be a change } - ngOnInit() { - this._logIt(`onInit`); - } - - ngAfterContentInit(){ - this._logIt(`afterContentInit`); - } - - // Called after every change detection check - // of the component (directive) CONTENT // Beware! Called frequently! - ngAfterContentChecked(){ - let counter = this._afterContentCheckedCounter++; - let msg = `afterContentChecked (${counter})`; - this._logIt(msg); - } + // Called in every change detection cycle anywhere on the page + ngDoCheck(){ this._logIt(`DoCheck`); } - ngAfterViewInit(){ - this._logIt(`afterViewInit`); - } + ngAfterContentInit(){ this._logIt(`AfterContentInit`); } - // Called after every change detection check - // of the component (directive) VIEW // Beware! Called frequently! + // Called in every change detection cycle anywhere on the page + ngAfterContentChecked(){ this._logIt(`AfterContentChecked`); } - ngAfterViewChecked(){ - let counter = this._afterViewCheckedCounter++; - let msg = `afterViewChecked (${counter})`; - this._logIt(msg); - } + ngAfterViewInit(){ this._logIt(`AfterViewInit`); } - ngOnDestroy() { - this._logIt(`onDestroy`); - } + // Beware! Called frequently! + // Called in every change detection cycle anywhere on the page + ngAfterViewChecked(){ this._logIt(`AfterViewChecked`); } - private _logIt(msg:string){ - // Don't tick or else - // the AfterContentChecked and AfterViewChecked recurse. - // Let parent call tick() - this._logger.log(`#${this._id } ${msg}`, true); - } -} \ No newline at end of file + ngOnDestroy() { this._logIt(`OnDestroy`); } +} diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts index 9a82b2faa8..6fb97b2935 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts @@ -8,48 +8,52 @@ import {Spy} from './spy.directive'; template: `

Spy Directive

- - - - - -

-
- {{hero}} -
- -

-- Spy Lifecycle Hook Log --

+

+ + + +

` + +// #docregion template + `
+ {{hero}} +
` +// #enddocregion template ++ `

-- Spy Lifecycle Hook Log --

{{msg}}
`, styles: [ - '.parent {background: khaki; padding: 10px; margin:100px 8px}', + '.parent {background: khaki;}', '.heroes {background: LightYellow; padding: 0 8px}' ], directives: [Spy], - providers: [LoggerService] + providers: [LoggerService] }) export class SpyParentComponent { newName = 'Herbie'; heroes:string[] = ['Windstorm', 'Magneta']; spyLog:string[]; - private _logger:LoggerService; - - constructor(logger:LoggerService){ - this._logger = logger; - this.spyLog = logger.logs; + 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(); } } diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts b/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts index 31c54ba9bc..8e3ee0e4c2 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts +++ b/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts @@ -1,33 +1,23 @@ // #docregion -import {Directive, Input, - OnInit, OnDestroy} from 'angular2/core'; - +import {Directive, OnInit, OnDestroy} from 'angular2/core'; import {LoggerService} from './logger.service'; -/***************************************/ let nextId = 1; +// #docregion spy-directive // Spy on any element to which it is applied. // Usage:
...
@Directive({selector: '[my-spy]'}) export class Spy implements OnInit, OnDestroy { - private _id = nextId++; - private _logger:LoggerService; + constructor(private _logger:LoggerService) { } - constructor(logger:LoggerService){ - this._logger = logger; - } + ngOnInit() { this._logIt(`onInit`); } - ngOnInit() { - this._logIt(`onInit`); - } - - ngOnDestroy() { - this._logIt(`onDestroy`); - } + ngOnDestroy() { this._logIt(`onDestroy`); } private _logIt(msg:string){ - this._logger.log(`Spy #${this._id } ${msg}`); + this._logger.log(`Spy #${nextId++} ${msg}`); } } +// #enddocregion spy-directive diff --git a/public/docs/_examples/lifecycle-hooks/ts/index.html b/public/docs/_examples/lifecycle-hooks/ts/index.html index a55cab995a..946d328629 100644 --- a/public/docs/_examples/lifecycle-hooks/ts/index.html +++ b/public/docs/_examples/lifecycle-hooks/ts/index.html @@ -5,7 +5,8 @@ Angular 2 Lifecycle Hooks - + + @@ -17,7 +18,7 @@