angular2
Version:
Angular 2 - a web framework for modern web apps
491 lines (412 loc) • 17 kB
text/typescript
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;
}
}