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:
Georgios Kalpakas
2016-10-20 13:42:57 +03:00
committed by vikerman
parent f0cdb428f5
commit 469010ea8e
3 changed files with 793 additions and 47 deletions

View File

@ -6,7 +6,7 @@
* 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 {looseIdentical} from '../facade/lang';
@ -34,13 +34,17 @@ interface IBindingDestination {
}
interface IControllerInstance extends IBindingDestination {
$onDestroy?: () => void;
$onInit?: () => void;
$postLink?: () => void;
}
type LifecycleHook = '$onChanges' | '$onDestroy' | '$onInit' | '$postLink';
/**
* @experimental
*/
export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
private $injector: angular.IInjectorService;
private $compile: angular.ICompileService;
private $templateCache: angular.ITemplateCacheService;
@ -80,33 +84,27 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
this.$componentScope = $parentScope.$new(!!this.directive.scope);
const controllerType = this.directive.controller;
// QUESTION: shouldn't we be building the controller in any case?
if (this.directive.bindToController) {
if (controllerType) {
this.bindingDestination = this.controllerInstance = this.buildController(
controllerType, this.$componentScope, this.$element, this.directive.controllerAs);
} else {
throw new Error(
`Upgraded directive '${name}' specifies 'bindToController' but no controller.`);
}
} else {
this.bindingDestination = this.$componentScope;
const bindToController = this.directive.bindToController;
if (controllerType) {
this.controllerInstance = this.buildController(
controllerType, this.$componentScope, this.$element, this.directive.controllerAs);
} else if (bindToController) {
throw new Error(
`Upgraded directive '${name}' specifies 'bindToController' but no controller.`);
}
this.bindingDestination = bindToController ? this.controllerInstance : this.$componentScope;
this.setupOutputs();
}
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 transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
const linkController = this.resolveRequired(this.$element, this.directive.require);
this.callLifecycleHook('$onInit', this.controllerInstance);
const link = this.directive.link;
const preLink = (typeof link == 'object') && (link as angular.IDirectivePrePost).pre;
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);
}
if (this.controllerInstance && this.controllerInstance.$onInit) {
this.controllerInstance.$onInit();
}
this.callLifecycleHook('$postLink', this.controllerInstance);
}
ngOnChanges(changes: SimpleChanges) {
// Forward input changes to `bindingDestination`
Object.keys(changes).forEach(
propName => { this.bindingDestination[propName] = changes[propName].currentValue; });
propName => this.bindingDestination[propName] = changes[propName].currentValue);
if (this.bindingDestination.$onChanges) {
this.bindingDestination.$onChanges(changes);
}
this.callLifecycleHook('$onChanges', this.bindingDestination, changes);
}
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 {
const directives: angular.IDirective[] = this.$injector.get(name + 'Directive');
if (directives.length > 1) {
@ -254,6 +259,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck {
private buildController(
controllerType: angular.IController, $scope: angular.IScope,
$element: angular.IAugmentedJQuery, controllerAs: string) {
// TODO: Document that we do not pre-assign bindings on the controller instance
var locals = {$scope, $element};
var controller = this.$controller(controllerType, locals, null, controllerAs);
$element.data(controllerKey(this.directive.name), controller);