From 49527ab4950317b7ecdeb23d2710273330686d71 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Wed, 9 Mar 2016 12:05:15 -0800 Subject: [PATCH] fix(ngFor): give more instructive error when binding to non-iterable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, you'd get an error like: ``` EXCEPTION: Cannot find a differ supporting object ‘[object Object]’ in [users in UsersCmp@2:14] ``` Now, you get: ``` EXCEPTION: Cannot find a differ supporting object ‘[object Object]’ of type 'Object'. Did you mean to bind ngFor to an Array? in [users in UsersCmp@2:14] ``` --- .../angular2/src/common/directives/ng_for.ts | 10 ++++++++-- .../differs/iterable_differs.ts | 5 +++-- modules/angular2/src/facade/lang.dart | 2 +- modules/angular2/src/facade/lang.ts | 5 ++++- .../test/common/directives/ng_for_spec.ts | 19 +++++++++++++++++++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/modules/angular2/src/common/directives/ng_for.ts b/modules/angular2/src/common/directives/ng_for.ts index b3d315b746..4fd502fee3 100644 --- a/modules/angular2/src/common/directives/ng_for.ts +++ b/modules/angular2/src/common/directives/ng_for.ts @@ -9,11 +9,12 @@ import { EmbeddedViewRef, TrackByFn } from 'angular2/core'; -import {isPresent, isBlank} from 'angular2/src/facade/lang'; +import {isPresent, isBlank, stringify, getTypeNameForDebugging} from 'angular2/src/facade/lang'; import { DefaultIterableDiffer, CollectionChangeRecord } from "../../core/change_detection/differs/default_iterable_differ"; +import {BaseException} from "../../facade/exceptions"; /** * The `NgFor` directive instantiates a template once per item from an iterable. The context for @@ -77,7 +78,12 @@ 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._ngForTrackBy); + try { + this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy); + } catch (e) { + throw new BaseException( + `Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`); + } } } diff --git a/modules/angular2/src/core/change_detection/differs/iterable_differs.ts b/modules/angular2/src/core/change_detection/differs/iterable_differs.ts index fd3eb148d3..8deaf7acc5 100644 --- a/modules/angular2/src/core/change_detection/differs/iterable_differs.ts +++ b/modules/angular2/src/core/change_detection/differs/iterable_differs.ts @@ -1,4 +1,4 @@ -import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang'; +import {isBlank, isPresent, CONST, getTypeNameForDebugging} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import {ListWrapper} from 'angular2/src/facade/collection'; import {ChangeDetectorRef} from '../change_detector_ref'; @@ -86,7 +86,8 @@ export class IterableDiffers { if (isPresent(factory)) { return factory; } else { - throw new BaseException(`Cannot find a differ supporting object '${iterable}'`); + throw new BaseException( + `Cannot find a differ supporting object '${iterable}' of type '${getTypeNameForDebugging(iterable)}'`); } } } diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index 41f7a8dd58..cafb9fd257 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -5,7 +5,7 @@ import 'dart:math' as math; import 'dart:convert' as convert; import 'dart:async' show Future, Zone; -String getTypeNameForDebugging(Type type) => type.toString(); +String getTypeNameForDebugging(Object type) => type.toString(); class Math { static final _random = new math.Random(); diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index edfb50dc5b..b0d0909575 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -62,7 +62,10 @@ export interface Type extends Function {} export interface ConcreteType extends Type { new (...args): any; } export function getTypeNameForDebugging(type: Type): string { - return type['name']; + if (type['name']) { + return type['name']; + } + return typeof type; } diff --git a/modules/angular2/test/common/directives/ng_for_spec.ts b/modules/angular2/test/common/directives/ng_for_spec.ts index dd8efbe40f..12eecb4e2d 100644 --- a/modules/angular2/test/common/directives/ng_for_spec.ts +++ b/modules/angular2/test/common/directives/ng_for_spec.ts @@ -14,6 +14,7 @@ import { } from 'angular2/testing_internal'; import {ListWrapper} from 'angular2/src/facade/collection'; +import {IS_DART} from 'angular2/src/facade/lang'; import {Component, TemplateRef, ContentChild} from 'angular2/core'; import {NgFor} from 'angular2/src/common/directives/ng_for'; import {NgIf} from 'angular2/src/common/directives/ng_if'; @@ -158,6 +159,24 @@ export function main() { }); })); + if (!IS_DART) { + it('should throw on non-iterable ref and suggest using an array', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideTemplate(TestComponent, TEMPLATE) + .createAsync(TestComponent) + .then((fixture) => { + fixture.debugElement.componentInstance.items = 'whaaa'; + try { + fixture.detectChanges() + } catch (e) { + expect(e.message).toContain( + `Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays.`); + async.done(); + } + }); + })); + } + it('should throw on ref changing to string', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideTemplate(TestComponent, TEMPLATE)