From f816666ede46eb0ef070e33e71effec56e4197c3 Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Mon, 29 Jan 2018 10:36:56 -0800 Subject: [PATCH] fix(ivy): generate lifecycle pattern (#21865) Implement the lifecycle pattern defined in #21793 PR Close #21865 --- .../compiler/src/render3/r3_view_compiler.ts | 41 ++++++++- .../test/render3/r3_view_compiler_spec.ts | 90 +++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/render3/r3_view_compiler.ts b/packages/compiler/src/render3/r3_view_compiler.ts index 3b9f74a8f2..d78550bd97 100644 --- a/packages/compiler/src/render3/r3_view_compiler.ts +++ b/packages/compiler/src/render3/r3_view_compiler.ts @@ -12,6 +12,7 @@ import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertA import {ConstantPool, DefinitionKind} from '../constant_pool'; import {AST} from '../expression_parser/ast'; import {Identifiers} from '../identifiers'; +import {LifecycleHooks} from '../lifecycle_reflector'; import * as o from '../output/output_ast'; import {ParseSourceSpan} from '../parse_util'; import {CssSelector} from '../selector'; @@ -20,8 +21,6 @@ import {OutputContext, error} from '../util'; import {Identifiers as R3} from './r3_identifiers'; - - /** Name of the context parameter passed into a template function */ const CONTEXT_NAME = 'ctx'; @@ -49,6 +48,12 @@ export function compileDirective( const templateFactory = createFactory(directive.type, outputCtx, reflector); definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); + // e.g 'inputs: {a: 'a'}` + if (Object.getOwnPropertyNames(directive.inputs).length > 0) { + definitionMapValues.push( + {key: 'inputs', quoted: false, value: mapToExpression(directive.inputs)}); + } + const className = identifierName(directive.type) !; className || error(`Cannot resolver the name of ${directive.type}`); @@ -114,6 +119,21 @@ export function compileComponent( .buildTemplateFunction(template, []); definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false}); + // e.g `inputs: {a: 'a'}` + if (Object.getOwnPropertyNames(component.inputs).length > 0) { + definitionMapValues.push( + {key: 'inputs', quoted: false, value: mapToExpression(component.inputs)}); + } + + // e.g. `features: [NgOnChangesFeature(MyComponent)]` + const features: o.Expression[] = []; + if (component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges)) { + features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([outputCtx.importExpr( + component.type.reference)])); + } + if (features.length) { + definitionMapValues.push({key: 'features', quoted: false, value: o.literalArr(features)}); + } const className = identifierName(component.type) !; className || error(`Cannot resolver the name of ${component.type}`); @@ -282,6 +302,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { templateVisitAll(this, asts); + const creationMode = this._creationMode.length > 0 ? + [o.ifStmt(o.variable(CREATION_MODE_FLAG), this._creationMode)] : + []; + return o.fn( [ new o.FnParam(this.contextParameter, null), new o.FnParam(CREATION_MODE_FLAG, o.BOOL_TYPE) @@ -291,7 +315,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { ...this._prefix, // Creating mode (i.e. if (cm) { ... }) - o.ifStmt(o.variable(CREATION_MODE_FLAG), this._creationMode), + ...creationMode, // Binding mode (i.e. ɵp(...)) ...this._bindingMode, @@ -464,6 +488,12 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { o.importExpr(R3.bind).callFn([convertedBinding.currValExpr])); } + // e.g. MyDirective.ngDirectiveDef.h(0, 0); + this._hostMode.push( + this.definitionOf(directiveType, kind) + .callMethod(R3.HOST_BINDING_METHOD, [o.literal(directiveIndex), o.literal(nodeIndex)]) + .toStmt()); + // e.g. r(0, 0); this.instruction( this._refreshMode, directive.sourceSpan, R3.refreshComponent, o.literal(directiveIndex), @@ -695,3 +725,8 @@ function asLiteral(value: any): o.Expression { } return o.literal(value, o.INFERRED_TYPE); } + +function mapToExpression(map: {[key: string]: any}): o.Expression { + return o.literalMap(Object.getOwnPropertyNames(map).map( + key => ({key, quoted: false, value: o.literal(map[key])}))); +} diff --git a/packages/compiler/test/render3/r3_view_compiler_spec.ts b/packages/compiler/test/render3/r3_view_compiler_spec.ts index 1c9ec8afce..e3fe2a4ee5 100644 --- a/packages/compiler/test/render3/r3_view_compiler_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_spec.ts @@ -237,6 +237,8 @@ describe('r3_view_compiler', () => { IDENT.ɵe(); IDENT.ɵT(3, '!'); } + ChildComponent.ngComponentDef.h(1, 0); + SomeDirective.ngDirectiveDef.h(2, 0); IDENT.ɵr(1, 0); IDENT.ɵr(2, 0); } @@ -300,6 +302,7 @@ describe('r3_view_compiler', () => { IDENT.ɵe(); } const IDENT = IDENT.ɵm(1); + IfDirective.ngDirectiveDef.h(3,2); IDENT.ɵcR(2); IDENT.ɵr(3,2); IDENT.ɵcr(); @@ -443,6 +446,90 @@ describe('r3_view_compiler', () => { expectEmit(source, locals, 'Incorrect locals constant definition'); }); + describe('lifecycle hooks', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Input, NgModule} from '@angular/core'; + + let events: string[] = []; + + @Component({selector: 'lifecycle-comp', template: ''}) + export class LifecycleComp { + @Input('name') nameMin: string; + + ngOnChanges() { events.push('changes' + this.nameMin); } + + ngOnInit() { events.push('init' + this.nameMin); } + ngDoCheck() { events.push('check' + this.nameMin); } + + ngAfterContentInit() { events.push('content init' + this.nameMin); } + ngAfterContentChecked() { events.push('content check' + this.nameMin); } + + ngAfterViewInit() { events.push('view init' + this.nameMin); } + ngAfterViewChecked() { events.push('view check' + this.nameMin); } + + ngOnDestroy() { events.push(this.nameMin); } + } + + @Component({ + selector: 'simple-layout', + template: \` + + + \` + }) + export class SimpleLayout { + name1 = '1'; + name2 = '2'; + } + + @NgModule({declarations: [LifecycleComp, SimpleLayout]} + export class LifecycleModule {} + ` + } + }; + + it('should gen hooks with a few simple components', () => { + const LifecycleCompDefinition = ` + static ngComponentDef = IDENT.ɵdefineComponent({ + type: LifecycleComp, + tag: 'lifecycle-comp', + factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, + template: function LifecycleComp_Template(ctx: any, cm: boolean) {}, + inputs: {nameMin: 'name'}, + features: [IDENT.ɵNgOnChangesFeature(LifecycleComp)] + });`; + + const SimpleLayoutDefinition = ` + static ngComponentDef = IDENT.ɵdefineComponent({ + type: SimpleLayout, + tag: 'simple-layout', + factory: function SimpleLayout_Factory() { return new SimpleLayout(); }, + template: function SimpleLayout_Template(ctx: any, cm: boolean) { + if (cm) { + IDENT.ɵE(0, LifecycleComp); + IDENT.ɵe(); + IDENT.ɵE(2, LifecycleComp); + IDENT.ɵe(); + } + IDENT.ɵp(0, 'name', IDENT.ɵb(ctx.name1)); + IDENT.ɵp(2, 'name', IDENT.ɵb(ctx.name2)); + IDENT.h(1, 0); + IDENT.h(3, 2); + IDENT.ɵr(1, 0); + IDENT.ɵr(3, 2); + } + });`; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition'); + expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition'); + }); + }); + describe('template variables', () => { const shared = { shared: { @@ -545,6 +632,7 @@ describe('r3_view_compiler', () => { IDENT.ɵe(); } IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items)); + ForOfDirective.ngDirectiveDef.h(2, 1); IDENT.ɵcR(1); IDENT.ɵr(2, 1); IDENT.ɵcr(); @@ -619,6 +707,7 @@ describe('r3_view_compiler', () => { IDENT.ɵe(); } IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items)); + IDENT.h(2,1); IDENT.ɵcR(1); IDENT.ɵr(2, 1); IDENT.ɵcr(); @@ -636,6 +725,7 @@ describe('r3_view_compiler', () => { } const IDENT = ctx0.$implicit; IDENT.ɵp(4, 'forOf', IDENT.ɵb(IDENT.infos)); + IDENT.h(5,4); IDENT.ɵt(2, IDENT.ɵb1('', IDENT.name, '')); IDENT.ɵcR(4); IDENT.ɵr(5, 4);