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: ` -
+ {{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: `+ {{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: `{{hero.name}} can {{power}}
+ +Power: | |
Hero.name: |
{{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: ` -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: `+ + + +
` + +// #docregion template + `