fix(ivy): set proper implementation for module injector (#28667)

Prior to this change we used current injector implementation for module injector, which was causing problems and produces circular dependencies in case the same token is referenced (with @SkipSelf flag) in the `deps` array. The origin of the problem was that once `directiveInject` implementation becomes active, it was used for module injector as well, thus searching deps in Component/Directive DI scope. This fix sets `injectInjectorOnly` implementation for module injector to resolve the problem.

PR Close #28667
This commit is contained in:
Andrew Kushnir
2019-02-12 09:46:39 -08:00
committed by Miško Hevery
parent 5cafd44654
commit 553f80ff46
4 changed files with 259 additions and 571 deletions

View File

@ -7,10 +7,11 @@
*/
import {CommonModule} from '@angular/common';
import {ChangeDetectorRef, Component, Pipe, PipeTransform} from '@angular/core';
import {ChangeDetectorRef, Component, Directive, Inject, LOCALE_ID, Optional, Pipe, PipeTransform, SkipSelf, ViewChild} from '@angular/core';
import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
describe('di', () => {
describe('ChangeDetectorRef', () => {
it('should inject host component ChangeDetectorRef into directives on templates', () => {
@ -39,4 +40,94 @@ describe('di', () => {
expect((pipeInstance !.cdr as ViewRef<MyApp>).context).toBe(fixture.componentInstance);
});
});
it('should not cause cyclic dependency if same token is requested in deps with @SkipSelf', () => {
@Component({
selector: 'my-comp',
template: '...',
providers: [{
provide: LOCALE_ID,
useFactory: () => 'ja-JP',
// Note: `LOCALE_ID` is also provided within APPLICATION_MODULE_PROVIDERS, so we use it here
// as a dep and making sure it doesn't cause cyclic dependency (since @SkipSelf is present)
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
}]
})
class MyComp {
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({declarations: [MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.localeId).toBe('ja-JP');
});
it('module-level deps should not access Component/Directive providers', () => {
@Component({
selector: 'my-comp',
template: '...',
providers: [{
provide: 'LOCALE_ID_DEP', //
useValue: 'LOCALE_ID_DEP_VALUE'
}]
})
class MyComp {
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({
declarations: [MyComp],
providers: [{
provide: LOCALE_ID,
// we expect `localeDepValue` to be undefined, since it's not provided at a module level
useFactory: (localeDepValue: any) => localeDepValue || 'en-GB',
deps: [[new Inject('LOCALE_ID_DEP'), new Optional()]]
}]
});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.localeId).toBe('en-GB');
});
it('should skip current level while retrieving tokens if @SkipSelf is defined', () => {
@Component({
selector: 'my-comp',
template: '...',
providers: [{provide: LOCALE_ID, useFactory: () => 'en-GB'}]
})
class MyComp {
constructor(@SkipSelf() @Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({declarations: [MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
// takes `LOCALE_ID` from module injector, since we skip Component level with @SkipSelf
expect(fixture.componentInstance.localeId).toBe('en-US');
});
it('should work when injecting dependency in Directives', () => {
@Directive({
selector: '[dir]', //
providers: [{provide: LOCALE_ID, useValue: 'ja-JP'}]
})
class MyDir {
constructor(@SkipSelf() @Inject(LOCALE_ID) public localeId: string) {}
}
@Component({
selector: 'my-comp',
template: '<div dir></div>',
providers: [{provide: LOCALE_ID, useValue: 'en-GB'}]
})
class MyComp {
@ViewChild(MyDir) myDir !: MyDir;
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.myDir.localeId).toBe('en-GB');
});
});