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);