12

I'm having real trouble wrapping my head around observables and subscriptions in angular2. The problem I am currently having is the following:

I have a service which contains methods to post and get data from an API. The service is injected into a component, which directly calls those methods in the service. The service then retrieves data and stores it itself, but I then want to process that data within the component. I cannot work out how to have the component execute a function after the service has retrieved and stored the data itself.

service.ts

import { Injectable } from 'angular2/core';    
import { Http } from 'angular2/router';

@Injectable()
export class Service {
    result: Object;

    constructor(http: Http) {
        this.http = http;
    }

    httpGet(url) {
        return this.http.get(url).subscribe(
            result => this.result = result.json(),
            error => console.log(error),
            () => {}
        )
    }
}

component.ts

import { Component } from 'angular2/core';
import { Service } from './service';

@Component({
    ...
})
export class Component {
    formattedResult: Object;

    constructor(service: Service) {
        this.service = service;
        this.service.httpGet('/api')

        // How do I call format result on service.result only after it is completed?
        this.formatResult(service.result) // Need to execute this after the http call is completed

        // I want something like:
        this.service.httpGet('/api').then(() => formatResult(this.service.result));
    }

    formatResult(result) {
        this.formattedResult = result.map(x => x.length) // For example
    }
}
Mel
  • 5,837
  • 10
  • 37
  • 42
Peter Trotman
  • 433
  • 1
  • 4
  • 9
  • 1
    http://stackoverflow.com/questions/34739574/notify-component-when-service-function-is-complete-in-angular/34739630#34739630 – Günter Zöchbauer Jan 12 '16 at 14:01
  • @GünterZöchbauer Thanks, its close but I wanted to keep a .subscribe in the service since it stores that information itself for later access. It isn't possible to have two .subscribes on one http.get (it will call it twice). Unless the accepted design pattern is to have .subscribe only in the components and explicitly set the service variables from the component? I wanted to keep the service and component logic separated is all. – Peter Trotman Jan 12 '16 at 14:15
  • http://stackoverflow.com/a/34405243/215945 - that answer chains observables using `flatMap()`. – Mark Rajcok Jan 13 '16 at 03:38

4 Answers4

6

To answer my own question:

In the app root, import Rxjs with:

import 'rxjs/Rx';

This gives you access to the complete Observable object (not just the 'Observable-lite' included with Angular). This enables you to .map .reduce etc. the Http requests.

You can now use .map on an Http request to carry out arbitrary code in the context of the service, even if it is the component that subscribes to the result. So to achieve what I set out to do in the beginning, we can:

service.ts

import { Injectable } from 'angular2/core';    
import { Http } from 'angular2/router';

@Injectable()
export class Service {
    result: Object;

    constructor(http: Http) {
        this.http = http;
    }

    httpGet(url) {
        return this.http.get(url).map(
            result => {
                let data = result.json();
                this.result = data;
                return data
            }
        )
    }
}

component.ts

import { Component } from 'angular2/core';
import { Service } from './service';

@Component({
    // Component setup
})
export class Component {
    formattedResult: Object;

    constructor(service: Service) {
        this.service = service;
        this.service.httpGet('/api').subscribe(
            data => this.formatResult(data);
        );
    }

    formatResult(result) {
        this.formattedResult = result.map(x => x.length) // For example
    }
}

Thanks to Gunter and Mark for the replies, helped me wrap my head around this a bit and I feel like I understand Observables much better having gone through a lot of the docs solving this problem!

Peter Trotman
  • 433
  • 1
  • 4
  • 9
  • To include operators from rxjs create file rxjs-extensions.ts and include it in your polyfils, add imports for used operators as import 'rxjs/operator/map' etc. Also never import full lib as import 'rxjs/Rx' cause it is not needed. You can check what is included in your maps with source map explorers of different kind for webpack, angular, js – Adam Michalski Feb 08 '18 at 07:56
  • After lots of struggle. Finally its working for me, thanks man. – Sumit Sep 18 '18 at 06:47
2

Check if the result already arrived, if yes, create a new promise and complete it with the result, not not, fetch it, and return it as promise as well.

@Injectable()
export class Service {
    result: Object;

    constructor(http: Http) {
        this.http = http;
    }

    httpGet(url) {
        if(result === undefined) {
          return this.http.get(url).toPromise().then(
              result => this.result = result.json(),
              error => console.log(error);
          );
        } else {
          return new Promise().resolve(result);
        }
    }
}

I don't know TS and this code might contain some errors (I use Angular only Dart), but you should get the idea.

See also
- http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Ok, cool I can see how that works. Seems a bit of a shame to just convert it back to Promises so you can .then chain. I was hoping there was some new observable way of doing it that I just didn't know about. Oh well - thanks! – Peter Trotman Jan 12 '16 at 14:54
  • As I mentioned, I'm not a TS developer, but from other posts I have seen, it seems the way to go. – Günter Zöchbauer Jan 12 '16 at 14:57
0

Well you could use a call back function in that case,

Taking example consider this is the post function which triggers the post service in service module

postData(id: string, name: string) {
    console.log("Got you",id);
  this._employeeService.postServices({id: id, name: name})
    .subscribe(
      (response:Response) => this.consoleMyOutput(),
      error => console.log(error)
    );  
}

Here note the function consoleMyOutput(). This will be triggered once the response is got after the service has been called

Now,

consoleMyOutput(){console.log("Write Your call back function")};

This function will be triggered just after that.

mxr7350
  • 1,438
  • 3
  • 21
  • 29
0

It can happen simply with Rxjs methods , here is simple and classic way to call services once after application loaded then we can subscribe it into multiple components. Make sure services should be reusable for all component, so do much as u can do code in services and map, subscribe only in component :

Services:

 import { Injectable } from 'angular2/core';    
 import { Http } from 'angular2/http';

 @Injectable()
 export class Service {
     result: Object;

     constructor(private http: Http) {}

     private _urlGet = '/api';

     httpGet(){
        return this.http.get(this._urlGet)
          .map(res => JSON.parse(res.json()))
          .do(result => {
             this.result = result;
           }, error => console.log(error))
     }
  }

Component

 export class Component {
    formattedResult: Object;

    constructor(private service: Service) { }

    ngOnInit(){
       this.service.httpGet().subscribe(result => {
          this.formattedResult = result;
       }
    }
 }
asad mohd
  • 181
  • 1
  • 1
  • 7