diff --git a/public/docs/_examples/lifecycle-hooks/ts/.gitignore b/public/docs/_examples/lifecycle-hooks/ts/.gitignore new file mode 100644 index 0000000000..cf44e148ba --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/.gitignore @@ -0,0 +1 @@ +**/*.js \ No newline at end of file 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 new file mode 100644 index 0000000000..1774470535 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/after-content.component.ts @@ -0,0 +1,102 @@ +// #docregion +import { + Component, Input, Output, + AfterContentChecked, AfterContentInit, ContentChild, + AfterViewInit, ViewChild +} from 'angular2/core'; + +import {ChildComponent} from './child.component'; +import {LoggerService} from './logger.service'; + +@Component({ + selector: 'after-content', + template: ` +
+
-- child content begins --
+ + + +
-- child content ends --
+
+ `, + styles: ['.after-content {background: LightCyan; padding: 8px;}'], + +}) +export class AfterContentComponent + implements AfterContentChecked, AfterContentInit, AfterViewInit { + + private _logger:LoggerService; + + constructor(logger:LoggerService){ + this._logger = logger; + logger.log('AfterContent ctor: ' + this._getMessage()); + } + + // 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; + + + ///// Hooks + ngAfterContentInit() { + // contentChild is set after the content has been initialized + this._logger.log('AfterContentInit: ' + this._getMessage()); + } + + 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()); + } + + private _getMessage(): string { + let cmp = this.contentChild; + return cmp ? `"${cmp.hero}" child content` : 'no child content'; + } + +} + +/***************************************/ + +@Component({ + selector: 'after-content-parent', + template: ` +
+

AfterContent

+ + + + + + + + +

-- Lifecycle Hook Log --

+
{{msg}}
+
+ `, + styles: ['.parent {background: powderblue; padding: 8px; margin:100px 8px;}'], + directives: [AfterContentComponent, ChildComponent], + providers:[LoggerService] +}) +export class AfterContentParentComponent { + + hookLog:string[]; + hero = 'Magneta'; + showChild = true; + + constructor(logger:LoggerService){ + this.hookLog = logger.logs; + } +} 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 new file mode 100644 index 0000000000..47f12b53ae --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/after-view.component.ts @@ -0,0 +1,80 @@ +// #docregion +import { + Component, Input, Output, + AfterContentInit, ContentChild, + AfterViewChecked, AfterViewInit, ViewChild +} from 'angular2/core'; + +import {ChildComponent} from './child.component'; +import {LoggerService} from './logger.service'; + +@Component({ + selector: 'after-view-parent', + template: ` +
+

AfterView

+ +
+ + + + +
+ +

-- Lifecycle Hook Log --

+
{{msg}}
+
+ `, + styles: ['.parent {background: burlywood; padding: 8px; margin:100px 8px;}'], + directives: [ChildComponent], + providers:[LoggerService] +}) +export class AfterViewParentComponent + implements AfterContentInit, AfterViewChecked, AfterViewInit { + + private _logger:LoggerService; + + constructor(logger:LoggerService){ + this._logger = logger; + this.hookLog = logger.logs; + logger.log('AfterView ctor: ' + this._getMessage()); + } + + 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`); + } + + 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.ts b/public/docs/_examples/lifecycle-hooks/ts/app/app.component.ts new file mode 100644 index 0000000000..14f1bdab24 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/app.component.ts @@ -0,0 +1,43 @@ +// #docregion +import {Component} from 'angular2/core'; + +import {AfterContentParentComponent} from './after-content.component'; +import {AfterViewParentComponent} from './after-view.component'; +import {CounterParentComponent} from './counter.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: ` + + + + + + + `, + directives: [ + AfterContentParentComponent, + AfterViewParentComponent, + OnChangesParentComponent, + PeekABooParentComponent, + SpyParentComponent, + CounterParentComponent + ] +}) +export class AppComponent { +} diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/boot.ts b/public/docs/_examples/lifecycle-hooks/ts/app/boot.ts new file mode 100644 index 0000000000..4651bd780a --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/boot.ts @@ -0,0 +1,4 @@ +import {bootstrap} from 'angular2/platform/browser'; +import {AppComponent} from './app.component'; + +bootstrap(AppComponent).catch(err => console.error(err)); \ No newline at end of file diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts new file mode 100644 index 0000000000..a027d9180c --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/child.component.ts @@ -0,0 +1,20 @@ +// #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 new file mode 100644 index 0000000000..7a502a25b0 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/counter.component.ts @@ -0,0 +1,86 @@ +// #docregion +import { + Component, Input, Output, + OnChanges, SimpleChange, +} from 'angular2/core'; + +import {Spy} from './spy.directive'; +import {LoggerService} from './logger.service'; + +@Component({ + selector: 'my-counter', + template: ` +
+ Counter = {{counter}} + +
-- Counter Change Log --
+
{{chg}}
+
+ `, + styles: ['.counter {background: LightYellow; padding: 8px; margin-top: 8px}'], + directives:[Spy] +}) +export class MyCounter implements OnChanges { + @Input() counter: number; + changeLog:string[] = []; + + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + + // 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 prop = changes['counter']; + let cur = prop.currentValue; + let prev = JSON.stringify(prop.previousValue); // first time is {}; after is integer + this.changeLog.push(`counter: currentValue = ${cur}, previousValue = ${prev}`); + } + +} + +/***************************************/ + +@Component({ + selector: 'counter-parent', + template: ` +
+

Counter Spy

+ + + + + + +

-- Spy Lifecycle Hook Log --

+
{{msg}}
+
+ `, + styles: ['.parent {background: gold; padding: 10px; margin:100px 8px;}'], + directives: [MyCounter], + 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; + } + + reset(){ + this._logger.log('-- reset --'); + this.value=0; + } +} + diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts b/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts new file mode 100644 index 0000000000..209bd6a2a5 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/logger.service.ts @@ -0,0 +1,19 @@ +import {Injectable} from 'angular2/core'; + +@Injectable() +export class LoggerService { + logs:string[] = []; + + log(msg:string, noTick:boolean = false) { + if (!noTick) { this.tick(); } + this.logs.push(msg); + } + + clear() {this.logs.length = 0;} + + tick() { + setTimeout(() => { + // console.log('tick') + }, 0); + } +} 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 new file mode 100644 index 0000000000..32a2185aba --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/on-changes.component.ts @@ -0,0 +1,84 @@ +// #docregion +import { + Component, Input, Output, + OnChanges, SimpleChange, +} from 'angular2/core'; + + +export class Hero { + constructor(public name:string){} +} + +@Component({ + selector: 'my-hero', + 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 MyHeroComponent implements OnChanges { + @Input() hero: Hero; + @Input() power: string; + @Input() reset: {}; + + changeLog:string[] = []; + + 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 + this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); + } + } +} + +/***************************************/ + +@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] +}) +export class OnChangesParentComponent { + hero:Hero; + power:string; + resetTrigger = false; + + constructor() { + this.reset(); + } + + reset(){ + // new Hero object every time; triggers onChange + this.hero = new Hero('Windstorm'); + // setting power only triggers onChange if this value is different + this.power = 'sing'; + // always triggers onChange ... which is interpreted as a reset + this.resetTrigger = !this.resetTrigger; + } +} 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 new file mode 100644 index 0000000000..57e4754866 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo-parent.component.ts @@ -0,0 +1,54 @@ +// #docregion +import {Component} from 'angular2/core'; +import {PeekABooComponent} from './peek-a-boo.component' +import {LoggerService} from './logger.service'; + +@Component({ + selector: 'peek-a-boo-parent', + template: ` +
+

Peek-A-Boo

+ + + + + + + +

-- Lifecycle Hook Log --

+
{{msg}}
+
+ `, + styles: ['.parent {background: moccasin; padding: 10px; margin:100px 8px}'], + directives: [PeekABooComponent], + 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(); + } +} 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 new file mode 100644 index 0000000000..5800554cf4 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/peek-a-boo.component.ts @@ -0,0 +1,98 @@ +// #docregion +// #docregion lc-imports +import { + OnChanges, SimpleChange, + OnInit, + // DoCheck, // not demonstrated + 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; + +@Component({ + selector: 'peek-a-boo', + template: '

Now you see my hero, {{name}}

', + 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 + implements OnChanges, OnInit,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; + } + + // only called if there is an @input variable set by parent. + ngOnChanges(changes: {[propertyName: string]: SimpleChange}){ + 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 (${this._onChangesCounter++}): ${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); + } + + ngAfterViewInit(){ + this._logIt(`afterViewInit`); + } + + // Called after every change detection check + // of the component (directive) VIEW + // Beware! Called frequently! + + ngAfterViewChecked(){ + let counter = this._afterViewCheckedCounter++; + let msg = `afterViewChecked (${counter})`; + this._logIt(msg); + } + + ngOnDestroy() { + this._logIt(`onDestroy`); + } + + 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 diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts b/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts new file mode 100644 index 0000000000..9a82b2faa8 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/spy.component.ts @@ -0,0 +1,55 @@ +// #docregion +import {Component} from 'angular2/core'; +import {LoggerService} from './logger.service'; +import {Spy} from './spy.directive'; + +@Component({ + selector: 'spy-parent', + template: ` +
+

Spy Directive

+ + + + + +

+
+ {{hero}} +
+ +

-- Spy Lifecycle Hook Log --

+
{{msg}}
+
+ `, + styles: [ + '.parent {background: khaki; padding: 10px; margin:100px 8px}', + '.heroes {background: LightYellow; padding: 0 8px}' + ], + directives: [Spy], + 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; + } + + addHero() { + if (this.newName.trim()) { + this.heroes.push(this.newName.trim()); + this.newName = ''; + } + } + + reset(){ + this._logger.log('-- reset --'); + this.heroes.length = 0; + } +} diff --git a/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts b/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts new file mode 100644 index 0000000000..31c54ba9bc --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/app/spy.directive.ts @@ -0,0 +1,33 @@ +// #docregion +import {Directive, Input, + OnInit, OnDestroy} from 'angular2/core'; + +import {LoggerService} from './logger.service'; + +/***************************************/ +let nextId = 1; + +// 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(logger:LoggerService){ + this._logger = logger; + } + + ngOnInit() { + this._logIt(`onInit`); + } + + ngOnDestroy() { + this._logIt(`onDestroy`); + } + + private _logIt(msg:string){ + this._logger.log(`Spy #${this._id } ${msg}`); + } +} diff --git a/public/docs/_examples/lifecycle-hooks/ts/example-config.json b/public/docs/_examples/lifecycle-hooks/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/lifecycle-hooks/ts/index.html b/public/docs/_examples/lifecycle-hooks/ts/index.html new file mode 100644 index 0000000000..b706287879 --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/index.html @@ -0,0 +1,29 @@ + + + + + + Angular 2 Lifecycle Hooks + + + + + + + + + Loading... + + + \ No newline at end of file diff --git a/public/docs/_examples/lifecycle-hooks/ts/plnkr.json b/public/docs/_examples/lifecycle-hooks/ts/plnkr.json new file mode 100644 index 0000000000..366b8325ce --- /dev/null +++ b/public/docs/_examples/lifecycle-hooks/ts/plnkr.json @@ -0,0 +1,7 @@ +{ + "description": "Lifecycle Hooks", + "files":["!**/*.d.ts", "!**/*.js"], + "tags": ["lifecycle", "hooks", + "onInit", "onDestroy", "onChange", + "ngOnInit", "ngOnDestroy", "ngOnChange"] +} \ No newline at end of file diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index adfa83188e..323f39208f 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -48,6 +48,11 @@ "intro": "Discover the basics of screen navigation with the Angular 2 router" }, + "lifecycle-hooks": { + "title": "Lifecycle Hooks", + "intro": "Angular calls lifecycle hook methods on our directives and components as it creates, changes, and destroys them." + }, + "attribute-directives": { "title": "Attribute Directives", "intro": "Attribute directives attach behavior to elements." diff --git a/public/docs/ts/latest/guide/lifecycle-hooks.jade b/public/docs/ts/latest/guide/lifecycle-hooks.jade new file mode 100644 index 0000000000..2750797013 --- /dev/null +++ b/public/docs/ts/latest/guide/lifecycle-hooks.jade @@ -0,0 +1,90 @@ +include ../../../../_includes/_util-fns + +:marked + # Component Lifecycle + A Component has a lifecycle managed by Angular itself. Angular creates it, renders it, creates and renders its children, + checks it when its data-bound properties change, and destroys it before removing it from the DOM. + + Angular offers **Lifecycle hooks** + that give us visibility into these key moments and the ability to act when they occur. + + We cover these hooks in this chapter and demonstrate how they work in code. + + [Live Example](/resources/live-examples/lifecycle-hooks/ts/plnkr.html) + + + +.l-main-section +:marked + ## The Lifecycle Hooks + Directive and component instances have a lifecycle + as Angular creates, updates, and destroys them. + + Developers can tap into key moments in that lifecycle by implementing + one or more of the "Lifecycle Hook" interfaces, all of them available + in the `angular2/core` library. + + Here is the complete lifecycle hook interface inventory: + + * `OnInit` + * `OnDestroy` + * `DoCheck` + * `OnChanges` + * `AfterContentInit` + * `AfterContentChecked` + * `AfterViewInit` + * `AfterViewChecked` + + No directive or component will implement all of them and some of them only make + sense for components. + + Each interface has a single hook method whose name is the interface name prefixed with `ng`. + For example, the `OnInit` interface has a hook method names `ngOnInit`. + + Angular calls these hook methods in the following order: + * `ngOnChanges` - called when an input or output binding value changes + * `ngOnInit` - after the first `ngOnChanges` + * `ngDoCheck` - developer's custom change detection + * `ngAfterContentInit` - after component content initialized + * `ngAfterContentChecked` - after every check of component content + * `ngAfterViewInit` - after component's view(s) are initialized + * `ngAfterViewChecked` - after every check of a component's view(s) + * `ngOnDestroy` - just before the directive is destroyed. + + The [live example](/resources/live-examples/lifecycle-hooks/ts/plnkr.html) demonstrates + these hooks. + +:marked + ## Peek-a-boo + The `PeekABooComponent` demonstrates all of the hooks in the same component. +.l-sub-section + :marked + Except for `DoCheck`. If our component superseded regular Angular change detection + with its own change detection processing + we would also add a `ngDoCheck` method. We would **not** implement `ngOnChanges`. + We write either `ngOnChanges` or `ngDoCheck`, not both. + + Custom change detection and `ngDoCheck` are on our documentation backlog. +:marked + Peek-a-boo is a demo. We'd rarely if ever implement all interfaces like this in real life. + + We look forward to explaining the Peek-a-boo example and the other lifecycle hook examples in + an update to this chapter. Meanwhile, please enjoy poking around in the + [code](/resources/live-examples/lifecycle-hooks/ts/plnkr.html). + + ## Interface optional? + The lifecycle interfaces are optional. + We recommend adding them to benefit from TypeScript's strong typing and editor tooling. + + But they disappear from the transpiled JavaScript. + Angular can't see them at runtime. And they are useless to someone developing in + a language without interfaces (such as pure JavaScript). + + Fortunately, they aren't necessary. + We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves. + + Angular instead inspects our directive and component classes + and calls the hook methods *if they are defined*. + Angular will find and call methods like `ngOnInit()`, with or without the interfaces.