feat(UpgradeComponent): add/improve support for lifecycle hooks
Add support for the `$postDigest()` and `$onDestroy()` lifecycle hooks. Better align the behavior of the `$onChanges()` and `$onInit()` lifecycle hooks with Angular 1.x: - Call `$onInit()` before pre-linking. - Always instantiate the controller before calling `$onChanges()`.
This commit is contained in:
parent
f0cdb428f5
commit
469010ea8e
modules/@angular/upgrade
tools/public_api_guard/upgrade
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnInit, SimpleChanges} from '@angular/core';
|
import {DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core';
|
||||||
|
|
||||||
import * as angular from '../angular_js';
|
import * as angular from '../angular_js';
|
||||||
import {looseIdentical} from '../facade/lang';
|
import {looseIdentical} from '../facade/lang';
|
||||||
@ -34,13 +34,17 @@ interface IBindingDestination {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IControllerInstance extends IBindingDestination {
|
interface IControllerInstance extends IBindingDestination {
|
||||||
|
$onDestroy?: () => void;
|
||||||
$onInit?: () => void;
|
$onInit?: () => void;
|
||||||
|
$postLink?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LifecycleHook = '$onChanges' | '$onDestroy' | '$onInit' | '$postLink';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
|
export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
private $injector: angular.IInjectorService;
|
private $injector: angular.IInjectorService;
|
||||||
private $compile: angular.ICompileService;
|
private $compile: angular.ICompileService;
|
||||||
private $templateCache: angular.ITemplateCacheService;
|
private $templateCache: angular.ITemplateCacheService;
|
||||||
@ -80,33 +84,27 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
|
|||||||
this.$componentScope = $parentScope.$new(!!this.directive.scope);
|
this.$componentScope = $parentScope.$new(!!this.directive.scope);
|
||||||
|
|
||||||
const controllerType = this.directive.controller;
|
const controllerType = this.directive.controller;
|
||||||
// QUESTION: shouldn't we be building the controller in any case?
|
const bindToController = this.directive.bindToController;
|
||||||
if (this.directive.bindToController) {
|
|
||||||
if (controllerType) {
|
if (controllerType) {
|
||||||
this.bindingDestination = this.controllerInstance = this.buildController(
|
this.controllerInstance = this.buildController(
|
||||||
controllerType, this.$componentScope, this.$element, this.directive.controllerAs);
|
controllerType, this.$componentScope, this.$element, this.directive.controllerAs);
|
||||||
} else {
|
} else if (bindToController) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Upgraded directive '${name}' specifies 'bindToController' but no controller.`);
|
`Upgraded directive '${name}' specifies 'bindToController' but no controller.`);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.bindingDestination = this.$componentScope;
|
this.bindingDestination = bindToController ? this.controllerInstance : this.$componentScope;
|
||||||
}
|
|
||||||
|
|
||||||
this.setupOutputs();
|
this.setupOutputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
// QUESTION: why not just use $compile instead of reproducing parts of it
|
|
||||||
if (!this.directive.bindToController && this.directive.controller) {
|
|
||||||
this.controllerInstance = this.buildController(
|
|
||||||
this.directive.controller, this.$componentScope, this.$element,
|
|
||||||
this.directive.controllerAs);
|
|
||||||
}
|
|
||||||
const attrs: angular.IAttributes = NOT_SUPPORTED;
|
const attrs: angular.IAttributes = NOT_SUPPORTED;
|
||||||
const transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
const transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
||||||
const linkController = this.resolveRequired(this.$element, this.directive.require);
|
const linkController = this.resolveRequired(this.$element, this.directive.require);
|
||||||
|
|
||||||
|
this.callLifecycleHook('$onInit', this.controllerInstance);
|
||||||
|
|
||||||
const link = this.directive.link;
|
const link = this.directive.link;
|
||||||
const preLink = (typeof link == 'object') && (link as angular.IDirectivePrePost).pre;
|
const preLink = (typeof link == 'object') && (link as angular.IDirectivePrePost).pre;
|
||||||
const postLink = (typeof link == 'object') ? (link as angular.IDirectivePrePost).post : link;
|
const postLink = (typeof link == 'object') ? (link as angular.IDirectivePrePost).post : link;
|
||||||
@ -131,19 +129,15 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
|
|||||||
postLink(this.$componentScope, this.$element, attrs, linkController, transcludeFn);
|
postLink(this.$componentScope, this.$element, attrs, linkController, transcludeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.controllerInstance && this.controllerInstance.$onInit) {
|
this.callLifecycleHook('$postLink', this.controllerInstance);
|
||||||
this.controllerInstance.$onInit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
// Forward input changes to `bindingDestination`
|
// Forward input changes to `bindingDestination`
|
||||||
Object.keys(changes).forEach(
|
Object.keys(changes).forEach(
|
||||||
propName => { this.bindingDestination[propName] = changes[propName].currentValue; });
|
propName => this.bindingDestination[propName] = changes[propName].currentValue);
|
||||||
|
|
||||||
if (this.bindingDestination.$onChanges) {
|
this.callLifecycleHook('$onChanges', this.bindingDestination, changes);
|
||||||
this.bindingDestination.$onChanges(changes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDoCheck() {
|
ngDoCheck() {
|
||||||
@ -165,6 +159,17 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.callLifecycleHook('$onDestroy', this.controllerInstance);
|
||||||
|
this.$componentScope.$destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private callLifecycleHook(method: LifecycleHook, context: IBindingDestination, arg?: any) {
|
||||||
|
if (context && typeof context[method] === 'function') {
|
||||||
|
context[method](arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getDirective(name: string): angular.IDirective {
|
private getDirective(name: string): angular.IDirective {
|
||||||
const directives: angular.IDirective[] = this.$injector.get(name + 'Directive');
|
const directives: angular.IDirective[] = this.$injector.get(name + 'Directive');
|
||||||
if (directives.length > 1) {
|
if (directives.length > 1) {
|
||||||
@ -254,6 +259,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
|
|||||||
private buildController(
|
private buildController(
|
||||||
controllerType: angular.IController, $scope: angular.IScope,
|
controllerType: angular.IController, $scope: angular.IScope,
|
||||||
$element: angular.IAugmentedJQuery, controllerAs: string) {
|
$element: angular.IAugmentedJQuery, controllerAs: string) {
|
||||||
|
// TODO: Document that we do not pre-assign bindings on the controller instance
|
||||||
var locals = {$scope, $element};
|
var locals = {$scope, $element};
|
||||||
var controller = this.$controller(controllerType, locals, null, controllerAs);
|
var controller = this.$controller(controllerType, locals, null, controllerAs);
|
||||||
$element.data(controllerKey(this.directive.name), controller);
|
$element.data(controllerKey(this.directive.name), controller);
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, Directive, ElementRef, EventEmitter, Injector, Input, NO_ERRORS_SCHEMA, NgModule, Output, destroyPlatform} from '@angular/core';
|
import {Component, Directive, ElementRef, EventEmitter, Injector, Input, NO_ERRORS_SCHEMA, NgModule, Output, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||||
import {async, fakeAsync, tick} from '@angular/core/testing';
|
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
@ -1381,8 +1381,307 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('lifecycle hooks', () => {
|
describe('lifecycle hooks', () => {
|
||||||
xit('should call `$onChanges()` on controller', () => {});
|
it('should call `$onChanges()` on binding destination (prototype)', fakeAsync(() => {
|
||||||
xit('should call `$onChanges()` on scope', () => {});
|
const scopeOnChanges = jasmine.createSpy('scopeOnChanges');
|
||||||
|
const controllerOnChangesA = jasmine.createSpy('controllerOnChangesA');
|
||||||
|
const controllerOnChangesB = jasmine.createSpy('controllerOnChangesB');
|
||||||
|
let ng2ComponentInstance: Ng2Component;
|
||||||
|
|
||||||
|
// Define `ng1Directive`
|
||||||
|
const ng1DirectiveA: angular.IDirective = {
|
||||||
|
template: '',
|
||||||
|
scope: {inputA: '<'},
|
||||||
|
bindToController: false,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
$onChanges(changes: SimpleChanges) {
|
||||||
|
controllerOnChangesA(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ng1DirectiveB: angular.IDirective = {
|
||||||
|
template: '',
|
||||||
|
scope: {inputB: '<'},
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
Object.getPrototypeOf($scope)['$onChanges'] = scopeOnChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
$onChanges(changes: SimpleChanges) {
|
||||||
|
controllerOnChangesB(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1A'})
|
||||||
|
class Ng1ComponentAFacade extends UpgradeComponent {
|
||||||
|
@Input() inputA: any;
|
||||||
|
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1A', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1B'})
|
||||||
|
class Ng1ComponentBFacade extends UpgradeComponent {
|
||||||
|
@Input() inputB: any;
|
||||||
|
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1B', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1A [inputA]="data"></ng1A> | <ng1B [inputB]="data"></ng1B>'
|
||||||
|
})
|
||||||
|
class Ng2Component {
|
||||||
|
data = {foo: 'bar'};
|
||||||
|
|
||||||
|
constructor() { ng2ComponentInstance = this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module = angular.module('ng1Module', [])
|
||||||
|
.directive('ng1A', () => ng1DirectiveA)
|
||||||
|
.directive('ng1B', () => ng1DirectiveB)
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||||
|
// Initial change
|
||||||
|
expect(scopeOnChanges.calls.count()).toBe(1);
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(1);
|
||||||
|
|
||||||
|
expect(scopeOnChanges.calls.argsFor(0)[0]).toEqual({inputA: jasmine.any(Object)});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(0)[0].inputA.currentValue).toEqual({foo: 'bar'});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(0)[0].inputA.isFirstChange()).toBe(true);
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(0)[0].inputB.currentValue).toEqual({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(0)[0].inputB.isFirstChange()).toBe(true);
|
||||||
|
|
||||||
|
// Change: Re-assign `data`
|
||||||
|
ng2ComponentInstance.data = {foo: 'baz'};
|
||||||
|
digest(adapter);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(scopeOnChanges.calls.count()).toBe(2);
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(2);
|
||||||
|
|
||||||
|
expect(scopeOnChanges.calls.argsFor(1)[0]).toEqual({inputA: jasmine.any(Object)});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(1)[0].inputA.previousValue).toEqual({foo: 'bar'});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(1)[0].inputA.currentValue).toEqual({foo: 'baz'});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(1)[0].inputA.isFirstChange()).toBe(false);
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(1)[0].inputB.previousValue).toEqual({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(1)[0].inputB.currentValue).toEqual({
|
||||||
|
foo: 'baz'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(1)[0].inputB.isFirstChange()).toBe(false);
|
||||||
|
|
||||||
|
// No change: Update internal property
|
||||||
|
ng2ComponentInstance.data.foo = 'qux';
|
||||||
|
digest(adapter);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(scopeOnChanges.calls.count()).toBe(2);
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(2);
|
||||||
|
|
||||||
|
// Change: Re-assign `data` (even if it looks the same)
|
||||||
|
ng2ComponentInstance.data = {foo: 'qux'};
|
||||||
|
digest(adapter);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(scopeOnChanges.calls.count()).toBe(3);
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(3);
|
||||||
|
|
||||||
|
expect(scopeOnChanges.calls.argsFor(2)[0]).toEqual({inputA: jasmine.any(Object)});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(2)[0].inputA.previousValue).toEqual({foo: 'qux'});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(2)[0].inputA.currentValue).toEqual({foo: 'qux'});
|
||||||
|
expect(scopeOnChanges.calls.argsFor(2)[0].inputA.isFirstChange()).toBe(false);
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(2)[0].inputB.previousValue).toEqual({
|
||||||
|
foo: 'qux'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(2)[0].inputB.currentValue).toEqual({
|
||||||
|
foo: 'qux'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(2)[0].inputB.isFirstChange()).toBe(false);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call `$onChanges()` on binding destination (instance)', fakeAsync(() => {
|
||||||
|
const scopeOnChangesA = jasmine.createSpy('scopeOnChangesA');
|
||||||
|
const scopeOnChangesB = jasmine.createSpy('scopeOnChangesB');
|
||||||
|
const controllerOnChangesA = jasmine.createSpy('controllerOnChangesA');
|
||||||
|
const controllerOnChangesB = jasmine.createSpy('controllerOnChangesB');
|
||||||
|
let ng2ComponentInstance: Ng2Component;
|
||||||
|
|
||||||
|
// Define `ng1Directive`
|
||||||
|
const ng1DirectiveA: angular.IDirective = {
|
||||||
|
template: '',
|
||||||
|
scope: {inputA: '<'},
|
||||||
|
bindToController: false,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['$onChanges'] = scopeOnChangesA;
|
||||||
|
(this as any).$onChanges = controllerOnChangesA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ng1DirectiveB: angular.IDirective = {
|
||||||
|
template: '',
|
||||||
|
scope: {inputB: '<'},
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['$onChanges'] = scopeOnChangesB;
|
||||||
|
(this as any).$onChanges = controllerOnChangesB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1A'})
|
||||||
|
class Ng1ComponentAFacade extends UpgradeComponent {
|
||||||
|
@Input() inputA: any;
|
||||||
|
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1A', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1B'})
|
||||||
|
class Ng1ComponentBFacade extends UpgradeComponent {
|
||||||
|
@Input() inputB: any;
|
||||||
|
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1B', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1A [inputA]="data"></ng1A> | <ng1B [inputB]="data"></ng1B>'
|
||||||
|
})
|
||||||
|
class Ng2Component {
|
||||||
|
data = {foo: 'bar'};
|
||||||
|
|
||||||
|
constructor() { ng2ComponentInstance = this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module = angular.module('ng1Module', [])
|
||||||
|
.directive('ng1A', () => ng1DirectiveA)
|
||||||
|
.directive('ng1B', () => ng1DirectiveB)
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||||
|
// Initial change
|
||||||
|
expect(scopeOnChangesA.calls.count()).toBe(1);
|
||||||
|
expect(scopeOnChangesB).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(1);
|
||||||
|
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(0)[0].inputA.currentValue).toEqual({foo: 'bar'});
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(0)[0].inputA.isFirstChange()).toBe(true);
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(0)[0].inputB.currentValue).toEqual({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(0)[0].inputB.isFirstChange()).toBe(true);
|
||||||
|
|
||||||
|
// Change: Re-assign `data`
|
||||||
|
ng2ComponentInstance.data = {foo: 'baz'};
|
||||||
|
digest(adapter);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(scopeOnChangesA.calls.count()).toBe(2);
|
||||||
|
expect(scopeOnChangesB).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(2);
|
||||||
|
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(1)[0].inputA.previousValue).toEqual({foo: 'bar'});
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(1)[0].inputA.currentValue).toEqual({foo: 'baz'});
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(1)[0].inputA.isFirstChange()).toBe(false);
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(1)[0].inputB.previousValue).toEqual({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(1)[0].inputB.currentValue).toEqual({
|
||||||
|
foo: 'baz'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(1)[0].inputB.isFirstChange()).toBe(false);
|
||||||
|
|
||||||
|
// No change: Update internal property
|
||||||
|
ng2ComponentInstance.data.foo = 'qux';
|
||||||
|
digest(adapter);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(scopeOnChangesA.calls.count()).toBe(2);
|
||||||
|
expect(scopeOnChangesB).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(2);
|
||||||
|
|
||||||
|
// Change: Re-assign `data` (even if it looks the same)
|
||||||
|
ng2ComponentInstance.data = {foo: 'qux'};
|
||||||
|
digest(adapter);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(scopeOnChangesA.calls.count()).toBe(3);
|
||||||
|
expect(scopeOnChangesB).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnChangesB.calls.count()).toBe(3);
|
||||||
|
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(2)[0].inputA.previousValue).toEqual({foo: 'qux'});
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(2)[0].inputA.currentValue).toEqual({foo: 'qux'});
|
||||||
|
expect(scopeOnChangesA.calls.argsFor(2)[0].inputA.isFirstChange()).toBe(false);
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(2)[0].inputB.previousValue).toEqual({
|
||||||
|
foo: 'qux'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(2)[0].inputB.currentValue).toEqual({
|
||||||
|
foo: 'qux'
|
||||||
|
});
|
||||||
|
expect(controllerOnChangesB.calls.argsFor(2)[0].inputB.isFirstChange()).toBe(false);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should call `$onInit()` on controller', async(() => {
|
it('should call `$onInit()` on controller', async(() => {
|
||||||
// Define `ng1Directive`
|
// Define `ng1Directive`
|
||||||
@ -1400,9 +1699,10 @@ export function main() {
|
|||||||
template: 'Called: {{ called }}',
|
template: 'Called: {{ called }}',
|
||||||
bindToController: true,
|
bindToController: true,
|
||||||
controller: class {
|
controller: class {
|
||||||
constructor(private $scope: angular.IScope) { $scope['called'] = 'no'; }
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['called'] = 'no';
|
||||||
$onInit() { this.$scope['called'] = 'yes'; }
|
(this as any)['$onInit'] = () => $scope['called'] = 'yes';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1445,11 +1745,8 @@ export function main() {
|
|||||||
// Bootstrap
|
// Bootstrap
|
||||||
const element = html(`<ng2></ng2>`);
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(Ng2Module).then(ref => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(() => {
|
||||||
var adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
|
expect(multiTrim(element.textContent)).toBe('Called: yes | Called: yes');
|
||||||
adapter.bootstrap(element, [ng1Module.name]);
|
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent)).toBe('Called: yes | Called: yes');
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -1462,6 +1759,7 @@ export function main() {
|
|||||||
constructor($scope: angular.IScope) {
|
constructor($scope: angular.IScope) {
|
||||||
$scope['called'] = 'no';
|
$scope['called'] = 'no';
|
||||||
$scope['$onInit'] = () => $scope['called'] = 'yes';
|
$scope['$onInit'] = () => $scope['called'] = 'yes';
|
||||||
|
Object.getPrototypeOf($scope)['$onInit'] = () => $scope['called'] = 'yes';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1473,6 +1771,7 @@ export function main() {
|
|||||||
constructor($scope: angular.IScope) {
|
constructor($scope: angular.IScope) {
|
||||||
$scope['called'] = 'no';
|
$scope['called'] = 'no';
|
||||||
$scope['$onInit'] = () => $scope['called'] = 'yes';
|
$scope['$onInit'] = () => $scope['called'] = 'yes';
|
||||||
|
Object.getPrototypeOf($scope)['$onInit'] = () => $scope['called'] = 'yes';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1516,20 +1815,460 @@ export function main() {
|
|||||||
// Bootstrap
|
// Bootstrap
|
||||||
const element = html(`<ng2></ng2>`);
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(Ng2Module).then(ref => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(() => {
|
||||||
var adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
|
expect(multiTrim(element.textContent)).toBe('Called: no | Called: no');
|
||||||
adapter.bootstrap(element, [ng1Module.name]);
|
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent)).toBe('Called: no | Called: no');
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
xit('should call `$onPostDigest()` on controller', () => {});
|
it('should call `$postLink()` on controller', async(() => {
|
||||||
xit('should not call `$onPostDigest()` on scope', () => {});
|
// Define `ng1Directive`
|
||||||
|
const ng1DirectiveA: angular.IDirective = {
|
||||||
|
template: 'Called: {{ called }}',
|
||||||
|
bindToController: false,
|
||||||
|
controller: class {
|
||||||
|
constructor(private $scope: angular.IScope) { $scope['called'] = 'no'; }
|
||||||
|
|
||||||
xit('should call `$onDestroy()` on controller', () => {});
|
$postLink() { this.$scope['called'] = 'yes'; }
|
||||||
xit('should not call `$onDestroy()` on scope', () => {});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ng1DirectiveB: angular.IDirective = {
|
||||||
|
template: 'Called: {{ called }}',
|
||||||
|
bindToController: true,
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['called'] = 'no';
|
||||||
|
(this as any)['$postLink'] = () => $scope['called'] = 'yes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1A'})
|
||||||
|
class Ng1ComponentAFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1A', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1B'})
|
||||||
|
class Ng1ComponentBFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1B', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component({selector: 'ng2', template: '<ng1A></ng1A> | <ng1B></ng1B>'})
|
||||||
|
class Ng2Component {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module = angular.module('ng1Module', [])
|
||||||
|
.directive('ng1A', () => ng1DirectiveA)
|
||||||
|
.directive('ng1B', () => ng1DirectiveB)
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(() => {
|
||||||
|
expect(multiTrim(element.textContent)).toBe('Called: yes | Called: yes');
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not call `$postLink()` on scope', async(() => {
|
||||||
|
// Define `ng1Directive`
|
||||||
|
const ng1DirectiveA: angular.IDirective = {
|
||||||
|
template: 'Called: {{ called }}',
|
||||||
|
bindToController: false,
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['called'] = 'no';
|
||||||
|
$scope['$postLink'] = () => $scope['called'] = 'yes';
|
||||||
|
Object.getPrototypeOf($scope)['$postLink'] = () => $scope['called'] = 'yes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ng1DirectiveB: angular.IDirective = {
|
||||||
|
template: 'Called: {{ called }}',
|
||||||
|
bindToController: true,
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['called'] = 'no';
|
||||||
|
$scope['$postLink'] = () => $scope['called'] = 'yes';
|
||||||
|
Object.getPrototypeOf($scope)['$postLink'] = () => $scope['called'] = 'yes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1A'})
|
||||||
|
class Ng1ComponentAFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1A', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1B'})
|
||||||
|
class Ng1ComponentBFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1B', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component({selector: 'ng2', template: '<ng1A></ng1A> | <ng1B></ng1B>'})
|
||||||
|
class Ng2Component {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module = angular.module('ng1Module', [])
|
||||||
|
.directive('ng1A', () => ng1DirectiveA)
|
||||||
|
.directive('ng1B', () => ng1DirectiveB)
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(() => {
|
||||||
|
expect(multiTrim(element.textContent)).toBe('Called: no | Called: no');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should call `$onDestroy()` on controller', async(() => {
|
||||||
|
const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA');
|
||||||
|
const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB');
|
||||||
|
let ng2ComponentInstance: Ng2Component;
|
||||||
|
|
||||||
|
// Define `ng1Directive`
|
||||||
|
const ng1DirectiveA: angular.IDirective = {
|
||||||
|
template: 'ng1A',
|
||||||
|
scope: {},
|
||||||
|
bindToController: false,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {$onDestroy() { controllerOnDestroyA(); }}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ng1DirectiveB: angular.IDirective = {
|
||||||
|
template: 'ng1B',
|
||||||
|
scope: {},
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
constructor() {
|
||||||
|
(this as any)['$onDestroy'] = controllerOnDestroyB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1A'})
|
||||||
|
class Ng1ComponentAFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1A', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1B'})
|
||||||
|
class Ng1ComponentBFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1B', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component(
|
||||||
|
{selector: 'ng2', template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>'})
|
||||||
|
class Ng2Component {
|
||||||
|
@Input() show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1Module', [])
|
||||||
|
.directive('ng1A', () => ng1DirectiveA)
|
||||||
|
.directive('ng1B', () => ng1DirectiveB)
|
||||||
|
.directive(
|
||||||
|
'ng2', downgradeComponent({component: Ng2Component, inputs: ['show']}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>');
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||||
|
const $rootScope = adapter.$injector.get('$rootScope') as angular.IRootScopeService;
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
|
||||||
|
expect(controllerOnDestroyA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnDestroyB).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
$rootScope.$apply('destroyFromNg1 = true');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('');
|
||||||
|
expect(controllerOnDestroyA).toHaveBeenCalled();
|
||||||
|
expect(controllerOnDestroyB).toHaveBeenCalled();
|
||||||
|
|
||||||
|
controllerOnDestroyA.calls.reset();
|
||||||
|
controllerOnDestroyB.calls.reset();
|
||||||
|
$rootScope.$apply('destroyFromNg1 = false');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
|
||||||
|
expect(controllerOnDestroyA).not.toHaveBeenCalled();
|
||||||
|
expect(controllerOnDestroyB).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
$rootScope.$apply('destroyFromNg2 = true');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('');
|
||||||
|
expect(controllerOnDestroyA).toHaveBeenCalled();
|
||||||
|
expect(controllerOnDestroyB).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not call `$onDestroy()` on scope', async(() => {
|
||||||
|
const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy');
|
||||||
|
let ng2ComponentInstance: Ng2Component;
|
||||||
|
|
||||||
|
// Define `ng1Directive`
|
||||||
|
const ng1DirectiveA: angular.IDirective = {
|
||||||
|
template: 'ng1A',
|
||||||
|
scope: {},
|
||||||
|
bindToController: false,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['$onDestroy'] = scopeOnDestroy;
|
||||||
|
Object.getPrototypeOf($scope)['$onDestroy'] = scopeOnDestroy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ng1DirectiveB: angular.IDirective = {
|
||||||
|
template: 'ng1B',
|
||||||
|
scope: {},
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: '$ctrl',
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope['$onDestroy'] = scopeOnDestroy;
|
||||||
|
Object.getPrototypeOf($scope)['$onDestroy'] = scopeOnDestroy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1A'})
|
||||||
|
class Ng1ComponentAFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1A', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1B'})
|
||||||
|
class Ng1ComponentBFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1B', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component(
|
||||||
|
{selector: 'ng2', template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>'})
|
||||||
|
class Ng2Component {
|
||||||
|
@Input() show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1Module', [])
|
||||||
|
.directive('ng1A', () => ng1DirectiveA)
|
||||||
|
.directive('ng1B', () => ng1DirectiveB)
|
||||||
|
.directive(
|
||||||
|
'ng2', downgradeComponent({component: Ng2Component, inputs: ['show']}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>');
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||||
|
const $rootScope = adapter.$injector.get('$rootScope') as angular.IRootScopeService;
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
|
||||||
|
expect(scopeOnDestroy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
$rootScope.$apply('destroyFromNg1 = true');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('');
|
||||||
|
expect(scopeOnDestroy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
$rootScope.$apply('destroyFromNg1 = false');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
|
||||||
|
expect(scopeOnDestroy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
$rootScope.$apply('destroyFromNg2 = true');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toBe('');
|
||||||
|
expect(scopeOnDestroy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be called in order `$onChanges()` > `$onInit()` > `$postLink()`', async(() => {
|
||||||
|
// Define `ng1Component`
|
||||||
|
const ng1Component: angular.IComponent = {
|
||||||
|
template: '{{ $ctrl.calls.join(" > ") }}',
|
||||||
|
bindings: {value: '<'},
|
||||||
|
controller: class {
|
||||||
|
calls: string[] = [];
|
||||||
|
|
||||||
|
$onChanges() { this.calls.push('$onChanges'); } $onInit() {
|
||||||
|
this.calls.push('$onInit');
|
||||||
|
} $postLink() { this.calls.push('$postLink'); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1'})
|
||||||
|
class Ng1ComponentFacade extends UpgradeComponent {
|
||||||
|
@Input() value: any;
|
||||||
|
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component({selector: 'ng2', template: '<ng1 value="foo"></ng1>'})
|
||||||
|
class Ng2Component {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module = angular.module('ng1Module', [])
|
||||||
|
.component('ng1', ng1Component)
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentFacade, Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html(`<ng2></ng2>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(() => {
|
||||||
|
expect(multiTrim(element.textContent)).toBe('$onChanges > $onInit > $postLink');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy `$componentScope` when destroying the upgraded component', async(() => {
|
||||||
|
const scopeDestroyListener = jasmine.createSpy('scopeDestroyListener');
|
||||||
|
let ng2ComponentAInstance: Ng2ComponentA;
|
||||||
|
|
||||||
|
// Define `ng1Component`
|
||||||
|
const ng1Component: angular.IComponent = {
|
||||||
|
controller: class {
|
||||||
|
constructor($scope: angular.IScope) {
|
||||||
|
$scope.$on('$destroy', scopeDestroyListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define `Ng1ComponentFacade`
|
||||||
|
@Directive({selector: 'ng1'})
|
||||||
|
class Ng1ComponentFacade extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `Ng2Component`
|
||||||
|
@Component({selector: 'ng2A', template: '<ng2B *ngIf="!destroyIt"></ng2B>'})
|
||||||
|
class Ng2ComponentA {
|
||||||
|
destroyIt = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
ng2ComponentAInstance = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'ng2B', template: '<ng1></ng1>'})
|
||||||
|
class Ng2ComponentB {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define `ng1Module`
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1Module', [])
|
||||||
|
.component('ng1', ng1Component)
|
||||||
|
.directive('ng2A', downgradeComponent({component: Ng2ComponentA}));
|
||||||
|
|
||||||
|
// Define `Ng2Module`
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB],
|
||||||
|
entryComponents: [Ng2ComponentA],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap
|
||||||
|
const element = html(`<ng2-a></ng2-a>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||||
|
expect(scopeDestroyListener).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
ng2ComponentAInstance.destroyIt = true;
|
||||||
|
digest(adapter);
|
||||||
|
|
||||||
|
expect(scopeDestroyListener).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
// it('should support ng2 > ng1 > ng2', async(() => {
|
// it('should support ng2 > ng1 > ng2', async(() => {
|
||||||
// const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
// const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
3
tools/public_api_guard/upgrade/static.d.ts
vendored
3
tools/public_api_guard/upgrade/static.d.ts
vendored
@ -5,10 +5,11 @@ export declare function downgradeComponent(info: ComponentInfo): angular.IInject
|
|||||||
export declare function downgradeInjectable(token: any): (string | ((i: Injector) => any))[];
|
export declare function downgradeInjectable(token: any): (string | ((i: Injector) => any))[];
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class UpgradeComponent implements OnInit, OnChanges, DoCheck {
|
export declare class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
constructor(name: string, elementRef: ElementRef, injector: Injector);
|
constructor(name: string, elementRef: ElementRef, injector: Injector);
|
||||||
ngDoCheck(): void;
|
ngDoCheck(): void;
|
||||||
ngOnChanges(changes: SimpleChanges): void;
|
ngOnChanges(changes: SimpleChanges): void;
|
||||||
|
ngOnDestroy(): void;
|
||||||
ngOnInit(): void;
|
ngOnInit(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user