How to restructure, store and reuse data retrieved from HTTP call using interfaces

Good day,

I have the following:

this.profileService.fetchUserInfo()
    .subscribe(resData => {
      this.uProfile = resData;
      console.log('ResData: ', resData);
    });

the fetchUserInfo() method returns data from http call and is stored in:

public uProfile = [];

if I do the following in html file, it does show me all the option3 values:

<ul *ngFor="let item of uProfile">
    <li>{{ item.option3}}</li>
</ul>

when I console.log resData, i can see all the results as follow:

0: {option1: "1", option2: "1", option3: "venue", option4: "hall"}
1: {option1: "2", option2: "1", option3: "location", option4: "city"}
3: {option1: "3", option2: "1", option3: "capacity", option4: "500"}
4: {option1: "4", option2: "1", option3: "bookings", option4: "required"}
5: {option1: "5", option2: "1", option3: "manager", option4: "yes"}

How would I filter or get the value of one option based on another, ie:

I would like to get the VALUE of option4 WHERE option3 = bookings.

I have tried using .map & .filter… but to no avail.

Thank you in advance for your help!

How about next:

this.uProfile.filter(x => x.option3 === 'bookings');

this should retreive array of uProfiles where option3 is bookings. On the other hand you can use:

this.uProfile.find(x => x.option3 === 'bookings');

to get the first element of uProfiles that matches the condition.

Thank you very much! It did now only have that one set which is great.

on my html file I can get the value by simply doing:

        <ul *ngFor="let item of uProfile">
          <li>{{ item.option4}}</li>
        </ul>

but how do I get only the value of option 4 in the .ts file?

console.log(this.uProfile['option4']);

does not work.
Thank you again for your guidance!

I’m not yet convinced the thread title accurately reflects the fundamental desire here.

@Luzaan: especially given your comment above mine here, isn’t what you really want a more usable business-level object? I think this is a dereliction of duty on the part of profileService, and would change it to return something much more usable. A major point of services like this is to convert data into a format that is as easy for consumers (i.e. pages and components) to consume. Pages shouldn’t ever see data that is so unwieldy. Can you start by defining what the page wants to work with? Something like:

interface Venue {
  flavor: "hall" | "club" | "arena" | "church";
  location: "city" | "countryside" | "mountain" | "beach";
  capacity: number;
  bookings: "required" | "available" | "none";
  manager: boolean;
}

Thank you very much.

However… I am trying to get a grasp on these concepts… and struggling somewhat…

I have re-written much to try and simplify, what I have so far:

If I understand correctly, the interface is merely a structure that the data has to follow, but does not contain the data directly.

Upon your suggestion @rapropos, I tried to also move everything to the service.
The new service is called: FetchServiceService (I forgot Ionic added the Service text already :rofl: )
so my service code starts with defining the interface - the way I want the data in the end after formatting:

export interface CustProfile {
  venue: string;
  location: string;
  capacity: number;
  bookings: string;
  manager: string;
}

and the rest of the service code:

@Injectable({
  providedIn: 'root'
})
export class FetchServiceService {

  public custProfile: CustProfile[];

  constructor(private http: HttpClient) { }
  postData: any;

  fetchProfileInfo() {

    this.postData = {
      userId: '1',
    };

    return this.http.post<CustProfile[]>(`${fetchUrl}dummyData`, this.postData)
    .pipe(map(resData => {
      // const userProfile = [];
      console.log(resData);

      for (const prop in resData) {
        if (resData.hasOwnProperty(prop)) {
          console.log('FILTERED: ', resData[prop].manager);
        }
      }
    }));

  }
}

on the home page where I am testing I am just calling the method:

ngOnInit() {
    this.fetchService.fetchProfileInfo().subscribe();
  }

so in the service file, my first console log returns:

(5) [{…}, {…}, {…}, {…}, {…}]
0: {id: "1", option1: "1", option2: "1", option3: "venue", option4: "hall"}
1: {id: "2", option1: "2", option2: "1", option3: "location", option4: "city"}
2: {id: "3", option1: "3", option2: "1", option3: "capacity", option4: "500"}
3: {id: "4", option1: "4", option2: "1", option3: "bookings", option4: "required"}
4: {id: "5", option1: "5", option2: "1", option3: "manager", option4: "yes"}
length: 5
__proto__: Array(0)

and the second console log returns:

FILTERED:  undefined

Can you please help me understand:

  1. How do I take this fetchedData and using the interface assign it to a new object that can be used in the correct structure in the project?

  2. How do I access this data from let’s say the home.page.ts file?

Thank you very much, I REALLY do appreciate your help!

PS> I changed the title to try to be more relevant to the content.

1 Like

Yes, that’s a decent way to think of it.

There are still some outstanding concerns about what the other option* fields mean and what multiple venues look like (if that happens), but let’s assume there’s only one venue per response.

export interface CustProfile {
  venue: string;
  location: string;
  capacity: number;
  bookings: string;
  manager: string;
  // NOTE 1: remember next line
  [ k: string ]: string | number;
}

// this is what comes over the wire
interface WiredCustProfile {
  option3: string;
  option4: string;
}

fetchProfileInfo(): Observable<CustProfile> {
  return this.http.post<WiredCustProfile[]>(`${fetchUrl}dummyData`, this.postData).pipe(map(resData => {
    // cast unnecessary if CustProfile fields are all optional
    let rv = {} as CustProfile;
    for (let f of resData) {
      // enabling this assignment is why NOTE1 exists
      rv[f.option3] = f.option4;
    }
    // unary plus my preferred way of converting strings to numbers
    // capacity comes over the wire as a a string
    rv.capacity = +rv.capacity;
    return rv;
  });
}

How do I access this data from let’s say the home.page.ts file?

class HomePage {
  profile?: CustProfile;
  constructor(private profiler: FetchServiceService) {}
  foo(): void {
    this.profiler.fetchProfileInfo().subscribe(prof => this.profile = prof);
  }
}

Thank you so much @rapropos, this helps me alot and I am starting to make sense of it all…

I do however want to ask about one thing still that I do not understand.

In this code section:

const rv = {} as CustProfile;
      for (const f of resData) {
        rv[f.option3] = f.option4;
        // just took out the capacity for simplicity first.
        //   rv.capacity = +rv.capacity;
        return rv;
      }

so what I do not understand is that if I

console.log('f.option3: ', f.option3);

I get:

f.option3:  venue
f.option3:  location
f.option3:  capacity
f.option3:  bookings
f.option3:  manager

which are all the ‘option3’ options received.

But the moment that I include this section:

rv[f.option3] = f.option4;
return rv;

it only returns the first option which is ‘venue’.
What happened to the other option?

Am I correct in assuming that this

const rv = {} as CustProfile;

can only handle one set?

If so, then how can I store all sets for use?

Thank you once again for your guidance!
Much appreciated!

You moved the return rv; statement inside the loop, so it returns after only one trip through. Look at my original code: that statement is after the closing } of the loop.

Also, why did you change things to const?

Yes, that’s what I was referencing when saying:

what multiple venues look like (if that happens), but let’s assume there’s only one venue per response

That depends on what is coming over the wire. Make a request that returns multiple venues at once, and paste it here (preferably as JSON; that’s easier for me to read than what sits in the console window - definitely please as text and not an image).

:shushing_face: I moved it while testing and never moved it back!! My apologies.

The reason I changed it is because Studio Code kept giving me the following:


So I changed according to their suggestion.

Ok by now my head is spinning… I am realllly confused. Sorry.

Here is the full set of data, and by JSON I presume you meant like this:
(if not, please let me know how better, then I will send it through)

[
  {
    "id": "1",
    "option1": "1",
    "option2": "1",
    "option3": "venue",
    "option4": "hall"
  },
  {
    "id": "2",
    "option1": "2",
    "option2": "1",
    "option3": "location",
    "option4": "city"
  },
  {
    "id": "3",
    "option1": "3",
    "option2": "1",
    "option3": "capacity",
    "option4": "test"
  },
  {
    "id": "4",
    "option1": "4",
    "option2": "1",
    "option3": "bookings",
    "option4": "required"
  },
  {
    "id": "5",
    "option1": "5",
    "option2": "1",
    "option3": "manager",
    "option4": "yes"
  },
  {
    "id": "6",
    "option1": "6",
    "option2": "2",
    "option3": "venue",
    "option4": "room"
  },
  {
    "id": "7",
    "option1": "7",
    "option2": "3",
    "option3": "venue",
    "option4": "stadium"
  },
  {
    "id": "8",
    "option1": "8",
    "option2": "4",
    "option3": "venue",
    "option4": "conference"
  },
  {
    "id": "9",
    "option1": "9",
    "option2": "2",
    "option3": "bookings",
    "option4": "discuss"
  }
]

This is how it looks in mysql:
image

How do I store this data inside a variable / array / something and how do I easily reference it?

Examples of typical questions would be:

a.) what is ‘option4’ value where ‘option3’ = ‘location’
b.) what are all the ‘option4’ values where ‘option3’ = ‘venue’

for each of these, I then would like to have it inside a variable inside the .ts file to use for conditions in there.

I really hope I give enough detail and that I can get to grip with how to do these type of operations.
Already I have learned much, so thank you already for your help!

I really do appreciate your patience and guidance!

IMHO const in JavaScript is less than worthless. Here’s another person’s rant (warning: potentially NSFW language if curse words bother you) on the topic that makes lots of other good points. To join this team, simply open tslint.json and change "prefer-const": true to false.

For format, that’s fine. I don’t understand what option1, option2 are for, and what constitutes a single logical record. I initially assumed that a CustProfile was a useful business object, and it had the attributes in that interface we had been discussing. Now I’m not so sure, and since you say this is “the full set of data”, then maybe this is some sort of Rosetta Stone that is decoding a bunch of numerical values. So I no longer understand really what we are looking at in human-understandable terms, so can only answer the specific queries you ask for, until I understand what the object model is supposed to look like.

in our service

export type VenueOptionMap = { [ k: string ] : string[] };
interface WiredVenueOption { option3: string; option4: string; }
fetchVenueOptions(): Observable<VenueOptionMap> {
  return this.http.post<WiredVenueOption[]>(url, postbody).pipe(map(wvos => {
    let rv: VenueOptionMap = {};
    for (let wvo of wvos) { 
      if (!rv.hasOwnProperty(wvo.option3)) { 
        rv[wvo.option3] = [wvo.option4]; 
      } else { 
        rv[wvo.option3].push(wvo.option4); 
      } 
    }
    return rv;
  }));

in our page

venueOptions?: VenueOptionMap;
this.service.fetchVenueOptions().subscribe(vom => this.venueOptions = vom);

Now, after that subscription resolves, venueOptions should look like the following, given the dataset you gave me:

{
  venue: [ 'hall', 'room', 'stadium', 'conference' ],
  location: [ 'city' ],
  capacity: [ 'test' ],
  bookings: [ 'required', 'discuss' ],
  manager: [ 'yes' ]
}

…so,

this.venueOptions.location, which is [ 'city' ]

b.) what are all the ‘option4’ values where ‘option3’ = ‘venue’

this.venueOptions.venue, which is [ 'hall', 'room', 'stadium', 'conference' ]

Hi, Thank you once again! This really solved the issues for me!

Yes, this is a copy of the actual table and the layout is very poor imho.

However, I did manage to find another use for EXACTLY the same concept and it also has to do with a Profile :slight_smile: can you believe it.
They fit perfectly.

Here is the scenario that makes more sense and is working fine on my side:

It is when I use this to retrieve the user meta from a user in Wordpress.

In the Service:

export interface ProfileOptionsMap { [ k: string ]: string[]; }
export interface CustProfile {
  uid: number;
  firstName: string;
  lastName: string;
  nickName: string;
  userStatus: string;
  profilePic: string;

  // NOTE 1: remember next line
  [ k: string ]: string | number;
}
// this is what comes over the wire
interface WiredCustProfile {
  id: number;
  option1: string;
  option2: string;
  meta_key: string;
  meta_value: string;
}

then inside the class:

fetchProfileOptions(): Observable<ProfileOptionsMap> {
// This option is hardcoded for now, will bring in the dynamic data later.
    this.postData = {
      userId: '1',
    };

    return this.http.post<WiredCustProfile[]>(`${this.fullApiUrl}getProfile`, this.postData).pipe(map(profResults => {
      let rv: ProfileOptionsMap = {};
      // console.log('FULL: ', profResults);
      for (let profField of profResults) {
        if (!rv.hasOwnProperty(profField.meta_key)) {
          rv[profField.meta_key] = [profField.meta_value];
        } else {
          rv[profField.meta_key].push(profField.meta_value);
        }
      }
      // console.log('RV: ', rv);
      return rv;
    }));
  }

inside the Display Page:

  firstName: string;
  lastName: string;
  nickName: string;

  profileOptions?: ProfileOptionsMap;

and

ionViewWillEnter() {
    this.profileService.fetchProfileOptions().subscribe(vom => {
      this.profileOptions = vom;
      // console.log('TEST 2: ', this.profileOptions);
      this.firstName = this.profileOptions.first_name.toString();
      this.lastName = this.profileOptions.last_name.toString();
      this.nickName = this.profileOptions.nickname.toString();
    });
  }

and inside the HTML:

<ion-row>
      <ion-col size-sm="6" offset-sm="3">
        <ion-item>
          <ion-label position="floating">First Name</ion-label>
          <ion-input type="text" autocomplete autocorrect value="{{this.firstName}}"></ion-input>
        </ion-item>
      </ion-col>
      <ion-col size-sm="6" offset-sm="3">
        <ion-item>
          <ion-label position="floating">Last Name</ion-label>
          <ion-input type="text" autocomplete autocorrect value="{{this.lastName}}"></ion-input>
        </ion-item>
      </ion-col>
    </ion-row>
    <ion-row>
      <ion-col size-sm="6" offset-sm="3">
        <ion-item>
          <ion-label position="floating">Nickname</ion-label>
          <ion-input type="text" autocomplete autocorrect value="{{this.nickName}}"></ion-input>
        </ion-item>
      </ion-col>
      <ion-col size-sm="6" offset-sm="3"></ion-col>
    </ion-row>

Thank you very much for all your help @rapropos and I hope that the user Profile code I shared is actually more usable as an example for someone else.

1 Like

Hello share some Knowledge with you```
this.uProfile.filter(x => x.option3 === ‘bookings’);


this should retreive array of uProfiles where option3 is bookings. On the other hand you can use:

this.uProfile.find(x => x.option3 === ‘bookings’);


to get the first element of uProfiles that matches the condition.