diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts
index b7c2090d93..6df8bb54a7 100644
--- a/packages/core/test/acceptance/di_spec.ts
+++ b/packages/core/test/acceptance/di_spec.ts
@@ -7,13 +7,480 @@
*/
import {CommonModule} from '@angular/common';
-import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, INJECTOR, Inject, Injector, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, SkipSelf, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
+import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, HostBinding, INJECTOR, Inject, Injectable, Injector, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef} from '@angular/core';
import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
import {onlyInIvy} from '@angular/private/testing';
-
describe('di', () => {
+ describe('no dependencies', () => {
+ it('should create directive with no deps', () => {
+ @Directive({selector: '[dir]', exportAs: 'dir'})
+ class MyDirective {
+ value = 'Created';
+ }
+ @Component({template: '
{{ dir.value }}
'})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [MyDirective, MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toContain('Created');
+ });
+ });
+
+ describe('directive injection', () => {
+
+ let log: string[] = [];
+
+ @Directive({selector: '[dirB]', exportAs: 'dirB'})
+ class DirectiveB {
+ @Input() value = 'DirB';
+ constructor() { log.push(this.value); }
+ }
+
+ beforeEach(() => log = []);
+
+ it('should create directive with intra view dependencies', () => {
+ @Directive({selector: '[dirA]', exportAs: 'dirA'})
+ class DirectiveA {
+ value = 'DirA';
+ }
+ @Directive({selector: '[dirC]', exportAs: 'dirC'})
+ class DirectiveC {
+ value: string;
+ constructor(dirA: DirectiveA, dirB: DirectiveB) { this.value = dirA.value + dirB.value; }
+ }
+ @Component({
+ template: `
+
+ {{ dir.value }}
+
+ `
+ })
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, DirectiveC, MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('span');
+ expect(divElement.textContent).toContain('DirADirB');
+ });
+
+ it('should instantiate injected directives in dependency order', () => {
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ value = 'dirA';
+ constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); }
+ }
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
+ });
+
+ it('should fallback to the module injector', () => {
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ value = 'dirA';
+ constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); }
+ }
+ // - dirB is know to the node injectors
+ // - then when dirA tries to inject dirB, it will check the node injector first tree
+ // - if not found, it will check the module injector tree
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({
+ declarations: [DirectiveA, DirectiveB, MyComp],
+ providers: [{provide: DirectiveB, useValue: {value: 'module'}}]
+ });
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(['DirB', 'DirA (dep: module)']);
+ });
+
+ it('should instantiate injected directives before components', () => {
+ @Component({selector: 'my-comp', template: ''})
+ class MyComp {
+ constructor(dirB: DirectiveB) { log.push(`Comp (dep: ${dirB.value})`); }
+ }
+ @Component({template: ''})
+ class MyApp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveB, MyComp, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
+ });
+
+ it('should inject directives in the correct order in a for loop', () => {
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(dir: DirectiveB) { log.push(`DirA (dep: ${dir.value})`); }
+ }
+ @Component({template: ''})
+ class MyComp {
+ array = [1, 2, 3];
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(
+ ['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
+ });
+
+ it('should instantiate directives with multiple out-of-order dependencies', () => {
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ value = 'DirA';
+ constructor() { log.push(this.value); }
+ }
+ @Directive({selector: '[dirC]'})
+ class DirectiveC {
+ value = 'DirC';
+ constructor() { log.push(this.value); }
+ }
+ @Directive({selector: '[dirB]'})
+ class DirectiveB {
+ constructor(dirA: DirectiveA, dirC: DirectiveC) {
+ log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`);
+ }
+ }
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, DirectiveC, MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
+ });
+
+ it('should instantiate in the correct order for complex case', () => {
+ @Directive({selector: '[dirC]'})
+ class DirectiveC {
+ value = 'DirC';
+ constructor(dirB: DirectiveB) { log.push(`DirC (dep: ${dirB.value})`); }
+ }
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ value = 'DirA';
+ constructor(dirC: DirectiveC) { log.push(`DirA (dep: ${dirC.value})`); }
+ }
+ @Directive({selector: '[dirD]'})
+ class DirectiveD {
+ value = 'DirD';
+ constructor(dirA: DirectiveA) { log.push(`DirD (dep: ${dirA.value})`); }
+ }
+ @Component({selector: 'my-comp', template: ''})
+ class MyComp {
+ constructor(dirD: DirectiveD) { log.push(`Comp (dep: ${dirD.value})`); }
+ }
+ @Component({template: ''})
+ class MyApp {
+ }
+ TestBed.configureTestingModule(
+ {declarations: [DirectiveA, DirectiveB, DirectiveC, DirectiveD, MyComp, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(
+ ['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
+ });
+
+ it('should instantiate in correct order with mixed parent and peer dependencies', () => {
+ @Component({template: ''})
+ class MyApp {
+ value = 'App';
+ }
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(dirB: DirectiveB, app: MyApp) {
+ log.push(`DirA (deps: ${dirB.value} and ${app.value})`);
+ }
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
+ });
+
+ it('should not use a parent when peer dep is available', () => {
+ let count = 1;
+ @Directive({selector: '[dirB]'})
+ class DirectiveB {
+ count: number;
+ constructor() {
+ log.push(`DirB`);
+ this.count = count++;
+ }
+ }
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(dirB: DirectiveB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }
+ }
+ @Component({selector: 'my-comp', template: ''})
+ class MyComp {
+ }
+ @Component({template: ''})
+ class MyApp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
+ });
+
+ describe('dependencies in parent views', () => {
+
+ @Directive({selector: '[dirA]', exportAs: 'dirA'})
+ class DirectiveA {
+ injector: Injector;
+ constructor(public dirB: DirectiveB, public vcr: ViewContainerRef) {
+ this.injector = vcr.injector;
+ }
+ }
+
+ @Component(
+ {selector: 'my-comp', template: '{{ dir.dirB.value }}
'})
+ class MyComp {
+ }
+
+ it('should find dependencies on component hosts', () => {
+ @Component({template: ''})
+ class MyApp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toEqual('DirB');
+ });
+
+ it('should find dependencies for directives in embedded views', () => {
+ @Component({
+ template: ``
+ })
+ class MyApp {
+ showing = false;
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.componentInstance.showing = true;
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toEqual('DirB');
+ });
+
+ it('should find dependencies of directives nested deeply in inline views', () => {
+ @Component({
+ template: `
+
+
+ {{ dir.dirB.value }}
+
+
+
`
+ })
+ class MyApp {
+ skipContent = false;
+ skipContent2 = false;
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toEqual('DirB');
+ });
+
+ it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
+ @Directive({selector: '[structuralDir]'})
+ class StructuralDirective {
+ @Input() tmp !: TemplateRef;
+ constructor(public vcr: ViewContainerRef) {}
+
+ create() { this.vcr.createEmbeddedView(this.tmp); }
+ }
+ @Component({
+ template: `
+
+ {{ dir.dirB.value }}
+
+
+
+ `
+ })
+ class MyComp {
+ @ViewChild(StructuralDirective) structuralDir !: StructuralDirective;
+ }
+ TestBed.configureTestingModule(
+ {declarations: [StructuralDirective, DirectiveA, DirectiveB, MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+ fixture.componentInstance.structuralDir.create();
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div[value=insertion]');
+ expect(divElement.textContent).toEqual('declaration');
+ });
+
+ it('should create injectors on second template pass', () => {
+ @Component({
+ template: `
+
+
+
`
+ })
+ class MyApp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toEqual('DirBDirB');
+ });
+
+ it('should create injectors and host bindings in same view', () => {
+ @Directive({selector: '[hostBindingDir]'})
+ class HostBindingDirective {
+ @HostBinding('id') id = 'foo';
+ }
+
+ @Component({
+ template: ``
+ })
+ class MyApp {
+ @ViewChild(HostBindingDirective) hostBindingDir !: HostBindingDirective;
+ @ViewChild(DirectiveA) dirA !: DirectiveA;
+ }
+ TestBed.configureTestingModule(
+ {declarations: [DirectiveA, DirectiveB, HostBindingDirective, MyApp]});
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toEqual('DirB');
+ expect(divElement.id).toEqual('foo');
+
+ const dirA = fixture.componentInstance.dirA;
+ expect(dirA.vcr.injector).toEqual(dirA.injector);
+
+ const hostBindingDir = fixture.componentInstance.hostBindingDir;
+ hostBindingDir.id = 'bar';
+ fixture.detectChanges();
+ expect(divElement.id).toBe('bar');
+ });
+ });
+
+ it('should throw if directive is not found anywhere', () => {
+ @Directive({selector: '[dirB]'})
+ class DirectiveB {
+ constructor() {}
+ }
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(siblingDir: DirectiveB) {}
+ }
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
+ expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for DirectiveB/);
+ });
+
+ it('should throw if directive is not found in ancestor tree', () => {
+ @Directive({selector: '[dirB]'})
+ class DirectiveB {
+ constructor() {}
+ }
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(siblingDir: DirectiveB) {}
+ }
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
+ expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for DirectiveB/);
+ });
+
+ onlyInIvy('Ivy has different error message for circular dependency')
+ .it('should throw if directives try to inject each other', () => {
+ @Directive({selector: '[dirB]'})
+ class DirectiveB {
+ constructor(@Inject(forwardRef(() => DirectiveA)) siblingDir: DirectiveA) {}
+ }
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(siblingDir: DirectiveB) {}
+ }
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
+ expect(() => TestBed.createComponent(MyComp)).toThrowError(/Circular dep for/);
+ });
+
+ onlyInIvy('Ivy has different error message for circular dependency')
+ .it('should throw if directive tries to inject itself', () => {
+ @Directive({selector: '[dirA]'})
+ class DirectiveA {
+ constructor(siblingDir: DirectiveA) {}
+ }
+ @Component({template: ''})
+ class MyComp {
+ }
+ TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
+ expect(() => TestBed.createComponent(MyComp)).toThrowError(/Circular dep for/);
+ });
+ });
+
+ describe('service injection', () => {
+
+ it('should create instance even when no injector present', () => {
+ @Injectable({providedIn: 'root'})
+ class MyService {
+ value = 'MyService';
+ }
+ @Component({template: '{{myService.value}}
'})
+ class MyComp {
+ constructor(public myService: MyService) {}
+ }
+ TestBed.configureTestingModule({declarations: [MyComp]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+
+ const divElement = fixture.nativeElement.querySelector('div');
+ expect(divElement.textContent).toEqual('MyService');
+ });
+ });
+
describe('Special tokens', () => {
describe('Injector', () => {
diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts
index 0426e06777..3aa1d037df 100644
--- a/packages/core/test/render3/di_spec.ts
+++ b/packages/core/test/render3/di_spec.ts
@@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ChangeDetectorRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, ɵɵdefineInjectable, ɵɵdefineInjector} from '@angular/core';
+import {ChangeDetectorRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, ViewContainerRef, ɵɵdefineInjector} from '@angular/core';
import {createLView, createNodeAtIndex, createTView} from '@angular/core/src/render3/instructions/shared';
import {ComponentType, RenderFlags} from '@angular/core/src/render3/interfaces/definition';
import {createInjector} from '../../src/di/r3_injector';
import {ɵɵdefineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
-import {ɵɵProvidersFeature, ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation2, ɵɵprojection, ɵɵprojectionDef, ɵɵreference, ɵɵtemplate, ɵɵtemplateRefExtractor, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index';
+import {ɵɵProvidersFeature, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation2, ɵɵprojection, ɵɵprojectionDef, ɵɵreference, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index';
import {TNODE} from '../../src/render3/interfaces/injector';
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
@@ -26,36 +26,6 @@ import {getRendererFactory2} from './imported_renderer2';
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
describe('di', () => {
- describe('no dependencies', () => {
- it('should create directive with no deps', () => {
- class Directive {
- value: string = 'Created';
- static ngDirectiveDef = ɵɵdefineDirective({
- type: Directive,
- selectors: [['', 'dir', '']],
- factory: () => new Directive,
- exportAs: ['dir']
- });
- }
-
- /** {{ dir.value }}
*/
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['dir', ''], ['dir', 'dir']);
- { ɵɵtext(2); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const tmp = ɵɵreference(1) as any;
- ɵɵtextBinding(2, ɵɵbind(tmp.value));
- }
- }, 3, 1, [Directive]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Created
');
- });
- });
-
describe('directive injection', () => {
let log: string[] = [];
@@ -73,129 +43,10 @@ describe('di', () => {
beforeEach(() => log = []);
- it('should create directive with intra view dependencies', () => {
- class DirA {
- value: string = 'DirA';
- static ngDirectiveDef = ɵɵdefineDirective(
- {type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA()});
- }
-
- class DirC {
- value: string;
- constructor(a: DirA, b: DirB) { this.value = a.value + b.value; }
- static ngDirectiveDef = ɵɵdefineDirective({
- type: DirC,
- selectors: [['', 'dirC', '']],
- factory: () => new DirC(ɵɵdirectiveInject(DirA), ɵɵdirectiveInject(DirB)),
- exportAs: ['dirC']
- });
- }
-
- /**
- *
- * {{ dir.value }}
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['dirA', '']);
- {
- ɵɵelementStart(1, 'span', ['dirB', '', 'dirC', ''], ['dir', 'dirC']);
- { ɵɵtext(3); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const tmp = ɵɵreference(2) as any;
- ɵɵtextBinding(3, ɵɵbind(tmp.value));
- }
- }, 4, 1, [DirA, DirB, DirC]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('DirADirB
');
- });
-
- it('should instantiate injected directives in dependency order', () => {
- class DirA {
- constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirA', '']],
- type: DirA,
- factory: () => new DirA(ɵɵdirectiveInject(DirB)),
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dirA', '', 'dirB', '']);
- }
- }, 1, 0, [DirA, DirB]);
-
- new ComponentFixture(App);
- expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
- });
-
- it('should fallback to the module injector', () => {
- class DirA {
- constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirA', '']],
- type: DirA,
- factory: () => new DirA(ɵɵdirectiveInject(DirB)),
- });
- }
-
- // ``
- // - dirB is know to the node injectors (it uses the diPublic feature)
- // - then when dirA tries to inject dirB, it will check the node injector first tree
- // - if not found, it will check the module injector tree
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dirB', '']);
- ɵɵelement(1, 'div', ['dirA', '']);
- }
- }, 2, 0, [DirA, DirB]);
-
- const fakeModuleInjector: any = {
- get: function(token: any) {
- const value = token === DirB ? 'module' : 'fail';
- return {value: value};
- }
- };
-
- new ComponentFixture(App, {injector: fakeModuleInjector});
- expect(log).toEqual(['DirB', 'DirA (dep: module)']);
- });
-
- it('should instantiate injected directives before components', () => {
- class Comp {
- constructor(dir: DirB) { log.push(`Comp (dep: ${dir.value})`); }
-
- static ngComponentDef = ɵɵdefineComponent({
- selectors: [['comp']],
- type: Comp,
- consts: 0,
- vars: 0,
- factory: () => new Comp(ɵɵdirectiveInject(DirB)),
- template: (rf: RenderFlags, ctx: Comp) => {}
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'comp', ['dirB', '']);
- }
- }, 1, 0, [Comp, DirB]);
-
- new ComponentFixture(App);
- expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
- });
-
+ /**
+ * This test needs to be moved to acceptance/di_spec.ts
+ * when Ivy compiler supports inline views.
+ */
it('should inject directives in the correct order in a for loop', () => {
class DirA {
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
@@ -235,184 +86,6 @@ describe('di', () => {
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
});
- it('should instantiate directives with multiple out-of-order dependencies', () => {
- class DirA {
- value = 'DirA';
- constructor() { log.push(this.value); }
-
- static ngDirectiveDef = ɵɵdefineDirective(
- {selectors: [['', 'dirA', '']], type: DirA, factory: () => new DirA()});
- }
-
- class DirB {
- constructor(dirA: DirA, dirC: DirC) {
- log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`);
- }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirB', '']],
- type: DirB,
- factory: () => new DirB(ɵɵdirectiveInject(DirA), ɵɵdirectiveInject(DirC))
- });
- }
-
- class DirC {
- value = 'DirC';
- constructor() { log.push(this.value); }
-
- static ngDirectiveDef = ɵɵdefineDirective(
- {selectors: [['', 'dirC', '']], type: DirC, factory: () => new DirC()});
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dirA', '', 'dirB', '', 'dirC', '']);
- }
- }, 1, 0, [DirA, DirB, DirC]);
-
- new ComponentFixture(App);
- expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
- });
-
- it('should instantiate in the correct order for complex case', () => {
- class Comp {
- constructor(dir: DirD) { log.push(`Comp (dep: ${dir.value})`); }
-
- static ngComponentDef = ɵɵdefineComponent({
- selectors: [['comp']],
- type: Comp,
- consts: 0,
- vars: 0,
- factory: () => new Comp(ɵɵdirectiveInject(DirD)),
- template: (ctx: any, fm: boolean) => {}
- });
- }
-
- class DirA {
- value = 'DirA';
- constructor(dir: DirC) { log.push(`DirA (dep: ${dir.value})`); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirA', '']],
- type: DirA,
- factory: () => new DirA(ɵɵdirectiveInject(DirC))
- });
- }
-
- class DirC {
- value = 'DirC';
- constructor(dir: DirB) { log.push(`DirC (dep: ${dir.value})`); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirC', '']],
- type: DirC,
- factory: () => new DirC(ɵɵdirectiveInject(DirB))
- });
- }
-
- class DirD {
- value = 'DirD';
- constructor(dir: DirA) { log.push(`DirD (dep: ${dir.value})`); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirD', '']],
- type: DirD,
- factory: () => new DirD(ɵɵdirectiveInject(DirA))
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'comp', ['dirA', '', 'dirB', '', 'dirC', '', 'dirD', '']);
- }
- }, 1, 0, [Comp, DirA, DirB, DirC, DirD]);
-
- new ComponentFixture(App);
- expect(log).toEqual(
- ['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
- });
-
- it('should instantiate in correct order with mixed parent and peer dependencies', () => {
- class DirA {
- constructor(dirB: DirB, app: App) {
- log.push(`DirA (deps: ${dirB.value} and ${app.value})`);
- }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirA', '']],
- type: DirA,
- factory: () => new DirA(ɵɵdirectiveInject(DirB), ɵɵdirectiveInject(App)),
- });
- }
-
- class App {
- value = 'App';
-
- static ngComponentDef = ɵɵdefineComponent({
- selectors: [['app']],
- type: App,
- factory: () => new App(),
- consts: 1,
- vars: 0,
- /** */
- template: (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']);
- }
- },
- directives: [DirA, DirB]
- });
- }
-
- new ComponentFixture(App);
- expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
- });
-
- it('should not use a parent when peer dep is available', () => {
- let count = 1;
-
- class DirA {
- constructor(dirB: DirB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirA', '']],
- type: DirA,
- factory: () => new DirA(ɵɵdirectiveInject(DirB)),
- });
- }
-
- class DirB {
- count: number;
-
- constructor() {
- log.push(`DirB`);
- this.count = count++;
- }
-
- static ngDirectiveDef = ɵɵdefineDirective(
- {selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB()});
- }
-
- /** */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dirA', '', 'dirB', '']);
- }
- }, 1, 0, [DirA, DirB]);
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'parent', ['dirB', '']);
- }
- }, 1, 0, [Parent, DirB]);
-
- new ComponentFixture(App);
- expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
- });
-
describe('dependencies in parent views', () => {
class DirA {
@@ -431,78 +104,9 @@ describe('di', () => {
}
/**
- *
- * {{ dir.dirB.value }}
- *
+ * This test needs to be moved to acceptance/di_spec.ts
+ * when Ivy compiler supports inline views.
*/
- const Comp = createComponent('comp', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
- { ɵɵtext(2); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const dir = ɵɵreference(1) as DirA;
- ɵɵtextBinding(2, ɵɵbind(dir.dirB.value));
- }
- }, 3, 1, [DirA]);
-
- it('should find dependencies on component hosts', () => {
- /** /comp> */
- const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'comp', ['dirB', '']);
- }
- }, 1, 0, [Comp, DirB]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.hostElement.textContent).toEqual(`DirB`);
- });
-
- it('should find dependencies for directives in embedded views', () => {
-
- function IfTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵelementStart(1, 'div', ['dirA', ''], ['dir', 'dirA']);
- { ɵɵtext(3); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
-
- if (rf & RenderFlags.Update) {
- const dir = ɵɵreference(2) as DirA;
- ɵɵtextBinding(3, ɵɵbind(dir.dirB.value));
- }
- }
-
- /**
- *
- *
- *
{{ dir.dirB.value }}
- *
- *
- */
- const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['dirB', '']);
- { ɵɵtemplate(1, IfTemplate, 4, 1, 'div', [AttributeMarker.Template, 'ngIf']); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(1, 'ngIf', ɵɵbind(ctx.showing));
- }
- }, 2, 1, [DirA, DirB, NgIf]);
-
- const fixture = new ComponentFixture(App);
- fixture.component.showing = true;
- fixture.update();
-
- expect(fixture.hostElement.textContent).toEqual(`DirB`);
- });
-
it('should find dependencies of directives nested deeply in inline views', () => {
/**
*
@@ -560,293 +164,6 @@ describe('di', () => {
const fixture = new ComponentFixture(App);
expect(fixture.hostElement.textContent).toEqual(`DirB`);
});
-
- it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
- let structuralDir !: StructuralDir;
-
- class StructuralDir {
- // @Input()
- tmp !: TemplateRef
;
-
- constructor(public vcr: ViewContainerRef) {}
-
- create() { this.vcr.createEmbeddedView(this.tmp); }
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: StructuralDir,
- selectors: [['', 'structuralDir', '']],
- factory: () => structuralDir =
- new StructuralDir(ɵɵdirectiveInject(ViewContainerRef as any)),
- inputs: {tmp: 'tmp'}
- });
- }
-
- function FooTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
- { ɵɵtext(2); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const dir = ɵɵreference(1) as DirA;
- ɵɵtextBinding(2, ɵɵbind(dir.dirB.value));
- }
- }
-
- /**
- *
- *
- * {{ dir.dirB.value }}
- *
- *
- *
- *
- *
- * // insertion point
- *
- */
- const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['dirB', '', 'value', 'declaration']);
- {
- ɵɵtemplate(
- 1, FooTemplate, 3, 1, 'ng-template', null, ['foo', ''], ɵɵtemplateRefExtractor);
- }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'div', ['dirB', '', 'value', 'insertion']);
- { ɵɵelement(4, 'div', ['structuralDir', '']); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const foo = ɵɵreference(2) as any;
- ɵɵelementProperty(4, 'tmp', ɵɵbind(foo));
- }
- }, 5, 1, [DirA, DirB, StructuralDir]);
-
- const fixture = new ComponentFixture(App);
- structuralDir.create();
- fixture.update();
- expect(fixture.hostElement.textContent).toEqual(`declaration`);
- });
-
- it('should create injectors on second template pass', () => {
- /**
- *
- *
- */
- const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'comp', ['dirB', '']);
- ɵɵelement(1, 'comp', ['dirB', '']);
- }
- }, 2, 0, [Comp, DirB]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.hostElement.textContent).toEqual(`DirBDirB`);
- });
-
- it('should create injectors and host bindings in same view', () => {
- let hostBindingDir !: HostBindingDir;
-
- class HostBindingDir {
- // @HostBinding('id')
- id = 'foo';
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: HostBindingDir,
- selectors: [['', 'hostBindingDir', '']],
- factory: () => hostBindingDir = new HostBindingDir(),
- hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
- if (rf & RenderFlags.Create) {
- ɵɵallocHostVars(1);
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(elementIndex, 'id', ɵɵbind(ctx.id));
- }
- }
- });
- }
-
- let dir !: DirA;
- /**
- *
- *
- * {{ dir.dirB.value }}
- *
- *
- */
- const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', [
- 'dirB',
- '',
- 'hostBindingDir',
- '',
- ]);
- {
- ɵɵelementStart(1, 'p', ['dirA', ''], ['dir', 'dirA']);
- { ɵɵtext(3); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- dir = ɵɵreference(2) as DirA;
- ɵɵtextBinding(3, ɵɵbind(dir.dirB.value));
- }
- }, 4, 1, [HostBindingDir, DirA, DirB]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.hostElement.textContent).toEqual(`DirB`);
- const hostDirEl = fixture.hostElement.querySelector('div') as HTMLElement;
- expect(hostDirEl.id).toEqual('foo');
- // The injector should not be overwritten by host bindings
- expect(dir.vcr.injector).toEqual(dir.injector);
-
- hostBindingDir.id = 'bar';
- fixture.update();
- expect(hostDirEl.id).toEqual('bar');
- });
- });
-
- it('should create instance even when no injector present', () => {
- class MyService {
- value = 'MyService';
- static ngInjectableDef =
- ɵɵdefineInjectable({providedIn: 'root', factory: () => new MyService()});
- }
-
- class MyComponent {
- constructor(public myService: MyService) {}
- static ngComponentDef = ɵɵdefineComponent({
- type: MyComponent,
- selectors: [['my-component']],
- consts: 1,
- vars: 1,
- factory: () => new MyComponent(ɵɵdirectiveInject(MyService)),
- template: function(rf: RenderFlags, ctx: MyComponent) {
- if (rf & RenderFlags.Create) {
- ɵɵtext(0);
- }
- if (rf & RenderFlags.Update) {
- ɵɵtextBinding(0, ɵɵbind(ctx.myService.value));
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyComponent);
- fixture.update();
- expect(fixture.html).toEqual('MyService');
- });
-
- it('should throw if directive is not found anywhere', () => {
- class Dir {
- constructor(siblingDir: OtherDir) {}
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dir', '']],
- type: Dir,
- factory: () => new Dir(ɵɵdirectiveInject(OtherDir))
- });
- }
-
- class OtherDir {
- static ngDirectiveDef = ɵɵdefineDirective(
- {selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dir', '']);
- }
- }, 1, 0, [Dir, OtherDir]);
-
- 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))
- });
- }
-
- class OtherDir {
- static ngDirectiveDef = ɵɵdefineDirective(
- {selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
- }
-
- /**
- *
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['other', '']);
- ɵɵelement(1, 'div', ['dir', '']);
- }
- }, 2, 0, [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) {}
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirA', '']],
- type: DirA,
- factory: () => new DirA(ɵɵdirectiveInject(DirB))
- });
- }
-
- class DirB {
- constructor(dir: DirA) {}
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dirB', '']],
- type: DirB,
- factory: () => new DirB(ɵɵdirectiveInject(DirA))
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dirA', '', 'dirB', '']);
- }
- }, 1, 0, [DirA, DirB]);
-
- expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
- });
-
- it('should throw if directive tries to inject itself', () => {
- class Dir {
- constructor(dir: Dir) {}
-
- static ngDirectiveDef = ɵɵdefineDirective({
- selectors: [['', 'dir', '']],
- type: Dir,
- factory: () => new Dir(ɵɵdirectiveInject(Dir))
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['dir', '']);
- }
- }, 1, 0, [Dir]);
-
- expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
});
describe('flags', () => {