2017-07-11 09:15:51 -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
|
|
|
|
*/
|
|
|
|
|
2017-09-25 20:07:12 -04:00
|
|
|
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ApplicationRef, Component, DoCheck, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, StaticProvider, ViewRef, destroyPlatform} from '@angular/core';
|
2017-07-18 13:16:04 -04:00
|
|
|
import {async, fakeAsync, tick} from '@angular/core/testing';
|
2017-07-11 09:15:51 -04:00
|
|
|
import {BrowserModule} from '@angular/platform-browser';
|
|
|
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
2017-08-02 19:09:38 -04:00
|
|
|
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
2017-07-11 09:15:51 -04:00
|
|
|
import {downgradeComponent, downgradeModule} from '@angular/upgrade/static';
|
2018-02-15 12:14:20 -05:00
|
|
|
import * as angular from '@angular/upgrade/static/src/common/angular1';
|
|
|
|
import {$ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/static/src/common/constants';
|
|
|
|
import {LazyModuleRef} from '@angular/upgrade/static/src/common/util';
|
2017-07-11 09:15:51 -04:00
|
|
|
|
2018-02-15 12:21:18 -05:00
|
|
|
import {html, multiTrim, withEachNg1Version} from '../test_helpers';
|
2017-07-11 09:15:51 -04:00
|
|
|
|
2017-07-18 13:16:04 -04:00
|
|
|
|
2018-02-15 12:21:18 -05:00
|
|
|
withEachNg1Version(() => {
|
2017-07-18 13:16:04 -04:00
|
|
|
[true, false].forEach(propagateDigest => {
|
|
|
|
describe(`lazy-load ng2 module (propagateDigest: ${propagateDigest})`, () => {
|
|
|
|
|
|
|
|
beforeEach(() => destroyPlatform());
|
|
|
|
|
|
|
|
it('should support downgrading a component and propagate inputs', async(() => {
|
|
|
|
@Component(
|
|
|
|
{selector: 'ng2A', template: 'a({{ value }}) | <ng2B [value]="value"></ng2B>'})
|
|
|
|
class Ng2AComponent {
|
|
|
|
@Input() value = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'ng2B', template: 'b({{ value }})'})
|
|
|
|
class Ng2BComponent {
|
|
|
|
@Input() value = -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2AComponent, Ng2BComponent],
|
|
|
|
entryComponents: [Ng2AComponent],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2AComponent, propagateDigest}))
|
|
|
|
.run(($rootScope: angular.IRootScopeService) => $rootScope.value = 0);
|
|
|
|
|
|
|
|
const element = html('<div><ng2 [value]="value" ng-if="loadNg2"></ng2></div>');
|
|
|
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
|
|
|
|
|
|
|
expect(element.textContent).toBe('');
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
|
|
|
|
|
|
|
$rootScope.$apply('value = 1');
|
|
|
|
expect(element.textContent).toBe('');
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
|
|
|
|
|
|
|
$rootScope.$apply('loadNg2 = true');
|
|
|
|
expect(element.textContent).toBe('');
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
|
|
|
|
|
|
|
// Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => {
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
|
|
|
|
|
|
|
|
// Wait for `$evalAsync()` to propagate inputs.
|
|
|
|
setTimeout(() => expect(element.textContent).toBe('a(1) | b(1)'));
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should support using an upgraded service', async(() => {
|
|
|
|
class Ng2Service {
|
|
|
|
constructor(@Inject('ng1Value') private ng1Value: string) {}
|
|
|
|
getValue = () => `${this.ng1Value}-bar`;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'ng2', template: '{{ value }}'})
|
|
|
|
class Ng2Component {
|
|
|
|
value: string;
|
|
|
|
constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
providers: [
|
|
|
|
Ng2Service,
|
|
|
|
{
|
|
|
|
provide: 'ng1Value',
|
|
|
|
useFactory: (i: angular.IInjectorService) => i.get('ng1Value'),
|
|
|
|
deps: ['$injector'],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
|
|
|
.value('ng1Value', 'foo');
|
|
|
|
|
|
|
|
const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>');
|
|
|
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
|
|
|
|
|
|
|
expect(element.textContent).toBe('');
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
|
|
|
|
|
|
|
$rootScope.$apply('loadNg2 = true');
|
|
|
|
expect(element.textContent).toBe('');
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
|
|
|
|
|
|
|
// Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => {
|
|
|
|
expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
|
|
|
|
|
|
|
|
// Wait for `$evalAsync()` to propagate inputs.
|
|
|
|
setTimeout(() => expect(element.textContent).toBe('foo-bar'));
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should create components inside the Angular zone', async(() => {
|
|
|
|
@Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'})
|
|
|
|
class Ng2Component {
|
|
|
|
private inTheZone = false;
|
|
|
|
constructor() { this.inTheZone = NgZone.isInAngularZone(); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
|
|
|
|
|
|
|
const element = html('<ng2></ng2>');
|
|
|
|
angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
|
|
|
|
// Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => {
|
|
|
|
// Wait for `$evalAsync()` to propagate inputs.
|
|
|
|
setTimeout(() => expect(element.textContent).toBe('In the zone: true'));
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-09-25 05:39:51 -04:00
|
|
|
it('should destroy components inside the Angular zone', async(() => {
|
|
|
|
let destroyedInTheZone = false;
|
|
|
|
|
|
|
|
@Component({selector: 'ng2', template: ''})
|
|
|
|
class Ng2Component implements OnDestroy {
|
|
|
|
ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
|
|
|
|
|
|
|
const element = html('<ng2 ng-if="!hideNg2"></ng2>');
|
|
|
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
|
|
|
|
|
|
|
// Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => {
|
|
|
|
$rootScope.$apply('hideNg2 = true');
|
|
|
|
expect(destroyedInTheZone).toBe(true);
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-07-18 13:16:04 -04:00
|
|
|
it('should propagate input changes inside the Angular zone', async(() => {
|
|
|
|
let ng2Component: Ng2Component;
|
|
|
|
|
|
|
|
@Component({selector: 'ng2', template: ''})
|
|
|
|
class Ng2Component implements OnChanges {
|
|
|
|
@Input() attrInput = 'foo';
|
|
|
|
@Input() propInput = 'foo';
|
|
|
|
|
|
|
|
constructor() { ng2Component = this; }
|
|
|
|
ngOnChanges() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
|
|
|
.run(($rootScope: angular.IRootScopeService) => {
|
|
|
|
$rootScope.attrVal = 'bar';
|
|
|
|
$rootScope.propVal = 'bar';
|
|
|
|
});
|
|
|
|
|
|
|
|
const element = html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
|
|
|
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
|
|
|
|
|
|
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
|
|
|
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
|
|
|
|
const changesSpy =
|
|
|
|
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
|
|
|
|
|
|
|
|
expect(ng2Component.attrInput).toBe('bar');
|
|
|
|
expect(ng2Component.propInput).toBe('bar');
|
|
|
|
|
|
|
|
$rootScope.$apply('attrVal = "baz"');
|
|
|
|
expect(ng2Component.attrInput).toBe('baz');
|
|
|
|
expect(ng2Component.propInput).toBe('bar');
|
|
|
|
expect(changesSpy).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
$rootScope.$apply('propVal = "qux"');
|
|
|
|
expect(ng2Component.attrInput).toBe('baz');
|
|
|
|
expect(ng2Component.propInput).toBe('qux');
|
|
|
|
expect(changesSpy).toHaveBeenCalledTimes(2);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should wire up the component for change detection', async(() => {
|
|
|
|
@Component(
|
|
|
|
{selector: 'ng2', template: '{{ count }}<button (click)="increment()"></button>'})
|
|
|
|
class Ng2Component {
|
|
|
|
private count = 0;
|
|
|
|
increment() { ++this.count; }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
|
|
|
|
|
|
|
const element = html('<ng2></ng2>');
|
|
|
|
angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
|
|
|
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
|
|
|
const button = element.querySelector('button') !;
|
|
|
|
expect(element.textContent).toBe('0');
|
|
|
|
|
|
|
|
button.click();
|
|
|
|
expect(element.textContent).toBe('1');
|
|
|
|
|
|
|
|
button.click();
|
|
|
|
expect(element.textContent).toBe('2');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-09-25 20:07:12 -04:00
|
|
|
it('should run the lifecycle hooks in the correct order', async(() => {
|
|
|
|
const logs: string[] = [];
|
|
|
|
let rootScope: angular.IRootScopeService;
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'ng2',
|
|
|
|
template: `
|
|
|
|
{{ value }}
|
|
|
|
<button (click)="value = 'qux'"></button>
|
|
|
|
<ng-content></ng-content>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
class Ng2Component implements AfterContentChecked,
|
|
|
|
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy,
|
|
|
|
OnInit {
|
|
|
|
@Input() value = 'foo';
|
|
|
|
|
|
|
|
ngAfterContentChecked() { this.log('AfterContentChecked'); }
|
|
|
|
ngAfterContentInit() { this.log('AfterContentInit'); }
|
|
|
|
ngAfterViewChecked() { this.log('AfterViewChecked'); }
|
|
|
|
ngAfterViewInit() { this.log('AfterViewInit'); }
|
|
|
|
ngDoCheck() { this.log('DoCheck'); }
|
|
|
|
ngOnChanges() { this.log('OnChanges'); }
|
|
|
|
ngOnDestroy() { this.log('OnDestroy'); }
|
|
|
|
ngOnInit() { this.log('OnInit'); }
|
|
|
|
|
|
|
|
private log(hook: string) { logs.push(`${hook}(${this.value})`); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
|
|
|
.run(($rootScope: angular.IRootScopeService) => {
|
|
|
|
rootScope = $rootScope;
|
|
|
|
rootScope.value = 'bar';
|
|
|
|
});
|
|
|
|
|
|
|
|
const element =
|
|
|
|
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
|
|
|
|
angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
|
|
|
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
|
|
|
const button = element.querySelector('button') !;
|
|
|
|
|
|
|
|
// Once initialized.
|
|
|
|
expect(multiTrim(element.textContent)).toBe('bar Content');
|
|
|
|
expect(logs).toEqual([
|
|
|
|
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
|
|
|
|
'OnChanges(bar)',
|
|
|
|
// Initial CD triggered directly through the `detectChanges()` or `inputChanges`
|
|
|
|
// $watcher (for `propagateDigest` true/false respectively).
|
|
|
|
'OnInit(bar)',
|
|
|
|
'DoCheck(bar)',
|
|
|
|
'AfterContentInit(bar)',
|
|
|
|
'AfterContentChecked(bar)',
|
|
|
|
'AfterViewInit(bar)',
|
|
|
|
'AfterViewChecked(bar)',
|
|
|
|
...(propagateDigest ?
|
|
|
|
[
|
|
|
|
// CD triggered directly through the `detectChanges()` $watcher (2nd
|
|
|
|
// $digest).
|
|
|
|
'DoCheck(bar)',
|
|
|
|
'AfterContentChecked(bar)',
|
|
|
|
'AfterViewChecked(bar)',
|
|
|
|
] :
|
|
|
|
[]),
|
|
|
|
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
|
|
|
|
'DoCheck(bar)',
|
|
|
|
'AfterContentChecked(bar)',
|
|
|
|
'AfterViewChecked(bar)',
|
|
|
|
]);
|
|
|
|
logs.length = 0;
|
|
|
|
|
|
|
|
// Change inputs and run `$digest`.
|
|
|
|
rootScope.$apply('value = "baz"');
|
|
|
|
expect(multiTrim(element.textContent)).toBe('baz Content');
|
|
|
|
expect(logs).toEqual([
|
|
|
|
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
|
|
|
|
'OnChanges(baz)',
|
|
|
|
// `propagateDigest: true` (3 CD runs):
|
|
|
|
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
|
|
|
// $watcher).
|
|
|
|
// - CD triggered directly through the `detectChanges()` $watcher.
|
|
|
|
// - CD triggered due to entering/leaving the NgZone (in `detectChanges`
|
|
|
|
// $watcher).
|
|
|
|
// `propagateDigest: false` (2 CD runs):
|
|
|
|
// - CD triggered directly through the `inputChanges` $watcher.
|
|
|
|
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
|
|
|
// $watcher).
|
|
|
|
'DoCheck(baz)',
|
|
|
|
'AfterContentChecked(baz)',
|
|
|
|
'AfterViewChecked(baz)',
|
|
|
|
'DoCheck(baz)',
|
|
|
|
'AfterContentChecked(baz)',
|
|
|
|
'AfterViewChecked(baz)',
|
|
|
|
...(propagateDigest ?
|
|
|
|
[
|
|
|
|
'DoCheck(baz)',
|
|
|
|
'AfterContentChecked(baz)',
|
|
|
|
'AfterViewChecked(baz)',
|
|
|
|
] :
|
|
|
|
[]),
|
|
|
|
]);
|
|
|
|
logs.length = 0;
|
|
|
|
|
|
|
|
// Run `$digest` (without changing inputs).
|
|
|
|
rootScope.$digest();
|
|
|
|
expect(multiTrim(element.textContent)).toBe('baz Content');
|
|
|
|
expect(logs).toEqual(
|
|
|
|
propagateDigest ?
|
|
|
|
[
|
|
|
|
// CD triggered directly through the `detectChanges()` $watcher.
|
|
|
|
'DoCheck(baz)',
|
|
|
|
'AfterContentChecked(baz)',
|
|
|
|
'AfterViewChecked(baz)',
|
|
|
|
// CD triggered due to entering/leaving the NgZone (in the above $watcher).
|
|
|
|
'DoCheck(baz)',
|
|
|
|
'AfterContentChecked(baz)',
|
|
|
|
'AfterViewChecked(baz)',
|
|
|
|
] :
|
|
|
|
[]);
|
|
|
|
logs.length = 0;
|
|
|
|
|
|
|
|
// Trigger change detection (without changing inputs).
|
|
|
|
button.click();
|
|
|
|
expect(multiTrim(element.textContent)).toBe('qux Content');
|
|
|
|
expect(logs).toEqual([
|
|
|
|
'DoCheck(qux)',
|
|
|
|
'AfterContentChecked(qux)',
|
|
|
|
'AfterViewChecked(qux)',
|
|
|
|
]);
|
|
|
|
logs.length = 0;
|
|
|
|
|
|
|
|
// Destroy the component.
|
|
|
|
rootScope.$apply('hideNg2 = true');
|
|
|
|
expect(logs).toEqual([
|
|
|
|
'OnDestroy(qux)',
|
|
|
|
]);
|
|
|
|
logs.length = 0;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-09-25 06:22:35 -04:00
|
|
|
it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
|
|
|
|
let ng2Component: Ng2Component;
|
|
|
|
|
|
|
|
@Component({selector: 'ng2', template: ''})
|
|
|
|
class Ng2Component {
|
|
|
|
constructor(public appRef: ApplicationRef) {
|
|
|
|
ng2Component = this;
|
|
|
|
spyOn(appRef, 'attachView').and.callThrough();
|
|
|
|
spyOn(appRef, 'detachView').and.callThrough();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
|
|
|
|
|
|
|
const element = html('<ng2 ng-if="!hideNg2"></ng2>');
|
|
|
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
|
|
|
|
2017-09-25 20:07:12 -04:00
|
|
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`).
|
|
|
|
const hostView: ViewRef =
|
|
|
|
(ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0];
|
2017-09-25 06:22:35 -04:00
|
|
|
|
2017-09-25 20:07:12 -04:00
|
|
|
expect(hostView.destroyed).toBe(false);
|
2017-09-25 06:22:35 -04:00
|
|
|
|
2017-09-25 20:07:12 -04:00
|
|
|
$rootScope.$apply('hideNg2 = true');
|
2017-09-25 06:22:35 -04:00
|
|
|
|
2017-09-25 20:07:12 -04:00
|
|
|
expect(hostView.destroyed).toBe(true);
|
|
|
|
expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView);
|
|
|
|
});
|
2017-09-25 06:22:35 -04:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-07-18 13:16:04 -04:00
|
|
|
it('should only retrieve the Angular zone once (and cache it for later use)',
|
|
|
|
fakeAsync(() => {
|
|
|
|
let count = 0;
|
|
|
|
let getNgZoneCount = 0;
|
|
|
|
|
|
|
|
@Component(
|
|
|
|
{selector: 'ng2', template: 'Count: {{ count }} | In the zone: {{ inTheZone }}'})
|
|
|
|
class Ng2Component {
|
|
|
|
private count = ++count;
|
|
|
|
private inTheZone = false;
|
|
|
|
constructor() { this.inTheZone = NgZone.isInAngularZone(); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
constructor(injector: Injector) {
|
|
|
|
const originalGet = injector.get;
|
|
|
|
injector.get = function(token: any) {
|
|
|
|
if (token === NgZone) ++getNgZoneCount;
|
|
|
|
return originalGet.apply(injector, arguments);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
2017-08-02 19:09:38 -04:00
|
|
|
const tickDelay = browserDetection.isIE ? 100 : 0;
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
|
|
|
|
|
|
|
const element = html('<div><ng2 ng-if="showNg2"></ng2></div>');
|
|
|
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
|
|
|
|
|
|
|
$rootScope.$apply('showNg2 = true');
|
2017-08-02 19:09:38 -04:00
|
|
|
tick(tickDelay); // Wait for the module to be bootstrapped and `$evalAsync()` to
|
|
|
|
// propagate inputs.
|
2017-07-18 13:16:04 -04:00
|
|
|
|
|
|
|
const injector = ($injector.get(LAZY_MODULE_REF) as LazyModuleRef).injector !;
|
|
|
|
const injectorGet = injector.get;
|
|
|
|
spyOn(injector, 'get').and.callFake((...args: any[]) => {
|
|
|
|
expect(args[0]).not.toBe(NgZone);
|
|
|
|
return injectorGet.apply(injector, args);
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(element.textContent).toBe('Count: 1 | In the zone: true');
|
|
|
|
|
|
|
|
$rootScope.$apply('showNg2 = false');
|
|
|
|
expect(element.textContent).toBe('');
|
|
|
|
|
|
|
|
$rootScope.$apply('showNg2 = true');
|
2017-08-02 19:09:38 -04:00
|
|
|
tick(tickDelay); // Wait for `$evalAsync()` to propagate inputs.
|
2017-07-18 13:16:04 -04:00
|
|
|
expect(element.textContent).toBe('Count: 2 | In the zone: true');
|
|
|
|
|
|
|
|
$rootScope.$destroy();
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should give access to both injectors in the Angular module\'s constructor', async(() => {
|
|
|
|
let $injectorFromNg2: angular.IInjectorService|null = null;
|
|
|
|
|
|
|
|
@Component({selector: 'ng2', template: ''})
|
|
|
|
class Ng2Component {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [Ng2Component],
|
|
|
|
entryComponents: [Ng2Component],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
})
|
|
|
|
class Ng2Module {
|
|
|
|
constructor(injector: Injector) {
|
|
|
|
$injectorFromNg2 = injector.get<angular.IInjectorService>('$injector' as any);
|
|
|
|
}
|
|
|
|
|
|
|
|
ngDoBootstrap() {}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
2017-07-18 13:16:04 -04:00
|
|
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
|
|
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
|
|
|
const ng1Module =
|
|
|
|
angular.module('ng1', [lazyModuleName])
|
|
|
|
.directive(
|
|
|
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
|
|
|
|
|
|
|
const element = html('<ng2></ng2>');
|
|
|
|
const $injectorFromNg1 = angular.bootstrap(element, [ng1Module.name]);
|
|
|
|
|
|
|
|
// Wait for the module to be bootstrapped.
|
|
|
|
setTimeout(() => expect($injectorFromNg2).toBe($injectorFromNg1));
|
|
|
|
}));
|
|
|
|
});
|
2017-07-11 09:15:51 -04:00
|
|
|
});
|
2018-02-15 12:21:18 -05:00
|
|
|
});
|