fix(ivy): set namespace for host elements of dynamically created components (#35136)

Prior to this change, element namespace was not set for host elements of dynamically created components that resulted in incorrect rendering in a browser. This commit adds the logic to pick and set correct namespace for host element when component is created dynamically.

PR Close #35136
This commit is contained in:
Andrew Kushnir
2020-02-03 18:02:03 -08:00
committed by Kara Erickson
parent d5d9971c28
commit ae0253f34a
6 changed files with 127 additions and 12 deletions

View File

@ -8,12 +8,12 @@
import {CommonModule, DOCUMENT} from '@angular/common';
import {computeMsgId} from '@angular/compiler';
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, Renderer2, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
import {Input} from '@angular/core/src/metadata';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {TestBed, TestComponentRenderer} from '@angular/core/testing';
import {clearTranslations, loadTranslations} from '@angular/localize';
import {By, DomSanitizer} from '@angular/platform-browser';
import {By, DomSanitizer, ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
@ -160,6 +160,97 @@ describe('ViewContainerRef', () => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML).toContain('Hello');
});
describe('element namespaces', () => {
function runTestWithSelectors(svgSelector: string, mathMLSelector: string) {
it('should be set correctly for host elements of dynamically created components', () => {
@Component({
selector: svgSelector,
template: '<svg><g></g></svg>',
})
class SvgComp {
}
@Component({
selector: mathMLSelector,
template: '<math><matrix></matrix></math>',
})
class MathMLComp {
}
@NgModule({
entryComponents: [SvgComp, MathMLComp],
declarations: [SvgComp, MathMLComp],
// View Engine doesn't have MathML tags listed in `DomElementSchemaRegistry`, thus
// throwing "unknown element" error (':math:matrix' is not a known element). Ignore
// these errors by adding `NO_ERRORS_SCHEMA` to this NgModule.
schemas: [NO_ERRORS_SCHEMA],
})
class RootModule {
}
@Component({
template: `
<ng-container #svg></ng-container>
<ng-container #mathml></ng-container>
`
})
class TestComp {
@ViewChild('svg', {read: ViewContainerRef}) svgVCRef !: ViewContainerRef;
@ViewChild('mathml', {read: ViewContainerRef}) mathMLVCRef !: ViewContainerRef;
constructor(public cfr: ComponentFactoryResolver) {}
createDynamicComponents() {
const svgFactory = this.cfr.resolveComponentFactory(SvgComp);
this.svgVCRef.createComponent(svgFactory);
const mathMLFactory = this.cfr.resolveComponentFactory(MathMLComp);
this.mathMLVCRef.createComponent(mathMLFactory);
}
}
function _document(): any {
// Tell Ivy about the global document
ɵsetDocument(document);
return document;
}
TestBed.configureTestingModule({
declarations: [TestComp],
imports: [RootModule],
providers: [
{provide: DOCUMENT, useFactory: _document, deps: []},
// TODO(FW-811): switch back to default server renderer (i.e. remove the line below)
// once it starts to support Ivy namespace format (URIs) correctly. For now, use
// `DomRenderer` that supports Ivy namespace format.
{provide: RendererFactory2, useClass: DomRendererFactory2}
],
});
const fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
fixture.componentInstance.createDynamicComponents();
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('svg').namespaceURI)
.toEqual('http://www.w3.org/2000/svg');
// View Engine doesn't set MathML namespace, since it's not present in the list of
// known namespaces here:
// https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/dom_renderer.ts#L14
if (ivyEnabled) {
expect(fixture.nativeElement.querySelector('math').namespaceURI)
.toEqual('http://www.w3.org/1998/MathML/');
}
});
}
runTestWithSelectors('svg[some-attr]', 'math[some-attr]');
// Also test with selector that has element name in uppercase
runTestWithSelectors('SVG[some-attr]', 'MATH[some-attr]');
});
});
describe('insert', () => {