When building Angular components, we often use data structures like arrays and hashtable to build a collection of objects. This enables us to use HTML templates to iterate the collection and display the object properties using the ngFor directive. It is here that we come across a behavior that seems to confuse some developers. In some cases, ngFor raises the only support iterables error when iterating over these collections. The question is why do we get this error?
To provide an answer, we must first talk about the differences between an array and a hashtable. Let’s first look at the following code snippets:
interface Vehicle {
id: number
year: number; make: string; model: string; } interface VehicleList { [index: number]: Vehicle; } inventoryArray:Vehicle[] = [ {id:0,year:2018,make:'nissan',model:'xterra'} ,{id:1,year:2018,make:'nissan',model:'altima'} ,{id:2,year:2018,make:'nissan',model:'maxima'} ]; inventoryList:VehicleList = { "0":{id:0,year:2018,make:'nissan',model:'xterra'} ,"1":{id:1,year:2018,make:'nissan',model:'altima'} ,"2":{id:2,year:2018,make:'nissan',model:'maxima' }; |
In the previous code, we first define the Vehicle interface. This is the object that we display on the template. We also define a VehicleList interface which provides us the ability to use a Hashtable with an number as key as we are using the id property.
Once the interfaces are defined, we can create collections with two different data structures. We need to look at this in detail as the differences may not be too clear. We first declare the inventoryArray which is of type Array of Vehicles (items in an array [] square brackets). We also create the inventoryList (items in an object notation {} brackets) which is an object that has keys matching the vehicle id.
Both of the collections have the same objects, but the way to iterate them is different. Let’s take a look at the concept a bit closer.
What are Iterables?
Iterable is a data structure that allows access to its elements in a sequential way by providing an iterator which acts as a pointer to the elements. This is supported by Arrays, so we can access its elements using a for...of (notice not a for...in) loop. Hashtable entries are accessible as object properties, so they are not iterable natively.
Angular ngFor uses the for...of implementation to iterate the elements. This is why when an object is used with that directive, the error “only supports binding to Iterables” is raised. We can see that by looking at the template implementation in which we use the ngFor directive with the component inventory property.
@Component({
selector: 'app', template: ` <table> <thead> <tr><th>Id</th><th>Year</th><th>Make</th><th>Model</th></tr> </thead> <tbody> <tr *ngFor="let car of inventory"> <td>{{car.id}}</td> <td>{{car.year}} </td> <td>{{car.make}} </td> <td> {{car.model}}</td> </tr> </tbody> </table> `, }) class HomeComponent {
...
} |
Not that we understad more about iterables and for...of loop, we can take a look at our code and identify areas where this problem can surface. If we work with object properties instead of arrays of objects, how can we address this problem without having to refactor a lot of code. Well, we can do this by a simple approach on the component code. Let’s review that solution.
Component Approach
The approach here is to transform the Hashtable data structure into an array. This can be done by using the Object constructor values method which basically does a for...in loop and returns the object property values without the keys. This essentially changes the data structure to an array which we can assign to inventory property that is used on the template to display the data.
this.inventoryList = {
"0":{id:0,year:2018,make:'nissan',model:'xterra'} ,"1":{id:1,year:2018,make:'nissan',model:'altima'} ,"2":{id:2,year:2018,make:'nissan',model:'maxima'} }; this.inventory = Object.values(this.inventoryList); |
See in Action
Conclusion
As we implement new solutions, we need to be mindful of the framework specifications and define our data structures in a way that is compliant with the framework. When we try to refactor code from previous frameworks like AngularJS, we need to identify some of the areas that can cause problems and refactor them tactically with minimum code changes.
Thanks for reading.