2016-09-01 07:00:14 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {CommonModule} from '@angular/common';
|
|
|
|
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
|
perf: switch angular to use StaticInjector instead of ReflectiveInjector
This change allows ReflectiveInjector to be tree shaken resulting
in not needed Reflect polyfil and smaller bundles.
Code savings for HelloWorld using Closure:
Reflective: bundle.js: 105,864(34,190 gzip)
Static: bundle.js: 154,889(33,555 gzip)
645( 2%)
BREAKING CHANGE:
`platformXXXX()` no longer accepts providers which depend on reflection.
Specifically the method signature when from `Provider[]` to
`StaticProvider[]`.
Example:
Before:
```
[
MyClass,
{provide: ClassA, useClass: SubClassA}
]
```
After:
```
[
{provide: MyClass, deps: [Dep1,...]},
{provide: ClassA, useClass: SubClassA, deps: [Dep1,...]}
]
```
NOTE: This only applies to platform creation and providers for the JIT
compiler. It does not apply to `@Compotent` or `@NgModule` provides
declarations.
Benchpress note: Previously Benchpress also supported reflective
provides, which now require static providers.
DEPRECATION:
- `ReflectiveInjector` is now deprecated as it will be remove. Use
`Injector.create` as a replacement.
closes #18496
2017-08-03 15:33:29 -04:00
|
|
|
import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, StaticProvider, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
2017-01-25 20:41:08 -05:00
|
|
|
import {TestBed, async, fakeAsync} from '@angular/core/testing';
|
2017-03-02 15:12:46 -05:00
|
|
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
2016-09-01 07:00:14 -04:00
|
|
|
|
2017-12-16 17:42:55 -05:00
|
|
|
{
|
2016-09-01 07:00:14 -04:00
|
|
|
describe('insert/remove', () => {
|
|
|
|
|
|
|
|
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
|
|
|
|
|
|
|
|
it('should do nothing if component is null', async(() => {
|
2017-01-09 16:16:46 -05:00
|
|
|
const template = `<ng-template *ngComponentOutlet="currentComponent"></ng-template>`;
|
2016-09-01 07:00:14 -04:00
|
|
|
TestBed.overrideComponent(TestComponent, {set: {template: template}});
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = null;
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should insert content specified by a component', async(() => {
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('foo');
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should emit a ComponentRef once a component was created', async(() => {
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
|
|
|
|
fixture.componentInstance.cmpRef = null;
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('foo');
|
|
|
|
expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
|
2017-03-24 12:54:02 -04:00
|
|
|
expect(fixture.componentInstance.cmpRef !.instance).toBeAnInstanceOf(InjectedComponent);
|
2016-09-01 07:00:14 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
it('should clear view if component becomes null', async(() => {
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('foo');
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = null;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
it('should swap content if component changes', async(() => {
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('foo');
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponentAgain;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('bar');
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should use the injector, if one supplied', async(() => {
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
const uniqueValue = {};
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
perf: switch angular to use StaticInjector instead of ReflectiveInjector
This change allows ReflectiveInjector to be tree shaken resulting
in not needed Reflect polyfil and smaller bundles.
Code savings for HelloWorld using Closure:
Reflective: bundle.js: 105,864(34,190 gzip)
Static: bundle.js: 154,889(33,555 gzip)
645( 2%)
BREAKING CHANGE:
`platformXXXX()` no longer accepts providers which depend on reflection.
Specifically the method signature when from `Provider[]` to
`StaticProvider[]`.
Example:
Before:
```
[
MyClass,
{provide: ClassA, useClass: SubClassA}
]
```
After:
```
[
{provide: MyClass, deps: [Dep1,...]},
{provide: ClassA, useClass: SubClassA, deps: [Dep1,...]}
]
```
NOTE: This only applies to platform creation and providers for the JIT
compiler. It does not apply to `@Compotent` or `@NgModule` provides
declarations.
Benchpress note: Previously Benchpress also supported reflective
provides, which now require static providers.
DEPRECATION:
- `ReflectiveInjector` is now deprecated as it will be remove. Use
`Injector.create` as a replacement.
closes #18496
2017-08-03 15:33:29 -04:00
|
|
|
fixture.componentInstance.injector = Injector.create(
|
2016-09-01 07:00:14 -04:00
|
|
|
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
2017-03-24 12:54:02 -04:00
|
|
|
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
|
2016-09-01 07:00:14 -04:00
|
|
|
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
|
|
|
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
|
|
|
expect(cmpRef.instance.testToken).toBe(uniqueValue);
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should resolve a with injector', async(() => {
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.componentInstance.cmpRef = null;
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
|
|
|
fixture.detectChanges();
|
2017-03-24 12:54:02 -04:00
|
|
|
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
|
2016-09-01 07:00:14 -04:00
|
|
|
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
|
|
|
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
|
|
|
expect(cmpRef.instance.testToken).toBeNull();
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should render projectable nodes, if supplied', async(() => {
|
2017-01-09 16:16:46 -05:00
|
|
|
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
|
2016-09-01 07:00:14 -04:00
|
|
|
TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
|
|
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
|
|
|
|
|
|
|
TestBed
|
|
|
|
.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
|
|
|
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
|
|
|
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
|
|
|
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
|
|
|
fixture.componentInstance.projectables =
|
|
|
|
[fixture.componentInstance.vcRef
|
|
|
|
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
|
|
|
|
.rootNodes];
|
|
|
|
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('projected foo');
|
|
|
|
}));
|
2017-01-25 20:41:08 -05:00
|
|
|
|
|
|
|
it('should resolve components from other modules, if supplied', async(() => {
|
|
|
|
const compiler = TestBed.get(Compiler) as Compiler;
|
|
|
|
let fixture = TestBed.createComponent(TestComponent);
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('');
|
|
|
|
|
|
|
|
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
|
|
|
|
fixture.componentInstance.currentComponent = Module2InjectedComponent;
|
|
|
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('baz');
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should clean up moduleRef, if supplied', async(() => {
|
|
|
|
let destroyed = false;
|
|
|
|
const compiler = TestBed.get(Compiler) as Compiler;
|
|
|
|
const fixture = TestBed.createComponent(TestComponent);
|
|
|
|
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
|
|
|
|
fixture.componentInstance.currentComponent = Module2InjectedComponent;
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
|
|
|
|
spyOn(moduleRef, 'destroy').and.callThrough();
|
|
|
|
|
2017-03-24 12:54:02 -04:00
|
|
|
expect(moduleRef !.destroy).not.toHaveBeenCalled();
|
2017-01-25 20:41:08 -05:00
|
|
|
fixture.destroy();
|
2017-03-24 12:54:02 -04:00
|
|
|
expect(moduleRef !.destroy).toHaveBeenCalled();
|
2017-01-25 20:41:08 -05:00
|
|
|
}));
|
|
|
|
|
|
|
|
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
|
|
|
|
const compiler = TestBed.get(Compiler) as Compiler;
|
|
|
|
const fixture = TestBed.createComponent(TestComponent);
|
2017-03-14 19:26:17 -04:00
|
|
|
|
2017-01-25 20:41:08 -05:00
|
|
|
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
|
|
|
|
fixture.componentInstance.currentComponent = Module2InjectedComponent;
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement).toHaveText('baz');
|
|
|
|
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
|
2017-03-14 19:26:17 -04:00
|
|
|
|
2017-01-25 20:41:08 -05:00
|
|
|
fixture.componentInstance.currentComponent = Module2InjectedComponent2;
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
expect(fixture.nativeElement).toHaveText('baz2');
|
|
|
|
expect(moduleRef).toBe(fixture.componentInstance.ngComponentOutlet['_moduleRef']);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should re-create moduleRef when changed', async(() => {
|
|
|
|
const compiler = TestBed.get(Compiler) as Compiler;
|
|
|
|
const fixture = TestBed.createComponent(TestComponent);
|
|
|
|
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
|
|
|
|
fixture.componentInstance.currentComponent = Module2InjectedComponent;
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
expect(fixture.nativeElement).toHaveText('baz');
|
|
|
|
|
|
|
|
fixture.componentInstance.module = compiler.compileModuleSync(TestModule3);
|
|
|
|
fixture.componentInstance.currentComponent = Module3InjectedComponent;
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
expect(fixture.nativeElement).toHaveText('bat');
|
|
|
|
}));
|
2016-09-01 07:00:14 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-01-03 19:54:46 -05:00
|
|
|
const TEST_TOKEN = new InjectionToken('TestToken');
|
2016-09-01 07:00:14 -04:00
|
|
|
@Component({selector: 'injected-component', template: 'foo'})
|
|
|
|
class InjectedComponent {
|
|
|
|
constructor(@Optional() @Inject(TEST_TOKEN) public testToken: any) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Component({selector: 'injected-component-again', template: 'bar'})
|
|
|
|
class InjectedComponentAgain {
|
|
|
|
}
|
|
|
|
|
|
|
|
const TEST_CMP_TEMPLATE =
|
2017-01-09 16:16:46 -05:00
|
|
|
`<ng-template *ngComponentOutlet="currentComponent; injector: injector; content: projectables; ngModuleFactory: module;"></ng-template>`;
|
2016-09-01 07:00:14 -04:00
|
|
|
@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
|
|
|
|
class TestComponent {
|
2017-03-24 12:54:02 -04:00
|
|
|
currentComponent: Type<any>|null;
|
2016-09-01 07:00:14 -04:00
|
|
|
injector: Injector;
|
|
|
|
projectables: any[][];
|
2017-01-25 20:41:08 -05:00
|
|
|
module: NgModuleFactory<any>;
|
2016-09-01 07:00:14 -04:00
|
|
|
|
2017-03-24 12:54:02 -04:00
|
|
|
get cmpRef(): ComponentRef<any>|null { return this.ngComponentOutlet['_componentRef']; }
|
|
|
|
set cmpRef(value: ComponentRef<any>|null) { this.ngComponentOutlet['_componentRef'] = value; }
|
2016-09-01 07:00:14 -04:00
|
|
|
|
|
|
|
@ViewChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
|
|
|
@ViewChild(NgComponentOutlet) ngComponentOutlet: NgComponentOutlet;
|
|
|
|
|
|
|
|
constructor(public vcRef: ViewContainerRef) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
imports: [CommonModule],
|
|
|
|
declarations: [TestComponent, InjectedComponent, InjectedComponentAgain],
|
|
|
|
exports: [TestComponent, InjectedComponent, InjectedComponentAgain],
|
|
|
|
entryComponents: [InjectedComponent, InjectedComponentAgain]
|
|
|
|
})
|
|
|
|
export class TestModule {
|
|
|
|
}
|
2017-01-25 20:41:08 -05:00
|
|
|
|
2017-03-14 19:26:17 -04:00
|
|
|
@Component({selector: 'module-2-injected-component', template: 'baz'})
|
2017-01-25 20:41:08 -05:00
|
|
|
class Module2InjectedComponent {
|
|
|
|
}
|
|
|
|
|
2017-03-14 19:26:17 -04:00
|
|
|
@Component({selector: 'module-2-injected-component-2', template: 'baz2'})
|
2017-01-25 20:41:08 -05:00
|
|
|
class Module2InjectedComponent2 {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
imports: [CommonModule],
|
|
|
|
declarations: [Module2InjectedComponent, Module2InjectedComponent2],
|
|
|
|
exports: [Module2InjectedComponent, Module2InjectedComponent2],
|
|
|
|
entryComponents: [Module2InjectedComponent, Module2InjectedComponent2]
|
|
|
|
})
|
|
|
|
export class TestModule2 {
|
|
|
|
}
|
|
|
|
|
2017-03-14 19:26:17 -04:00
|
|
|
@Component({selector: 'module-3-injected-component', template: 'bat'})
|
2017-01-25 20:41:08 -05:00
|
|
|
class Module3InjectedComponent {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
imports: [CommonModule],
|
|
|
|
declarations: [Module3InjectedComponent],
|
|
|
|
exports: [Module3InjectedComponent],
|
|
|
|
entryComponents: [Module3InjectedComponent]
|
|
|
|
})
|
|
|
|
export class TestModule3 {
|
2017-03-02 15:12:46 -05:00
|
|
|
}
|