0

I have an Angular Service which sends a data service call and returns an array of objects containing multiple layers of data. I want to be able to filter thrhough that data and return only the object that contains a specific value.

        this.service.getRecords().pipe(
        map(item => item.filter(items => items.attributes['identifier'].value === '101723102')),
        tap(item => console.log(item)),

    ).subscribe();

The attributes[] has thosands of objects like the one stated below:

[20000]: {identifier:"1239213", something: "42142", something: "42142", something: "42142".....}

I am trying to filter and return the objects if the identifier is equal to something but am not sure how to access the value since it is nested and I cannot use dot notation. Current code returns error: Cannot read property 'identifier' of undefined

[EDIT]: This is the service I am using

export class DataViewerService {

_recordDetails: Observable<DataClass[]>;

// resource service contains code to retrieve data constructor(private _resourceService: ResourceService, ) { }

    getRecords() {
    return this._recordDetails = this._resourceService.search(CONSTANT, {})
        .pipe(map(extractRecords))
        .pipe(shareReplay()) as Observable<Data[]>;

}


function extractRecords(result) {
const records = [];
if (!isNullOrUndefined(result)) {
    result.forEach(r => {
        if (!isNullOrUndefined(r['payload'])) {
            records.push(r.payload);
        }
    });
}
return records;

}

[EDIT]: Items Object is as follows:

{
  attributes: [],
  descrtiption: "",
  name: "",
  label: ""
}

I need to access the objects within the attributes[] of filter which looks like
attributes: [
{
     identifier: "1243212",
     something: "",
...
}

This is the interface and the object returns is of type Data

export interface Data {
name: string;
label: string;
attributes: DataAttributes[];

}

interface DataAttributes {
    attributes: [
           identifier: string;
           something: string;
     ];
    something: string;
...
 
}

And the service returns the attributes[] which contains the values I want to filter

4
  • can you provide an example of items object ? Commented Jul 9, 2020 at 1:08
  • Hi @SupunDeSilva, thanks for the reply, I have edited the question to add a description of the items object Commented Jul 9, 2020 at 1:19
  • Your Interface model does not seem to align with the attribute array example Commented Jul 9, 2020 at 1:46
  • can you please add a cut-down version of a realistic example for Items Object having an entry or 2 inside attributes ? I added an answer relying on the model interface you have provided Commented Jul 9, 2020 at 1:59

4 Answers 4

1

It might work

map(item => item.filter(items => items.attributes['identifier'] && items.attributes['identifier'].value === '101723102')),

or

map(item => item.filter(items => item.attributes !== undefined && items.attributes['identifier'] != undefined && items.attributes['identifier'].value === '101723102'))

also it will be better if you edit DataAttributes like this:

interface DataAttributes {
    name: string;
    label: string;
    identifier?: any;
}
Sign up to request clarification or add additional context in comments.

4 Comments

Hi @fro, thanks for the reply. Unfortunately this still prompts the same type error: core.js:4002 ERROR TypeError: Cannot read property 'identifier' of undefined
@rkras, then try to use this: map(item => item.filter(items => items.attributes !== undefined && items.attributes['identifier'] !== undefined && items.attributes['identifier'].value === '101723102'))
No more type error but it seems to be returning the same object with 3000 nested objects, this filter should return 1 object as the identifier is unique. How would I return just that row of data in this method? Thanks in advance
Then you have to count items by identifier value and get object that count is only one.
0

Using New model interface as follows

export interface DataModel {
  name: string;
  label: string;
  attributes: object[];
}

Using a Mock Data as follows

{
    attributes: [
        { identifier: '1239213', something: 100 },
        { identifier: 'foo', someOtherThing: 100},
        { identifier: '101723102', description: "101723102"},
        { notIdentifier: '101723102', description: "101723102"}        
    ],
    name: "",
    label: ""
}

Using a Mock Data Service as follows

import { Injectable } from '@angular/core';
import { Subject, BehaviorSubject } from 'rxjs';
import { DataModel } from './models/data';

@Injectable()
export class DataService {

  private mockData : DataModel = {
    attributes: [
        { identifier: '1239213', something: 100 },
        { identifier: 'foo', someOtherThing: 100},
        { identifier: '101723102', description: "101723102"},
        { notIdentifier: '101723102', description: "101723102"}    
    ],
    name: "",
    label: ""
  }

  private sbj : BehaviorSubject<{}> = new BehaviorSubject<any>(this.mockData);
  constructor() { }

  getRecords() {
    return this.sbj;
  }
}

Do the following

import { Component, VERSION, OnInit } from '@angular/core';
import { DataService } from './data.service';
import { map, tap } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  name = 'Angular ' + VERSION.major;

  public filteredObjStr = ''
  constructor(private service: DataService) {

  }

  ngOnInit() {
    this.service.getRecords().pipe(
        map(item =>   {
          if (item['attributes'] == null) {
            return []
          }
          return item['attributes'].filter(x => x['identifier'] === '101723102')
        }),
        tap(filteredAttribs => 
        {
          this.filteredObjStr = JSON.stringify(filteredAttribs, null, 2);
          console.log('filteredAttribs : ', filteredAttribs);
        })

    ).subscribe();
  }

  public filter_changed(value) {
    console.log(value)
  }
}

Example - Check App console

https://stackblitz.com/edit/angular-ivy-uvxxjk

7 Comments

Thanks so much for the reply, this looks like it is close to what I am trying to achieve, however I am unable to use a mock object to pass straight through to the Behaviour Subject. There is code manipulating the data and the service I have written does retrieve data into the component when calling this.getRecords, I am just struggling to filter it as the identifier is nested in the attributes array... Any Idea how I may achieve this in the compoent using rxjs operators?
You do not need use the mock, All you have to do is use your getRecords method. I had to use a mock as I did not have your data source. Also if you can add a realistic example (cut down version) with actual content inside attributes for Items Object it will be a lot easier
Ah I see what you mean. Sorry a bit new to this game, I know the question is terrible. I have added the item object in an edit which shows the values. I am affraid with your solution I am getting an error of cannot read property 'filter' of undefined
Please see my latest edit of items object if you can, maybe that helps.
cannot read property 'filter' of undefined says the the property attributes does not not exist in your data that comes from the API. If you can add a cut-down version of the data that is returned from the API, I can help out. Having a null check might not fix your issue.
|
0

If you don't mind not having an algorithm for this you can rely on lodash for these kind of operations with objects in Javascript (not Angular per se).

First install lodash (npm install lodash).

Since you want to filter by a nested key, you should use filter method with a function as predicate.

import * as _ from 'lodash';

// let's asume a variable called attributes holds your array of objects
const filteredData = _.filter(attributes, function(item) { return item.value === 'something'; })

I don't know if I understand the structure of your data. You can read de docs for filter here.

Comments

0

It's very unclear what exactly you're trying to do. Assuming your goal is to take your Data objects and select certain DataAttributes objects that matches some name, then you could do something like this:

  1. map each Data item to its array of attributes
  2. flatten those arrays of attributes to a single stream of DataAttributes objects
  3. filter those DataAttributes objects by the desired name
const records: Observable<Data[]> = ...;
const selectAttribute = 'some name';
records.pipe(
  concatMap(records => records.map(({attributes}) => attributes)),
  concatAll(),
  filter(({name}) => name === selectAttribute)
);

https://stackblitz.com/edit/so-62805815

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.