0

I am using Angular8, and have the following code below.

Questions:

  1. Why in the component does the Observable only have one item (should have four).
  2. In the html, why cant I iterate over the object? I thought it is an Array.

Service:

private listProductsJSON: any;
private listProducts$: BehaviorSubject<Product[]>;

public getItems(url: string): Observable<Product[]> {
  this.listProducts$ = <BehaviorSubject<Product[]>> new BehaviorSubject({});
  this.listProductsJSON = require('/Users/richardmarais/Development/examples/angular-menu-routes/menu-routes-app/src/app/model/products.json');  // gets some hard coded json with 4 rows
  Object.keys(this.listProductsJSON.products).map(key => {
    //console.log(this.listProductsJSON.products[key]); // logs 4 rows
    this.listProducts$.next(this.listProductsJSON.products[key]);
  });

  return this.listProducts$.asObservable();
}

Component:

private productsData: Product[];

ngOnInit() {
    this._productService.getItems(this.dataService.serviceData.URL).subscribe((products: Product[]) => {
        console.log(products);  // only logs 1 row        
        this.productsData = products;
    });
}

html:

    <ul>
      <div *ngFor="let product of productsData">
        <li>{{ product }}</li>
      </div>
    </ul>

Console log: (should be four items, but only one)

{status: "validated", name: "lions", description: "", id: "yXfhYcDpzYmyWGYptNme", order: 4}

Error:

ProductsComponent.html:5 ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'lions'. NgFor only supports binding to Iterables such as Arrays.
    at NgForOf.ngDoCheck (common.js:4841)
    at checkAndUpdateDirectiveInline (core.js:31912)
    at checkAndUpdateNodeInline (core.js:44366)
    at checkAndUpdateNode (core.js:44305)
    at debugCheckAndUpdateNode (core.js:45327)
    at debugCheckDirectivesFn (core.js:45270)
    at Object.eval [as updateDirectives] (ProductsComponent.html:5)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:45258)
    at checkAndUpdateView (core.js:44270)
    at callViewAction (core.js:44636)
View_ProductsComponent_1 @ ProductsComponent.html:5
logError @ core.js:45545
handleError @ core.js:6066
(anonymous) @ core.js:41057
invoke @ zone-evergreen.js:359
run @ zone-evergreen.js:124
runOutsideAngular @ core.js:39571
tick @ core.js:41054
(anonymous) @ core.js:40892
invoke @ zone-evergreen.js:359
onInvoke @ core.js:39698
invoke @ zone-evergreen.js:358
run @ zone-evergreen.js:124
run @ core.js:39510
next @ core.js:40889
schedulerFn @ core.js:35335
__tryOrUnsub @ Subscriber.js:185
next @ Subscriber.js:124
_next @ Subscriber.js:72
next @ Subscriber.js:49
next @ Subject.js:39
emit @ core.js:35297
checkStable @ core.js:39641
onHasTask @ core.js:39718
hasTask @ zone-evergreen.js:411
_updateTaskCount @ zone-evergreen.js:431
_updateTaskCount @ zone-evergreen.js:264
runTask @ zone-evergreen.js:185
drainMicroTaskQueue @ zone-evergreen.js:559
invokeTask @ zone-evergreen.js:469
invokeTask @ zone-evergreen.js:1603
globalZoneAwareCallback @ zone-evergreen.js:1629
Show 2 more frames
ProductsComponent.html:5 ERROR CONTEXT DebugContext_ {view: {…}, nodeIndex: 5, nodeDef: {…}, elDef: {…}, elView: {…}}

product.ts

export class Product {
    status:      string;
    name:        string;
    description: string;
    id:          string;
    order:       number;
 }

products.json

{
    "products": {
        "drivers": {
            "status": "validated",
            "name": "drivers",
            "description": "",
            "id": "5rI6n3O2T2z3uKLqbEGj",
            "order": 1
        },
        "zombies": {
            "status": "validated",
            "name": "zombies",
            "description": "active",
            "id": "e6T8tOrbUQH1qyo2ECfj",
            "order": 2
        },
        "passengers": {
            "status": "validated",
            "name": "passengers",
            "description": "",
            "id": "tP4SkWIYRubHN6mCZRA5",
            "order": 3
        },
        "lions": {
            "status": "validated",
            "name": "lions",
            "description": "",
            "id": "yXfhYcDpzYmyWGYptNme",
            "order": 4
        }
    }
}
Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
Richard
  • 8,193
  • 28
  • 107
  • 228
  • array =/= object, check this, how to iterate object properties https://stackoverflow.com/a/16735184/2796268 – Progs Sep 02 '19 at 19:53
  • @B.J. A.A. Thank you for the info. As far as I can see, it mentions the use of `hasOwnProperty`. But I am not sure where this will help me? – Richard Sep 02 '19 at 20:03
  • Possible duplicate of [Iterate over object in Angular](https://stackoverflow.com/questions/31490713/iterate-over-object-in-angular) – ctaleck Sep 02 '19 at 20:04
  • @Richard `hasOwnProperty` if used to check if the object actually has the property, but check the original questions and the other answers – Progs Sep 03 '19 at 19:19

3 Answers3

1

You should use keyvalue pipe to iterate over the object.

For example

Html

 <ul>
  <div *ngFor="let product of productsData | keyvalue">
    <li>{{ product.value.status }}</li>
  </div>
</ul>
Stefan
  • 1,431
  • 2
  • 17
  • 33
0

This is your JSON:

{
    "products": {
        "drivers": {
        }
        ... (3 more objects)
     }
}

As you can see here, you have made an object that has a value of "products". If you were to place this into a variable you could access it this way, object.products. The "products" object has 4 properties, drivers being one of them (object.products.drivers). You're expecting an array with 4 objects.

When you attempt to cast the json into the typescript object here: (products: Product[]) it silently (not sure if there is logging for this) fails to find an array but instead places the "products" object into an array.

To fix this, you need to fix the JSON ->

{
    "products": [ <- array of objects, not an object with properties
        "drivers": {
        }
     ]
}

Why did this work?

this.productsData = Object.values(products).sort((a,b)=>b.order-a.order);

Object.values(), takes all the values from the "products" object you gave it and places it into an iterable array.

Phil
  • 10,948
  • 17
  • 69
  • 101
  • thank you for your feedback. The `Object.values(products).sort((a,b)=>b.order-a.order)` didn't work, it just split it into an array with the individual product items. – Richard Sep 02 '19 at 20:11
  • Unfortunately I cannot change the JSON, because this is what is coming back from a server that I have no control over. – Richard Sep 02 '19 at 20:11
0

products is not an array, the JSON is a nested Object

Also you should be using an interface instead of export class Product

   export interface Drivers {
        status: string;
        name: string;
        description: string;
        id: string;
        order: number;
    }

    export interface Zombies {
        status: string;
        name: string;
        description: string;
        id: string;
        order: number;
    }

    export interface Passengers {
        status: string;
        name: string;
        description: string;
        id: string;
        order: number;
    }

    export interface Lions {
        status: string;
        name: string;
        description: string;
        id: string;
        order: number;
    }

    export interface Products {
        drivers: Drivers;
        zombies: Zombies;
        passengers: Passengers;
        lions: Lions;
    }

    export interface ProductRootObject {
        products: Products;
    }
dota2pro
  • 7,220
  • 7
  • 44
  • 79