feat(ivy): support injection flags at runtime (#23518)

PR Close #23518
This commit is contained in:
Kara Erickson
2018-04-23 18:24:40 -07:00
committed by Igor Minar
parent ab5bc42da0
commit db77d8dc92
4 changed files with 324 additions and 52 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, ElementRef, InjectFlags, TemplateRef, ViewContainerRef, defineInjectable} from '@angular/core';
import {ChangeDetectorRef, ElementRef, Host, InjectFlags, Optional, Self, SkipSelf, TemplateRef, ViewContainerRef, defineInjectable} from '@angular/core';
import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
import {defineComponent} from '../../src/render3/definition';
@ -129,8 +129,8 @@ describe('di', () => {
}
/** <div dirA dirB></div> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
elementEnd();
}
@ -153,8 +153,8 @@ describe('di', () => {
}
/** <comp dirB></comp> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'comp', ['dirB', '']);
elementEnd();
}
@ -180,8 +180,8 @@ describe('di', () => {
* <div dirA dirB></div>
* % }
*/
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
container(0);
}
containerRefreshStart(0);
@ -240,8 +240,8 @@ describe('di', () => {
}
/** <div dirA dirB dirC></div> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '', 'dirB', '', 'dirC', '']);
elementEnd();
}
@ -300,8 +300,8 @@ describe('di', () => {
}
/** <comp dirA dirB dirC dirD></comp> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'comp', ['dirA', '', 'dirB', '', 'dirC', '', 'dirD', '']);
elementEnd();
}
@ -378,16 +378,16 @@ describe('di', () => {
}
/** <div dirA dirB></div> */
const Parent = createComponent('parent', function(ctx: any, cm: boolean) {
if (cm) {
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
elementEnd();
}
}, [DirA, DirB]);
/** <parent dirB></parent> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'parent', ['dirB', '']);
elementEnd();
}
@ -426,7 +426,7 @@ describe('di', () => {
expect(fixture.html).toEqual('MyService');
});
it('should throw if directive is not found', () => {
it('should throw if directive is not found anywhere', () => {
class Dir {
constructor(siblingDir: OtherDir) {}
@ -448,8 +448,8 @@ describe('di', () => {
}
/** <div dir></div> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '']);
elementEnd();
}
@ -458,6 +458,44 @@ describe('di', () => {
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
});
it('should throw if directive is not found in ancestor tree', () => {
class Dir {
constructor(siblingDir: OtherDir) {}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dir', '']],
type: Dir,
factory: () => new Dir(directiveInject(OtherDir)),
features: [PublicFeature]
});
}
class OtherDir {
static ngDirectiveDef = defineDirective({
selectors: [['', 'other', '']],
type: OtherDir,
factory: () => new OtherDir(),
features: [PublicFeature]
});
}
/**
* <div other></div>
* <div dir></div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['other', '']);
elementEnd();
elementStart(1, 'div', ['dir', '']);
elementEnd();
}
}, [Dir, OtherDir]);
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
});
it('should throw if directives try to inject each other', () => {
class DirA {
constructor(dir: DirB) {}
@ -482,8 +520,8 @@ describe('di', () => {
}
/** <div dirA dirB></div> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '', 'dirB', '']);
elementEnd();
}
@ -505,8 +543,8 @@ describe('di', () => {
}
/** <div dir></div> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '']);
elementEnd();
}
@ -515,6 +553,217 @@ describe('di', () => {
expect(() => new ComponentFixture(App)).toThrowError(/Cannot instantiate cyclic dependency!/);
});
describe('flags', () => {
class DirB {
value: string;
static ngDirectiveDef = defineDirective({
type: DirB,
selectors: [['', 'dirB', '']],
factory: () => new DirB(),
inputs: {value: 'dirB'},
features: [PublicFeature]
});
}
it('should not throw if dependency is @Optional', () => {
let dirA: DirA;
class DirA {
constructor(@Optional() public dirB: DirB|null) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Optional))
});
}
/** <div dirA></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '']);
elementEnd();
}
}, [DirA, DirB]);
expect(() => {
const fixture = new ComponentFixture(App);
expect(dirA !.dirB).toEqual(null);
}).not.toThrow();
});
it('should not throw if dependency is @Optional but defined elsewhere', () => {
let dirA: DirA;
class DirA {
constructor(@Optional() public dirB: DirB|null) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Optional))
});
}
/**
* <div dirB></div>
* <div dirA></div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
elementEnd();
elementStart(1, 'div', ['dirA', '']);
elementEnd();
}
}, [DirA, DirB]);
expect(() => {
const fixture = new ComponentFixture(App);
expect(dirA !.dirB).toEqual(null);
}).not.toThrow();
});
it('should skip the current node with @SkipSelf', () => {
let dirA: DirA;
class DirA {
constructor(@SkipSelf() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.SkipSelf))
});
}
/** <div dirA dirB="self"></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '', 'dirB', 'self']);
elementEnd();
}
}, [DirA, DirB]);
/* <comp dirB="parent"></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'comp', ['dirB', 'parent']);
elementEnd();
}
}, [Comp, DirB]);
const fixture = new ComponentFixture(App);
expect(dirA !.dirB.value).toEqual('parent');
});
it('should check only the current node with @Self', () => {
let dirA: DirA;
class DirA {
constructor(@Self() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Self))
});
}
/**
* <div dirB>
* <div dirA></div>
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
elementStart(1, 'div', ['dirA', '']);
elementEnd();
elementEnd();
}
}, [DirA, DirB]);
expect(() => {
const fixture = new ComponentFixture(App);
}).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
});
it('should check only the current node with @Self even with false positive', () => {
let dirA: DirA;
class DirA {
constructor(@Self() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Self))
});
}
const DirC = createDirective('dirC');
/**
* <div dirB>
* <div dirA dirC></div>
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
elementStart(1, 'div', ['dirA', '', 'dirC', '']);
elementEnd();
elementEnd();
}
}, [DirA, DirB, DirC]);
expect(() => {
(DirA as any)['__NG_ELEMENT_ID__'] = 1;
(DirC as any)['__NG_ELEMENT_ID__'] = 257;
const fixture = new ComponentFixture(App);
}).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
});
it('should not pass component boundary with @Host', () => {
let dirA: DirA;
class DirA {
constructor(@Host() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Host))
});
}
/** <div dirA></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '']);
elementEnd();
}
}, [DirA, DirB]);
/* <comp dirB></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'comp', ['dirB', '']);
elementEnd();
}
}, [Comp, DirB]);
expect(() => {
const fixture = new ComponentFixture(App);
}).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
});
});
});
describe('ElementRef', () => {
@ -1025,16 +1274,16 @@ describe('di', () => {
bloomAdd(di, { __NG_ELEMENT_ID__: 223 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 255 } as any);
expect(bloomFindPossibleInjector(di, 0)).toEqual(di);
expect(bloomFindPossibleInjector(di, 1)).toEqual(null);
expect(bloomFindPossibleInjector(di, 32)).toEqual(di);
expect(bloomFindPossibleInjector(di, 64)).toEqual(di);
expect(bloomFindPossibleInjector(di, 96)).toEqual(di);
expect(bloomFindPossibleInjector(di, 127)).toEqual(di);
expect(bloomFindPossibleInjector(di, 161)).toEqual(di);
expect(bloomFindPossibleInjector(di, 188)).toEqual(di);
expect(bloomFindPossibleInjector(di, 223)).toEqual(di);
expect(bloomFindPossibleInjector(di, 255)).toEqual(di);
expect(bloomFindPossibleInjector(di, 0, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 1, InjectFlags.Default)).toEqual(null);
expect(bloomFindPossibleInjector(di, 32, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 64, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 96, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 127, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 161, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 188, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 223, InjectFlags.Default)).toEqual(di);
expect(bloomFindPossibleInjector(di, 255, InjectFlags.Default)).toEqual(di);
});
});