На основе вашего упрощенного сценария я построил рабочий пример, но самое интересное - понять, что происходит.
Прежде всего, я создал службу для имитации HTTP и избегания настоящих HTTP-вызовов:
export interface SomeData {
some: {
data: boolean;
};
}
@Injectable()
export class HttpClientMockService {
private cpt = 1;
constructor() {}
get<T>(url: string): Observable<T> {
return of({
some: {
data: true,
},
}).pipe(
tap(() => console.log(`Request n°${this.cpt++} - URL "${url}"`)),
// simulate a network delay
delay(500)
) as any;
}
}
Into AppModule
Я заменил настоящий HttpClient, чтобы использовать поддельный:
{ provide: HttpClient, useClass: HttpClientMockService }
Теперь общий сервис:
@Injectable()
export class SharedService {
private cpt = 1;
public myDataRes$: Observable<SomeData> = this.http
.get<SomeData>("some-url")
.pipe(share());
constructor(private http: HttpClient) {}
getSomeData(): Observable<SomeData> {
console.log(`Calling the service for the ${this.cpt++} time`);
return this.myDataRes$;
}
}
Если из метода getSomeData
вы вернете новый экземпляр, у вас будет 2 разных наблюдаемых. Независимо от того, используете ли вы долю или нет. Итак, идея состоит в том, чтобы подготовить запрос. CF myDataRes$
. Это просто запрос, за которым следует share
. Но он объявляется только один раз и возвращает эту ссылку из метода getSomeData
.
И теперь, если вы подписываетесь с двух разных компонентов на наблюдаемый (результат вызова службы), в вашей консоли будет следующее:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Как видите, у нас 2 звонка в сервис, но сделан только один запрос.
Ага!
И если вы хотите убедиться, что все работает должным образом, просто закомментируйте строку с помощью .pipe(share())
:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Request n°2 - URL "some-url"
Но ... Это далеко не идеально.
delay
в имитируемую службу - это круто, чтобы имитировать задержку в сети. Но также скрывает потенциальную ошибку.
Из воспроизведения stackblitz перейдите к компоненту second
и раскомментируйте setTimeout. Он позвонит в службу через 1 сек.
Мы замечаем, что теперь, даже если мы используем share
из службы, у нас есть следующее:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Request n°2 - URL "some-url"
Почему так? Потому что, когда первый компонент подписывается на наблюдаемое, в течение 500 мс ничего не происходит из-за задержки (или задержки в сети). Так что подписка все еще жива в это время. Как только задержка в 500 мс завершена, наблюдаемое завершается (это не долгоживущее наблюдаемое, точно так же, как HTTP-запрос возвращает только одно значение, это тоже, потому что мы используем of
).
Но share
- это не что иное, как publish
и refCount
. Публикация позволяет нам выполнять многоадресную рассылку результата, а refCount позволяет нам закрыть подписку, когда никто не слушает наблюдаемое.
Итак, с вашим решением с использованием общего ресурса, если один из ваших компонентов создается позже, чем требуется для выполнения первого запроса , у вас все равно будет еще один запрос.
Чтобы этого избежать, я не могу придумать блестящего решения. При использовании многоадресной рассылки нам пришлось бы использовать метод подключения, но где именно? Создание условия и счетчика, чтобы узнать, первый звонок или нет? Неправильно.
Так что это, вероятно, не лучшая идея, и я был бы рад, если бы кто-то мог предложить лучшее решение, но пока вот что мы можем сделать, чтобы сохранить наблюдаемое:
private infiniteStream$: Observable<any> = new Subject<void>().asObservable();
public myDataRes$: Observable<SomeData> = merge(
this
.http
.get<SomeData>('some-url'),
this.infiniteStream$
).pipe(shareReplay(1))
Поскольку infiniteStream $ никогда не закрывается, и мы объединяем оба результата плюс использование shareReplay(1)
, теперь у нас есть ожидаемый результат:
Один HTTP-вызов, даже если к сервису сделано несколько вызовов. Неважно, сколько времени займет первый запрос.
Вот демонстрация Stackblitz, чтобы проиллюстрировать все это: https://stackblitz.com/edit/angular-n9tvx7 < / а>
person
maxime1992
schedule
14.06.2018
publish
- это то, что вам нужно, что обычно сочетается сrefCount()
. Итак,getSomeData()
метод должен быть:return this.http.get<any>('...').pipe(publish(), refCount());
. - person Siri0S   schedule 14.06.2018publishReplay()
это то, что тебе нужно. Вот демонстрация - person Siri0S   schedule 15.06.2018set
иget
. - person Sampgun   schedule 29.11.2019