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
This commit is contained in:
Kristiyan Kostadinov 2021-04-17 11:15:15 +02:00 committed by Andrew Kushnir
parent 364ff96f28
commit 73824d5337
6 changed files with 143 additions and 6 deletions

View File

@ -38,6 +38,62 @@ export declare class MyModule {
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>; static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
} }
/****************************************************************************************************
* PARTIAL FILE: svg_embedded_view.js
****************************************************************************************************/
import { Component, NgModule } from '@angular/core';
import * as i0 from "@angular/core";
export class MyComponent {
constructor() {
this.condition = true;
}
}
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
<svg>
<ng-template [ngIf]="condition">
<text>Hello</text>
</ng-template>
</svg>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
type: Component,
args: [{
selector: 'my-component',
template: `
<svg>
<ng-template [ngIf]="condition">
<text>Hello</text>
</ng-template>
</svg>
`
}]
}] });
export class MyModule {
}
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
type: NgModule,
args: [{ declarations: [MyComponent] }]
}] });
/****************************************************************************************************
* PARTIAL FILE: svg_embedded_view.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyComponent {
condition: boolean;
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never>;
}
export declare class MyModule {
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
}
/**************************************************************************************************** /****************************************************************************************************
* PARTIAL FILE: mathml.js * PARTIAL FILE: mathml.js
****************************************************************************************************/ ****************************************************************************************************/

View File

@ -27,6 +27,23 @@
} }
] ]
}, },
{
"description": "should handle SVG with an embedded ng-template",
"inputFiles": [
"svg_embedded_view.ts"
],
"expectations": [
{
"files": [
{
"expected": "svg_embedded_view_template.js",
"generated": "svg_embedded_view.js"
}
],
"failureMessage": "Incorrect template."
}
]
},
{ {
"description": "should handle MathML", "description": "should handle MathML",
"inputFiles": [ "inputFiles": [

View File

@ -0,0 +1,19 @@
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: `
<svg>
<ng-template [ngIf]="condition">
<text>Hello</text>
</ng-template>
</svg>
`
})
export class MyComponent {
condition = true;
}
@NgModule({declarations: [MyComponent]})
export class MyModule {
}

View File

@ -0,0 +1,24 @@
function MyComponent__svg_ng_template_1_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵnamespaceSVG();
i0.ɵɵelementStart(0, "text");
i0.ɵɵtext(1, "Hello");
i0.ɵɵelementEnd();
}
}
// NOTE: AttributeMarker.Bindings = 3
consts: [[3, "ngIf"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵnamespaceSVG();
i0.ɵɵelementStart(0, "svg");
i0.ɵɵtemplate(1, MyComponent__svg_ng_template_1_Template, 2, 0, "ng-template", 0);
i0.ɵɵelementEnd();
}
if (rf & 2) {
i0.ɵɵadvance(1);
i0.ɵɵproperty("ngIf", ctx.condition);
}
}

View File

@ -875,17 +875,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.i18n.appendTemplate(template.i18n!, templateIndex); this.i18n.appendTemplate(template.i18n!, templateIndex);
} }
const tagName = sanitizeIdentifier(template.tagName || ''); const tagNameWithoutNamespace =
const contextName = `${this.contextName}${tagName ? '_' + tagName : ''}_${templateIndex}`; template.tagName ? splitNsName(template.tagName)[1] : template.tagName;
const contextName = `${this.contextName}${
template.tagName ? '_' + sanitizeIdentifier(template.tagName) : ''}_${templateIndex}`;
const templateName = `${contextName}_Template`; const templateName = `${contextName}_Template`;
const parameters: o.Expression[] = [ const parameters: o.Expression[] = [
o.literal(templateIndex), o.literal(templateIndex),
o.variable(templateName), o.variable(templateName),
// We don't care about the tag's namespace here, because we infer // We don't care about the tag's namespace here, because we infer
// it based on the parent nodes inside the template instruction. // it based on the parent nodes inside the template instruction.
o.literal(template.tagName ? splitNsName(template.tagName)[1] : template.tagName), o.literal(tagNameWithoutNamespace),
]; ];
// find directives matching on a given <ng-template> node // find directives matching on a given <ng-template> node
@ -937,7 +937,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.templatePropertyBindings(templateIndex, template.templateAttrs); this.templatePropertyBindings(templateIndex, template.templateAttrs);
// Only add normal input/output binding instructions on explicit <ng-template> elements. // Only add normal input/output binding instructions on explicit <ng-template> elements.
if (template.tagName === NG_TEMPLATE_TAG_NAME) { if (tagNameWithoutNamespace === NG_TEMPLATE_TAG_NAME) {
const [i18nInputs, inputs] = const [i18nInputs, inputs] =
partitionArray<t.BoundAttribute, t.BoundAttribute>(template.inputs, hasI18nMeta); partitionArray<t.BoundAttribute, t.BoundAttribute>(template.inputs, hasI18nMeta);

View File

@ -1990,6 +1990,27 @@ describe('acceptance integration tests', () => {
expect(logs).toEqual(['Baggins']); expect(logs).toEqual(['Baggins']);
}); });
it('should render SVG nodes placed inside ng-template', () => {
@Component({
template: `
<svg>
<ng-template [ngIf]="condition">
<text>Hello</text>
</ng-template>
</svg>
`,
})
class MyComp {
condition = true;
}
TestBed.configureTestingModule({declarations: [MyComp], imports: [CommonModule]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('<text>Hello</text>');
});
describe('tView.firstUpdatePass', () => { describe('tView.firstUpdatePass', () => {
function isFirstUpdatePass() { function isFirstUpdatePass() {
const lView = getLView(); const lView = getLView();