Как использовать Observables для межкомпонентного взаимодействия

Я использую Observables и Subjects из Rxjs для связи между двумя компонентами. Вот сервисная часть:

import {
  Injectable,
  EventEmitter,
  Output
} from '@angular/core';
import {
  HttpClientModule,
  HttpClient
} from '@angular/common/http';

import {
  Observable
} from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import {
  Subject
} from 'rxjs/Subject';


import {
  AppConstants
} from './../config/constants';

@Injectable()

export class GlobalService {
  private subject = new Subject < any > ();
  @Output() LoggedIn: EventEmitter < any > = new EventEmitter();
  mfaData: any;
  constructor(private http: HttpClient) {

  }

  validateCreds(postData, institutionId, customerId) {
    return this.http.post(AppConstants.baseUrl + AppConstants.serverRoutes.validateCreds + institutionId + '/' + customerId, postData)
      .subscribe(response => {
        console.log(response);
        if (response['status'] == 203) {
          this.mfaData = response['body'].questions;
          if (this.mfaData[0].choices || this.mfaData[0].imageChoices) {
            console.log('hey there');
            this.subject.next({
              mfaData: JSON.stringify(this.mfaData)
            });
           
          }
        }
      })
  }


  

  refreshHeaders(): Observable < any > {
    return this.subject.asObservable();
  }





}
and I am calling the subscriber from the constructor of another component the snippet of that is:

import {
  Subscription
} from 'rxjs/Subscription';
import {
  GlobalService
} from '../../../providers/global.serivce';

export class MfaChallengeComponent implements OnInit {
  subscription: Subscription;
  constructor(private activatedRoute: ActivatedRoute, private bankService: BanksService, private globalService: GlobalService) {
    this.subscription = this.globalService.refreshHeaders().subscribe(message => {
      console.log(message);
    });
  }
}

Но когда я получаю данные из серверной части, я вызываю следующий метод субъекта и снова вызываю его в конструкторе другого компонента. Это не работает, хотя я видел примеры того, как это работает. Услуга была внедрена глобально.


person Raghav    schedule 06.10.2017    source источник
comment
Ниже уже есть действительно хорошее объяснение. Я бы только добавил, что с угловой точки зрения constructor очень редко бывает хорошим местом для вызова службы. Вместо этого обычно должно происходить ngOnInit().   -  person Igor Soloydenko    schedule 08.10.2017


Ответы (1)


Боюсь, вы неправильно понимаете некоторые основные концепции RxJS. В RxJS субъекты являются горячими объектами наблюдения. Горячая наблюдаемая будет генерировать события вне зависимости от того, кто ее слушает или нет. (ознакомьтесь с этой статьей для получения дополнительной информации https://blog.thoughtram.io/angular/2016/06/16/cold-vs-hot-observables.html).

Итак, скажем, в вашем сервисе вы выполняете внутренний вызов, где вы «передаете» результат этого вызова через субъект. Позже в коде запускается ваш компонент, выполняется конструктор, и вы начинаете слушать субъект. Событие, которое вы "пропустили" через субъекта, к сожалению, уже прошло.

Чтобы исправить это, вы можете изменить тему с помощью ReplaySubject(1). Если я не ошибаюсь, это должно решить проблему.

Однако есть некоторые другие проблемы с вашим кодом. Тот факт, что вы передаете результат через субъект, здесь не нужен. Если я посмотрю на ваш код, я думаю, что вы хотите сделать внутренний вызов один раз, а затем кэшировать результат. Для этого существует специальный оператор, который называется shareReplay. Используя его, ваш код будет выглядеть так:

export class GlobalService {
  cachedObservable$;
  @Output() LoggedIn: EventEmitter < any > = new EventEmitter();
  mfaData: any;
  constructor(private http: HttpClient) {
  }

  validateCreds(postData, institutionId, customerId) {
    this.cachedObservable$ = this.http.post(AppConstants.baseUrl + AppConstants.serverRoutes.validateCreds + institutionId + '/' + customerId, postData)
      .map(response => {
        if (response['status'] == 203) {
          this.mfaData = response['body'].questions;
          if (this.mfaData[0].choices || this.mfaData[0].imageChoices) {
            return {
              mfaData: JSON.stringify(this.mfaData)
            };
          }
        }
      })
      .shareReplay(1);
  }

  refreshHeaders(): Observable < any > {
    return this.cachedObservable$;
  }
}

Вы создаете наблюдаемую, которая будет выполняться один раз, а результат впоследствии будет кэшироваться. Вы должны стараться избегать использования подписок в сервисах и темах, насколько это возможно. Чтобы узнать больше о shareReplay, ознакомьтесь с записью в блоге об операторах многоадресной рассылки в RxJS, которую я написал: https://blog.kwintenp.com/multicasting-operators-in-rxjs

person KwintenP    schedule 06.10.2017
comment
Привет, я попробовал код с некоторыми изменениями, и он начал работать, но проблема, которая возникает снова, заключается в том, что когда я передаю данные через метод next объекта, вызывается подписка, и она прослушивает событие. Но когда вызывается тот же компонент с функцией слушателя, я не получаю данные. - person Raghav; 12.10.2017