fix(ivy): ensure pipe declarations are populated lazily when a forward ref is detected (#26765)
PR Close #26765
This commit is contained in:
parent
2fd4c372d5
commit
8171a2ab94
@ -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());
|
||||||
|
@ -1473,7 +1473,7 @@ describe('compiler compliance', () => {
|
|||||||
app: {
|
app: {
|
||||||
'spec.ts': `
|
'spec.ts': `
|
||||||
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
|
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'myPipe',
|
name: 'myPipe',
|
||||||
pure: false
|
pure: false
|
||||||
@ -1483,7 +1483,7 @@ describe('compiler compliance', () => {
|
|||||||
transform(value: any, ...args: any[]) { return value; }
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
ngOnDestroy(): void { }
|
ngOnDestroy(): void { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'myPurePipe',
|
name: 'myPurePipe',
|
||||||
pure: true,
|
pure: true,
|
||||||
@ -1491,7 +1491,7 @@ describe('compiler compliance', () => {
|
|||||||
export class MyPurePipe implements PipeTransform {
|
export class MyPurePipe implements PipeTransform {
|
||||||
transform(value: any, ...args: any[]) { return value; }
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
template: '{{name | myPipe:size | myPurePipe:size }}<p>{{ name | myPipe:1:2:3:4:5 }}</p>'
|
template: '{{name | myPipe:size | myPurePipe:size }}<p>{{ name | myPipe:1:2:3:4:5 }}</p>'
|
||||||
@ -1500,7 +1500,7 @@ describe('compiler compliance', () => {
|
|||||||
name = 'World';
|
name = 'World';
|
||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
|
@NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
|
||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`
|
`
|
||||||
@ -1566,7 +1566,7 @@ describe('compiler compliance', () => {
|
|||||||
app: {
|
app: {
|
||||||
'spec.ts': `
|
'spec.ts': `
|
||||||
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
|
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'myPipe',
|
name: 'myPipe',
|
||||||
pure: false
|
pure: false
|
||||||
@ -1576,14 +1576,14 @@ describe('compiler compliance', () => {
|
|||||||
transform(value: any, ...args: any[]) { return value; }
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
ngOnDestroy(): void { }
|
ngOnDestroy(): void { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
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}}'
|
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 {
|
export class MyApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({declarations:[MyPipe, MyApp]})
|
@NgModule({declarations:[MyPipe, MyApp]})
|
||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`
|
`
|
||||||
@ -1609,8 +1609,8 @@ describe('compiler compliance', () => {
|
|||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵtextBinding(0, $r3$.ɵinterpolation5(
|
$r3$.ɵtextBinding(0, $r3$.ɵinterpolation5(
|
||||||
"0:", i0.ɵpipeBind1(1, 5, ctx.name),
|
"0:", i0.ɵpipeBind1(1, 5, ctx.name),
|
||||||
"1:", i0.ɵpipeBind2(2, 7, ctx.name, 1),
|
"1:", i0.ɵpipeBind2(2, 7, ctx.name, 1),
|
||||||
"2:", i0.ɵpipeBind3(3, 10, ctx.name, 1, 2),
|
"2:", i0.ɵpipeBind3(3, 10, ctx.name, 1, 2),
|
||||||
"3:", i0.ɵpipeBind4(4, 14, ctx.name, 1, 2, 3),
|
"3:", i0.ɵpipeBind4(4, 14, ctx.name, 1, 2, 3),
|
||||||
"4:", i0.ɵpipeBindV(5, 19, $r3$.ɵpureFunction1(25, $c0$, ctx.name)),
|
"4:", i0.ɵpipeBindV(5, 19, $r3$.ɵpureFunction1(25, $c0$, ctx.name)),
|
||||||
@ -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', () => {
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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) :
|
||||||
|
Loading…
x
Reference in New Issue
Block a user