diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/GOLDEN_PARTIAL.js index 4fe9828218..cbf1c08743 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/GOLDEN_PARTIAL.js @@ -38,6 +38,62 @@ export declare class MyModule { static ɵinj: i0.ɵɵInjectorDeclaration; } +/**************************************************************************************************** + * 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: ` + + + Hello + + + `, 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: ` + + + Hello + + + ` + }] + }] }); +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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} +export declare class MyModule { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: mathml.js ****************************************************************************************************/ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/TEST_CASES.json index e8fdcfccbd..8c57978716 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/TEST_CASES.json @@ -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", "inputFiles": [ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/svg_embedded_view.ts b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/svg_embedded_view.ts new file mode 100644 index 0000000000..535fd1cc29 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/svg_embedded_view.ts @@ -0,0 +1,19 @@ +import {Component, NgModule} from '@angular/core'; + +@Component({ + selector: 'my-component', + template: ` + + + Hello + + + ` +}) +export class MyComponent { + condition = true; +} + +@NgModule({declarations: [MyComponent]}) +export class MyModule { +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/svg_embedded_view_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/svg_embedded_view_template.js new file mode 100644 index 0000000000..faa4c684a1 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/elements/svg_embedded_view_template.js @@ -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); + } +} diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 530ce30ccc..686b93ceeb 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -875,17 +875,17 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.i18n.appendTemplate(template.i18n!, templateIndex); } - const tagName = sanitizeIdentifier(template.tagName || ''); - const contextName = `${this.contextName}${tagName ? '_' + tagName : ''}_${templateIndex}`; + const tagNameWithoutNamespace = + template.tagName ? splitNsName(template.tagName)[1] : template.tagName; + const contextName = `${this.contextName}${ + template.tagName ? '_' + sanitizeIdentifier(template.tagName) : ''}_${templateIndex}`; const templateName = `${contextName}_Template`; - const parameters: o.Expression[] = [ o.literal(templateIndex), o.variable(templateName), - // We don't care about the tag's namespace here, because we infer // 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 node @@ -937,7 +937,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.templatePropertyBindings(templateIndex, template.templateAttrs); // Only add normal input/output binding instructions on explicit elements. - if (template.tagName === NG_TEMPLATE_TAG_NAME) { + if (tagNameWithoutNamespace === NG_TEMPLATE_TAG_NAME) { const [i18nInputs, inputs] = partitionArray(template.inputs, hasI18nMeta); diff --git a/packages/core/test/acceptance/integration_spec.ts b/packages/core/test/acceptance/integration_spec.ts index 8d45e0541c..e01aa0ab36 100644 --- a/packages/core/test/acceptance/integration_spec.ts +++ b/packages/core/test/acceptance/integration_spec.ts @@ -1990,6 +1990,27 @@ describe('acceptance integration tests', () => { expect(logs).toEqual(['Baggins']); }); + it('should render SVG nodes placed inside ng-template', () => { + @Component({ + template: ` + + + Hello + + + `, + }) + class MyComp { + condition = true; + } + + TestBed.configureTestingModule({declarations: [MyComp], imports: [CommonModule]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML).toContain('Hello'); + }); + describe('tView.firstUpdatePass', () => { function isFirstUpdatePass() { const lView = getLView();