fix(ivy): don't create TNodes for native projectable nodes (#28275)
Before this commit we were creating a "fake" TNode for each and every projectable node passed during dynamic component creation. This approach had several problems: - the existing TView structure had to be mutated to accomodate new TNodes and it was very easy to "corrupt" TView / TNode data structures; - TNodes are not really needed to fully support projectable nodes so we were creating objects and updating existing data structures for nothing. This commit changes the approach so we don't create "fake" TNodes for projectable nodes but instead we process projectable nodes directly in the projection instruction. As a result we've got less code, less object allocation and - as a bonus - we fix few bugs where TView / TNode data structures were corrupted when using projectable nodes. PR Close #28275
This commit is contained in:

committed by
Alex Rickabaugh

parent
d8f2318811
commit
cf8770f3cc
@ -10,7 +10,6 @@ import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgM
|
||||
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {$EXCEPTION_HANDLER} from '@angular/upgrade/src/common/constants';
|
||||
import {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter';
|
||||
@ -3098,33 +3097,31 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
|
||||
.it('should respect hierarchical dependency injection for ng2', async(() => {
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
it('should respect hierarchical dependency injection for ng2', async(() => {
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
||||
@Component(
|
||||
{selector: 'ng2-parent', template: `ng2-parent(<ng-content></ng-content>)`})
|
||||
class Ng2Parent {
|
||||
}
|
||||
@Component({selector: 'ng2-child', template: `ng2-child`})
|
||||
class Ng2Child {
|
||||
constructor(parent: Ng2Parent) {}
|
||||
}
|
||||
@Component({selector: 'ng2-parent', template: `ng2-parent(<ng-content></ng-content>)`})
|
||||
class Ng2Parent {
|
||||
}
|
||||
@Component({selector: 'ng2-child', template: `ng2-child`})
|
||||
class Ng2Child {
|
||||
constructor(parent: Ng2Parent) {}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]})
|
||||
class Ng2Module {
|
||||
}
|
||||
@NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]})
|
||||
class Ng2Module {
|
||||
}
|
||||
|
||||
const element = html('<ng2-parent><ng2-child></ng2-child></ng2-parent>');
|
||||
const element = html('<ng2-parent><ng2-child></ng2-child></ng2-parent>');
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||
ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent))
|
||||
.directive('ng2Child', adapter.downgradeNg2Component(Ng2Child));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(document.body.textContent).toEqual('ng2-parent(ng2-child)');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||
ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent))
|
||||
.directive('ng2Child', adapter.downgradeNg2Component(Ng2Child));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(document.body.textContent).toEqual('ng2-parent(ng2-child)');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('testability', () => {
|
||||
|
@ -705,37 +705,36 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
|
||||
.it('should respect hierarchical dependency injection for ng2', async(() => {
|
||||
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
|
||||
class ParentComponent {
|
||||
}
|
||||
it('should respect hierarchical dependency injection for ng2', async(() => {
|
||||
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
|
||||
class ParentComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'child', template: 'child'})
|
||||
class ChildComponent {
|
||||
constructor(parent: ParentComponent) {}
|
||||
}
|
||||
@Component({selector: 'child', template: 'child'})
|
||||
class ChildComponent {
|
||||
constructor(parent: ParentComponent) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ParentComponent, ChildComponent],
|
||||
entryComponents: [ParentComponent, ChildComponent],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [ParentComponent, ChildComponent],
|
||||
entryComponents: [ParentComponent, ChildComponent],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module =
|
||||
angular.module('ng1', [])
|
||||
.directive('parent', downgradeComponent({component: ParentComponent}))
|
||||
.directive('child', downgradeComponent({component: ChildComponent}));
|
||||
const ng1Module =
|
||||
angular.module('ng1', [])
|
||||
.directive('parent', downgradeComponent({component: ParentComponent}))
|
||||
.directive('child', downgradeComponent({component: ChildComponent}));
|
||||
|
||||
const element = html('<parent><child></child></parent>');
|
||||
const element = html('<parent><child></child></parent>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
expect(multiTrim(document.body.textContent)).toBe('parent(child)');
|
||||
});
|
||||
}));
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
expect(multiTrim(document.body.textContent)).toBe('parent(child)');
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy(
|
||||
'FW-717: Injector on lazy loaded components are not the same as their NgModule\'s injector')
|
||||
|
@ -330,93 +330,91 @@ withEachNg1Version(() => {
|
||||
expect(multiTrim(element.children[1].textContent)).toBe('Counter:1');
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
|
||||
.it('should correctly traverse the injector tree of downgraded components', async(() => {
|
||||
@Component({
|
||||
selector: 'ng2A',
|
||||
template: 'ng2A(<ng-content></ng-content>)',
|
||||
providers: [
|
||||
{provide: 'FOO', useValue: 'CompA-foo'},
|
||||
{provide: 'BAR', useValue: 'CompA-bar'},
|
||||
],
|
||||
})
|
||||
class Ng2ComponentA {
|
||||
}
|
||||
it('should correctly traverse the injector tree of downgraded components', async(() => {
|
||||
@Component({
|
||||
selector: 'ng2A',
|
||||
template: 'ng2A(<ng-content></ng-content>)',
|
||||
providers: [
|
||||
{provide: 'FOO', useValue: 'CompA-foo'},
|
||||
{provide: 'BAR', useValue: 'CompA-bar'},
|
||||
],
|
||||
})
|
||||
class Ng2ComponentA {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng2B',
|
||||
template: `
|
||||
@Component({
|
||||
selector: 'ng2B',
|
||||
template: `
|
||||
FOO:{{ foo }}
|
||||
BAR:{{ bar }}
|
||||
BAZ:{{ baz }}
|
||||
QUX:{{ qux }}
|
||||
`,
|
||||
providers: [
|
||||
{provide: 'FOO', useValue: 'CompB-foo'},
|
||||
],
|
||||
})
|
||||
class Ng2ComponentB {
|
||||
constructor(
|
||||
@Inject('FOO') public foo: string, @Inject('BAR') public bar: string,
|
||||
@Inject('BAZ') public baz: string, @Inject('QUX') public qux: string) {}
|
||||
}
|
||||
providers: [
|
||||
{provide: 'FOO', useValue: 'CompB-foo'},
|
||||
],
|
||||
})
|
||||
class Ng2ComponentB {
|
||||
constructor(
|
||||
@Inject('FOO') public foo: string, @Inject('BAR') public bar: string,
|
||||
@Inject('BAZ') public baz: string, @Inject('QUX') public qux: string) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2ComponentA, Ng2ComponentB],
|
||||
entryComponents: [Ng2ComponentA, Ng2ComponentB],
|
||||
imports: [BrowserModule],
|
||||
providers: [
|
||||
{provide: 'FOO', useValue: 'Mod-foo'},
|
||||
{provide: 'BAR', useValue: 'Mod-bar'},
|
||||
{provide: 'BAZ', useValue: 'Mod-baz'},
|
||||
],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [Ng2ComponentA, Ng2ComponentB],
|
||||
entryComponents: [Ng2ComponentA, Ng2ComponentB],
|
||||
imports: [BrowserModule],
|
||||
providers: [
|
||||
{provide: 'FOO', useValue: 'Mod-foo'},
|
||||
{provide: 'BAR', useValue: 'Mod-bar'},
|
||||
{provide: 'BAZ', useValue: 'Mod-baz'},
|
||||
],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) => {
|
||||
const platformRef = getPlatform() || platformBrowserDynamic([
|
||||
...extraProviders,
|
||||
{provide: 'FOO', useValue: 'Plat-foo'},
|
||||
{provide: 'BAR', useValue: 'Plat-bar'},
|
||||
{provide: 'BAZ', useValue: 'Plat-baz'},
|
||||
{provide: 'QUX', useValue: 'Plat-qux'},
|
||||
]);
|
||||
return platformRef.bootstrapModule(Ng2Module);
|
||||
};
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) => {
|
||||
const platformRef = getPlatform() || platformBrowserDynamic([
|
||||
...extraProviders,
|
||||
{provide: 'FOO', useValue: 'Plat-foo'},
|
||||
{provide: 'BAR', useValue: 'Plat-bar'},
|
||||
{provide: 'BAZ', useValue: 'Plat-baz'},
|
||||
{provide: 'QUX', useValue: 'Plat-qux'},
|
||||
]);
|
||||
return platformRef.bootstrapModule(Ng2Module);
|
||||
};
|
||||
|
||||
const downMod = downgradeModule(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [downMod])
|
||||
.directive(
|
||||
'ng2A', downgradeComponent({component: Ng2ComponentA, propagateDigest}))
|
||||
.directive(
|
||||
'ng2B',
|
||||
downgradeComponent({component: Ng2ComponentB, propagateDigest}));
|
||||
const downMod = downgradeModule(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [downMod])
|
||||
.directive(
|
||||
'ng2A', downgradeComponent({component: Ng2ComponentA, propagateDigest}))
|
||||
.directive(
|
||||
'ng2B', downgradeComponent({component: Ng2ComponentB, propagateDigest}));
|
||||
|
||||
const element = html(`
|
||||
const element = html(`
|
||||
<ng2-a><ng2-b ng-if="showB1"></ng2-b></ng2-a>
|
||||
<ng2-b ng-if="showB2"></ng2-b>
|
||||
`);
|
||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||
|
||||
// Wait for the module to be bootstrapped.
|
||||
setTimeout(() => {
|
||||
expect(multiTrim(element.textContent)).toBe('ng2A()');
|
||||
// Wait for the module to be bootstrapped.
|
||||
setTimeout(() => {
|
||||
expect(multiTrim(element.textContent)).toBe('ng2A()');
|
||||
|
||||
// Nested component B.
|
||||
$rootScope.$apply('showB1 = true');
|
||||
expect(multiTrim(element.children[0].textContent))
|
||||
.toBe('ng2A( FOO:CompB-foo BAR:CompA-bar BAZ:Mod-baz QUX:Plat-qux )');
|
||||
// Nested component B.
|
||||
$rootScope.$apply('showB1 = true');
|
||||
expect(multiTrim(element.children[0].textContent))
|
||||
.toBe('ng2A( FOO:CompB-foo BAR:CompA-bar BAZ:Mod-baz QUX:Plat-qux )');
|
||||
|
||||
// Standalone component B.
|
||||
$rootScope.$apply('showB2 = true');
|
||||
expect(multiTrim(element.children[1].textContent))
|
||||
.toBe('FOO:CompB-foo BAR:Mod-bar BAZ:Mod-baz QUX:Plat-qux');
|
||||
});
|
||||
}));
|
||||
// Standalone component B.
|
||||
$rootScope.$apply('showB2 = true');
|
||||
expect(multiTrim(element.children[1].textContent))
|
||||
.toBe('FOO:CompB-foo BAR:Mod-bar BAZ:Mod-baz QUX:Plat-qux');
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
|
||||
.it('should correctly traverse the injector tree of downgraded components (from different modules)',
|
||||
|
Reference in New Issue
Block a user