web-dev-qa-db-fra.com

Création et retour de Observable à partir du Angular 2 Service

C'est plus une question de "meilleures pratiques". Il y a trois joueurs: un Component, un Service et un Model. Le Component appelle le Service pour obtenir des données d'une base de données. La Service utilise:

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

renvoyer un Observable.

La Component pourrait simplement s'abonner à la Observable:

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

Cependant, ce que je veux vraiment, c’est que la Service renvoie un objet Array of Model créé à partir des données que la Service a extraite de la base de données. J'ai réalisé que le Component pourrait simplement créer ce tableau dans la méthode subscribe, mais je pense que ce serait plus propre si le service le faisait et le rendait disponible pour le Component.

Comment le Service peut-il créer un nouveau Observable contenant ce tableau et le renvoyer?

124
Joseph Genchik

UPDATE: 24/09/16 Angular 2.0 Stable

Cette question génère encore beaucoup de trafic, je voulais donc la mettre à jour. Avec la folie des changements apportés par les candidats Alpha, Beta et 7 RC, j'ai arrêté de mettre à jour mes réponses SO jusqu'à ce qu'elles deviennent stables.

C'est le cas idéal pour utiliser Subjects et ReplaySubjects

Je personnellement préfère utiliser ReplaySubject(1) car cela permet de transmettre la dernière valeur stockée lorsque de nouveaux abonnés se connectent même en retard:

let project = new ReplaySubject(1);

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result));

http.get('path/to/whatever/projects/1234').subscribe(result => {
    //Push onto subject
    project.next(result));

    //add delayed subscription AFTER loaded
    setTimeout(()=> project.subscribe(result => console.log('Delayed Stream:', result)), 3000);
});

//Output
//Subscription Streaming: 1234
//*After load and delay*
//Delayed Stream: 1234

Ainsi, même si j'attache tard ou si je dois charger plus tard, je peux toujours recevoir le dernier appel sans m'inquiéter de rater le rappel.

Cela vous permet également d’utiliser le même flux pour appuyer sur:

project.next(5678);
//output
//Subscription Streaming: 5678

Mais que se passe-t-il si vous êtes sûr à 100% que vous ne devez téléphoner qu'une seule fois? Laisser des sujets et des observables ouverts n’est pas une bonne chose, mais il y a toujours cela "What If?"

C'est là que AsyncSubject entre en jeu.

let project = new AsyncSubject();

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result),
                  err => console.log(err),
                  () => console.log('Completed'));

http.get('path/to/whatever/projects/1234').subscribe(result => {
    //Push onto subject and complete
    project.next(result));
    project.complete();

    //add a subscription even though completed
    setTimeout(() => project.subscribe(project => console.log('Delayed Sub:', project)), 2000);
});

//Output
//Subscription Streaming: 1234
//Completed
//*After delay and completed*
//Delayed Sub: 1234

Impressionnant! Même si nous avons fermé le sujet, il a quand même répondu avec la dernière chose chargée.

Une autre chose est de savoir comment nous avons souscrit à cet appel http et traité la réponse. Map est idéal pour traiter la réponse.

public call = http.get(whatever).map(res => res.json())

Mais si nous devions imbriquer ces appels? Oui, vous pouvez utiliser des sujets ayant une fonction spéciale:

getThing() {
    resultSubject = new ReplaySubject(1);

    http.get('path').subscribe(result1 => {
        http.get('other/path/' + result1).get.subscribe(response2 => {
            http.get('another/' + response2).subscribe(res3 => resultSubject.next(res3))
        })
    })
    return resultSubject;
}
var myThing = getThing();

Mais c'est beaucoup et signifie que vous avez besoin d'une fonction pour le faire. Entrez FlatMap :

var myThing = http.get('path').flatMap(result1 => 
                    http.get('other/' + result1).flatMap(response2 => 
                        http.get('another/' + response2)));

Doux, la var est une observable qui récupère les données de l’appel http final.

OK c'est super mais je veux un service angular2!

Je vous ai compris:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { ReplaySubject } from 'rxjs';

@Injectable()
export class ProjectService {

  public activeProject:ReplaySubject<any> = new ReplaySubject(1);

  constructor(private http: Http) {}

  //load the project
  public load(projectId) {
    console.log('Loading Project:' + projectId, Date.now());
    this.http.get('/projects/' + projectId).subscribe(res => this.activeProject.next(res));
    return this.activeProject;
  }

 }

 //component

@Component({
    selector: 'nav',
    template: `<div>{{project?.name}}<a (click)="load('1234')">Load 1234</a></div>`
})
 export class navComponent implements OnInit {
    public project:any;

    constructor(private projectService:ProjectService) {}

    ngOnInit() {
        this.projectService.activeProject.subscribe(active => this.project = active);
    }

    public load(projectId:string) {
        this.projectService.load(projectId);
    }

 }

Je suis un grand fan d'observateurs et d'observables, alors j'espère que cette mise à jour aidera!

Réponse originale

Je pense que ceci est un cas d'utilisation de Observable Subject ou de Angular2 the EventEmitter.

Dans votre service, vous créez une EventEmitter qui vous permet de transmettre des valeurs dessus. En Alpha 45 vous devez le convertir avec toRx(), mais je sais qu'ils travaillaient pour se débarrasser de cela, donc en Alpha 46 vous pourrez peut-être simplement renvoyer le EvenEmitter.

class EventService {
  _emitter: EventEmitter = new EventEmitter();
  rxEmitter: any;
  constructor() {
    this.rxEmitter = this._emitter.toRx();
  }
  doSomething(data){
    this.rxEmitter.next(data);
  }
}

De cette façon, il y a le EventEmitter unique que vos différentes fonctions de service peuvent maintenant utiliser.

Si vous souhaitez renvoyer un observable directement à partir d'un appel, vous pouvez procéder de la manière suivante:

myHttpCall(path) {
    return Observable.create(observer => {
        http.get(path).map(res => res.json()).subscribe((result) => {
            //do something with result. 
            var newResultArray = mySpecialArrayFunction(result);
            observer.next(newResultArray);
            //call complete if you want to close this stream (like a promise)
            observer.complete();
        });
    });
}

Cela vous permettrait de le faire dans le composant: peopleService.myHttpCall('path').subscribe(people => this.people = people);

Et déconner avec les résultats de l'appel dans votre service.

J'aime créer lui-même le flux EventEmitter au cas où je devrais y accéder à partir d'autres composants, mais je pouvais voir que les deux méthodes fonctionnaient ...

Voici un plunker qui montre un service de base avec un émetteur d'événement: Plunkr

154
Dennis Smolek

Ceci est un exemple tiré de Angular2 docs de la manière dont vous pouvez créer et utiliser vos propres observables:

Le 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)
  }
}

Le composant

    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');
      }
    }

Des exemples complets et pratiques sont disponibles ici: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

29
tibbus

J'aimerais ajouter que si l'objet créé est statique et qu'il ne passe pas par http, il est possible de faire quelque chose comme ça:

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: Pour Angular, le mappage 7.xx doit être effectué à l'aide de pipe () comme décrit ici ( https: //stackoverflow.com/a/54085359/98616 ):

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
      }
    }

de la réponse à ma question sur les observateurs et les données statiques: https://stackoverflow.com/a/35219772/98616

18
Michail Michailidis

Je suis un peu en retard pour la fête, mais je pense que mon approche a l'avantage de ne pas utiliser EventEmitters and Subjects.

Alors, voici mon approche. Nous ne pouvons pas sortir de subscribe (), et nous ne voulons pas. Dans cet esprit, notre service retournera un Observable<T> avec un observateur qui a notre précieuse cargaison. A partir de l'appelant, nous allons initialiser une variable, Observable<T>, et obtenir le Observable<T> du service. Ensuite, nous nous abonnerons à cet objet. Enfin, vous obtenez votre "T"! de votre service.

Tout d’abord, notre service clientèle, mais le vôtre ne passe pas les paramètres, c’est plus réaliste:

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, comme vous pouvez le constater, nous renvoyons une Observable de type "people". La signature de la méthode, même le dit! Nous rentrons l'objet _people dans notre observateur. Nous accéderons à ce type à partir de notre appelant dans le composant, ensuite!

Dans le composant:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

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

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

Nous initialisons notre _peopleObservable en renvoyant ce Observable<people> de notre PeopleService. Ensuite, nous nous abonnons à cette propriété. Enfin, nous définissons this.people sur notre réponse de données (people).

Cette architecture présente un avantage majeur par rapport au service typique: modèle map (...) et composant: "subscribe (...)". Dans le monde réel, nous devons mapper le json sur nos propriétés de notre classe et, parfois, nous y fabriquons des produits personnalisés. Donc, cette cartographie peut se produire dans notre service. Et, généralement, comme notre appel de service ne sera pas utilisé une seule fois, mais probablement dans d’autres endroits de notre code, nous n’avons pas à effectuer ce mappage dans certains composants. De plus, si nous ajoutons un nouveau champ aux gens? ....

15
LargeDachshund

Notez que vous utilisez Observable # map pour convertir l'objet brut Response que votre observable de base émet en une représentation analysée de la réponse JSON.

Si je vous ai bien compris, vous voulez à nouveau map. Mais cette fois, nous convertissons ce JSON brut en instances de votre Model. Donc, vous feriez quelque chose comme:

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

Donc, vous avez commencé avec un observable qui émet un objet Response, transformé en observable émettant un objet du JSON analysé de cette réponse, puis transformé en un autre observable transformant ce JSON brut en tableau de vos modèles.

7
julioolvr

Dans le fichier service.ts -

une. importation 'de' d'observable/de
b. créer une liste json
c. retourne un objet json en utilisant 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);
    }
};

Dans le composant où nous appelons la fonction get du service -

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