diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index e68bdfd6ef..8f5b5d931e 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -204,7 +204,7 @@ export class ComponentDecoratorHandler implements // analyzed and the full compilation scope for the component can be realized. pipes: EMPTY_MAP, directives: EMPTY_MAP, - wrapDirectivesInClosure: false, // + wrapDirectivesAndPipesInClosure: false, // animations, viewProviders }, @@ -237,8 +237,8 @@ export class ComponentDecoratorHandler implements const {pipes, containsForwardDecls} = scope; const directives = new Map(); scope.directives.forEach((meta, selector) => directives.set(selector, meta.directive)); - const wrapDirectivesInClosure: boolean = !!containsForwardDecls; - metadata = {...metadata, directives, pipes, wrapDirectivesInClosure}; + const wrapDirectivesAndPipesInClosure: boolean = !!containsForwardDecls; + metadata = {...metadata, directives, pipes, wrapDirectivesAndPipesInClosure}; } const res = compileComponentFromMetadata(metadata, pool, makeBindingParser()); diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index cc480b8a1d..68d1454ddb 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -1473,7 +1473,7 @@ describe('compiler compliance', () => { app: { 'spec.ts': ` import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core'; - + @Pipe({ name: 'myPipe', pure: false @@ -1483,7 +1483,7 @@ describe('compiler compliance', () => { transform(value: any, ...args: any[]) { return value; } ngOnDestroy(): void { } } - + @Pipe({ name: 'myPurePipe', pure: true, @@ -1491,7 +1491,7 @@ describe('compiler compliance', () => { export class MyPurePipe implements PipeTransform { transform(value: any, ...args: any[]) { return value; } } - + @Component({ selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}

{{ name | myPipe:1:2:3:4:5 }}

' @@ -1500,7 +1500,7 @@ describe('compiler compliance', () => { name = 'World'; size = 0; } - + @NgModule({declarations:[MyPipe, MyPurePipe, MyApp]}) export class MyModule {} ` @@ -1566,7 +1566,7 @@ describe('compiler compliance', () => { app: { 'spec.ts': ` import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core'; - + @Pipe({ name: 'myPipe', pure: false @@ -1576,14 +1576,14 @@ describe('compiler compliance', () => { transform(value: any, ...args: any[]) { return value; } ngOnDestroy(): void { } } - + @Component({ selector: 'my-app', template: '0:{{name | myPipe}}1:{{name | myPipe:1}}2:{{name | myPipe:1:2}}3:{{name | myPipe:1:2:3}}4:{{name | myPipe:1:2:3:4}}' }) export class MyApp { } - + @NgModule({declarations:[MyPipe, MyApp]}) export class MyModule {} ` @@ -1609,8 +1609,8 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵtextBinding(0, $r3$.ɵinterpolation5( - "0:", i0.ɵpipeBind1(1, 5, ctx.name), - "1:", i0.ɵpipeBind2(2, 7, ctx.name, 1), + "0:", i0.ɵpipeBind1(1, 5, ctx.name), + "1:", i0.ɵpipeBind2(2, 7, ctx.name, 1), "2:", i0.ɵpipeBind3(3, 10, ctx.name, 1, 2), "3:", i0.ɵpipeBind4(4, 14, ctx.name, 1, 2, 3), "4:", i0.ɵpipeBindV(5, 19, $r3$.ɵpureFunction1(25, $c0$, ctx.name)), @@ -2225,6 +2225,80 @@ describe('compiler compliance', () => { 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: \` + + \` + }) + 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: \` +
...
+ \` + }) + 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', () => { diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 1782f7d64f..e92454cc8d 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -159,11 +159,11 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata { directives: Map; /** - * 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. diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index fde52edc29..ae7de11467 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -239,7 +239,7 @@ export function compileComponentFromMetadata( // e.g. `directives: [MyDirective]` if (directivesUsed.size) { let directivesExpr: o.Expression = o.literalArr(Array.from(directivesUsed)); - if (meta.wrapDirectivesInClosure) { + if (meta.wrapDirectivesAndPipesInClosure) { directivesExpr = o.fn([], [new o.ReturnStatement(directivesExpr)]); } definitionMap.set('directives', directivesExpr); @@ -247,7 +247,11 @@ export function compileComponentFromMetadata( // e.g. `pipes: [MyPipe]` 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]` @@ -331,7 +335,7 @@ export function compileComponentFromRender2( directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx), pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx), viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx), - wrapDirectivesInClosure: false, + wrapDirectivesAndPipesInClosure: false, styles: (summary.template && summary.template.styles) || EMPTY_ARRAY, encapsulation: (summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated, diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index bbc3a023c6..f82aef121e 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -77,7 +77,7 @@ export function compileComponent(type: Type, metadata: Component): void { directives: new Map(), pipes: new Map(), viewQueries: [], - wrapDirectivesInClosure: false, + wrapDirectivesAndPipesInClosure: false, styles: metadata.styles || [], encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, animations, viewProviders: metadata.viewProviders ? new WrappedNodeExpr(metadata.viewProviders) :