Cyberpunker
Cyberpunker

Reputation: 113

.map() not working in Angular

I'm trying to make an http request to a server in Angular 2, but the .map() part doesn't seem to be working, even though no error is shown.

Here's my component:

**import { Component, OnInit } from '@angular/core';
import {Song} from '../interfaces/Song';
import { HttpClient } from '@angular/common/http';
import {Http, Response} from '@angular/http';
import {Observable} from 'rxjs/Rx';

import { Constants } from '../app.constants';


export class PlayerComponent implements OnInit {
    query;
    results;
    constructor(private http: HttpClient) {}

    ngOnInit() {
    }

    searchSoundCloud(query: string) {
        const maxResults = 100;
        query = encodeURIComponent(query.replace(/ /gi, '+'));
        const url = `https://api.soundcloud.com/tracks.json?client_id=${Constants.API_KEY}&q=${query}&limit=${maxResults}&linked_partitioning=1`;
        this.http
        .get(url)
        .map(res => this.handleResponse(res)) /// <-- this.handleResponse doesn't get fired!!!
        .catch((error) => {
            if (error.status === 500) {
                return Observable.throw(new Error(error.status));
            }
            else if (error.status === 400) {
                return Observable.throw(new Error(error.status));
            }
            else if (error.status === 409) {
                return Observable.throw(new Error(error.status));
            }
            else if (error.status === 406) {
                return Observable.throw(new Error(error.status));
            }
        });
    }

    handleResponse(res: any): any{
        var data = res.json();
        var result = [];
        if (data && data.collection) {
            data.collection.forEach(function(item) {
                var song: Song = <Song>{};
                song.streamUrl = item.stream_url;
                song.name = item.title;
                song.artist = item.user.username;
                song.provider = 1;
                song.idFromProvider = item.id;
                song.duration = item.duration;
                song.imageUrl = item.artwork_url;
                song.link = item.permalink_url;
                result.push(song);
                console.log(result)
            });
        }

        return result;
    }
}**

Any idea why?

Upvotes: 1

Views: 11670

Answers (4)

ThermeshB
ThermeshB

Reputation: 61

Use .pipe with .map

Example:

.map(x => x.json());

instead you can use this:

.pipe(map(x => x.json()));

Upvotes: 0

JGFMK
JGFMK

Reputation: 8904

When you do the map you can just do this kind of thing. song-service.ts.

import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
...
export class SongService {
   constructor(private http:Http) {}
   getSongs() : Observable<Song[]> {
      this.http.get(url)
    .map((res:Response) => res.json() as Observable<Song[]>
   }   
}

You'd include the http in a service layer returning the Observable.

You should look a little more at the other Http package classes too I included a few in the import statements to give you some inspiriation. You can get better type checking that way in your IDE. The HTTP get method can also take RequestOptions, which can be constructed with a Header (useful for things like Basic Authentication), and optionally URLSearchParams (you could add that to yours). You create a new SearchParams variable then use .set method to assign properties..

app-module.ts

import { HttpModule } from '@angular/http'
@NgModule({
...
  imports: [ HttpModule]
...
  providers:[SongService]
})
export class AppModule {}

In the mycomponent.ts you can do:

export class myComponent implements ngOnInit {
  songs$: Observable<Song[]>;
  constructor(songService:SongService) {}
  ...
 onInit() {
  this.songs$ = this.songService.getSongs()
}

mycomponent.html

<ul *ngFor="let song of songs$ | async">
  <li>{{song.duration}}</li>
</ul>

If you do use subscribe you will have a stateful component and, you can get into problems sometimes if you don't unsubscribe.

Async pipe makes that problem go away and you have a stateless component.

See this video for a bit more on async pipe.

You can sometimes get null pointer references with async pipe, so use elvis operator if things go awry like {{song?.duration}}.

For a stateful component, the sort of syntax you the usually have in mycomponent.ts would be:

export class myComponent implements ngOnInit, ngOnDestroy {
songs$: Observable<Song[]>;
songs: Song[];
onInit() {
  this.songs$ = this.songService.getSongs().
    subscribe(songs => {this.songs = songs},
              error=>  {this.songs = [] as Song[];
                        console.error(error);
                        switch (error.status) 
                          {
                            case "400":
                            case "406":
                            case "409":
                            case "500":
                            // do something
                               break;
                           }
                       }
              );

}
onDestroy() {
  this.song$.unsubscribe();
}

At that point async pipe is not required in html as you can use songs array state variable.

<ul *ngFor="let song of songs">
  <li>{{song.duration}}</li>
</ul>

Upvotes: 2

wosevision
wosevision

Reputation: 133

As the comments indicate, a ".subscribe()" from wherever you want to consume the data is necessary to "activate" the Observable. To expand on this, what you're seeing is the effect of "cold" observables – the emitter doesn't emit its first value until it is asked for, at which point it becomes "hot".

This can often be surprising for people coming from Angular 1.x and Promises (which produce values as soon as they've been called). Even if nothing consumes the value, it will still be brought into existence; this is not the case with Observables, which makes them very efficient vehicles for requesting remote data and not overloading with server calls.

Upvotes: 0

user4923309
user4923309

Reputation:

1.) are you calling the method searchSoundCloud somewhere? Because you have a ngOnInit() method, but I don't see the call for searchSoundCloud.

2.) You must subscribe to observables. If you don't subscribe, they want "do" their logic. Example:

let observable = Observable.create(observer => {
    console.log('foobar');
    observer.complete();
});

This won't log the text "foobar". You have to call subscribe on it:

observable.subscribe(() => {});

Only now, "foobar" will appear in your logs.

So you should make sure, that you call the searchSoundCloud method somewhere and also call subscribe on the observable, after the .catch() handler:

this.http
    .get(url)
    .map(res => this.handleResponse(res)) /// <-- this.handleResponse doesn't get fired!!!
    .catch((error) => {
        if (error.status === 500) {
            return Observable.throw(new Error(error.status));
        }
        else if (error.status === 400) {
            return Observable.throw(new Error(error.status));
        }
        else if (error.status === 409) {
            return Observable.throw(new Error(error.status));
        }
        else if (error.status === 406) {
            return Observable.throw(new Error(error.status));
        }
    })
    .subscribe((result: any) => {
        // do something with the result
    })
;

Upvotes: 0

Related Questions