179

Now, I have an initial page where I have three links. Once you click on the last 'friends' link, appropriate friends Component gets initiated. In there, I want to fetch/get list of my friends strored into friends.json file. Till now everything works fine. But I'm still a newbie for angular2's HTTP service using RxJs's observables, map, subscribe concept. I've tried to understand it and read few articles but until I get into practical work, I'm not gonna understand those concepts properly.

Here I have already made plnkr which is working except HTTP related work.

Plnkr

myfriends.ts

 import {Component,View,CORE_DIRECTIVES} from 'angular2/core';
 import {Http, Response,HTTP_PROVIDERS} from 'angular2/http';
 import 'rxjs/Rx';
 @Component({
    template: `
    <h1>My Friends</h1>
    <ul>
      <li *ngFor="#frnd of result">
          {{frnd.name}} is {{frnd.age}} years old.
      </li>
    </ul>
    `,
    directive:[CORE_DIRECTIVES]
  })

  export class FriendsList{

      result:Array<Object>; 
      constructor(http: Http) { 
        console.log("Friends are being called");

       // below code is new for me. So please show me correct way how to do it and please explain about .map and .subscribe functions and observable pattern.

        this.result = http.get('friends.json')
                      .map(response => response.json())
                      .subscribe(result => this.result =result.json());

        //Note : I want to fetch data into result object and display it through ngFor.

       }
  }

Please guide and explain properly. I know it will be so beneficial to lots of new developers.

nyks
  • 2,103
  • 6
  • 14
  • 17

3 Answers3

208

Here is where you went wrong:

this.result = http.get('friends.json')
                  .map(response => response.json())
                  .subscribe(result => this.result =result.json());

it should be:

http.get('friends.json')
                  .map(response => response.json())
                  .subscribe(result => this.result =result);

or

http.get('friends.json')
                  .subscribe(result => this.result =result.json());

You have made two mistakes:

1- You assigned the observable itself to this.result. When you actually wanted to assign the list of friends to this.result. The correct way to do it is:

  • you subscribe to the observable. .subscribe is the function that actually executes the observable. It takes three callback parameters as follow:

    .subscribe(success, failure, complete);

for example:

.subscribe(
    function(response) { console.log("Success Response" + response)},
    function(error) { console.log("Error happened" + error)},
    function() { console.log("the subscription is completed")}
);

Usually, you take the results from the success callback and assign it to your variable. the error callback is self explanatory. the complete callback is used to determine that you have received the last results without any errors. On your plunker, the complete callback will always be called after either the success or the error callback.

2- The second mistake, you called .json() on .map(res => res.json()), then you called it again on the success callback of the observable. .map() is a transformer that will transform the result to whatever you return (in your case .json()) before it's passed to the success callback you should called it once on either one of them.

Abdulrahman Alsoghayer
  • 16,462
  • 7
  • 51
  • 56
  • Can you make my plunk working with your suggestions? I tried but doesn't work. – nyks Jan 08 '16 at 08:36
  • 2
    here you go [your plunker](http://plnkr.co/edit/oYFhsMEJFMi7NzGPpvT1?p=preview) . I changed lines: 21, 23 on myfriends.ts – Abdulrahman Alsoghayer Jan 08 '16 at 08:40
  • Done. Please guide more on subscribe() by editing your answer. – nyks Jan 08 '16 at 08:41
  • 1
    What I didn't understand is why use the "map" function here at all? We could just call the .json on the result. So what's the benefit of doing so? – rubmz Aug 15 '16 at 12:24
  • 5
    You are right @rubmz. You could do that as I mentioned in my answer.But, one huge benefit is separating the logic. For example, in your service, you have a function `getFriends(){return http.get('friends.json').map(r => r.json());}`. Now, you can call `getFriends().subscribe(...)` without having the need to call `.json()` every time. – Abdulrahman Alsoghayer Aug 15 '16 at 13:10
  • 3
    Yes, this is just a little bit confusing for newbies. What that misterious map() does and what not... But finally I got that too :) – rubmz Aug 15 '16 at 17:03
  • 1
    @Abdulrahman , maybe you'll be interested to have a look at this question too: http://stackoverflow.com/questions/40505691/observable-why-result-not-in-subscribe-success-function-if-preceded-by-map – nyluje Nov 09 '16 at 11:19
  • @Abdulrahman you noted that "he complete callback is used to determine that you have received the last results (i.e. when used for calling a Websocket endpoint). On your plunker, the complete callback will always be called after either the success or the error callback." But according to this https://stackoverflow.com/questions/33783967/rxjs-observable-doesnt-complete-when-an-error-occurs `complete` is not called on error. you need to use `finally` on that. What have I got wrong? – Panais Sep 13 '17 at 22:28
  • @Panais You are right, `complete` is not called on error. I apologize for the mistake, I will correct soon. Thanks for noting – Abdulrahman Alsoghayer Sep 15 '17 at 20:14
  • Does the map with res.json() also applies if the error callback function is called? – Fernando Zamperin Nov 06 '17 at 18:26
  • @FernandoZamperin it depends on the type of the error. For example, if the error was caused by a bad url `res.json()` will throw an error. – Abdulrahman Alsoghayer Nov 07 '17 at 07:46
  • @Abdulrahman Please can you elaborate .subscribe(result => this.result =result.json this line, here we add **result** but where they find? – Brijesh Mavani Feb 06 '18 at 12:30
  • 1
    @BrijeshMavani `result` is just a parameter name. You can replace it with any name you like. You can write it as `.subscribe(a=>this.result= a.json)` . It's the exact same thing – Abdulrahman Alsoghayer Feb 06 '18 at 14:50
139

Concepts

Observables in short tackles asynchronous processing and events. Comparing to promises this could be described as observables = promises + events.

What is great with observables is that they are lazy, they can be canceled and you can apply some operators in them (like map, ...). This allows to handle asynchronous things in a very flexible way.

A great sample describing the best the power of observables is the way to connect a filter input to a corresponding filtered list. When the user enters characters, the list is refreshed. Observables handle corresponding AJAX requests and cancel previous in-progress requests if another one is triggered by new value in the input. Here is the corresponding code:

this.textValue.valueChanges
    .debounceTime(500)
    .switchMap(data => this.httpService.getListValues(data))
    .subscribe(data => console.log('new list values', data));

(textValue is the control associated with the filter input).

Here is a wider description of such use case: How to watch for form changes in Angular.

There are two great presentations at AngularConnect 2015 and EggHead:

Christoph Burgdorf also wrote some great blog posts on the subject:

In action

In fact regarding your code, you mixed two approaches ;-) Here are they:

  • Manage the observable by your own. In this case, you're responsible to call the subscribe method on the observable and assign the result into an attribute of the component. You can then use this attribute in the view for iterate over the collection:

      @Component({
        template: `
          <h1>My Friends</h1>
          <ul>
            <li *ngFor="#frnd of result">
              {{frnd.name}} is {{frnd.age}} years old.
            </li>
          </ul>
        `,
        directive:[CORE_DIRECTIVES]
      })
      export class FriendsList implement OnInit, OnDestroy {
        result:Array<Object>; 
    
        constructor(http: Http) {
        }
    
        ngOnInit() {
          this.friendsObservable = http.get('friends.json')
                        .map(response => response.json())
                        .subscribe(result => this.result = result);
         }
    
         ngOnDestroy() {
           this.friendsObservable.dispose();
         }
      }
    

    Returns from both get and map methods are the observable not the result (in the same way than with promises).

  • Let manage the observable by the Angular template. You can also leverage the async pipe to implicitly manage the observable. In this case, there is no need to explicitly call the subscribe method.

      @Component({
        template: `
          <h1>My Friends</h1>
          <ul>
            <li *ngFor="#frnd of (result | async)">
              {{frnd.name}} is {{frnd.age}} years old.
            </li>
          </ul>
        `,
        directive:[CORE_DIRECTIVES]
      })
      export class FriendsList implement OnInit {
        result:Array<Object>; 
    
        constructor(http: Http) {
        }
    
        ngOnInit() {
          this.result = http.get('friends.json')
                        .map(response => response.json());
         }
      }
    

You can notice that observables are lazy. So the corresponding HTTP request will be only called once a listener with attached on it using the subscribe method.

You can also notice that the map method is used to extract the JSON content from the response and use it then in the observable processing.

starball
  • 20,030
  • 7
  • 43
  • 238
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Thanks for all references. But can you help me with my plunk? – nyks Jan 08 '16 at 08:37
  • I updated my answer with more details regarding your code. Hope it will help you ;-) – Thierry Templier Jan 08 '16 at 09:14
  • sorry that I couldn't accept you answer. It is more clear but accepted and appreciated answer helped me to understand enough about my question. But hopefully you will get good hits for your clear answer as you have more detailed explanation. Accepted answer too for good basic understating. – micronyks Jan 08 '16 at 12:23
  • 2
    Thierry Templier this is an excellent answer but one thing is not clear to me, I tought http.get('friends.json') .map(response => response.json()) returns observable>. If yes then how come you send it to this.result? they are diffrent types. – Stav Alfi Jul 14 '16 at 15:10
  • @StavAlfi `pipes` are also an `observables`. check out this video : https://www.youtube.com/watch?v=bVI5gGTEQ_U suggested by thierry for more info. – candidJ Aug 04 '16 at 14:54
  • @StavAlfi in fact, the `async` pipe handles the subscription to the observable internally so you don't need to do it by your own... You're right your code returns `Observable>`. – Thierry Templier Nov 02 '16 at 21:05
11
import { HttpClientModule } from '@angular/common/http';

The HttpClient API was introduced in the version 4.3.0. It is an evolution of the existing HTTP API and has it's own package @angular/common/http. One of the most notable changes is that now the response object is a JSON by default, so there's no need to parse it with map method anymore .Straight away we can use like below

http.get('friends.json').subscribe(result => this.result =result);
rajesh kumar
  • 1,578
  • 16
  • 14