fix(ivy): ensure pipe declarations are populated lazily when a forward ref is detected (#26765)

PR Close #26765
This commit is contained in:
Matias Niemelä 2018-10-25 11:13:14 -07:00
parent 2fd4c372d5
commit 8171a2ab94
5 changed files with 97 additions and 19 deletions

View File

@ -204,7 +204,7 @@ export class ComponentDecoratorHandler implements
// analyzed and the full compilation scope for the component can be realized. // analyzed and the full compilation scope for the component can be realized.
pipes: EMPTY_MAP, pipes: EMPTY_MAP,
directives: EMPTY_MAP, directives: EMPTY_MAP,
wrapDirectivesInClosure: false, // wrapDirectivesAndPipesInClosure: false, //
animations, animations,
viewProviders viewProviders
}, },
@ -237,8 +237,8 @@ export class ComponentDecoratorHandler implements
const {pipes, containsForwardDecls} = scope; const {pipes, containsForwardDecls} = scope;
const directives = new Map<string, Expression>(); const directives = new Map<string, Expression>();
scope.directives.forEach((meta, selector) => directives.set(selector, meta.directive)); scope.directives.forEach((meta, selector) => directives.set(selector, meta.directive));
const wrapDirectivesInClosure: boolean = !!containsForwardDecls; const wrapDirectivesAndPipesInClosure: boolean = !!containsForwardDecls;
metadata = {...metadata, directives, pipes, wrapDirectivesInClosure}; metadata = {...metadata, directives, pipes, wrapDirectivesAndPipesInClosure};
} }
const res = compileComponentFromMetadata(metadata, pool, makeBindingParser()); const res = compileComponentFromMetadata(metadata, pool, makeBindingParser());

View File

@ -2225,6 +2225,80 @@ describe('compiler compliance', () => {
expectEmit(source, MyComponentDefinition, 'Invalid component definition'); expectEmit(source, MyComponentDefinition, 'Invalid component definition');
}); });
}); });
it('should instantiate directives in a closure when they are forward referenced', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Directive} from '@angular/core';
@Component({
selector: 'host-binding-comp',
template: \`
<my-forward-directive></my-forward-directive>
\`
})
export class HostBindingComp {
}
@Directive({
selector: 'my-forward-directive'
})
class MyForwardDirective {}
@NgModule({declarations: [HostBindingComp, MyForwardDirective]})
export class MyModule {}
`
}
};
const MyAppDefinition = `
directives: function () { return [MyForwardDirective]; }
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyAppDefinition, 'Invalid component definition');
});
it('should instantiate pipes in a closure when they are forward referenced', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Pipe} from '@angular/core';
@Component({
selector: 'host-binding-comp',
template: \`
<div [attr.style]="{} | my_forward_pipe">...</div>
\`
})
export class HostBindingComp {
}
@Pipe({
name: 'my_forward_pipe'
})
class MyForwardPipe {}
@NgModule({declarations: [HostBindingComp, MyForwardPipe]})
export class MyModule {}
`
}
};
const MyAppDefinition = `
pipes: function () { return [MyForwardPipe]; }
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyAppDefinition, 'Invalid component definition');
});
}); });
describe('inherited bare classes', () => { describe('inherited bare classes', () => {

View File

@ -159,11 +159,11 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
directives: Map<string, o.Expression>; directives: Map<string, o.Expression>;
/** /**
* Whether to wrap the 'directives' array, if one is generated, in a closure. * Whether to wrap the 'directives' and/or `pipes` array, if one is generated, in a closure.
* *
* This is done when the directives contain forward references. * This is done when the directives or pipes contain forward references.
*/ */
wrapDirectivesInClosure: boolean; wrapDirectivesAndPipesInClosure: boolean;
/** /**
* A collection of styling data that will be applied and scoped to the component. * A collection of styling data that will be applied and scoped to the component.

View File

@ -239,7 +239,7 @@ export function compileComponentFromMetadata(
// e.g. `directives: [MyDirective]` // e.g. `directives: [MyDirective]`
if (directivesUsed.size) { if (directivesUsed.size) {
let directivesExpr: o.Expression = o.literalArr(Array.from(directivesUsed)); let directivesExpr: o.Expression = o.literalArr(Array.from(directivesUsed));
if (meta.wrapDirectivesInClosure) { if (meta.wrapDirectivesAndPipesInClosure) {
directivesExpr = o.fn([], [new o.ReturnStatement(directivesExpr)]); directivesExpr = o.fn([], [new o.ReturnStatement(directivesExpr)]);
} }
definitionMap.set('directives', directivesExpr); definitionMap.set('directives', directivesExpr);
@ -247,7 +247,11 @@ export function compileComponentFromMetadata(
// e.g. `pipes: [MyPipe]` // e.g. `pipes: [MyPipe]`
if (pipesUsed.size) { if (pipesUsed.size) {
definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed))); let pipesExpr: o.Expression = o.literalArr(Array.from(pipesUsed));
if (meta.wrapDirectivesAndPipesInClosure) {
pipesExpr = o.fn([], [new o.ReturnStatement(pipesExpr)]);
}
definitionMap.set('pipes', pipesExpr);
} }
// e.g. `styles: [str1, str2]` // e.g. `styles: [str1, str2]`
@ -331,7 +335,7 @@ export function compileComponentFromRender2(
directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx), directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx),
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx), pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx), viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
wrapDirectivesInClosure: false, wrapDirectivesAndPipesInClosure: false,
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY, styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
encapsulation: encapsulation:
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated, (summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,

View File

@ -77,7 +77,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
directives: new Map(), directives: new Map(),
pipes: new Map(), pipes: new Map(),
viewQueries: [], viewQueries: [],
wrapDirectivesInClosure: false, wrapDirectivesAndPipesInClosure: false,
styles: metadata.styles || [], styles: metadata.styles || [],
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, animations, encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, animations,
viewProviders: metadata.viewProviders ? new WrappedNodeExpr(metadata.viewProviders) : viewProviders: metadata.viewProviders ? new WrappedNodeExpr(metadata.viewProviders) :