Search code examples
jsonangularangular2-templateangular2-pipe

How to display and filter deep-nested JSON with angular


I have the following JSON object: http://pastebin.com/1TguvZXc

I am trying to access the following property for every model, year, and style: Model.Model[].Years[].Styles[].submodel.modelName

My attempt:

<div *ngFor="let model of models?.models">
 <div *ngFor="let submodel of model['years']['styles']">
  Test: {{ submodel.modelName}}
 </div>
</div>

This returns no errors however it doesn't display my data.

Additionally, I would like to use the unique pipe from ngx-pipes to filter out duplicate modelName.

How can I display unique values of submodel.modelName?

The Following code:

<div *ngFor="let model of models?.models | unique">
  <div *ngFor="let year of model['years']">
    <div *ngFor="let style of year['styles']">
        {{model.name}}, {{ style.submodel.body }}
  </div>
 </div>
</div>

produces the following output:

2 Series, Coupe 2 Series, Coupe 2 Series, Convertible 2 Series, Convertible 2 Series, Convertible 2 Series, Coupe 2 Series, Convertible 2 Series, Coupe 3 Series, Sedan 3 Series, Sedan 3 Series, Sedan 3 Series, Sedan 3 Series, Wagon 3 Series, Sedan 3 Series, Sedan 3 Series, Wagon 3 Series, Sedan 3 Series, Sedan 3 Series, Sedan 3 Series Gran Turismo, Hatchback 3 Series Gran Turismo, Hatchback 4 Series, Convertible 4 Series, Convertible 4 Series, Convertible 4 Series, Convertible 4 Series, Coupe 4 Series, Coupe 4 Series, Coupe 4 Series, Coupe 4 Series Gran Coupe, Sedan 4 Series Gran Coupe, Sedan 4 Series Gran Coupe, Sedan 4 Series Gran Coupe, Sedan 5 Series, Sedan 5 Series Gran Turismo, Hatchback 5 Series Gran Turismo, Hatchback 5 Series Gran Turismo, Hatchback 6 Series, Convertible 6 Series, Coupe 6 Series, Convertible 6 Series, Convertible 6 Series, Coupe 6 Series, Convertible 6 Series, Coupe 6 Series, Coupe 6 Series Gran Coupe, Sedan 6 Series Gran Coupe, Sedan 6 Series Gran Coupe, Sedan 6 Series Gran Coupe, Sedan 7 Series, Sedan 7 Series, Sedan 7 Series, Sedan 7 Series, Sedan 7 Series, Sedan 7 Series, Sedan ALPINA B6 Gran Coupe, Sedan ALPINA B7, Sedan M2, Coupe M3, Sedan M4, Convertible M4, Coupe M6, Convertible M6, Coupe M6 Gran Coupe, Sedan X1, SUV X1, SUV X3, SUV X3, SUV X3, SUV X3, SUV X4, SUV X4, SUV X5, SUV X5, SUV X5, SUV X5, SUV X5, SUV X5 M, SUV X6, SUV X6, SUV X6, SUV X6 M, SUV i3, Hatchback i3, Hatchback i3, Hatchback i8, Coupe

which is far from ideal. I would like to filter the data so it's unique, like this:

2 Series, Coupe 2 Series, Convertible 3 Series, Sedan 3 Series, Wagon 3 Series Gran Turismo, Hatchback 4 Series, Convertible 4 Series, Coupe 4 Series Gran Coupe, Sedan 5 Series, Sedan 5 Series Gran Turismo, Hatchback 6 Series, Convertible 6 Series, Coupe 6 Series Gran Coupe, Sedan 7 Series, Sedan ALPINA B6 Gran Coupe, Sedan ALPINA B7, Sedan M2, Coupe M3, Sedan M4, Convertible M4, Coupe M6, Convertible M6, Coupe M6 Gran Coupe, Sedan X1, SUV X3, SUV X4, SUV X5, SUV X5 M, SUV X6, SUV X6 M, SUV i3, Hatchback i8, Coupe


Solution

  • Your mental model looks correct (second line) but your ngFors do not. Here's pseudo code of what I'd expect given the shape of your JSON:

    // div ngFor="let model of models?.models"
    //  div ngFor="let year of model.years"
    //   div ngFor="let style of year.styles"
    //    Test: {{ style.submodel | json }}
    

    Looking at the shape of the data with a JSON formatter might help (example: http://jsonformatter.org/).

    Working example


    Edit: If you need to filter the array, one solution is a custom pipe. I updated my plnkr to include an example. I piped the array inside the ngFor directive to the pipe and used a hash map to filter the result. In production code, I'd expect you'd replace the internal of the createHashKey() function with a better implementation to classify unique examples.

    Template excerpt:

    <div *ngFor="let model of models?.models">
      <div *ngFor="let year of model.years">
        <div *ngFor="let style of (year.styles | myCustomPipe:'submodel')">
          Test: {{ style.submodel | json }}
        </div>
      </div>
    </div>
    

    Custom pipe:

    @Pipe({
      name: 'myCustomPipe'
    })
    export class MyCustomPipe implements PipeTransform {
    
      transform(value: any[], ...args: string[]): any[] {
    
        let hashMap = {};
        let filterKey = args[0];
    
        for (let v of value) {
          const hashKey = createHashKey(v, filterKey);
          hashMap[hashKey] = v;
        }
        return Object.values(hashMap);
      }
    
    }
    
    function createHashKey(obj: any, filterKey: string): string {
      // For demonstration purposes only:
      return JSON.stringify(obj.filterKey);
    }