Angular5 HttpClient, установить переменные после вызова GET API - PullRequest
0 голосов
/ 02 мая 2018

У меня есть служба (назовем ее MySharedService), которую используют несколько компонентов. Внутри MySharedService я вызываю другую службу, которая выполняет вызовы API. MySharedService содержит объект JavaScript, назначенный после моего GET вызова.

Моя проблема в том, что мои компоненты полагаются на этот объект JavaScript для установки своих значений в своих конструкторах. Как я могу установить их значения, когда этот JavaScript может быть неопределенным, потому что вызов API еще не завершен? Вот образец:

ApiService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
/* Other imports */

@Injectable()
export class ApiService {

    constructor(private http: HttpClient) { }

    getTestData(): Observable<MyDataInterface> {
        const API_URL = 'some_url_here';
        return this.http.get<MyDataInterface>(API_URL, { observe: 'response' });
    }
}

MySharedService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
/* Other imports */

@Injectable()
export class MySharedService {

    myData: MyDataInterface;

    constructor(private apiServie: ApiService) {
        this.apiService.getTestData().subscribe((response) => {
            this.myData = { ...response.body };
            console.log(this.myData);
        });
    }
}

TestComponent

import { Component, OnInit } from '@angular/core';
import { MySharedService } from '../../../services/myshared.service';
/* Other imports */    

@Component({
    selector: 'app-test',
    templateUrl: './test.component.html',
    styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {

    name: string;
    /* Other variables here. */

    constructor(private mySharedService: MySharedService) {
        // Error here because myData has not been populated, yet.
        this.name = this.mySharedService.myData.name;
    }
}

Таким образом, проблема возникает внутри моих компонентов, когда я пытаюсь получить доступ к данным из объекта myData, потому что он еще не заполнен (console.log() в конечном итоге выводит данные через пару секунд). Как я могу получить значения? Я хочу вызвать службу отдыха только один раз и сохранить объект внутри MySharedService, после чего все мои компоненты будут использовать этот объект.

Ответы [ 4 ]

0 голосов
/ 02 мая 2018

Вы должны использовать BehaviorSubject

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
/* Other imports */

@Injectable()
export class MySharedService {

    myData: BehaviorSubject<MyDataInterface> = new BehaviorSubject(null);

    constructor(private apiServie: ApiService) {
        this.apiService.getTestData().subscribe((response) => {
            this.myData.next({ ...response.body });
            console.log(this.myData.getValue());
        });
    }
}

Это плохая практика - делать подписки в конструкторах компонентов, вместо этого используйте ngOnInit ловушку жизненного цикла.

export class TestComponent implements OnInit {

    name: string;
    /* Other variables here. */

    constructor(private mySharedService: MySharedService) {
    }

    ngOnInit() {
        this.mySharedService.myData.subscribe((data: MyDataInterface) => { 
            if (data)
                this.name = data.name;
        });
    }
}

Сетевой вызов будет сделан только один раз, и данные будут кэшированы в BehaviorSubject, на который будут подписаны все компоненты.

Теперь зачем использовать BehaviorSubject вместо Observable? Потому что,

  • При подписке BehaviorSubject возвращает последнее значение, тогда как обычная наблюдаемая срабатывает только при получении следующего.

  • Если вы хотите получить последнее значение BehaviorSubject в ненаблюдаемом коде (без подписки), вы можете использовать метод getValue().

0 голосов
/ 02 мая 2018

Внутри вашего TestComponent при вызове службы jus подписывайтесь, как указано ниже, также вызывайте ее в ngOnInit событие

ngOnInit() {

  if (this.mySharedService.myData) {
    this.name = this.mySharedService.myData.name;
  } else {
    this.apiService.getTestData().subscribe(
      data => {
        // On Success
        this.mySharedService.myData = data.body.myData;
        this.name = this.mySharedService.myData.name;
      },
      err => {
        // On Error
      });
  }

}
0 голосов
/ 02 мая 2018

Попробуй это и скажи мне.

MySharedService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
/* Other imports */

@Injectable()
export class MySharedService {

    myData: MyDataInterface;

    constructor(public apiService: ApiService) {
    }

    fetchData() {
       return this.apiService.getTestData();
    }

}

TestComponent

import { Component, OnInit } from '@angular/core';
import { MySharedService } from '../../../services/myshared.service';
/* Other imports */    

@Component({
    selector: 'app-test',
    templateUrl: './test.component.html',
    styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {

    name: string;
    /* Other variables here. */

    constructor(private mySharedService: MySharedService) {
        this.mySharedService.fetchData().subscribe((resp: any) => {
        this.name = resp.body; 
     });
    }
}
0 голосов
/ 02 мая 2018

Я делаю это стандартной практикой для обработки значений null / undefined из любого вида pub / sub

MySharedService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
/* Other imports */

@Injectable()
export class MySharedService {

public message = new Subject<any>();

myData: MyDataInterface;

constructor(private apiServie: ApiService) {
    this.apiService.getTestData().subscribe((response) => {
        if(response){
           this.myData = { ...response.body };
           this.sendUpdate(this.myData);
           console.log(this.myData);
        }else console.log("I guess you could do some sort of handling in the else")
    });


    sendUpdate(message) {
        this.message.next(message);
    }

    getUpdate(): Observable<any> {
    return this.message.asObservable();
    }

    clearUpdate() {
        this.message.next();
    }

}

TestComponent

import { Component, OnInit } from '@angular/core';
import { MySharedService } from '../../../services/myshared.service';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/takeWhile';
/* Other imports */    

@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit, OnDestroy {

subscription: Subscription;
private alive: boolean = true;
name: string;
/* Other variables here. */

constructor(private mySharedService: MySharedService) {
    // Error here because myData has not been populated, yet.
    this.subscription = this.mySharedService.getUpdate().takeWhile(() => this.alive).subscribe(message => {
            if (message) {
               this.name = message;
            }
        });
    this.name = this.mySharedService.myData.name;
   }
}
    //Best practice to clean up observables
    ngOnDestroy() {
    this.updateService.clearUpdate();
    this.subscription.unsubscribe();
    this.alive = false;
}

Внесенные мной изменения в полной мере используют современный сервис Angular, использующий функциональность подписки Rxjs IMO. За последний год я разделил время между

A) использование хранилищ с Flux / Redux / Mobx для передачи разрешенных данных по приложению. Дождитесь разрешения данных с помощью наблюдаемых или используйте методы Select для получения данных

или

B) использование шаблона pub / sub для получения обновлений от служб, когда у них есть фактические данные, которые вам нужны. Я считаю, что его проще использовать, и он пригодится для небольших приложений или для вставки директив и компонентов тут и там.

Ключ, который я думаю в вашей ситуации, - это обрабатывать, где есть данные, а также когда их нет. Хорошим примером этого является то, что «... загрузка» текста, что Angular предварительно заполняет представление каждого компонента, который вы создаете с помощью Angular CLI.

Просто помните, если вы собираетесь использовать наблюдаемые для очистки их с помощью ngOnDestroy (или может произойти непреднамеренное поведение)

Удачи !!

...