fix(upgrade): respect hierarchical injectors for downgraded components (#14037)

Correctly wire up hierarchical injectors for downgraded components in
`upgrade/static`: Downgraded components inherit the injector of the first
downgraded component up the DOM tree.

This is similar to (part of) d91a86a, but for `upgrade/static`.

POSSIBLE BREAKING CHANGE:

In order to enable more control over the wiring of downgraded components and
their content (which eventually allows better control over features like
injector setup and content projection), it was necessary to change the
implementation of the directives generated for downgraed components.

The directives are now terminal and manually take care of projecting and
compiling their contents in the post-linking function. This is similar to how
the dynamic version of `upgrade` does it.

This is not expected to affect apps, since the relative order of individual
operations is preserved. Still, it is difficult to predict how every possible
usecase may be affected.
This commit is contained in:
Georgios Kalpakas
2017-01-16 23:52:42 +02:00
committed by Miško Hevery
parent 9aafdc7b02
commit 1367cd9569
6 changed files with 165 additions and 74 deletions

View File

@ -24,8 +24,10 @@ export function main() {
it('should instantiate ng2 in ng1 template and project content', async(() => {
// the ng2 component that will be used in ng1 (downgraded)
@Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
@Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`})
class Ng2Component {
prop = 'NG2';
ngContent = 'ng2-content';
}
// our upgrade module to host the component to downgrade
@ -42,13 +44,16 @@ export function main() {
const ng1Module = angular
.module('ng1', [])
// create an ng1 facade of the ng2 component
.directive('ng2', downgradeComponent({component: Ng2Component}));
.directive('ng2', downgradeComponent({component: Ng2Component}))
.run(($rootScope: angular.IRootScopeService) => {
$rootScope['prop'] = 'NG1';
$rootScope['ngContent'] = 'ng1-content';
});
const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
const element = html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]');
});
}));
@ -56,12 +61,13 @@ export function main() {
@Component({
selector: 'ng2',
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
template: `{{ 'ng2(' }}<ng1>{{ transclude }}</ng1>{{ ')' }}`,
})
class Ng2Component {
prop = 'ng2';
transclude = 'ng2-transclude';
}
@Directive({selector: 'ng1'})
class Ng1WrapperComponent extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) {
@ -78,21 +84,22 @@ export function main() {
ngDoBootstrap() {}
}
const ng1Module = angular.module('ng1', [])
.directive(
'ng1',
() => {
return {
transclude: true,
template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'
};
})
.directive('ng2', downgradeComponent({component: Ng2Component}));
const ng1Module =
angular.module('ng1', [])
.directive('ng1', () => ({
transclude: true,
template: '{{ prop }}(<ng-transclude></ng-transclude>)'
}))
.directive('ng2', downgradeComponent({component: Ng2Component}))
.run(($rootScope: angular.IRootScopeService) => {
$rootScope['prop'] = 'ng1';
$rootScope['transclude'] = 'ng1-transclude';
});
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
const element = html('<div>{{ \'ng1(\' }}<ng2></ng2>{{ \')\' }}</div>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
expect(document.body.textContent).toEqual('ng1(ng2(ng1(ng2-transclude)))');
});
}));
});

View File

@ -324,5 +324,36 @@ export function main() {
expect(multiTrim(document.body.textContent)).toBe('It works!');
});
}));
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) {}
}
@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 element = html('<parent><child></child></parent>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(document.body.textContent)).toBe('parent(child)');
});
}));
});
}

View File

@ -3028,7 +3028,7 @@ export function main() {
});
}));
it('should ng1 > ng1 > ng2 > ng1 (with `require`)', async(() => {
it('should support ng2 > ng1 > ng2 > ng1 (with `require`)', async(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: 'ng1A(<ng2-b></ng2-b>)',