fix(Common): allow null/undefined values for `NgForTrackBy`

Reverts a breaking change introduced in 2.4.1 by #13420
fixes #13641
This commit is contained in:
Victor Berchet 2017-01-03 15:14:30 -08:00 committed by Igor Minar
parent f822f9599c
commit f88cd2f22e
2 changed files with 27 additions and 10 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core'; import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
import {getTypeNameForDebugging} from '../facade/lang'; import {getTypeNameForDebugging} from '../facade/lang';
@ -91,9 +91,13 @@ export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any; @Input() ngForOf: any;
@Input() @Input()
set ngForTrackBy(fn: TrackByFn) { set ngForTrackBy(fn: TrackByFn) {
if (typeof fn !== 'function') { if (isDevMode() && fn != null && typeof fn !== 'function') {
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}. // TODO(vicb): use a log service once there is a public one available
See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`); if (<any>console && <any>console.warn) {
console.warn(
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
}
} }
this._trackByFn = fn; this._trackByFn = fn;
} }

View File

@ -294,14 +294,25 @@ export function main() {
})); }));
describe('track by', () => { describe('track by', () => {
it('should throw if trackBy is not a function', async(() => { it('should console.warn if trackBy is not a function', async(() => {
// TODO(vicb): expect a warning message when we have a proper log service
const template = const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`; `<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value"></template>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.componentInstance.value = 0;
fixture.detectChanges();
}));
getComponent().items = [{id: 1}, {id: 2}]; it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
expect(() => fixture.detectChanges()) // TODO(vicb): expect no warning message when we have a proper log service
.toThrowError(/trackBy must be a function, but received null/); const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value">{{ item }}</template>`;
fixture = createTestComponent(template);
fixture.componentInstance.items = ['a', 'b', 'c'];
fixture.componentInstance.value = null;
detectChangesAndExpectText('abc');
fixture.componentInstance.value = undefined;
detectChangesAndExpectText('abc');
})); }));
it('should set the context to the component instance', async(() => { it('should set the context to the component instance', async(() => {
@ -343,6 +354,7 @@ export function main() {
getComponent().items = [{'id': 'a', 'color': 'red'}]; getComponent().items = [{'id': 'a', 'color': 'red'}];
detectChangesAndExpectText('red'); detectChangesAndExpectText('red');
})); }));
it('should move items around and keep them updated ', async(() => { it('should move items around and keep them updated ', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`; `<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
@ -378,6 +390,7 @@ class Foo {
@Component({selector: 'test-cmp', template: ''}) @Component({selector: 'test-cmp', template: ''})
class TestComponent { class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>; @ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
value: any;
items: any[] = [1, 2]; items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; } trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; } trackByIndex(index: number, item: any): number { return index; }