[angular] Creating and returning Observable from Angular 2 Service

This is more of a "best practices" question. There are three players: a Component, a Service and a Model. The Component is calling the Service to get data from a database. The Service is using:

this.people = http.get('api/people.json').map(res => res.json());

to return an Observable.

The Component could just subscribe to the Observable:

    peopleService.people
        .subscribe(people => this.people = people);
      }

However, what I really want is for the Service to return an Array of Model objects that was created from the data that the Service retrieved from the database. I realized that the Component could just create this array in the subscribe method, but I think it would be cleaner if the service do that and make it available to the Component.

How can the Service create a new Observable, containing that array, and return that?

This question is related to angular observable

The answer is


I would like to add that if the object that is created is static and not coming through http something like that can be done:

public fetchModel(uuid: string = undefined): Observable<string> {
      if(!uuid) { //static data
        return Observable.of(new TestModel()).map(o => JSON.stringify(o));
      }
      else {
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .map(res => res.text());
      }
    }

Edit: For Angular 7.x.x mapping needs to be done using pipe() as described here (https://stackoverflow.com/a/54085359/986160):

import {of,  Observable } from 'rxjs';
import { map } from 'rxjs/operators';
[...]
public fetchModel(uuid: string = undefined): Observable<string> {
      if(!uuid) { //static data
        return of(new TestModel());
      }
      else {
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .pipe(map((res:any) => res)) //already contains json
      }
    }

from answer to my question about observers and static data: https://stackoverflow.com/a/35219772/986160


Notice that you're using Observable#map to convert the raw Response object your base Observable emits to a parsed representation of the JSON response.

If I understood you correctly, you want to map again. But this time, converting that raw JSON to instances of your Model. So you would do something like:

http.get('api/people.json')
  .map(res => res.json())
  .map(peopleData => peopleData.map(personData => new Person(personData)))

So, you started with an Observable that emits a Response object, turned that into an observable that emits an object of the parsed JSON of that response, and then turned that into yet another observable that turned that raw JSON into an array of your models.


I'm a little late to the party, but I think my approach has the advantage that it lacks the use of EventEmitters and Subjects.

So, here's my approach. We can't get away from subscribe(), and we don't want to. In that vein, our service will return an Observable<T> with an observer that has our precious cargo. From the caller, we'll initialize a variable, Observable<T>, and it will get the service's Observable<T>. Next, we'll subscribe to this object. Finally, you get your "T"! from your service.

First, our people service, but yours doesnt pass parameters, that's more realistic:

people(hairColor: string): Observable<People> {
   this.url = "api/" + hairColor + "/people.json";

   return Observable.create(observer => {
      http.get(this.url)
          .map(res => res.json())
          .subscribe((data) => {
             this._people = data

             observer.next(this._people);
             observer.complete();


          });
   });
}

Ok, as you can see, we're returning an Observable of type "people". The signature of the method, even says so! We tuck-in the _people object into our observer. We'll access this type from our caller in the Component, next!

In the Component:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

getPeople(hairColor:string) {
   this._peopleObservable = this.peopleService.people(hairColor);

   this._peopleObservable.subscribe((data) => {
      this.people = data;
   });
}

We initialize our _peopleObservable by returning that Observable<people> from our PeopleService. Then, we subscribe to this property. Finally, we set this.people to our data(people) response.

Architecting the service in this fashion has one, major advantage over the typical service: map(...) and component: "subscribe(...)" pattern. In the real world, we need to map the json to our properties in our class and, sometimes, we do some custom stuff there. So this mapping can occur in our service. And, typically, because our service call will be used not once, but, probably, in other places in our code, we don't have to perform that mapping in some component, again. Moreover, what if we add a new field to people?....


This is an example from Angular2 docs of how you can create and use your own Observables :

The Service

import {Injectable} from 'angular2/core'
import {Subject}    from 'rxjs/Subject';
@Injectable()
export class MissionService {
  private _missionAnnouncedSource = new Subject<string>();
  missionAnnounced$ = this._missionAnnouncedSource.asObservable();

  announceMission(mission: string) {
    this._missionAnnouncedSource.next(mission)
  }
}

The Component

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

    export class MissionControlComponent {
      mission: string;

      constructor(private missionService: MissionService) {

        missionService.missionAnnounced$.subscribe(
          mission => {
            this.mission = mission;
          })
      }

      announce() {
        this.missionService.announceMission('some mission name');
      }
    }

Full and working example can be found here : https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service


In the service.ts file -

a. import 'of' from observable/of
b. create a json list
c. return json object using Observable.of()
Ex. -

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

@Injectable()
export class ClientListService {
    private clientList;

    constructor() {
        this.clientList = [
            {name: 'abc', address: 'Railpar'},
            {name: 'def', address: 'Railpar 2'},
            {name: 'ghi', address: 'Panagarh'},
            {name: 'jkl', address: 'Panagarh 2'},
        ];
    }

    getClientList () {
        return Observable.of(this.clientList);
    }
};

In the component where we are calling the get function of the service -

this.clientListService.getClientList().subscribe(res => this.clientList = res);

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to observable

Http post and get request in angular 6 Angular 4: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe' TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable How can I create an observable with a delay Return an empty Observable Angular/RxJs When should I unsubscribe from `Subscription` Using an array from Observable Object with ngFor and Async Pipe Angular 2 How to get data from observable in angular2 How to catch exception correctly from http.request()? How to create an Observable from static data similar to http one in Angular?