0

I am new to Typescript/Javascript and I am trying to get to the grips with the potential of rxjs. I use this code

return this.http.get<IXenoCantoResponse>(`${this.corsAnywhereUrl}${this.xenoCantoApiBaseUrl}${this.formatSearchTerm(searchTerm)}${this.recordingLength}`)
  .pipe(
    map(o => ({
      numRecordings: o.numRecordings,
      numSpecies: o.numSpecies,
      page: o.page,
      numPages: o.numPages,
      recordings: o.recordings
    }))
  );

to map the response from a public API to an interface, IXenoCanto:

export interface IXenoCantoResponse {
    numRecordings: string;
    numSpecies: string;
    page: string;
    numPages: string;
    recordings: [];
  }

It is actually the recordings[] that I am interested in. In subsequent code, I use the fields in this array to construct an url and add it to yet another object:

data.forEach((element, index) => {
  let sub = element.sono['full'].substr(0, this.getPosition(element.sono['full'], '\/', 6));
  urls.push({
    id: index + 1,
    url: `${sub}${element['file-name']}`
  });

});

export interface IVoice {
  id: number;
  url: string;
}

While it is true that this code does the job, I have been attempting to make it more effiently.

I wonder if it is possible to map the recordings any[] response straight to a IVoice[] in my initial map code. So I have tried changing the IXenoCantoResponse interface like so:

export interface IXenoCantoResponse {
    numRecordings: string;
    numSpecies: string;
    page: string;
    numPages: string;
    recordings: IVoice[]
    // recordings: [];
  }

And then I can try to map the [] repsonse to the IVoice[] directly, something like this:

return this.http.get<IXenoCantoResponse>(`${this.corsAnywhereUrl}${this.xenoCantoApiBaseUrl}${this.formatSearchTerm(searchTerm)}${this.recordingLength}`)
      .pipe(
        map(o => ({
          numRecordings: o.numRecordings,
          numSpecies: o.numSpecies,
          page: o.page,
          numPages: o.numPages,
          recordings: o.recordings.map((element, index) => {

              id: index + 1,
              url: `${element.sono['full'].substr(0, this.getPosition(element.sono['full'], '\/', 6))}${element['file-name']}`
          }
          });
        }))
      );

I have so far been unable to get the second approach to work. Is it possible to do this? I think the problem lies in how to setup the map operator with an array object. Is this right and can anyone point me in the right direction?

Edit:

Each object in the recordings array mentioned above has the following format:

        {
            "id": "477551",
            "gen": "Troglodytes",
            "sp": "troglodytes",
            "ssp": "troglodytes",
            "en": "Eurasian Wren",
            "rec": "\u00c9tienne Leroy",
            "cnt": "Poland",
            "loc": "Hajn\u00f3wka, hajnowski, podlaskie",
            "lat": "52.6907",
            "lng": "23.6035",
            "alt": "160",
            "type": "song",
            "url": "\/\/www.xeno-canto.org\/477551",
            "file": "\/\/www.xeno-canto.org\/477551\/download",
            "file-name": "XC477551-190503-Troglodyte [email protected]",
            "sono": {
                "small": "\/\/www.xeno-canto.org\/sounds\/uploaded\/ZWAQHOJFLZ\/ffts\/XC477551-small.png",
                "med": "\/\/www.xeno-canto.org\/sounds\/uploaded\/ZWAQHOJFLZ\/ffts\/XC477551-med.png",
                "large": "\/\/www.xeno-canto.org\/sounds\/uploaded\/ZWAQHOJFLZ\/ffts\/XC477551-large.png",
                "full": "\/\/www.xeno-canto.org\/sounds\/uploaded\/ZWAQHOJFLZ\/ffts\/XC477551-full.png"
            },
            "lic": "\/\/creativecommons.org\/licenses\/by-nc-sa\/4.0\/",
            "q": "A",
            "length": "1:13",
            "time": "08:00",
            "date": "2019-05-03",
            "uploaded": "2019-05-29",
            "also": [
                "Fringilla coelebs"
            ],
            "rmk": "Singing seated or in flight",
            "bird-seen": "yes",
            "playback-used": "no"
        }

1 Answer 1

1

I think you need to specify the type of created objects

return this.http.get<IXenoCantoResponse>(`${this.corsAnywhereUrl}${this.xenoCantoApiBaseUrl}${this.formatSearchTerm(searchTerm)}${this.recordingLength}`)
   .pipe(
      map(o => return new IXenoCantoResponse({
          numRecordings: o.numRecordings,
          numSpecies: o.numSpecies,
          page: o.page,
          numPages: o.numPages,
          recordings: o.recordings.map((element: IVoice, index) => new IVoice({
              id: index + 1,
              url: `${element['sono']['full'].substr(0, this.getPosition(element['sono']['full'], '\/', 6))}${element['file-name']}`
          })
        });
      }))
    );
Sign up to request clarification or add additional context in comments.

9 Comments

Hi, that change makes the element property of map((element, index) => new IVoice({ to type IVoice. So how do I access the properties of the response? i.e.: element.sono['full']
you need to specify type of your 'element'. But you need to provide more details. From your example I o.recordings have type IVoice[], to element will be IVoice. But, what is element.sono? I think you provide another element type from your API.
for angular 8 and below you need to use element['sono']['full']. For Angular 9, you can use element.sono.full. Best practice is to map your json result into a class with same attributes, and instead (element: any) to use (element: your_element_type)
more elegant, can be write as: map((element: any) => { return new IXenoCantoResponse(....) };
Avoid using braces if your code is writed only in 1 or max 2 lines. Else, use braces, code is more readable
|

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.