angular: Misleading error message "Cannot find a differ supporting object '[object Object]'"

This error message gives us no clue as to what is wrong or what to do.

Repro

Misko and I experienced this while refactoring a Component from one using the async pipe to one using an array.

The async pipe version:

    heroes: any;

    // <li *ngFor="#hero of heroes | async">
    this.heroes = this._heroService.getHeroes();

The refactored version:

    // <li *ngFor="#hero of heroes">
    this.heroes = this._heroService.getHeroes()
                      .subscribe(heroes => this.heroes = heroes);

The exception was:

EXCEPTION: Cannot find a differ supporting object '[object Object]' in [heroes  in TohComponent@4:8]

Discussion

Do you see our mistake … the one that threw the error? We didn’t … for about 5 head-scratching minutes (see below).

The developer has no idea what a “differ” is, what it is that the “differ” can’t support, what might cause this, or what to do.

The “[heroes in TohComponent@4:8]” didn’t help because line 4 of that component file is an import statement in the TS and nowhere near the template or the function in the transpiled JS. Something called “heroes” appears all over the place.

FWIW, I’ve received this error several times and I think the cause (bad list) was always the same.

Our mistake: after refactoring we were still assigning an observable to this.heroes.

Recommended solution

Improve error message. Link to problem/solutions page?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 63
  • Comments: 50 (11 by maintainers)

Commits related to this issue

Most upvoted comments

I just ran into the same issue. I’m not sure if the recommended solution will work for my case.

I’m using an observer to pull a more complicated data structure from an external source (ie an observer on a service that persists the result of a GET request).

The part I’m trying to iterate over is a subset of the external source.

Data:

"vitae": {
  {
    "location": {
      "address": "2445 Torrejon Pl",
      "city": "San Diego",
      "region": "CA",
      "code": "92009",
      "country": "US"
    },
    "social": [
      {
        "label": "",
        "network": "GitHub",
        "user": "evanplaice",
        "url": "https://github.com/evanplaice"
      },
      {
        "label": "",
        "network": "Twitter",
        "user": "@evanplaice",
        "url": "https://twitter.com/evanplaice"
      },
      {
        "label": "",
        "network": "StackOverflow",
        "user": "evanplaice",
        "url": "http://stackoverflow.com/users/290340/evan-plaice"
      },
      {
        "label": "",
        "network": "HackerNews",
        "user": "evanplaice",
        "url": "https://news.ycombinator.com/user?id=EvanPlaice"
      }
    ],
...

The source has more data but the point is, I want to extract the social array and iterate over it in my template.

<ul *ngFor="#profile of vitae.social">
  <li>{{ profile.network }}: <a href="{{ profile.url }}">{{ profile.user }}</a></li>
</ul>

Angular can’t seem to recognize that the incoming data is nothing more than an array of objects. To workaround, I created a hack that just creates a new array from the incoming array.

hack(val) {
  return Array.from(val);
}
<ul *ngFor="#profile of hack(vitae.social)">
  <li>{{ profile.network }}: <a href="{{ profile.url }}">{{ profile.user }}</a></li>
</ul>

The funny thing is, if I log the data before and after the hack it’s virtually identical.

Using:

hack(val) {
  console.log('Before:');
  console.log(val);
  val = Array.from(val);
  console.log('After:');
  console.log(val);
  return val;
}

Result: screen shot 2016-01-13 at 12 40 04 pm

imgres

@wardbell in your example, you’re assigning a Subscription to the this.heroes property, not an Observable. Typescript should catch this, though I agree the error message could be better. The only thing we can really say is “you’re not iterating over an array, you should be”?

There’s a lot of mixed information going on here, let me see if I can clear it up.

First, pretty much anything that is not a primitive value (string, boolean, number) in Javascript is an Object. This includes Promises, Observables, Arrays, etc - they all are subtypes of Object.

An Iterable in javascript is, in the simplest terms, a “collection” - it’s a data structure that allows iteration in an ordered manner. Arrays are the most common example of this. All arrays are iterable, but plain objects are not - importantly, the ecmascript specification does not guarantee the order of items in an Object - whereas an Array does guarantee order.

The IterableDiffer you see in the error message is an angular mechanism that compares two collections (iterables) and returns:

  • objects added to the collection
  • objects removed from the collection
  • objects that have changed their position in the collection

We do this to enable, for example - animations - we can tell when an item has moved from position 3 to position 0 in an array, so it can be appropriately animated:

//psuedo code!
//given two arrays:
let a = [1,2,3];
let b = [3,2,4];
iterableDiffer.create(a).diff(b) // 1 is removed, 4 is added, 2 and 3 are moved

Part of the reason (the other is performance) we don’t support objects in ngFor/differs is that this mechanism doesn’t work for objects - consider:

var foo = {};
foo.a = 1;
foo.b = 2;
Object.keys(foo) // [ 'a', 'b' ]
delete foo.a;
foo.a = 1;
Object.keys(foo) // [ 'b', 'a' ]

Objects have no concept of order - typically the keys are returned in insertion order (as above) - and so ngFor’ing over them doesn’t make sense!

You could, of course, implement your own objectToKeys pipe, but then you run into ordering problems, and thus you have to maintain the sort order yourself somehow - there are a lot of edge cases here that might impact performance so we leave it up to the developer.

Regarding @evanplaice’s example above - it’s important to understand how javascript handles values and references:

//imagine you have an object of objects:
this.foos = {
  a: { name: 'fooA'}
  b: { name: 'fooA'}
}
//turn this into an array
this.listOfFoos = Object.keys(this.foos).map(key => this.foos[key]) // [{name: 'fooA'}, {name: 'fooB'}];

//objects are copied by reference:
this.foos.a === this.listOfFoos[0] //true -> they are the same reference
this.foos.a.name = 'fooA!'
console.log(this.listOfFoos[0]) // { name: 'fooA!' }

So simply moving an object from one structure to another does not “break” the reference - they would continue to update. See plunker: https://plnkr.co/edit/34dPSfL0bIDfGkdySExX?p=preview

The same does not hold true for primitive values:

//imagine you have an object of numbers:
this.foos = {
  a: 1
  b: 2
}
//turn this into an array
this.listOfFoos = Object.keys(this.foos).map(key => this.foos[key]) // [1, 2];

//values are simply copied
this.foos.a === this.listOfFoos[0] //true -> they are the same value
this.foos.a = 3 //change the value
console.log(this.listOfFoos[0]) //still 1

So with primitive values, they are “disconnected”

All of the above deals with simple synchronous values. The second round of confusion here has to do with Promises and Observables. You can deal with async values in Angular in the following ways:

By simply .then() or .subscribe() to a Promise or Observable, respectively, and assigning the output value:

somePromiseThatGetsAnArrayOfFoos().then(foos => {
  this.foos = foos;
});

someObservableOfArrayOfBars.subscribe(bars => {
  this.bars = bars; 
});

To render these, you can simply ngFor over them:

<ul>
  <li *ngFor="#foo of foos">{{foo.name}}</li>
</ul>
<ul>
  <li *ngFor="#bar of bars">{{bar.name}}</li>
</ul>

What you cannot do here is *ngFor over a sub-property of an async delivered value - because when the view is initially rendered, the parent property is undefined.

//this.foo is undefined here!
somePromiseThatGetsAnObjectWithNestedBars().then(foo => {
 //  { bars: [ 1, 2, 3] }
  this.foo = foo;
});

So doing

<ul>
  <li *ngFor="#bar of foo.bars">{{bar}}</li>
</ul>

Will throw an error: EXCEPTION: TypeError: Cannot read property 'bars' of undefined in [foo.bars] because foo is undefined on initial render.

This is exactly the same behavior you see in regular Javascript:

var foo;

somePromise.then(fooData => {
  foo = fooData;
});
console.log(foo.bars) //Uncaught TypeError: Cannot read property 'bars' of undefined(…)

The simple fix is to use the ? operator:

<ul>
  <li *ngFor="#bar of foo?.bars">{{bar}}</li>
</ul>

The other way of handing async values is to use the async pipe:

this.foos = somePromiseThatReturnsFoos();
this.bars = someObservableOfBars;
<ul>
  <li *ngFor="#foo of foos | async">{{foo.name}}</li>
</ul>
<ul>
  <li *ngFor="#bar of bars | async">{{bar.name}}</li>
</ul>

Note here that we’re assigning the Promise / Observable to the class, not the result of .then() / .subscribe()

To handle nested values, you can use the same ? operator:

<ul>
  <li *ngFor="#bar of (foo | async)?.bars">{{bar}}</li>
</ul>

The same rules apply here with copy-by-value and copy-by-reference - so you could, in theory, store the returned objects in a service, return them in a promise, and two-way binding will work, as long as you maintain the references.

Summary:

  • collections of things should almost always be kept in arrays if order is important.
  • be sure to understand the difference between assigning an array to this and assigning a Promise or Observable of values to this
  • Databinding works fine as long as you aware of the semantics of javascript references.
  • Don’t attempt to access properties of undefined values that may be delivered at a later time.

Here’s the hack (endearingly named DerpPipe) that can be used to workaround the issue.

import { Pipe } from 'angular2/core';

/*
  # Description:

  Repackages an array subset as a new array.

  **Reasoning:**

  Angular2's change checker freaks out when you ngFor an array that's a subset
    of a larger data structure.

  # Usage:
  ``
  <div *ng-for="#value of arrayOfObjects | derp"> </div>
  ``
*/
@Pipe({ name: 'derp' })
export class DerpPipe {
  transform (value, args) {
    return Array.from(value);
  }
}

So, I think that still assigning an observable to this.heroes after the refactor, is just one of those dumb things we developers sometimes run into. I don’t think there’s anything that Angular can help with here.

I DO agree, that this error message is very unhelpful as @wardbell pointed out. It’s not clear what a differ is, it’s not clear what the differ can’t support. We should definitely improve this error message with something more useful.

I’m sorry for a probably uninformed comment, I’m very new to Angular 2. And I just came over this error with the code from the official Angular 2 tutorial that threw me a wonderful EXCEPTION: Cannot find a differ supporting object '[object Object]' in [heroes in AppComponent@4:16] (!!).

…but aren’t you talking about solving this at the wrong level? Should’n the actual mycrosyntax #item of items be made “smart enough” to just be able to iterate over “almost anything” (like be smart enough to handle Array, Promise<Array>, Object, Promise<Object> etc.) automagically even at the cost of maybe increasing rendering time by even ~20% or stmh? Should a developer actually need to care what he/she is iterating over when writing a template?


EDIT+: my bad, I actually had an error in my code. But this is a very confusing error message indeed! (And I still think that ngFor is the thing that should “behave smarter” here and try and to what “probably is the right thing” regardless of what’s given to it…)

I came to this issue from the opposite end, having my Service class with a method of getNewPlans() { return this.http.get('plans.json').map(res => res.json()); } my Component class with a method of ngOnInit() { Promise.resolve(this._newPlanService.getNewPlans()).then(newplans => this.newplans = newplans); } and then discovering I need to pipe async in my View template for it to stop giving that error about a differ supporting object <div *ngFor="#newplan of newplans"> to <div *ngFor="#newplan of newplans | async">

@webtrix I think this is a fundamental weakness of the Angular2 differ that needs to be identified. It doesn’t just apply to iterating over object values. It applies to any case where a dev tries to iterate over a property of a larger data structure.

To illustrate, imagine I have my resume stored in a resume.json format and want to load the data directly into a web template.

Example:

{
  "location": {},
  "employment": {},
  "projects": [],
  "skills": {},
}

So, I create a service that handles the resume data. Including a model, method to fetch the resume via HTTP, and an observable to provide access to components that inject the service.

There are 2 different approaches to managing data.


Approach 1 - Use one model to represent the larger data structure:

This is by far the easier approach. Specifying a basic model that represents the data as well as the ability to initialize to empty values (ie to avoid variable uninitialized errors in the template) is pretty easy and straightforward.

export class ResumeModel {
  location = {};
  employment = {};
  projects = [];
  skills = {};

  constructor (obj) {
    for (var prop in obj) this[prop] = obj[prop];
  }
}

The service only requires one observable to represent the structure, so wiring it up to a component is a piece of cake.

Unfortunately, when you write a ngFor to iterate over resume.projects the differ is going to throw the error described above because the differ doesn’t know how to handle updates on a subset of a larger data structure.

That’s where the DerpPipe comes in. It returns a new array using the resume.projects array contents, effectively detaching it from the larger resume object. Unfortunately, a side effect of that change is that any updates to the template array data won’t be reflected in resume.projects.

Approach 2 - Split the larger data structure into many models:

This would be the ‘preferred’ solution. Break the larger data structure into smaller parts.

  • LocationModel
  • EmploymentModel
  • ProjectsModel
  • SkillsModel

In addition to the models, the service will need to be modified to provide get/set-ers as well as observables for each structure.

I haven’t tested this approach but it should work without breaking the differ. It’s also the ideal approach if you plan to modify/update the data because the dirty checker is focused only on the subset of the overall data that changes.


Why wouldn’t one always use Approach 2?

Despite Approach 1 essentially being a hack, it takes considerably less engineering effort and complexity to implement. Especially, if you include unit and e2e testing.

Approach 1 is not ‘safe’ in regards to how Angular2 works. But… In cases where the data is used in a read-only manner, the time/engineering savings are a huge win for productivity.

You could probably rename DerpPipe to ReadOnly pipe (ie and include support for other data types) if you wanted to treat it like less hack and more solution.

i have the folowing working but can’t remember why i had put select:‘search’

<div *ngFor="let song of songs | async| select :'search'"

songs: Observable<Song[]>;``

cant find the documentation of select pipe. search is class method that update songs

@robwormwald Thos os probably a dumb question.

What of an app pulls data from a remote API as a large nested data structure. Can nested list values be iterated without triggering an update on sibling values – assuming the parent object reference isn’t updated?

Is ngrx-store a better fit for such a use case?

@webtrix That’ll work but you’re still going to face the same problem. Piping the data into a new data structure breaks you ability to update the source data.

It’s effectively breaking the link to the source data. Not a big deal if you only intend to use the data in a read-only manner. It may create a very hard-to-fix bug if you do plan to write the data back to the source object.

Unfortunately, this is the borderline where the Angular2 dirty checker abstraction leaks. Any dev that does a considerable amount of data manipulation will eventually run into this issue. I’m not saying it’s something that should be fixed (even if it can), it’s just an important distinction to keep in mind.

Bumping as I am experiencing the same issue.

@m1sh2 Yes. Sorry, I forgot to add the code block.

The ideal solution would to be to change prop4 to an array. If that’s not possible then DerpPipe may be the only option.

The unnecessary change detection cycling will occur because DerpPipe creates a new Array object (and therefore a new reference) every time it’s read. If all of the incoming data on your component (and it’s subtree) is from observables you could modify the ChangeDetectionStrategy to avoid the unnecessary cycling.

I haven’t used ngrx yet so I’m only remotely familiar with it. @robwormwald is the dev of ngrx so I’d follow his advice above.

@evanplaice @salibhai1 I updated it a little bit, for async:

//https://github.com/angular/angular/issues/6392
import { Pipe, PipeTransform } from '@angular/core'

/*
  # Description:

  Repackages an array subset as a new array.

  **Reasoning:**

  Angular2's change checker freaks out when you ngFor an array that's a subset
    of a larger data structure.

  # Usage:
  ``
  <div *ng-for="#value of arrayOfObjects | derp"> </div>
  ``
*/
@Pipe({
  name: 'derp',
  pure: false
})
export class DerpPipe implements PipeTransform {
  private result: Array<any>

  transform(value, args) {
    this.result = []
    value.subscribe(values => {
      if (Array.isArray(values))
        this.result = Array.from(values)
      else
        this.result = [values]
    })
    return this.result
  }
}