#angular #rxjs #angular2-observables #rxjs-observables
#угловой #rxjs #angular2-наблюдаемые #rxjs-наблюдаемые
Вопрос:
У меня есть две наблюдаемые, одна зависит от данных из другой.
Когда данные в первом наблюдаемом изменяются, он должен обновлять второй наблюдаемый.
К сожалению, это работает не так, как я ожидаю.
Вот основной наблюдаемый (который работает нормально)
export class LocaleService {
locale$: Observable<string>;
constructor(private router: Router) {
this.locale$ = router.events.pipe(
filter(evnt => envt instanceof ActivationEnd),
map((evnt: ActivationEnd) => evnt.snapshot.paramMap.get('locale')),
shareReplay(1),
);
}
}
Вот второе наблюдаемое, которое должно обновляться на основе значения первого наблюдаемого (см. Выше):
// This type is more complex, but made it simple for demonstrating the problem
interface ICountries { code: string; }
export class DataService {
countries$: Observable<ICountries>;
constructor(private http: HttpClient, private localService: LocaleService) {
this.localeService.locale$.subscribe(locale => {
// this is only here for debug purposes, showing it does change
console.log('changed locale', locale);
});
this.countries$ = localeService.locale$.pipe(
tap(locale => { console.log('http updated', locale); }), // called first time only, not on locale changes...
switchMap(locale => this.http.get<ICountries>(`http://localhost:8080/countries?lang=${locale}`)),
shareReplay(1),
);
}
}
и если это имеет значение, способ, которым я использую countries$
, выглядит следующим образом (опять же, упрощенный, чтобы продемонстрировать проблему) :
@Component({
template: `<ng-container *ngIf="countries$ | async as countries">{{ countries.code }}</ng-container>`,
})
export class CountryComponent implements OnInit {
countries$: Observable<ICountries>;
constructor(private dataService: DataService) {
this.countries$ = dataService.countries$;
}
}
Я боролся с этим уже несколько дней, искал ответы на StackOverflow, на многих других форумах и спрашивал друзей, которые чаще используют наблюдаемые, но, похоже, я не могу решить эту проблему. Любая помощь будет с благодарностью!
Комментарии:
1. @MichaelD правильно, tap вызывается только в первый раз, а не после изменения locale $. Я попытался удалить
shareReply(1)
LocaleService, но это не сработало, потому что DataService тогда не получает никаких обновлений. Или, возможно, я вас неправильно понял?2. вы пытались удалить shareReplay(1) из локального $ observable?
3. @FatehMohamed — MichaelD задал точно такой же вопрос, но затем удалил свой вопрос. Смотрите Мой комментарий над вашим вопросом.
4. Вы подписаны на свой наблюдаемый? Возможно
dataService.countries$.subscribe();
, где-то, чего мы здесь не видим? На самом деле,shareReplay(1)
он должен действовать как subscribe() , (это должно привести к оценке потока), но на самом деле его не следует использовать таким образом. Хотя просто предположение.5. @MrkSef нет подписки за пределами того, что вы видите, просто большая структура данных в шаблоне. Я попытался добавить дополнительный
.subscribe()
кcountries$
, но, к сожалению, это не имеет значения.
Ответ №1:
Я не совсем уверен, но вы могли бы попытаться разделить наблюдаемые на две разные части. На данный момент это один поток, как показано ниже
this.countries = localeService.locale$.pipe(
tap(locale => { console.log('http updated', locale); }), // called first time only, not on locale changes...
switchMap(locale => this.http.get<ICountries>(`http://localhost:8080/countries?lang=${locale}`)),
shareReplay(1),
tap(locale => { console.log('http updated', locale); }), // called first time only, not on locale changes...
switchMap(locale => this.http.get<ICountries>(`http://localhost:8080/countries?lang=${locale}`)),
shareReplay(1)
);
Вот мое предложение. Я только что разделил наблюдаемый поток на два разных потока, используя ReplaySubject
буфер 1 (такой же, как shareReplay
в вопросе)
LocaleService
export class LocaleService {
localeSource = new ReplaySubject<string>(1);
locale$ = this.localeSource.asObservable();
constructor(private router: Router) {
router.events.pipe(
filter(evnt => envt instanceof ActivationEnd),
map((evnt: ActivationEnd) => evnt.snapshot.paramMap.get('locale'))
).subscribe(event => this.localeSource.next(event));
}
}
DataService
// This type is more complex, but made it simple for demonstrating the problem
interface ICountries { code: string; }
export class DataService {
countriesSource = new ReplaySubject<ICountries>(1);
countries$ = this.countriesSource.asObservable();
constructor(private http: HttpClient, private localeService: LocaleService) {
this.localeService.locale$.pipe(
tap(locale => { console.log('http updated', locale); }),
switchMap(locale => this.http.get<ICountries>(`http://localhost:8080/countries?lang=${locale}`))
).subscribe(countries => this.countriesSource.next(countries));
}
}
CountryComponent
@Component({
template: `<ng-container *ngIf="(countries$ | async) as countries">{{ countries?.code }}</ng-container>`,
})
export class CountryComponent implements OnInit {
countries$: Observable<ICountries>;
constructor(private dataService: DataService) {
this.countries$ = this.dataService.countries$;
}
}
Комментарии:
1. Я полагаю, вы хотели удалить
this.locale$ =
иthis.countries$ =
? Несмотря на это, я попробовал это, на бумаге это звучало хорошо, но, к сожалению, я все равно получаю тот же результат.2. @JustinN: Вы правы. Я забыл удалить назначения в конструкторах служб. Я обновил ответ. То есть вы имеете
switchMap
в виду, что запускается вDataService
, ноtap
нет?3. Я имею в виду, что оба DataService видят первое значение из
locale$
, но он никогда не получает обновлений (tap
ниswitchMap
или ). Однако, если я добавлю LocalService. locale$.subscribe(console.log) — я вижу, что locale$ действительно меняется, он просто никогда не переходит каскадом к методам канала DataServices.4. Это звучит странно.
5. Очень похоже, и отчасти поэтому я решил попробовать SO за помощью, поскольку я не могу понять это, и никто другой, кого я могу спросить … 😀