UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

491 lines (412 loc) 17 kB
import {isPresent, isBlank, FunctionWrapper, StringWrapper} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {AbstractChangeDetector} from './abstract_change_detector'; import {EventBinding} from './event_binding'; import {BindingRecord, BindingTarget} from './binding_record'; import {DirectiveRecord, DirectiveIndex} from './directive_record'; import {Locals} from './parser/locals'; import {ChangeDispatcher, ChangeDetectorGenConfig} from './interfaces'; import {ChangeDetectionUtil, SimpleChange} from './change_detection_util'; import {ChangeDetectionStrategy, ChangeDetectorState} from './constants'; import {ProtoRecord, RecordType} from './proto_record'; import {reflector} from 'angular2/src/core/reflection/reflection'; import {ObservableWrapper} from 'angular2/src/facade/async'; export class DynamicChangeDetector extends AbstractChangeDetector<any> { values: any[]; changes: any[]; localPipes: any[]; prevContexts: any[]; constructor(id: string, numberOfPropertyProtoRecords: number, propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[], strategy: ChangeDetectionStrategy, private _records: ProtoRecord[], private _eventBindings: EventBinding[], private _directiveRecords: DirectiveRecord[], private _genConfig: ChangeDetectorGenConfig) { super(id, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices, strategy); var len = _records.length + 1; this.values = ListWrapper.createFixedSize(len); this.localPipes = ListWrapper.createFixedSize(len); this.prevContexts = ListWrapper.createFixedSize(len); this.changes = ListWrapper.createFixedSize(len); this.dehydrateDirectives(false); } handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { var preventDefault = false; this._matchingEventBindings(eventName, elIndex) .forEach(rec => { var res = this._processEventBinding(rec, locals); if (res === false) { preventDefault = true; } }); return preventDefault; } /** @internal */ _processEventBinding(eb: EventBinding, locals: Locals): any { var values = ListWrapper.createFixedSize(eb.records.length); values[0] = this.values[0]; for (var protoIdx = 0; protoIdx < eb.records.length; ++protoIdx) { var proto = eb.records[protoIdx]; if (proto.isSkipRecord()) { protoIdx += this._computeSkipLength(protoIdx, proto, values); } else { if (proto.lastInBinding) { this._markPathAsCheckOnce(proto); } var res = this._calculateCurrValue(proto, values, locals); if (proto.lastInBinding) { return res; } else { this._writeSelf(proto, res, values); } } } throw new BaseException("Cannot be reached"); } private _computeSkipLength(protoIndex: number, proto: ProtoRecord, values: any[]): number { if (proto.mode === RecordType.SkipRecords) { return proto.fixedArgs[0] - protoIndex - 1; } if (proto.mode === RecordType.SkipRecordsIf) { let condition = this._readContext(proto, values); return condition ? proto.fixedArgs[0] - protoIndex - 1 : 0; } if (proto.mode === RecordType.SkipRecordsIfNot) { let condition = this._readContext(proto, values); return condition ? 0 : proto.fixedArgs[0] - protoIndex - 1; } throw new BaseException("Cannot be reached"); } /** @internal */ _markPathAsCheckOnce(proto: ProtoRecord): void { if (!proto.bindingRecord.isDefaultChangeDetection()) { var dir = proto.bindingRecord.directiveRecord; this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce(); } } /** @internal */ _matchingEventBindings(eventName: string, elIndex: number): EventBinding[] { return this._eventBindings.filter(eb => eb.eventName == eventName && eb.elIndex === elIndex); } hydrateDirectives(dispatcher: ChangeDispatcher): void { this.values[0] = this.context; this.dispatcher = dispatcher; this.outputSubscriptions = []; for (var i = 0; i < this._directiveRecords.length; ++i) { var r = this._directiveRecords[i]; if (isPresent(r.outputs)) { r.outputs.forEach(output => { var eventHandler = <any>this._createEventHandler(r.directiveIndex.elementIndex, output[1]); var directive = this._getDirectiveFor(r.directiveIndex); var getter = reflector.getter(output[0]); this.outputSubscriptions.push( ObservableWrapper.subscribe(getter(directive), eventHandler)); }); } } } private _createEventHandler(boundElementIndex: number, eventName: string): Function { return (event) => this.handleEvent(eventName, boundElementIndex, event); } dehydrateDirectives(destroyPipes: boolean) { if (destroyPipes) { this._destroyPipes(); this._destroyDirectives(); } this.values[0] = null; ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1); ListWrapper.fill(this.changes, false); ListWrapper.fill(this.localPipes, null); ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized); } /** @internal */ _destroyPipes() { for (var i = 0; i < this.localPipes.length; ++i) { if (isPresent(this.localPipes[i])) { ChangeDetectionUtil.callPipeOnDestroy(this.localPipes[i]); } } } /** @internal */ _destroyDirectives() { for (var i = 0; i < this._directiveRecords.length; ++i) { var record = this._directiveRecords[i]; if (record.callOnDestroy) { this._getDirectiveFor(record.directiveIndex).ngOnDestroy(); } } } checkNoChanges(): void { this.runDetectChanges(true); } detectChangesInRecordsInternal(throwOnChange: boolean) { var protos = this._records; var changes = null; var isChanged = false; for (var protoIdx = 0; protoIdx < protos.length; ++protoIdx) { var proto: ProtoRecord = protos[protoIdx]; var bindingRecord = proto.bindingRecord; var directiveRecord = bindingRecord.directiveRecord; if (this._firstInBinding(proto)) { this.propertyBindingIndex = proto.propertyBindingIndex; } if (proto.isLifeCycleRecord()) { if (proto.name === "DoCheck" && !throwOnChange) { this._getDirectiveFor(directiveRecord.directiveIndex).ngDoCheck(); } else if (proto.name === "OnInit" && !throwOnChange && this.state == ChangeDetectorState.NeverChecked) { this._getDirectiveFor(directiveRecord.directiveIndex).ngOnInit(); } else if (proto.name === "OnChanges" && isPresent(changes) && !throwOnChange) { this._getDirectiveFor(directiveRecord.directiveIndex).ngOnChanges(changes); } } else if (proto.isSkipRecord()) { protoIdx += this._computeSkipLength(protoIdx, proto, this.values); } else { var change = this._check(proto, throwOnChange, this.values, this.locals); if (isPresent(change)) { this._updateDirectiveOrElement(change, bindingRecord); isChanged = true; changes = this._addChange(bindingRecord, change, changes); } } if (proto.lastInDirective) { changes = null; if (isChanged && !bindingRecord.isDefaultChangeDetection()) { this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce(); } isChanged = false; } } } /** @internal */ _firstInBinding(r: ProtoRecord): boolean { var prev = ChangeDetectionUtil.protoByIndex(this._records, r.selfIndex - 1); return isBlank(prev) || prev.bindingRecord !== r.bindingRecord; } afterContentLifecycleCallbacksInternal() { var dirs = this._directiveRecords; for (var i = dirs.length - 1; i >= 0; --i) { var dir = dirs[i]; if (dir.callAfterContentInit && this.state == ChangeDetectorState.NeverChecked) { this._getDirectiveFor(dir.directiveIndex).ngAfterContentInit(); } if (dir.callAfterContentChecked) { this._getDirectiveFor(dir.directiveIndex).ngAfterContentChecked(); } } } afterViewLifecycleCallbacksInternal() { var dirs = this._directiveRecords; for (var i = dirs.length - 1; i >= 0; --i) { var dir = dirs[i]; if (dir.callAfterViewInit && this.state == ChangeDetectorState.NeverChecked) { this._getDirectiveFor(dir.directiveIndex).ngAfterViewInit(); } if (dir.callAfterViewChecked) { this._getDirectiveFor(dir.directiveIndex).ngAfterViewChecked(); } } } /** @internal */ private _updateDirectiveOrElement(change, bindingRecord) { if (isBlank(bindingRecord.directiveRecord)) { super.notifyDispatcher(change.currentValue); } else { var directiveIndex = bindingRecord.directiveRecord.directiveIndex; bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue); } if (this._genConfig.logBindingUpdate) { super.logBindingUpdate(change.currentValue); } } /** @internal */ private _addChange(bindingRecord: BindingRecord, change, changes) { if (bindingRecord.callOnChanges()) { return super.addChange(changes, change.previousValue, change.currentValue); } else { return changes; } } /** @internal */ private _getDirectiveFor(directiveIndex: DirectiveIndex) { return this.dispatcher.getDirectiveFor(directiveIndex); } /** @internal */ private _getDetectorFor(directiveIndex: DirectiveIndex) { return this.dispatcher.getDetectorFor(directiveIndex); } /** @internal */ private _check(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals): SimpleChange { if (proto.isPipeRecord()) { return this._pipeCheck(proto, throwOnChange, values); } else { return this._referenceCheck(proto, throwOnChange, values, locals); } } /** @internal */ private _referenceCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals) { if (this._pureFuncAndArgsDidNotChange(proto)) { this._setChanged(proto, false); return null; } var currValue = this._calculateCurrValue(proto, values, locals); if (proto.shouldBeChecked()) { var prevValue = this._readSelf(proto, values); var detectedChange = throwOnChange ? !ChangeDetectionUtil.devModeEqual(prevValue, currValue) : ChangeDetectionUtil.looseNotIdentical(prevValue, currValue); if (detectedChange) { if (proto.lastInBinding) { var change = ChangeDetectionUtil.simpleChange(prevValue, currValue); if (throwOnChange) this.throwOnChangeError(prevValue, currValue); this._writeSelf(proto, currValue, values); this._setChanged(proto, true); return change; } else { this._writeSelf(proto, currValue, values); this._setChanged(proto, true); return null; } } else { this._setChanged(proto, false); return null; } } else { this._writeSelf(proto, currValue, values); this._setChanged(proto, true); return null; } } private _calculateCurrValue(proto: ProtoRecord, values: any[], locals: Locals) { switch (proto.mode) { case RecordType.Self: return this._readContext(proto, values); case RecordType.Const: return proto.funcOrValue; case RecordType.PropertyRead: var context = this._readContext(proto, values); return proto.funcOrValue(context); case RecordType.SafeProperty: var context = this._readContext(proto, values); return isBlank(context) ? null : proto.funcOrValue(context); case RecordType.PropertyWrite: var context = this._readContext(proto, values); var value = this._readArgs(proto, values)[0]; proto.funcOrValue(context, value); return value; case RecordType.KeyedWrite: var context = this._readContext(proto, values); var key = this._readArgs(proto, values)[0]; var value = this._readArgs(proto, values)[1]; context[key] = value; return value; case RecordType.Local: return locals.get(proto.name); case RecordType.InvokeMethod: var context = this._readContext(proto, values); var args = this._readArgs(proto, values); return proto.funcOrValue(context, args); case RecordType.SafeMethodInvoke: var context = this._readContext(proto, values); if (isBlank(context)) { return null; } var args = this._readArgs(proto, values); return proto.funcOrValue(context, args); case RecordType.KeyedRead: var arg = this._readArgs(proto, values)[0]; return this._readContext(proto, values)[arg]; case RecordType.Chain: var args = this._readArgs(proto, values); return args[args.length - 1]; case RecordType.InvokeClosure: return FunctionWrapper.apply(this._readContext(proto, values), this._readArgs(proto, values)); case RecordType.Interpolate: case RecordType.PrimitiveOp: case RecordType.CollectionLiteral: return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto, values)); default: throw new BaseException(`Unknown operation ${proto.mode}`); } } private _pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) { var context = this._readContext(proto, values); var selectedPipe = this._pipeFor(proto, context); if (!selectedPipe.pure || this._argsOrContextChanged(proto)) { var args = this._readArgs(proto, values); var currValue = selectedPipe.pipe.transform(context, args); if (proto.shouldBeChecked()) { var prevValue = this._readSelf(proto, values); var detectedChange = throwOnChange ? !ChangeDetectionUtil.devModeEqual(prevValue, currValue) : ChangeDetectionUtil.looseNotIdentical(prevValue, currValue); if (detectedChange) { currValue = ChangeDetectionUtil.unwrapValue(currValue); if (proto.lastInBinding) { var change = ChangeDetectionUtil.simpleChange(prevValue, currValue); if (throwOnChange) this.throwOnChangeError(prevValue, currValue); this._writeSelf(proto, currValue, values); this._setChanged(proto, true); return change; } else { this._writeSelf(proto, currValue, values); this._setChanged(proto, true); return null; } } else { this._setChanged(proto, false); return null; } } else { this._writeSelf(proto, currValue, values); this._setChanged(proto, true); return null; } } } private _pipeFor(proto: ProtoRecord, context) { var storedPipe = this._readPipe(proto); if (isPresent(storedPipe)) return storedPipe; var pipe = this.pipes.get(proto.name); this._writePipe(proto, pipe); return pipe; } private _readContext(proto: ProtoRecord, values: any[]) { if (proto.contextIndex == -1) { return this._getDirectiveFor(proto.directiveIndex); } return values[proto.contextIndex]; } private _readSelf(proto: ProtoRecord, values: any[]) { return values[proto.selfIndex]; } private _writeSelf(proto: ProtoRecord, value, values: any[]) { values[proto.selfIndex] = value; } private _readPipe(proto: ProtoRecord) { return this.localPipes[proto.selfIndex]; } private _writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; } private _setChanged(proto: ProtoRecord, value: boolean) { if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value; } private _pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean { return proto.isPureFunction() && !this._argsChanged(proto); } private _argsChanged(proto: ProtoRecord): boolean { var args = proto.args; for (var i = 0; i < args.length; ++i) { if (this.changes[args[i]]) { return true; } } return false; } private _argsOrContextChanged(proto: ProtoRecord): boolean { return this._argsChanged(proto) || this.changes[proto.contextIndex]; } private _readArgs(proto: ProtoRecord, values: any[]) { var res = ListWrapper.createFixedSize(proto.args.length); var args = proto.args; for (var i = 0; i < args.length; ++i) { res[i] = values[args[i]]; } return res; } }