UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

325 lines (269 loc) 11.9 kB
import {assertionsEnabled, isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang'; import {ListWrapper} from 'angular2/src/facade/collection'; import {ChangeDetectionUtil} from './change_detection_util'; import {ChangeDetectorRef, ChangeDetectorRef_} from './change_detector_ref'; import {DirectiveIndex} from './directive_record'; import {ChangeDetector, ChangeDispatcher} from './interfaces'; import {Pipes} from './pipes'; import { ChangeDetectionError, ExpressionChangedAfterItHasBeenCheckedException, DehydratedException } from './exceptions'; import {BindingTarget} from './binding_record'; import {Locals} from './parser/locals'; import {ChangeDetectionStrategy, ChangeDetectorState} from './constants'; import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile'; import {isObservable} from './observable_facade'; var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`); class _Context { constructor(public element: any, public componentElement: any, public context: any, public locals: any, public injector: any, public expression: any) {} } export class AbstractChangeDetector<T> implements ChangeDetector { contentChildren: any[] = []; viewChildren: any[] = []; parent: ChangeDetector; ref: ChangeDetectorRef; // The names of the below fields must be kept in sync with codegen_name_util.ts or // change detection will fail. state: ChangeDetectorState = ChangeDetectorState.NeverChecked; context: T; locals: Locals = null; mode: ChangeDetectionStrategy = null; pipes: Pipes = null; propertyBindingIndex: number; // This is an experimental feature. Works only in Dart. subscriptions: any[]; streams: any[]; constructor(public id: string, public dispatcher: ChangeDispatcher, public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[], public directiveIndices: DirectiveIndex[], public strategy: ChangeDetectionStrategy) { this.ref = new ChangeDetectorRef_(this); } addContentChild(cd: ChangeDetector): void { this.contentChildren.push(cd); cd.parent = this; } removeContentChild(cd: ChangeDetector): void { ListWrapper.remove(this.contentChildren, cd); } addViewChild(cd: ChangeDetector): void { this.viewChildren.push(cd); cd.parent = this; } removeViewChild(cd: ChangeDetector): void { ListWrapper.remove(this.viewChildren, cd); } remove(): void { this.parent.removeContentChild(this); } handleEvent(eventName: string, elIndex: number, locals: Locals): boolean { var res = this.handleEventInternal(eventName, elIndex, locals); this.markPathToRootAsCheckOnce(); return res; } handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; } detectChanges(): void { this.runDetectChanges(false); } checkNoChanges(): void { if (assertionsEnabled()) { this.runDetectChanges(true); } } runDetectChanges(throwOnChange: boolean): void { if (this.mode === ChangeDetectionStrategy.Detached || this.mode === ChangeDetectionStrategy.Checked || this.state === ChangeDetectorState.Errored) return; var s = _scope_check(this.id, throwOnChange); this.detectChangesInRecords(throwOnChange); this._detectChangesContentChildren(throwOnChange); if (!throwOnChange) this.afterContentLifecycleCallbacks(); this._detectChangesInViewChildren(throwOnChange); if (!throwOnChange) this.afterViewLifecycleCallbacks(); if (this.mode === ChangeDetectionStrategy.CheckOnce) this.mode = ChangeDetectionStrategy.Checked; this.state = ChangeDetectorState.CheckedBefore; wtfLeave(s); } // This method is not intended to be overridden. Subclasses should instead provide an // implementation of `detectChangesInRecordsInternal` which does the work of detecting changes // and which this method will call. // This method expects that `detectChangesInRecordsInternal` will set the property // `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to // facilitate error reporting. detectChangesInRecords(throwOnChange: boolean): void { if (!this.hydrated()) { this.throwDehydratedError(); } try { this.detectChangesInRecordsInternal(throwOnChange); } catch (e) { // throwOnChange errors aren't counted as fatal errors. if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedException)) { this.state = ChangeDetectorState.Errored; } this._throwError(e, e.stack); } } // Subclasses should override this method to perform any work necessary to detect and report // changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle // methods should be called, etc. // This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of // the // first proto record to facilitate error reporting. See {@link #detectChangesInRecords}. detectChangesInRecordsInternal(throwOnChange: boolean): void {} // This method is not intended to be overridden. Subclasses should instead provide an // implementation of `hydrateDirectives`. hydrate(context: T, locals: Locals, directives: any, pipes: Pipes): void { this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy); this.context = context; if (this.strategy === ChangeDetectionStrategy.OnPushObserve) { this.observeComponent(context); } this.locals = locals; this.pipes = pipes; this.hydrateDirectives(directives); this.state = ChangeDetectorState.NeverChecked; } // Subclasses should override this method to hydrate any directives. hydrateDirectives(directives: any): void {} // This method is not intended to be overridden. Subclasses should instead provide an // implementation of `dehydrateDirectives`. dehydrate(): void { this.dehydrateDirectives(true); // This is an experimental feature. Works only in Dart. if (this.strategy === ChangeDetectionStrategy.OnPushObserve) { this._unsubsribeFromObservables(); } this.context = null; this.locals = null; this.pipes = null; } // Subclasses should override this method to dehydrate any directives. This method should reverse // any work done in `hydrateDirectives`. dehydrateDirectives(destroyPipes: boolean): void {} hydrated(): boolean { return this.context !== null; } afterContentLifecycleCallbacks(): void { this.dispatcher.notifyAfterContentChecked(); this.afterContentLifecycleCallbacksInternal(); } afterContentLifecycleCallbacksInternal(): void {} afterViewLifecycleCallbacks(): void { this.dispatcher.notifyAfterViewChecked(); this.afterViewLifecycleCallbacksInternal(); } afterViewLifecycleCallbacksInternal(): void {} /** @internal */ _detectChangesContentChildren(throwOnChange: boolean): void { var c = this.contentChildren; for (var i = 0; i < c.length; ++i) { c[i].runDetectChanges(throwOnChange); } } /** @internal */ _detectChangesInViewChildren(throwOnChange: boolean): void { var c = this.viewChildren; for (var i = 0; i < c.length; ++i) { c[i].runDetectChanges(throwOnChange); } } markAsCheckOnce(): void { this.mode = ChangeDetectionStrategy.CheckOnce; } markPathToRootAsCheckOnce(): void { var c: ChangeDetector = this; while (isPresent(c) && c.mode !== ChangeDetectionStrategy.Detached) { if (c.mode === ChangeDetectionStrategy.Checked) c.mode = ChangeDetectionStrategy.CheckOnce; c = c.parent; } } // This is an experimental feature. Works only in Dart. private _unsubsribeFromObservables(): void { if (isPresent(this.subscriptions)) { for (var i = 0; i < this.subscriptions.length; ++i) { var s = this.subscriptions[i]; if (isPresent(this.subscriptions[i])) { s.cancel(); this.subscriptions[i] = null; } } } } // This is an experimental feature. Works only in Dart. observeValue(value: any, index: number): any { if (isObservable(value)) { this._createArrayToStoreObservables(); if (isBlank(this.subscriptions[index])) { this.streams[index] = value.changes; this.subscriptions[index] = value.changes.listen((_) => this.ref.markForCheck()); } else if (this.streams[index] !== value.changes) { this.subscriptions[index].cancel(); this.streams[index] = value.changes; this.subscriptions[index] = value.changes.listen((_) => this.ref.markForCheck()); } } return value; } // This is an experimental feature. Works only in Dart. observeDirective(value: any, index: number): any { if (isObservable(value)) { this._createArrayToStoreObservables(); var arrayIndex = this.numberOfPropertyProtoRecords + index + 2; // +1 is component this.streams[arrayIndex] = value.changes; this.subscriptions[arrayIndex] = value.changes.listen((_) => this.ref.markForCheck()); } return value; } // This is an experimental feature. Works only in Dart. observeComponent(value: any): any { if (isObservable(value)) { this._createArrayToStoreObservables(); var index = this.numberOfPropertyProtoRecords + 1; this.streams[index] = value.changes; this.subscriptions[index] = value.changes.listen((_) => this.ref.markForCheck()); } return value; } private _createArrayToStoreObservables(): void { if (isBlank(this.subscriptions)) { this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + this.directiveIndices.length + 2); this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + this.directiveIndices.length + 2); } } getDirectiveFor(directives: any, index: number): any { return directives.getDirectiveFor(this.directiveIndices[index]); } getDetectorFor(directives: any, index: number): ChangeDetector { return directives.getDetectorFor(this.directiveIndices[index]); } notifyDispatcher(value: any): void { this.dispatcher.notifyOnBinding(this._currentBinding(), value); } logBindingUpdate(value: any): void { this.dispatcher.logBindingUpdate(this._currentBinding(), value); } addChange(changes: {[key: string]: any}, oldValue: any, newValue: any): {[key: string]: any} { if (isBlank(changes)) { changes = {}; } changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue); return changes; } private _throwError(exception: any, stack: any): void { var error; try { var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null); var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals, c.injector, this._currentBinding().debug) : null; error = new ChangeDetectionError(this._currentBinding().debug, exception, stack, context); } catch (e) { // if an error happens during getting the debug context, we throw a ChangeDetectionError // without the extra information. error = new ChangeDetectionError(null, exception, stack, null); } throw error; } throwOnChangeError(oldValue: any, newValue: any): void { throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug, oldValue, newValue, null); } throwDehydratedError(): void { throw new DehydratedException(); } private _currentBinding(): BindingTarget { return this.bindingTargets[this.propertyBindingIndex]; } }