fix(ivy): unable to project into multiple slots with default selector (#30561)
With View engine it was possible to declare multiple projection definitions and to programmatically project nodes into the slots. e.g. ```html <ng-content></ng-content> <ng-content></ng-content> ``` Using `ViewContainerRef#createComponent` allowed projecting nodes into one of the projection defs (through index) This no longer works with Ivy as the `projectionDef` instruction only retrieves a list of selectors instead of also retrieving entries for reserved projection slots which appear when using the default selector multiple times (as seen above). In order to fix this issue, the Ivy compiler now passes all projection slots to the `projectionDef` instruction. Meaning that there can be multiple projection slots with the same wildcard selector. This allows multi-slot projection as seen in the example above, and it also allows us to match the multi-slot node projection order from View Engine (to avoid breaking changes). It basically ensures that Ivy fully matches the View Engine behavior except of a very small edge case that has already been discussed in FW-886 (with the conclusion of working as intended). Read more here: https://hackmd.io/s/Sy2kQlgTE PR Close #30561
This commit is contained in:

committed by
Miško Hevery

parent
f4cd3740b2
commit
aca339e864
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, Injector, Input, NO_ERRORS_SCHEMA, NgModule, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
@ -111,13 +112,48 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||
});
|
||||
|
||||
modifiedInIvy(
|
||||
'FW-886: `projectableNodes` passed to a componentFactory should be in the order of declaration')
|
||||
.it('should support passing projectable nodes via factory function', () => {
|
||||
it('should support passing projectable nodes via factory function', () => {
|
||||
@Component({
|
||||
selector: 'multiple-content-tags',
|
||||
template: '(<ng-content SELECT="h1"></ng-content>, <ng-content></ng-content>)',
|
||||
})
|
||||
class MultipleContentTagsComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MultipleContentTagsComponent],
|
||||
entryComponents: [MultipleContentTagsComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
})
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [MyModule]});
|
||||
const injector: Injector = TestBed.get(Injector);
|
||||
|
||||
const componentFactoryResolver: ComponentFactoryResolver =
|
||||
injector.get(ComponentFactoryResolver);
|
||||
const componentFactory =
|
||||
componentFactoryResolver.resolveComponentFactory(MultipleContentTagsComponent);
|
||||
expect(componentFactory.ngContentSelectors).toEqual(['h1', '*']);
|
||||
|
||||
const nodeOne = getDOM().createTextNode('one');
|
||||
const nodeTwo = getDOM().createTextNode('two');
|
||||
const component = componentFactory.create(injector, [[nodeOne], [nodeTwo]]);
|
||||
expect(component.location.nativeElement).toHaveText('(one, two)');
|
||||
});
|
||||
|
||||
modifiedInIvy(
|
||||
'FW-886: `projectableNodes` passed to a componentFactory should be in the order of' +
|
||||
'declaration. In Ivy, the ng-content slots are determined with breadth-first search.')
|
||||
.it('should respect order of declaration for projectable nodes', () => {
|
||||
@Component({
|
||||
selector: 'multiple-content-tags',
|
||||
template: '(<ng-content SELECT="h1"></ng-content>, <ng-content></ng-content>)',
|
||||
template: `
|
||||
1<ng-content select="h1"></ng-content>
|
||||
2<ng-template [ngIf]="true"><ng-content></ng-content></ng-template>
|
||||
3<ng-content select="h2"></ng-content>
|
||||
`,
|
||||
})
|
||||
class MultipleContentTagsComponent {
|
||||
}
|
||||
@ -125,6 +161,7 @@ describe('projection', () => {
|
||||
@NgModule({
|
||||
declarations: [MultipleContentTagsComponent],
|
||||
entryComponents: [MultipleContentTagsComponent],
|
||||
imports: [CommonModule],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
})
|
||||
class MyModule {
|
||||
@ -137,12 +174,14 @@ describe('projection', () => {
|
||||
injector.get(ComponentFactoryResolver);
|
||||
const componentFactory =
|
||||
componentFactoryResolver.resolveComponentFactory(MultipleContentTagsComponent);
|
||||
expect(componentFactory.ngContentSelectors).toEqual(['h1', '*']);
|
||||
expect(componentFactory.ngContentSelectors).toEqual(['h1', '*', 'h2']);
|
||||
|
||||
const nodeOne = getDOM().createTextNode('one');
|
||||
const nodeTwo = getDOM().createTextNode('two');
|
||||
const component = componentFactory.create(injector, [[nodeOne], [nodeTwo]]);
|
||||
expect(component.location.nativeElement).toHaveText('(one, two)');
|
||||
const nodeThree = getDOM().createTextNode('three');
|
||||
const component = componentFactory.create(injector, [[nodeOne], [nodeTwo], [nodeThree]]);
|
||||
component.changeDetectorRef.detectChanges();
|
||||
expect(component.location.nativeElement.textContent.trim()).toBe('1one 2two 3three');
|
||||
});
|
||||
|
||||
it('should redistribute only direct children', () => {
|
||||
|
Reference in New Issue
Block a user