fix(ivy): generate lifecycle pattern (#21865)
Implement the lifecycle pattern defined in #21793 PR Close #21865
This commit is contained in:
parent
d3c2aa5f95
commit
f816666ede
|
@ -12,6 +12,7 @@ import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertA
|
||||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||||
import {AST} from '../expression_parser/ast';
|
import {AST} from '../expression_parser/ast';
|
||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
|
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
import {CssSelector} from '../selector';
|
import {CssSelector} from '../selector';
|
||||||
|
@ -20,8 +21,6 @@ import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Name of the context parameter passed into a template function */
|
/** Name of the context parameter passed into a template function */
|
||||||
const CONTEXT_NAME = 'ctx';
|
const CONTEXT_NAME = 'ctx';
|
||||||
|
|
||||||
|
@ -49,6 +48,12 @@ export function compileDirective(
|
||||||
const templateFactory = createFactory(directive.type, outputCtx, reflector);
|
const templateFactory = createFactory(directive.type, outputCtx, reflector);
|
||||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
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) !;
|
const className = identifierName(directive.type) !;
|
||||||
className || error(`Cannot resolver the name of ${directive.type}`);
|
className || error(`Cannot resolver the name of ${directive.type}`);
|
||||||
|
|
||||||
|
@ -114,6 +119,21 @@ export function compileComponent(
|
||||||
.buildTemplateFunction(template, []);
|
.buildTemplateFunction(template, []);
|
||||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
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) !;
|
const className = identifierName(component.type) !;
|
||||||
className || error(`Cannot resolver the name of ${component.type}`);
|
className || error(`Cannot resolver the name of ${component.type}`);
|
||||||
|
@ -282,6 +302,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
|
|
||||||
templateVisitAll(this, asts);
|
templateVisitAll(this, asts);
|
||||||
|
|
||||||
|
const creationMode = this._creationMode.length > 0 ?
|
||||||
|
[o.ifStmt(o.variable(CREATION_MODE_FLAG), this._creationMode)] :
|
||||||
|
[];
|
||||||
|
|
||||||
return o.fn(
|
return o.fn(
|
||||||
[
|
[
|
||||||
new o.FnParam(this.contextParameter, null), new o.FnParam(CREATION_MODE_FLAG, o.BOOL_TYPE)
|
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,
|
...this._prefix,
|
||||||
|
|
||||||
// Creating mode (i.e. if (cm) { ... })
|
// Creating mode (i.e. if (cm) { ... })
|
||||||
o.ifStmt(o.variable(CREATION_MODE_FLAG), this._creationMode),
|
...creationMode,
|
||||||
|
|
||||||
// Binding mode (i.e. ɵp(...))
|
// Binding mode (i.e. ɵp(...))
|
||||||
...this._bindingMode,
|
...this._bindingMode,
|
||||||
|
@ -464,6 +488,12 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
|
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);
|
// e.g. r(0, 0);
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._refreshMode, directive.sourceSpan, R3.refreshComponent, o.literal(directiveIndex),
|
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);
|
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])})));
|
||||||
|
}
|
||||||
|
|
|
@ -237,6 +237,8 @@ describe('r3_view_compiler', () => {
|
||||||
IDENT.ɵe();
|
IDENT.ɵe();
|
||||||
IDENT.ɵT(3, '!');
|
IDENT.ɵT(3, '!');
|
||||||
}
|
}
|
||||||
|
ChildComponent.ngComponentDef.h(1, 0);
|
||||||
|
SomeDirective.ngDirectiveDef.h(2, 0);
|
||||||
IDENT.ɵr(1, 0);
|
IDENT.ɵr(1, 0);
|
||||||
IDENT.ɵr(2, 0);
|
IDENT.ɵr(2, 0);
|
||||||
}
|
}
|
||||||
|
@ -300,6 +302,7 @@ describe('r3_view_compiler', () => {
|
||||||
IDENT.ɵe();
|
IDENT.ɵe();
|
||||||
}
|
}
|
||||||
const IDENT = IDENT.ɵm(1);
|
const IDENT = IDENT.ɵm(1);
|
||||||
|
IfDirective.ngDirectiveDef.h(3,2);
|
||||||
IDENT.ɵcR(2);
|
IDENT.ɵcR(2);
|
||||||
IDENT.ɵr(3,2);
|
IDENT.ɵr(3,2);
|
||||||
IDENT.ɵcr();
|
IDENT.ɵcr();
|
||||||
|
@ -443,6 +446,90 @@ describe('r3_view_compiler', () => {
|
||||||
expectEmit(source, locals, 'Incorrect locals constant definition');
|
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: \`
|
||||||
|
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||||
|
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
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', () => {
|
describe('template variables', () => {
|
||||||
const shared = {
|
const shared = {
|
||||||
shared: {
|
shared: {
|
||||||
|
@ -545,6 +632,7 @@ describe('r3_view_compiler', () => {
|
||||||
IDENT.ɵe();
|
IDENT.ɵe();
|
||||||
}
|
}
|
||||||
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
||||||
|
ForOfDirective.ngDirectiveDef.h(2, 1);
|
||||||
IDENT.ɵcR(1);
|
IDENT.ɵcR(1);
|
||||||
IDENT.ɵr(2, 1);
|
IDENT.ɵr(2, 1);
|
||||||
IDENT.ɵcr();
|
IDENT.ɵcr();
|
||||||
|
@ -619,6 +707,7 @@ describe('r3_view_compiler', () => {
|
||||||
IDENT.ɵe();
|
IDENT.ɵe();
|
||||||
}
|
}
|
||||||
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
||||||
|
IDENT.h(2,1);
|
||||||
IDENT.ɵcR(1);
|
IDENT.ɵcR(1);
|
||||||
IDENT.ɵr(2, 1);
|
IDENT.ɵr(2, 1);
|
||||||
IDENT.ɵcr();
|
IDENT.ɵcr();
|
||||||
|
@ -636,6 +725,7 @@ describe('r3_view_compiler', () => {
|
||||||
}
|
}
|
||||||
const IDENT = ctx0.$implicit;
|
const IDENT = ctx0.$implicit;
|
||||||
IDENT.ɵp(4, 'forOf', IDENT.ɵb(IDENT.infos));
|
IDENT.ɵp(4, 'forOf', IDENT.ɵb(IDENT.infos));
|
||||||
|
IDENT.h(5,4);
|
||||||
IDENT.ɵt(2, IDENT.ɵb1('', IDENT.name, ''));
|
IDENT.ɵt(2, IDENT.ɵb1('', IDENT.name, ''));
|
||||||
IDENT.ɵcR(4);
|
IDENT.ɵcR(4);
|
||||||
IDENT.ɵr(5, 4);
|
IDENT.ɵr(5, 4);
|
||||||
|
|
Loading…
Reference in New Issue