feat(core): added afterContentInit, afterViewInit, and afterViewChecked hooks

Closes #3897
This commit is contained in:
vsavkin
2015-08-28 18:11:04 -07:00
committed by Victor Savkin
parent f93cd9ced7
commit d49bc438e8
36 changed files with 974 additions and 253 deletions

View File

@ -82,12 +82,19 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
this.mode === ChangeDetectionStrategy.Checked)
return;
var s = _scope_check(this.id, throwOnChange);
this.detectChangesInRecords(throwOnChange);
this._detectChangesInLightDomChildren(throwOnChange);
if (throwOnChange === false) this.callAfterContentChecked();
if (!throwOnChange) this.afterContentLifecycleCallbacks();
this._detectChangesInShadowDomChildren(throwOnChange);
if (!throwOnChange) this.afterViewLifecycleCallbacks();
if (this.mode === ChangeDetectionStrategy.CheckOnce)
this.mode = ChangeDetectionStrategy.Checked;
this.alreadyChecked = true;
wtfLeave(s);
}
@ -156,7 +163,19 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
hydrated(): boolean { return this.context !== null; }
callAfterContentChecked(): void { this.dispatcher.notifyAfterContentChecked(); }
afterContentLifecycleCallbacks(): void {
this.dispatcher.notifyAfterContentChecked();
this.afterContentLifecycleCallbacksInternal();
}
afterContentLifecycleCallbacksInternal(): void {}
afterViewLifecycleCallbacks(): void {
this.dispatcher.notifyAfterViewChecked();
this.afterViewLifecycleCallbacksInternal();
}
afterViewLifecycleCallbacksInternal(): void {}
_detectChangesInLightDomChildren(throwOnChange: boolean): void {
var c = this.lightDomChildren;

View File

@ -63,23 +63,23 @@ export class ChangeDetectorJITGenerator {
var ${CHANGES_LOCAL} = null;
${this.records.map((r) => this._genRecord(r)).join("\n")}
${this._names.getAlreadyCheckedName()} = true;
}
${this._maybeGenHandleEventInternal()}
${this._genCheckNoChanges()}
${this._maybeGenCallAfterContentChecked()}
${this._maybeGenAfterContentLifecycleCallbacks()}
${this._maybeGenAfterViewLifecycleCallbacks()}
${this._maybeGenHydrateDirectives()}
${this._maybeGenDehydrateDirectives()}
${this._genPropertyBindingTargets()};
${this._genPropertyBindingTargets()}
${this._genDirectiveIndices()};
${this._genDirectiveIndices()}
return function(dispatcher) {
return new ${this._typeName}(dispatcher);
@ -172,23 +172,26 @@ export class ChangeDetectorJITGenerator {
}`;
}
_maybeGenCallAfterContentChecked(): string {
var notifications = [];
var dirs = this.directiveRecords;
// NOTE(kegluneq): Order is important!
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callAfterContentChecked) {
notifications.push(
`${this._names.getDirectiveName(dir.directiveIndex)}.afterContentChecked();`);
}
}
_maybeGenAfterContentLifecycleCallbacks(): string {
var notifications = this._logic.genContentLifecycleCallbacks(this.directiveRecords);
if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n");
return `
${this._typeName}.prototype.callAfterContentChecked = function() {
${ABSTRACT_CHANGE_DETECTOR}.prototype.callAfterContentChecked.call(this);
${this._typeName}.prototype.afterContentLifecycleCallbacksInternal = function() {
${directiveNotifications}
}
`;
} else {
return '';
}
}
_maybeGenAfterViewLifecycleCallbacks(): string {
var notifications = this._logic.genViewLifecycleCallbacks(this.directiveRecords);
if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n");
return `
${this._typeName}.prototype.afterViewLifecycleCallbacksInternal = function() {
${directiveNotifications}
}
`;

View File

@ -183,4 +183,38 @@ export class CodegenLogicUtil {
}
return res.join("\n");
}
genContentLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
var res = [];
// NOTE(kegluneq): Order is important!
for (var i = directiveRecords.length - 1; i >= 0; --i) {
var dir = directiveRecords[i];
if (dir.callAfterContentInit) {
res.push(
`if(! ${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(dir.directiveIndex)}.afterContentInit();`);
}
if (dir.callAfterContentChecked) {
res.push(`${this._names.getDirectiveName(dir.directiveIndex)}.afterContentChecked();`);
}
}
return res;
}
genViewLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
var res = [];
// NOTE(kegluneq): Order is important!
for (var i = directiveRecords.length - 1; i >= 0; --i) {
var dir = directiveRecords[i];
if (dir.callAfterViewInit) {
res.push(
`if(! ${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(dir.directiveIndex)}.afterViewInit();`);
}
if (dir.callAfterViewChecked) {
res.push(`${this._names.getDirectiveName(dir.directiveIndex)}.afterViewChecked();`);
}
}
return res;
}
}

View File

@ -9,24 +9,33 @@ export class DirectiveIndex {
export class DirectiveRecord {
directiveIndex: DirectiveIndex;
callAfterContentInit: boolean;
callAfterContentChecked: boolean;
callAfterViewInit: boolean;
callAfterViewChecked: boolean;
callOnChanges: boolean;
callDoCheck: boolean;
callOnInit: boolean;
changeDetection: ChangeDetectionStrategy;
constructor({directiveIndex, callAfterContentChecked, callOnChanges, callDoCheck, callOnInit,
changeDetection}: {
constructor({directiveIndex, callAfterContentInit, callAfterContentChecked, callAfterViewInit,
callAfterViewChecked, callOnChanges, callDoCheck, callOnInit, changeDetection}: {
directiveIndex?: DirectiveIndex,
callAfterContentInit?: boolean,
callAfterContentChecked?: boolean,
callAfterViewInit?: boolean,
callAfterViewChecked?: boolean,
callOnChanges?: boolean,
callDoCheck?: boolean,
callOnInit?: boolean,
changeDetection?: ChangeDetectionStrategy
} = {}) {
this.directiveIndex = directiveIndex;
this.callAfterContentInit = normalizeBool(callAfterContentInit);
this.callAfterContentChecked = normalizeBool(callAfterContentChecked);
this.callOnChanges = normalizeBool(callOnChanges);
this.callAfterViewInit = normalizeBool(callAfterViewInit);
this.callAfterViewChecked = normalizeBool(callAfterViewChecked);
this.callDoCheck = normalizeBool(callDoCheck);
this.callOnInit = normalizeBool(callOnInit);
this.changeDetection = changeDetection;

View File

@ -159,8 +159,6 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
isChanged = false;
}
}
this.alreadyChecked = true;
}
_firstInBinding(r: ProtoRecord): boolean {
@ -168,17 +166,33 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
return isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
}
callAfterContentChecked() {
super.callAfterContentChecked();
afterContentLifecycleCallbacksInternal() {
var dirs = this.directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callAfterContentInit && !this.alreadyChecked) {
this._getDirectiveFor(dir.directiveIndex).afterContentInit();
}
if (dir.callAfterContentChecked) {
this._getDirectiveFor(dir.directiveIndex).afterContentChecked();
}
}
}
afterViewLifecycleCallbacksInternal() {
var dirs = this.directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callAfterViewInit && !this.alreadyChecked) {
this._getDirectiveFor(dir.directiveIndex).afterViewInit();
}
if (dir.callAfterViewChecked) {
this._getDirectiveFor(dir.directiveIndex).afterViewChecked();
}
}
}
_updateDirectiveOrElement(change, bindingRecord) {
if (isBlank(bindingRecord.directiveRecord)) {
super.notifyDispatcher(change.currentValue);

View File

@ -52,6 +52,7 @@ export interface ChangeDispatcher {
notifyOnBinding(bindingTarget: BindingTarget, value: any): void;
logBindingUpdate(bindingTarget: BindingTarget, value: any): void;
notifyAfterContentChecked(): void;
notifyAfterViewChecked(): void;
}
export interface ChangeDetector {