fix(ivy): add attributes and classes to host elements based on selector (#34481)
In View Engine, host element of dynamically created component received attributes and classes extracted from component's selector. For example, if component selector is `[attr] .class`, the `attr` attribute and `.class` class will be add to host element. This commit adds similar logic to Ivy, to make sure this behavior is aligned with View Engine. PR Close #34481
This commit is contained in:

committed by
Alex Rickabaugh

parent
3f4e02b8c7
commit
f95b8ce07e
@ -8,7 +8,7 @@
|
||||
|
||||
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, Renderer2, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
|
||||
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, Injector, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, 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';
|
||||
@ -251,6 +251,98 @@ describe('ViewContainerRef', () => {
|
||||
// Also test with selector that has element name in uppercase
|
||||
runTestWithSelectors('SVG[some-attr]', 'MATH[some-attr]');
|
||||
});
|
||||
|
||||
it('should apply attributes and classes to host element based on selector', () => {
|
||||
@Component({
|
||||
selector: '[attr-a=a].class-a:not(.class-b):not([attr-b=b]).class-c[attr-c]',
|
||||
template: 'Hello'
|
||||
})
|
||||
class HelloComp {
|
||||
}
|
||||
|
||||
@NgModule({entryComponents: [HelloComp], declarations: [HelloComp]})
|
||||
class HelloCompModule {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div id="factory" attr-a="a-original" class="class-original"></div>
|
||||
<div id="vcr">
|
||||
<ng-container #container></ng-container>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class TestComp {
|
||||
@ViewChild('container', {read: ViewContainerRef}) vcRef !: ViewContainerRef;
|
||||
|
||||
private helloCompFactory = this.cfr.resolveComponentFactory(HelloComp);
|
||||
|
||||
constructor(public cfr: ComponentFactoryResolver, public injector: Injector) {}
|
||||
|
||||
createComponentViaVCRef() {
|
||||
this.vcRef.createComponent(this.helloCompFactory); //
|
||||
}
|
||||
|
||||
createComponentViaFactory() {
|
||||
this.helloCompFactory.create(this.injector, undefined, '#factory');
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [TestComp], imports: [HelloCompModule]});
|
||||
const fixture = TestBed.createComponent(TestComp);
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.createComponentViaVCRef();
|
||||
fixture.componentInstance.createComponentViaFactory();
|
||||
fixture.detectChanges();
|
||||
|
||||
// Verify host element for a component created via `vcRef.createComponent` method
|
||||
const vcrHostElement = fixture.nativeElement.querySelector('#vcr > div');
|
||||
|
||||
expect(vcrHostElement.classList.contains('class-a')).toBe(true);
|
||||
// `class-b` should not be present, since it's wrapped in `:not()` selector
|
||||
expect(vcrHostElement.classList.contains('class-b')).toBe(false);
|
||||
expect(vcrHostElement.classList.contains('class-c')).toBe(true);
|
||||
|
||||
expect(vcrHostElement.getAttribute('attr-a')).toBe('a');
|
||||
// `attr-b` should not be present, since it's wrapped in `:not()` selector
|
||||
expect(vcrHostElement.getAttribute('attr-b')).toBe(null);
|
||||
expect(vcrHostElement.getAttribute('attr-c')).toBe('');
|
||||
|
||||
// Verify host element for a component created using `factory.createComponent` method when
|
||||
// also passing element selector as an argument
|
||||
const factoryHostElement = fixture.nativeElement.querySelector('#factory');
|
||||
|
||||
if (ivyEnabled) {
|
||||
// In Ivy, if selector is passed when component is created, matched host node (found using
|
||||
// this selector) retains all attrs/classes and selector-based attrs/classes should *not* be
|
||||
// added
|
||||
|
||||
// Verify original attrs and classes are still present
|
||||
expect(factoryHostElement.classList.contains('class-original')).toBe(true);
|
||||
expect(factoryHostElement.getAttribute('attr-a')).toBe('a-original');
|
||||
|
||||
// Make sure selector-based attrs and classes were not added to the host element
|
||||
expect(factoryHostElement.classList.contains('class-a')).toBe(false);
|
||||
expect(factoryHostElement.getAttribute('attr-c')).toBe(null);
|
||||
|
||||
} else {
|
||||
// In View Engine, selector-based attrs/classes are *always* added to the host element
|
||||
|
||||
expect(factoryHostElement.classList.contains('class-a')).toBe(true);
|
||||
// `class-b` should not be present, since it's wrapped in `:not()` selector
|
||||
expect(factoryHostElement.classList.contains('class-b')).toBe(false);
|
||||
expect(factoryHostElement.classList.contains('class-c')).toBe(true);
|
||||
// Make sure classes are overridden with ones used in component selector
|
||||
expect(factoryHostElement.classList.contains('class-original')).toBe(false);
|
||||
|
||||
// Note: `attr-a` attr is also present on host element, but we update the value with the
|
||||
// value from component selector (i.e. using `[attr-a=a]`)
|
||||
expect(factoryHostElement.getAttribute('attr-a')).toBe('a');
|
||||
// `attr-b` should not be present, since it's wrapped in `:not()` selector
|
||||
expect(factoryHostElement.getAttribute('attr-b')).toBe(null);
|
||||
expect(factoryHostElement.getAttribute('attr-c')).toBe('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('insert', () => {
|
||||
|
Reference in New Issue
Block a user