Skip to content

Commit 2bcbbda

Browse files
crisbetoAndrewKushnir
authored andcommitted
fix(compiler): not generating update instructions for ng-template inside alternate namespaces (#41669)
We have a check that determines whether to generate property binding instructions for an `ng-template`. The check looks at whether the tag name is exactly `ng-template`, but the problem is that if the tag is placed in a non-HTML namespace (e.g. `svg`), the tag name will actually be `:namespace:ng-template` and the check will fail. These changes resolve the issue by looking at the tag name without the namespace. Fixes #41308. PR Close #41669
1 parent 804a694 commit 2bcbbda

File tree

6 files changed

+143
-6
lines changed

6 files changed

+143
-6
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/GOLDEN_PARTIAL.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,62 @@ export declare class MyModule {
3838
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
3939
}
4040

41+
/****************************************************************************************************
42+
* PARTIAL FILE: svg_embedded_view.js
43+
****************************************************************************************************/
44+
import { Component, NgModule } from '@angular/core';
45+
import * as i0 from "@angular/core";
46+
export class MyComponent {
47+
constructor() {
48+
this.condition = true;
49+
}
50+
}
51+
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
52+
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
53+
<svg>
54+
<ng-template [ngIf]="condition">
55+
<text>Hello</text>
56+
</ng-template>
57+
</svg>
58+
`, isInline: true });
59+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
60+
type: Component,
61+
args: [{
62+
selector: 'my-component',
63+
template: `
64+
<svg>
65+
<ng-template [ngIf]="condition">
66+
<text>Hello</text>
67+
</ng-template>
68+
</svg>
69+
`
70+
}]
71+
}] });
72+
export class MyModule {
73+
}
74+
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
75+
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
76+
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
77+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
78+
type: NgModule,
79+
args: [{ declarations: [MyComponent] }]
80+
}] });
81+
82+
/****************************************************************************************************
83+
* PARTIAL FILE: svg_embedded_view.d.ts
84+
****************************************************************************************************/
85+
import * as i0 from "@angular/core";
86+
export declare class MyComponent {
87+
condition: boolean;
88+
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
89+
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never>;
90+
}
91+
export declare class MyModule {
92+
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
93+
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
94+
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
95+
}
96+
4197
/****************************************************************************************************
4298
* PARTIAL FILE: mathml.js
4399
****************************************************************************************************/

packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/TEST_CASES.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@
2727
}
2828
]
2929
},
30+
{
31+
"description": "should handle SVG with an embedded ng-template",
32+
"inputFiles": [
33+
"svg_embedded_view.ts"
34+
],
35+
"expectations": [
36+
{
37+
"files": [
38+
{
39+
"expected": "svg_embedded_view_template.js",
40+
"generated": "svg_embedded_view.js"
41+
}
42+
],
43+
"failureMessage": "Incorrect template."
44+
}
45+
]
46+
},
3047
{
3148
"description": "should handle MathML",
3249
"inputFiles": [
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {Component, NgModule} from '@angular/core';
2+
3+
@Component({
4+
selector: 'my-component',
5+
template: `
6+
<svg>
7+
<ng-template [ngIf]="condition">
8+
<text>Hello</text>
9+
</ng-template>
10+
</svg>
11+
`
12+
})
13+
export class MyComponent {
14+
condition = true;
15+
}
16+
17+
@NgModule({declarations: [MyComponent]})
18+
export class MyModule {
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
function MyComponent__svg_ng_template_1_Template(rf, ctx) {
2+
if (rf & 1) {
3+
i0.ɵɵnamespaceSVG();
4+
i0.ɵɵelementStart(0, "text");
5+
i0.ɵɵtext(1, "Hello");
6+
i0.ɵɵelementEnd();
7+
}
8+
}
9+
10+
11+
// NOTE: AttributeMarker.Bindings = 3
12+
consts: [[3, "ngIf"]],
13+
template: function MyComponent_Template(rf, ctx) {
14+
if (rf & 1) {
15+
i0.ɵɵnamespaceSVG();
16+
i0.ɵɵelementStart(0, "svg");
17+
i0.ɵɵtemplate(1, MyComponent__svg_ng_template_1_Template, 2, 0, "ng-template", 0);
18+
i0.ɵɵelementEnd();
19+
}
20+
if (rf & 2) {
21+
i0.ɵɵadvance(1);
22+
i0.ɵɵproperty("ngIf", ctx.condition);
23+
}
24+
}

packages/compiler/src/render3/view/template.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -875,17 +875,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
875875
this.i18n.appendTemplate(template.i18n!, templateIndex);
876876
}
877877

878-
const tagName = sanitizeIdentifier(template.tagName || '');
879-
const contextName = `${this.contextName}${tagName ? '_' + tagName : ''}_${templateIndex}`;
878+
const tagNameWithoutNamespace =
879+
template.tagName ? splitNsName(template.tagName)[1] : template.tagName;
880+
const contextName = `${this.contextName}${
881+
template.tagName ? '_' + sanitizeIdentifier(template.tagName) : ''}_${templateIndex}`;
880882
const templateName = `${contextName}_Template`;
881-
882883
const parameters: o.Expression[] = [
883884
o.literal(templateIndex),
884885
o.variable(templateName),
885-
886886
// We don't care about the tag's namespace here, because we infer
887887
// it based on the parent nodes inside the template instruction.
888-
o.literal(template.tagName ? splitNsName(template.tagName)[1] : template.tagName),
888+
o.literal(tagNameWithoutNamespace),
889889
];
890890

891891
// find directives matching on a given <ng-template> node
@@ -937,7 +937,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
937937
this.templatePropertyBindings(templateIndex, template.templateAttrs);
938938

939939
// Only add normal input/output binding instructions on explicit <ng-template> elements.
940-
if (template.tagName === NG_TEMPLATE_TAG_NAME) {
940+
if (tagNameWithoutNamespace === NG_TEMPLATE_TAG_NAME) {
941941
const [i18nInputs, inputs] =
942942
partitionArray<t.BoundAttribute, t.BoundAttribute>(template.inputs, hasI18nMeta);
943943

packages/core/test/acceptance/integration_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,27 @@ describe('acceptance integration tests', () => {
19901990
expect(logs).toEqual(['Baggins']);
19911991
});
19921992

1993+
it('should render SVG nodes placed inside ng-template', () => {
1994+
@Component({
1995+
template: `
1996+
<svg>
1997+
<ng-template [ngIf]="condition">
1998+
<text>Hello</text>
1999+
</ng-template>
2000+
</svg>
2001+
`,
2002+
})
2003+
class MyComp {
2004+
condition = true;
2005+
}
2006+
2007+
TestBed.configureTestingModule({declarations: [MyComp], imports: [CommonModule]});
2008+
const fixture = TestBed.createComponent(MyComp);
2009+
fixture.detectChanges();
2010+
2011+
expect(fixture.nativeElement.innerHTML).toContain('<text>Hello</text>');
2012+
});
2013+
19932014
describe('tView.firstUpdatePass', () => {
19942015
function isFirstUpdatePass() {
19952016
const lView = getLView();

0 commit comments

Comments
 (0)