Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions modules/angular2/src/common/directives/ng_for.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
IterableDiffers,
ViewContainerRef,
TemplateRef,
EmbeddedViewRef
EmbeddedViewRef,
TrackByFn
} from 'angular2/core';
import {isPresent, isBlank} from 'angular2/src/facade/lang';

Expand Down Expand Up @@ -59,10 +60,11 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
* example.
*/
@Directive({selector: '[ngFor][ngForOf]', inputs: ['ngForOf', 'ngForTemplate']})
@Directive({selector: '[ngFor][ngForOf]', inputs: ['ngForTrackBy', 'ngForOf', 'ngForTemplate']})
export class NgFor implements DoCheck {
/** @internal */
_ngForOf: any;
_ngForTrackBy: TrackByFn;
private _differ: IterableDiffer;

constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef,
Expand All @@ -71,7 +73,7 @@ export class NgFor implements DoCheck {
set ngForOf(value: any) {
this._ngForOf = value;
if (isBlank(this._differ) && isPresent(value)) {
this._differ = this._iterableDiffers.find(value).create(this._cdr);
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
}
}

Expand All @@ -81,6 +83,8 @@ export class NgFor implements DoCheck {
}
}

set ngForTrackBy(value: TrackByFn) { this._ngForTrackBy = value; }

ngDoCheck() {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._ngForOf);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class ObservableListDiff extends DefaultIterableDiffer {
class ObservableListDiffFactory implements IterableDifferFactory {
const ObservableListDiffFactory();
bool supports(obj) => obj is ObservableList;
IterableDiffer create(ChangeDetectorRef cdRef) {
IterableDiffer create(ChangeDetectorRef cdRef, [Function trackByFn]) {
return new ObservableListDiff(cdRef);
}
}
3 changes: 2 additions & 1 deletion modules/angular2/src/core/change_detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ export {
IterableDifferFactory,
KeyValueDiffers,
KeyValueDiffer,
KeyValueDifferFactory
KeyValueDifferFactory,
TrackByFn
} from './change_detection/change_detection';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs';
import {IterableDiffers, IterableDifferFactory, TrackByFn} from './differs/iterable_differs';
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs';
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
Expand Down Expand Up @@ -37,7 +37,12 @@ export {BindingRecord, BindingTarget} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record';
export {DynamicChangeDetector} from './dynamic_change_detector';
export {ChangeDetectorRef} from './change_detector_ref';
export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs';
export {
IterableDiffers,
IterableDiffer,
IterableDifferFactory,
TrackByFn
} from './differs/iterable_differs';
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
export {PipeTransform} from './pipe_transform';
export {WrappedValue, SimpleChange} from './change_detection_util';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {CONST} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
isListLikeIterable,
iterateListLike,
ListWrapper,
MapWrapper
} from 'angular2/src/facade/collection';
import {isListLikeIterable, iterateListLike, ListWrapper} from 'angular2/src/facade/collection';

import {
isBlank,
Expand All @@ -17,17 +12,21 @@ import {
} from 'angular2/src/facade/lang';

import {ChangeDetectorRef} from '../change_detector_ref';
import {IterableDiffer, IterableDifferFactory} from '../differs/iterable_differs';
import {IterableDiffer, IterableDifferFactory, TrackByFn} from '../differs/iterable_differs';

@CONST()
export class DefaultIterableDifferFactory implements IterableDifferFactory {
supports(obj: Object): boolean { return isListLikeIterable(obj); }
create(cdRef: ChangeDetectorRef): DefaultIterableDiffer { return new DefaultIterableDiffer(); }
create(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): DefaultIterableDiffer {
return new DefaultIterableDiffer(trackByFn);
}
}

var trackByIdentity = (index: number, item: any) => item;

export class DefaultIterableDiffer implements IterableDiffer {
private _collection = null;
private _length: number = null;
private _collection = null;
// Keeps track of the used records at any point in time (during & across `_check()` calls)
private _linkedRecords: _DuplicateMap = null;
// Keeps track of the removed records at any point in time during `_check()` calls.
Expand All @@ -42,6 +41,10 @@ export class DefaultIterableDiffer implements IterableDiffer {
private _removalsHead: CollectionChangeRecord = null;
private _removalsTail: CollectionChangeRecord = null;

constructor(private _trackByFn?: TrackByFn) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do _trackByFn: TrackByFn = trackByIdentity instead? Try to do this if ts2dart supports it. If not, what you have is good.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I wanted to do too :) It doesn't seem to be supported.

this._trackByFn = isPresent(this._trackByFn) ? this._trackByFn : trackByIdentity;
}

get collection() { return this._collection; }

get length(): number { return this._length; }
Expand Down Expand Up @@ -104,31 +107,37 @@ export class DefaultIterableDiffer implements IterableDiffer {
var mayBeDirty: boolean = false;
var index: number;
var item;

var itemTrackBy;
if (isArray(collection)) {
var list = collection;
this._length = collection.length;

for (index = 0; index < this._length; index++) {
item = list[index];
if (record === null || !looseIdentical(record.item, item)) {
record = this._mismatch(record, item, index);
itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true;
} else if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, index);
} else {
if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index);
}
record.item = item;
}

record = record._next;
}
} else {
index = 0;
iterateListLike(collection, (item) => {
if (record === null || !looseIdentical(record.item, item)) {
record = this._mismatch(record, item, index);
itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true;
} else if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, index);
record = this._verifyReinsertion(record, item, itemTrackBy, index);
}
record = record._next;
index++;
Expand Down Expand Up @@ -190,7 +199,8 @@ export class DefaultIterableDiffer implements IterableDiffer {
*
* @internal
*/
_mismatch(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
_mismatch(record: CollectionChangeRecord, item: any, itemTrackBy: any,
index: number): CollectionChangeRecord {
// The previous record after which we will append the current one.
var previousRecord: CollectionChangeRecord;

Expand All @@ -203,19 +213,20 @@ export class DefaultIterableDiffer implements IterableDiffer {
}

// Attempt to see if we have seen the item before.
record = this._linkedRecords === null ? null : this._linkedRecords.get(item, index);
record = this._linkedRecords === null ? null : this._linkedRecords.get(itemTrackBy, index);
if (record !== null) {
// We have seen this before, we need to move it forward in the collection.
this._moveAfter(record, previousRecord, index);
} else {
// Never seen it, check evicted list.
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
if (record !== null) {
// It is an item which we have evicted earlier: reinsert it back into the list.
this._reinsertAfter(record, previousRecord, index);
} else {
// It is a new item: add it.
record = this._addAfter(new CollectionChangeRecord(item), previousRecord, index);
record =
this._addAfter(new CollectionChangeRecord(item, itemTrackBy), previousRecord, index);
}
}
return record;
Expand Down Expand Up @@ -248,15 +259,17 @@ export class DefaultIterableDiffer implements IterableDiffer {
*
* @internal
*/
_verifyReinsertion(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
_verifyReinsertion(record: CollectionChangeRecord, item: any, itemTrackBy: any,
index: number): CollectionChangeRecord {
var reinsertRecord: CollectionChangeRecord =
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
if (reinsertRecord !== null) {
record = this._reinsertAfter(reinsertRecord, record._prev, index);
} else if (record.currentIndex != index) {
record.currentIndex = index;
this._addToMoves(record, index);
}
record.item = item;
return record;
}

Expand Down Expand Up @@ -457,31 +470,20 @@ export class DefaultIterableDiffer implements IterableDiffer {
}

toString(): string {
var record: CollectionChangeRecord;

var list = [];
for (record = this._itHead; record !== null; record = record._next) {
list.push(record);
}
this.forEachItem((record) => list.push(record));

var previous = [];
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
previous.push(record);
}
this.forEachPreviousItem((record) => previous.push(record));

var additions = [];
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
additions.push(record);
}
this.forEachAddedItem((record) => additions.push(record));

var moves = [];
for (record = this._movesHead; record !== null; record = record._nextMoved) {
moves.push(record);
}
this.forEachMovedItem((record) => moves.push(record));

var removals = [];
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
removals.push(record);
}
this.forEachRemovedItem((record) => removals.push(record));

return "collection: " + list.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" +
Expand Down Expand Up @@ -512,7 +514,7 @@ export class CollectionChangeRecord {
/** @internal */
_nextMoved: CollectionChangeRecord = null;

constructor(public item: any) {}
constructor(public item: any, public trackById: any) {}

toString(): string {
return this.previousIndex === this.currentIndex ?
Expand Down Expand Up @@ -550,13 +552,13 @@ class _DuplicateItemRecordList {
}
}

// Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and
// Returns a CollectionChangeRecord having CollectionChangeRecord.trackById == trackById and
// CollectionChangeRecord.currentIndex >= afterIndex
get(item: any, afterIndex: number): CollectionChangeRecord {
get(trackById: any, afterIndex: number): CollectionChangeRecord {
var record: CollectionChangeRecord;
for (record = this._head; record !== null; record = record._nextDup) {
if ((afterIndex === null || afterIndex < record.currentIndex) &&
looseIdentical(record.item, item)) {
looseIdentical(record.trackById, trackById)) {
return record;
}
}
Expand Down Expand Up @@ -599,7 +601,7 @@ class _DuplicateMap {

put(record: CollectionChangeRecord) {
// todo(vicb) handle corner cases
var key = getMapKey(record.item);
var key = getMapKey(record.trackById);

var duplicates = this.map.get(key);
if (!isPresent(duplicates)) {
Expand All @@ -610,17 +612,17 @@ class _DuplicateMap {
}

/**
* Retrieve the `value` using key. Because the CollectionChangeRecord value maybe one which we
* Retrieve the `value` using key. Because the CollectionChangeRecord value may be one which we
* have already iterated over, we use the afterIndex to pretend it is not there.
*
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
* have any more `a`s needs to return the last `a` not the first or second.
*/
get(value: any, afterIndex: number = null): CollectionChangeRecord {
var key = getMapKey(value);
get(trackById: any, afterIndex: number = null): CollectionChangeRecord {
var key = getMapKey(trackById);

var recordList = this.map.get(key);
return isBlank(recordList) ? null : recordList.get(value, afterIndex);
return isBlank(recordList) ? null : recordList.get(trackById, afterIndex);
}

/**
Expand All @@ -629,7 +631,7 @@ class _DuplicateMap {
* The list of duplicates also is removed from the map if it gets empty.
*/
remove(record: CollectionChangeRecord): CollectionChangeRecord {
var key = getMapKey(record.item);
var key = getMapKey(record.trackById);
// todo(vicb)
// assert(this.map.containsKey(key));
var recordList: _DuplicateItemRecordList = this.map.get(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ export interface IterableDiffer {
onDestroy();
}

/**
* An optional function passed into {@link NgFor} that defines how to track
* items in an iterable (e.g. by index or id)
*/
export interface TrackByFn { (index: number, item: any): any; }


/**
* Provides a factory for {@link IterableDiffer}.
*/
export interface IterableDifferFactory {
supports(objects: any): boolean;
create(cdRef: ChangeDetectorRef): IterableDiffer;
create(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): IterableDiffer;
}

/**
Expand Down
Loading