简体   繁体   中英

Angular 6 - HttpClient Keeping subscribe in the service but passing the result to the component

I have a project which contains a service (which gets the data) and a component (displays it).

I would like the keep the code to the minimum on the app.component.ts.

In the service I have:

getPosts() {
    return this.http.get('https://jsonplaceholder.typicode.com/posts', httpOptions).subscribe(
      result => {
        return result;
        this.theData = result;
      },
      error => {
        return error;
      }
    );
  }

and in my app.component.ts:

 getPostsFromService() {
    this.myService.getPosts();
  }

But of course, I need to get the result and pass it into my component but something like this wouldn't work:

myData;

  ...


  getPostsFromService() {
    this.myData = this.myService.getPosts();
  }

So, my question is, how can I do this or is it really recommended to call subscribe on my component and not in the service?

In new HttpClientModule JSON is an assumed default and no longer needs to be explicitly parsed using res.json()

You can tell HttpClient the type of the response to make consuming the output easier and more obvious.

Type checking of response can be done by using the type parameter
Create an Interface

export interface Idata{
  userId:number;
  Id:number;
  title:string;
  body:string;
}

Http returns an observable and We can tell the HttpClient.get to return response as Idata type When we use http.get<Idata[]>(...) then it returns the instance of Observable<Idata[]> type.
In your service

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {Observable} from 'rxjs';
import {Idata} from './Idata'
@Injectable()
export class ShareService {

    constructor(private httpc:HttpClient)
    {
    }

    public getPosts():Observable<Idata[]>
    {
        return this.httpc.get<Idata[]>('https://jsonplaceholder.typicode.com/posts');
    }
}

In your component subscribe to Observable<Idata[]> to get instance of Idata

  public data:Array<Idata>=[];
  constructor(public service:ShareService)
  {
     this.service.getPosts().subscribe(value => {
        this.data=value;
     });
  }

An alternative is Using async pipe AsyncPipe accepts as an argument an observable or a promise , calls subscribe or attaches a then handler, then waits for the asynchronous result before passing it through to the caller.

public response:Observable<Idata[]>;
constructor(public service:ShareService)
{
    this.response=this.service.getPosts();
}

HTML

<div *ngFor="let item of (response|async)">
  {{item.body}}
</div>


LIVEDEMO

my question is, how can I do this or is it really recommended to call subscribe on my component and not in the service?

It's Good Practice to Delegate complex component logic to services

From Angular Style Guide
Do limit logic in a component to only that required for the view. All other logic should be delegated to services.

Do move reusable logic to services and keep components simple and focused on their intended purpose.

Why? Logic may be reused by multiple components when placed within a service and exposed via a function.

Why? Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

Why? Removes dependencies and hides implementation details from the component.

Why? Keeps the component slim, trim, and focused.

Subscribing in Component lets you share a single http request with multiple observers , if you don't do so you would be violating DRY Principle
P:S To share a single http request for multiple observers , you need something like the share operator.

import {Observable} from 'rxjs';
import {share} from 'rxjs/operators'

export class dataService {

    public data$=Observable<Idata[]>
    constructor(http:HttpClient)
    {
        this.data$=this.http.get<Idata[]>('https://jsonplaceholder.typicode.com/posts').pipe(share());
    }
    public getPosts():Observable<Idata[]>
    {
        return this.data$
    }
}

This operator is a specialization of publish which creates a subscription when the number of observers goes from zero to one, then shares that subscription with all subsequent observers until the number of observers returns to zero, at which point the subscription is disposed.

Great! You can try with Observable and Async! In app.component

public posts: Observable<posts[]>
getPostsFromService() {
  this.posts = this.myService.getPosts();
}

And in html, you put that:

*ngFor="let post of posts | async"

And service

return this._http.get<posts>('https://jsonplaceholder.typicode.com/posts', httpOptions)

Try that and see if worked! I hope that helps!

If you desire to use a service methods must return observable response like this:

public getPosts(): Observable<IPosts[]> {

    const data = {
      '$type' : 'ReadRequest',
      'query' : 'getPosts',
      'parameters' : {}
    };

    return this.http.post(null, data)
      .map(res => (<any>res)._body === '' ? {} : res.json().result)
      .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    return Promise.reject(error.message || 'Server error: ' + error);
  }

Where IPosts is described Interface according your response.

After in any component you can use this:

public posts: IPosts[] = [];
this.servicePosts.getPosts().subscribe(data => {
       this.posts = data; // Fill data from response to component variable this.post
          }, error => {
            console.log(error);
          });

Your component.ts should be like this.

There is no issue in your service except subscribe should be map.

 this.myService.getPosts().subscribe((data) => {
         this.setMyData(data);
    });

    setMyData() {
        this.myData = data;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM