Tạo và trả lại Có thể quan sát được từ Dịch vụ Angular 2


132

Đây là nhiều hơn một câu hỏi "thực hành tốt nhất". Có ba người chơi: a Component, a Servicevà a Model. Các Componentđược gọi Serviceđể lấy dữ liệu từ một cơ sở dữ liệu. Các Servicelà cách sử dụng:

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

để trả lại một Observable.

Chỉ Componentcó thể đăng ký vào Observable:

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

Tuy nhiên, điều tôi thực sự muốn là Servicetrả về một Array of Modelđối tượng được tạo từ dữ liệu Serviceđược lấy từ cơ sở dữ liệu. Tôi nhận ra rằng Componentchỉ có thể tạo mảng này trong phương thức đăng ký, nhưng tôi nghĩ nó sẽ sạch hơn nếu dịch vụ làm điều đó và làm cho nó có sẵn cho Component.

Làm thế nào có thể Servicetạo một Observablemảng mới , chứa mảng đó và trả về nó?

Câu trả lời:


158

CẬP NHẬT: 9/24/16 Angular 2.0 Ổn định

Câu hỏi này nhận được rất nhiều lưu lượng truy cập, vì vậy, tôi muốn cập nhật nó. Với sự điên rồ của những thay đổi từ các ứng cử viên Alpha, Beta và 7 RC, tôi đã ngừng cập nhật các câu trả lời SO của mình cho đến khi chúng ổn định.

Đây là trường hợp hoàn hảo để sử dụng Chủ đềReplaySubjects

Cá nhân tôi thích sử dụng ReplaySubject(1)vì nó cho phép giá trị được lưu trữ cuối cùng được thông qua khi người đăng ký mới đính kèm ngay cả khi trễ:

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

Vì vậy, ngay cả khi tôi đính kèm muộn hoặc cần tải sau, tôi luôn có thể nhận được cuộc gọi mới nhất và không lo bị lỡ cuộc gọi lại.

Điều này cũng cho phép bạn sử dụng cùng một luồng để đẩy xuống:

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

Nhưng nếu bạn chắc chắn 100%, rằng bạn chỉ cần thực hiện cuộc gọi một lần thì sao? Rời khỏi các đối tượng mở và quan sát không tốt nhưng luôn có câu "Điều gì sẽ xảy ra?"

Đó là nơi AsyncSubject xuất hiện.

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

Tuyệt vời! Mặc dù chúng tôi đã đóng chủ đề, nó vẫn trả lời với điều cuối cùng nó tải.

Một điều nữa là cách chúng tôi đăng ký cuộc gọi http đó và xử lý phản hồi. Bản đồ là tuyệt vời để xử lý các phản ứng.

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

Nhưng nếu chúng ta cần lồng những cuộc gọi đó thì sao? Có, bạn có thể sử dụng các đối tượng có chức năng đặc biệt:

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();

Nhưng đó là rất nhiều và có nghĩa là bạn cần một chức năng để làm điều đó. Nhập Bản đồ phẳng :

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

Thật ngọt ngào, varcó thể quan sát được dữ liệu từ cuộc gọi http cuối cùng.

OK đó là tuyệt vời nhưng tôi muốn một dịch vụ angular2!

Tôi có bạn:

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

 }

Tôi là một fan hâm mộ lớn của các nhà quan sát và quan sát nên tôi hy vọng bản cập nhật này có ích!

Câu trả lời gốc

Tôi nghĩ rằng đây là một trường hợp sử dụng của việc sử dụng một đề Quan sát hoặc trong Angular2các EventEmitter.

Trong dịch vụ của bạn, bạn tạo một EventEmittercái cho phép bạn đẩy các giá trị lên nó. Trong Alpha 45 bạn phải chuyển đổi nó với toRx(), nhưng tôi biết họ đã làm việc để loại bỏ điều đó, vì vậy trong Alpha 46 bạn có thể chỉ cần trả lại EvenEmitter.

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

Cách này có một EventEmitterchức năng mà các chức năng dịch vụ khác nhau của bạn bây giờ có thể đẩy lên.

Nếu bạn muốn trả lại một quan sát trực tiếp từ một cuộc gọi, bạn có thể làm một cái gì đó như thế này:

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

Điều đó sẽ cho phép bạn làm điều này trong thành phần: peopleService.myHttpCall('path').subscribe(people => this.people = people);

Và lộn xộn với kết quả từ cuộc gọi trong dịch vụ của bạn.

Tôi thích tự tạo EventEmitterluồng trong trường hợp tôi cần truy cập vào luồng từ các thành phần khác, nhưng tôi có thể thấy cả hai cách hoạt động ...

Đây là một plunker hiển thị một dịch vụ cơ bản với trình phát sự kiện: Plunkr


Tôi đã thử cách tiếp cận này nhưng nhận được "Không thể sử dụng 'mới' với một biểu thức có kiểu thiếu cuộc gọi hoặc xây dựng chữ ký" -error. Bất cứ ai có một ý tưởng về những gì để làm?
Spock

3
@Spock thông số kỹ thuật dường như đã cập nhật kể từ câu hỏi ban đầu này. Bạn không còn cần "cái mới" để có thể quan sát được vì nó làm điều này cho bạn. Đơn giản chỉ cần loại bỏ cái mới và cho tôi biết những gì xảy ra. Bây giờ tôi đang gặp rắc rối với một số thứ, nếu nó cũng phù hợp với bạn, tôi sẽ cập nhật câu trả lời này
Dennis Smolek

1
Sử dụng EventEmittercho bất cứ điều gì nhưng @Output()không được khuyến khích. Xem thêm stackoverflow.com/questions
432376854 / Mạnh

@ GünterZöchbauer, Vâng, bây giờ ... Vào thời điểm đó, nó sẽ là EventEmitters khắp nơi nhưng họ đã được chuẩn hóa trên Rx Observables. Ví dụ quan sát của tôi vẫn hoạt động nhưng nếu bạn định sử dụng ví dụ EventEuctor tôi đã đề xuất, tôi sẽ đề xuất sử dụng Đối tượng trực tiếp: github.com/Reactive-Extensions/RxJS/blob/master/doc/api/
tựa

1
@maxisam Cảm ơn bạn đã chỉnh sửa, mặc dù câu trả lời là / liên quan đến việc Alpha loại bỏ "mới" cho
Đài

29

Đây là một ví dụ từ các tài liệu Angular2 về cách bạn có thể tạo và sử dụng các Đài quan sát của riêng mình:

Dịch vụ

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

Thanh phân

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

Ví dụ đầy đủ và hoạt động có thể được tìm thấy ở đây: https://angular.io/docs/ts/latest/cookbook/component-cransication.html#!#bidirectional-service


18

Tôi muốn thêm rằng nếu đối tượng được tạo là tĩnh và không đi qua http thì có thể thực hiện được điều tương tự:

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

Chỉnh sửa: Đối với ánh xạ Angular 7.xx cần được thực hiện bằng cách sử dụng pipe () như được mô tả tại đây ( 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
      }
    }

từ câu trả lời cho câu hỏi của tôi về người quan sát và dữ liệu tĩnh: https://stackoverflow.com/a353219772/986160


17

Tôi đến bữa tiệc muộn một chút, nhưng tôi nghĩ cách tiếp cận của tôi có lợi thế là nó thiếu việc sử dụng EventEmitters và Chủ đề.

Vì vậy, đây là cách tiếp cận của tôi. Chúng tôi không thể thoát khỏi đăng ký () và chúng tôi không muốn. Theo đó, dịch vụ của chúng tôi sẽ trả lại Observable<T>một người quan sát có hàng hóa quý giá của chúng tôi. Từ người gọi, chúng tôi sẽ khởi tạo một biến Observable<T>và nó sẽ nhận được dịch vụ Observable<T>. Tiếp theo, chúng tôi sẽ đăng ký vào đối tượng này. Cuối cùng, bạn nhận được "T"! từ dịch vụ của bạn.

Đầu tiên, dịch vụ con người của chúng tôi, nhưng dịch vụ của bạn không vượt qua các tham số, điều đó thực tế hơn:

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, như bạn có thể thấy, chúng tôi sẽ trả về một Observableloại "người". Chữ ký của phương pháp, thậm chí nói như vậy! Chúng tôi nhét _peoplevật vào người quan sát của chúng tôi. Chúng tôi sẽ truy cập loại này từ người gọi của chúng tôi trong Thành phần, tiếp theo!

Trong Thành phần:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

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

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

Chúng tôi khởi tạo _peopleObservablebằng cách trả lại Observable<people>từ chúng tôi PeopleService. Sau đó, chúng tôi đăng ký tài sản này. Cuối cùng, chúng tôi đặt phản hồi this.peopledữ liệu ( people) của chúng tôi .

Kiến trúc sư dịch vụ theo kiểu này có một ưu điểm lớn so với dịch vụ điển hình: bản đồ (...) và thành phần: mẫu "đăng ký (...)". Trong thế giới thực, chúng ta cần ánh xạ json đến các thuộc tính của chúng ta trong lớp và đôi khi, chúng ta thực hiện một số công cụ tùy chỉnh ở đó. Vì vậy, ánh xạ này có thể xảy ra trong dịch vụ của chúng tôi. Và, thông thường, bởi vì cuộc gọi dịch vụ của chúng tôi sẽ được sử dụng không chỉ một lần, nhưng, có lẽ, ở những nơi khác trong mã của chúng tôi, chúng tôi không phải thực hiện lại ánh xạ đó trong một số thành phần. Hơn nữa, nếu chúng ta thêm một lĩnh vực mới cho mọi người thì sao? ....


Tôi đồng ý rằng định dạng phải có trong dịch vụ và tôi cũng đã đăng một phương thức Có thể quan sát tiêu chuẩn nhưng lợi thế của Chủ thể trong dịch vụ là các chức năng khác có thể kích hoạt trên đó. Nếu bạn luôn chỉ cần cuộc gọi http trực tiếp thì tôi sẽ sử dụng phương pháp Có thể quan sát được ..
Dennis Smolek

9

Trong tệp service.ts -

a. nhập 'của' từ có thể quan sát / của
b. tạo một danh sách json
c. trả về đối tượng json bằng 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);
    }
};

Trong thành phần mà chúng ta gọi hàm get của dịch vụ -

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

Làm tốt lắm @Anirban, cũng chỉ có thể trả về (this.clientList);
foo-baar

7

Lưu ý rằng bạn đang sử dụng bản đồ # có thể quan sát để chuyển đổi Responseđối tượng thô mà cơ sở của bạn có thể quan sát được phát ra thành biểu diễn được phân tích cú pháp của phản hồi JSON.

Nếu tôi hiểu bạn chính xác, bạn muốn mapmột lần nữa. Nhưng lần này, chuyển đổi JSON thô đó thành các phiên bản của bạn Model. Vì vậy, bạn sẽ làm một cái gì đó như:

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

Vì vậy, bạn đã bắt đầu với một Observable phát ra một Responseđối tượng, biến nó thành một vật quan sát phát ra một đối tượng của JSON được phân tích cú pháp của phản hồi đó, và sau đó biến nó thành một vật thể quan sát khác có thể biến JSON thô đó thành một mảng các mô hình của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.