Skip to content

Commit

Permalink
feat(directives/linker): add new lifecycle hooks and implementation c…
Browse files Browse the repository at this point in the history
…hecker
  • Loading branch information
Hotell committed Feb 7, 2016
1 parent 031f5a5 commit ba70779
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/core/linker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export {
AfterContentInit,
AfterViewInit,
AfterContentChecked,
AfterViewChecked,
OnDestroy,
OnInit
} from './linker/directive_lifecycle_interfaces';
125 changes: 123 additions & 2 deletions src/core/linker/directive_lifecycle_interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ export enum LifecycleHooks {
OnInit,
OnDestroy,
AfterContentInit,
AfterViewInit
AfterContentChecked,
AfterViewInit,
AfterViewChecked,
_OnChildrenChanged
}

/**
Expand All @@ -12,14 +15,24 @@ export var LIFECYCLE_HOOKS_VALUES = [
LifecycleHooks.OnInit,
LifecycleHooks.OnDestroy,
LifecycleHooks.AfterContentInit,
LifecycleHooks.AfterViewInit
LifecycleHooks.AfterContentChecked,
LifecycleHooks.AfterViewInit,
LifecycleHooks.AfterViewChecked,
LifecycleHooks._OnChildrenChanged
];

export enum ChildrenChangeHook{
FromView,
FromContent
}

/**
* Lifecycle hooks are guaranteed to be called in the following order:
* - `OnInit` (after the first check only),
* - `AfterContentInit`,
* - `AfterContentChecked`,
* - `AfterViewInit`,
* - `AfterViewChecked`,
* - `OnDestroy` (at the very end before destruction)
*/

Expand Down Expand Up @@ -211,6 +224,54 @@ export interface OnDestroy { ngOnDestroy(); }
*/
export interface AfterContentInit { ngAfterContentInit(); }

/**
* Implement this interface to get notified after every check of your directive's content.
*
* ### Example ([live demo](http://plnkr.co/edit/tGdrytNEKQnecIPkD7NU?p=preview))
*
* ```typescript
* @Component({selector: 'child-cmp', template: `{{where}} child`})
* class ChildComponent {
* @Input() where: string;
* }
*
* @Component({selector: 'parent-cmp', template: `<ng-content></ng-content>`})
* class ParentComponent implements AfterContentChecked {
* @ContentChild(ChildComponent) contentChild: ChildComponent;
*
* constructor() {
* // contentChild is not initialized yet
* console.log(this.getMessage(this.contentChild));
* }
*
* ngAfterContentChecked() {
* // contentChild is updated after the content has been checked
* console.log('AfterContentChecked: ' + this.getMessage(this.contentChild));
* }
*
* private getMessage(cmp: ChildComponent): string {
* return cmp ? cmp.where + ' child' : 'no child';
* }
* }
*
* @Component({
* selector: 'app',
* template: `
* <parent-cmp>
* <button (click)="hasContent = !hasContent">Toggle content child</button>
* <child-cmp *ngIf="hasContent" where="content"></child-cmp>
* </parent-cmp>`,
* directives: [NgIf, ParentComponent, ChildComponent]
* })
* export class App {
* hasContent = true;
* }
*
* bootstrap(App).catch(err => console.error(err));
* ```
*/
export interface AfterContentChecked { ngAfterContentChecked(); }

/**
* Implement this interface to get notified when your component's view has been fully initialized.
*
Expand Down Expand Up @@ -257,3 +318,63 @@ export interface AfterContentInit { ngAfterContentInit(); }
* ```
*/
export interface AfterViewInit { ngAfterViewInit(); }

/**
* Implement this interface to get notified after every check of your component's view.
*
* ### Example ([live demo](http://plnkr.co/edit/0qDGHcPQkc25CXhTNzKU?p=preview))
*
* ```typescript
* @Component({selector: 'child-cmp', template: `{{where}} child`})
* class ChildComponent {
* @Input() where: string;
* }
*
* @Component({
* selector: 'parent-cmp',
* template: `
* <button (click)="showView = !showView">Toggle view child</button>
* <child-cmp *ngIf="showView" where="view"></child-cmp>`,
* directives: [NgIf, ChildComponent]
* })
* class ParentComponent implements AfterViewChecked {
* @ViewChild(ChildComponent) viewChild: ChildComponent;
* showView = true;
*
* constructor() {
* // viewChild is not initialized yet
* console.log(this.getMessage(this.viewChild));
* }
*
* ngAfterViewChecked() {
* // viewChild is updated after the view has been checked
* console.log('AfterViewChecked: ' + this.getMessage(this.viewChild));
* }
*
* private getMessage(cmp: ChildComponent): string {
* return cmp ? cmp.where + ' child' : 'no child';
* }
* }
*
* @Component({
* selector: 'app',
* template: `<parent-cmp></parent-cmp>`,
* directives: [ParentComponent]
* })
* export class App {
* }
*
* bootstrap(App).catch(err => console.error(err));
* ```
*/
export interface AfterViewChecked { ngAfterViewChecked(); }


/**
* this is a private interface which method is called from within postLink Fn or
* from children which are queryied via one of `@Query*` decorators.
* Component/Directive which is queried needs to call this from one of After(View|Content)Init methods to notify
* origin component about changes within After(Contet|View)Checked
* @private
*/
export interface OnChildrenChanged { _ngOnChildrenChanged?(type: ChildrenChangeHook, onFirstChangeDoneCb?: Function[]); }
38 changes: 36 additions & 2 deletions src/core/linker/directive_lifecycles_reflector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Type} from '../../facade/lang';
import {LifecycleHooks} from './directive_lifecycle_interfaces';
import { Type } from '../../facade/lang';
import { LifecycleHooks } from './directive_lifecycle_interfaces';

export function hasLifecycleHook(lcInterface: LifecycleHooks, token: any): boolean {

Expand All @@ -10,14 +10,48 @@ export function hasLifecycleHook(lcInterface: LifecycleHooks, token: any): boole
switch (lcInterface) {
case LifecycleHooks.AfterContentInit:
return !!proto.ngAfterContentInit;
case LifecycleHooks.AfterContentChecked:
return !!proto.ngAfterContentChecked;
case LifecycleHooks.AfterViewInit:
return !!proto.ngAfterViewInit;
case LifecycleHooks.AfterViewChecked:
return !!proto.ngAfterViewChecked;
case LifecycleHooks.OnDestroy:
return !!proto.ngOnDestroy;
case LifecycleHooks.OnInit:
return !!proto.ngOnInit;
case LifecycleHooks._OnChildrenChanged:
return !!proto._ngOnChildrenChanged;
default:
return false;
}

}


export type ImplementedLifeCycleHooks = {
ngOnInit: boolean,
ngAfterContentInit: boolean,
ngAfterContentChecked: boolean,
ngAfterViewInit: boolean,
ngAfterViewChecked: boolean,
ngOnDestroy: boolean,
_ngOnChildrenChanged: boolean
}
/**
*
* @param type
* @returns
* @internal
*/
export function resolveImplementedLifeCycleHooks( type: Type ): ImplementedLifeCycleHooks {
return {
ngOnInit: hasLifecycleHook( LifecycleHooks.OnInit, type ),
ngAfterContentInit: hasLifecycleHook( LifecycleHooks.AfterContentInit, type ),
ngAfterContentChecked: hasLifecycleHook( LifecycleHooks.AfterContentChecked, type ),
ngAfterViewInit: hasLifecycleHook( LifecycleHooks.AfterViewInit, type ),
ngAfterViewChecked: hasLifecycleHook( LifecycleHooks.AfterViewChecked, type ),
ngOnDestroy: hasLifecycleHook( LifecycleHooks.OnDestroy, type ),
_ngOnChildrenChanged: hasLifecycleHook( LifecycleHooks._OnChildrenChanged, type )
}
}
34 changes: 31 additions & 3 deletions test/core/linker/directive_lifecycles_reflector.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {expect} from 'chai';
import {hasLifecycleHook} from '../../../src/core/linker/directive_lifecycles_reflector';
import {LifecycleHooks} from '../../../src/core/linker/directive_lifecycle_interfaces';
import { expect } from 'chai';
import {
hasLifecycleHook,
resolveImplementedLifeCycleHooks
} from '../../../src/core/linker/directive_lifecycles_reflector';
import { LifecycleHooks } from '../../../src/core/linker/directive_lifecycle_interfaces';

describe( `linker/lifecycles_reflector`, ()=> {

Expand All @@ -23,4 +26,29 @@ describe( `linker/lifecycles_reflector`, ()=> {

} );

it( `should create BooleanMap with implemented lifecycles`, ()=> {

class Foo {
ngOnInit() {}

ngAfterViewInit() {}

ngOnDestroy() {}
}

const actual = resolveImplementedLifeCycleHooks(Foo);
const expected = {
ngOnInit: true,
ngAfterContentInit: false,
ngAfterContentChecked: false,
ngAfterViewInit: true,
ngAfterViewChecked: false,
ngOnDestroy: true,
_ngOnChildrenChanged: false
};

expect( actual ).to.deep.equal( expected );

} );

} );

0 comments on commit ba70779

Please sign in to comment.