Usando o HTTP, chamamos um método que faz uma chamada de rede e retorna um http observável:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
Se considerarmos isso observável e adicionar vários assinantes:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
O que queremos fazer é garantir que isso não cause várias solicitações de rede.
Pode parecer um cenário incomum, mas na verdade é bastante comum: por exemplo, se o chamador assina o observável para exibir uma mensagem de erro e a transmite ao modelo usando o canal assíncrono, já temos dois assinantes.
Qual é a maneira correta de fazer isso nos RxJs 5?
Ou seja, isso parece funcionar bem:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
Mas essa é a maneira idiomática de fazer isso nos RxJs 5, ou deveríamos fazer outra coisa?
Nota: Conforme o novo Angular 5 HttpClient
, a .map(res => res.json())
parte em todos os exemplos agora é inútil, pois o resultado JSON agora é assumido por padrão.
fonte
Respostas:
Coloque em cache os dados e, se disponível, em cache, retorne isso, caso contrário, faça a solicitação HTTP.
Exemplo de plunker
Este artigo https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html é uma ótima explicação sobre como armazenar em cache
shareReplay
.fonte
do()
ao contrário demap()
não modifica o evento. Você também pode usarmap()
, mas deve garantir que o valor correto seja retornado no final do retorno de chamada..subscribe()
isso não precisa do valor, você pode fazer isso porque ele pode ficar apenasnull
(dependendo do quethis.extractData
retornar), mas IMHO isso não expressa bem a intenção do código.this.extraData
termina como deextraData() { if(foo) { doSomething();}}
outra forma, o resultado da última expressão é retornado, o que pode não ser o que você deseja.if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
Por sugestão do @Cristian, esta é uma maneira que funciona bem para observáveis HTTP, que emitem apenas uma vez e depois são concluídos:
fonte
share
operador pode ser uma escolha razoável (embora com alguns casos extremos desagradáveis). Para uma discussão aprofundada sobre as opções, consulte a seção de comentários nesta postagem do blog: blog.jhades.org/…publishLast().refCount()
não possa ser cancelada, uma vez que todas as assinaturas retornadas por observávelrefCount
foram canceladas, o efeito líquido é que a fonte observável será cancelada, cancelando-a se "em vôo"ATUALIZAÇÃO: Ben Lesh diz que no próximo lançamento menor após a 5.2.0, você poderá chamar apenas shareReplay () para realmente armazenar em cache.
ANTERIORMENTE.....
Em primeiro lugar, não use share () ou publishReplay (1) .refCount (), eles são iguais e o problema é que ele só compartilha se as conexões forem feitas enquanto o observável estiver ativo, se você se conectar após a conclusão , ele cria um novo observável novamente, tradução, sem realmente armazenar em cache.
Birowski deu a solução correta acima, que é usar ReplaySubject. ReplaySubject armazenará em cache os valores que você atribuir (bufferSize) no nosso caso 1. Ele não criará um novo observável como share () quando refCount chegar a zero e você fizer uma nova conexão, que é o comportamento correto para o armazenamento em cache.
Aqui está uma função reutilizável
Veja como usá-lo
Abaixo está uma versão mais avançada da função que pode ser armazenada em cache. Essa permite ter sua própria tabela de pesquisa + a capacidade de fornecer uma tabela de pesquisa personalizada. Dessa forma, você não precisa verificar isso._cache como no exemplo acima. Observe também que, em vez de passar o observável como o primeiro argumento, você passa uma função que retorna os observáveis; isso ocorre porque o Http do Angular é executado imediatamente; portanto, ao retornar uma função executada preguiçosamente, podemos decidir não chamá-lo se já estiver em nosso cache.
Uso:
fonte
const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();
:? Portanto, ele se comporta mais como qualquer outro operador ..O rxjs 5.4.0 possui um novo método shareReplay .
O autor diz explicitamente "ideal para lidar com coisas como armazenar em cache resultados AJAX"
rxjs PR # 2443 feat (shareReplay): adiciona
shareReplay
variante depublishReplay
fonte
de acordo com este artigo
tão dentro se as declarações apenas acrescentam
para
.map(...)
fonte
A versão 5.4.0 do rxjs (09-05-2017) adiciona suporte ao shareReplay .
Você pode modificar facilmente um serviço angular para usá-lo e retornar um observável com um resultado em cache que apenas fará a chamada http uma única vez (supondo que a primeira chamada tenha sido bem-sucedida).
Exemplo de serviço angular
Aqui está um serviço ao cliente muito simples que usa
shareReplay
.customer.service.ts
Observe que a atribuição no construtor pode ser movida para o método,
getCustomers
mas como os observáveis retornadosHttpClient
são "frios", isso é aceitável no construtor, pois a chamada http será feita apenas com a primeira chamada parasubscribe
.Além disso, a suposição aqui é que os dados retornados iniciais não ficam obsoletos durante a vida útil da instância do aplicativo.
fonte
Eu estrelou a pergunta, mas tentarei tentar.
Aqui está a prova :)
Há apenas um argumento:
getCustomer().subscribe(customer$)
Não estamos assinando a resposta api de
getCustomer()
, estamos assinando um ReplaySubject que é observável, que também pode se inscrever em um Observable diferente e (e isso é importante) manter seu último valor emitido e republicá-lo em qualquer um deles (ReplaySubject's ) assinantes.fonte
Encontrei uma maneira de armazenar o resultado do http em sessionStorage e usá-lo para a sessão, para que ele nunca chame o servidor novamente.
Eu usei para chamar a API do github para evitar o limite de uso.
Para sua informação, o limite de sessionStorage é de 5M (ou 4,75M). Portanto, não deve ser usado assim para um grande conjunto de dados.
------ editar -------------
Se você deseja atualizar os dados com o F5, que usa dados de memória em vez de sessionStorage;
fonte
sessionStorage
diz, eu os usaria apenas para dados que se espera que sejam consistentes para toda a sessão.getCustomer
no seu exemplo. ;) Então, só queria avisar alguns ppl que pode não vêem os riscos :)A implementação que você escolher dependerá se você deseja cancelar a assinatura () para cancelar sua solicitação HTTP ou não.
De qualquer forma, os decoradores TypeScript são uma ótima maneira de padronizar o comportamento. Este é o que eu escrevi:
Definição do decorador:
fonte
Property 'connect' does not exist on type '{}'.
da linhareturnValue.connect();
. Você pode elaborar?Dados de resposta HTTP em cache usando Rxjs Observer / Observable + Caching + Subscription
Veja o código abaixo
* aviso de isenção de responsabilidade: sou novo no rxjs, portanto, lembre-se de que posso estar abusando da abordagem observável / observador. Minha solução é puramente um conglomerado de outras soluções que encontrei e é a conseqüência de não conseguir encontrar uma solução simples e bem documentada. Portanto, estou fornecendo minha solução completa de código (como gostaria de ter encontrado) na esperança de que ajude outras pessoas.
* observe que essa abordagem se baseia livremente no GoogleFirebaseObservables. Infelizmente, não tenho a experiência / tempo adequados para replicar o que eles fizeram sob o capô. Mas a seguir, é uma maneira simplista de fornecer acesso assíncrono a alguns dados capazes de cache.
Situação : Um componente 'lista de produtos' é encarregado de exibir uma lista de produtos. O site é um aplicativo da web de página única com alguns botões de menu que filtram os produtos exibidos na página.
Solução : O componente "assina" um método de serviço. O método de serviço retorna uma matriz de objetos do produto, que o componente acessa através do retorno de chamada da assinatura. O método de serviço agrupa sua atividade em um Observador recém-criado e retorna o observador. Dentro desse observador, ele procura dados em cache e os repassa para o assinante (o componente) e retorna. Caso contrário, emite uma chamada http para recuperar os dados, assina a resposta, onde você pode processar esses dados (por exemplo, mapear os dados para o seu próprio modelo) e depois repassá-los ao assinante.
O código
product-list.component.ts
product.service.ts
product.ts (o modelo)
Aqui está um exemplo da saída que vejo quando carrego a página no Chrome. Observe que, no carregamento inicial, os produtos são buscados a partir de http (chame meu serviço de descanso de nó, que está sendo executado localmente na porta 3000). Quando clico para navegar para uma exibição 'filtrada' dos produtos, eles são encontrados no cache.
Meu registro do Chrome (console):
... [clicou em um botão de menu para filtrar os produtos] ...
Conclusão: Essa é a maneira mais simples que eu encontrei (até agora) de implementar dados de resposta http em cache. No meu aplicativo angular, cada vez que navego para uma visualização diferente dos produtos, o componente da lista de produtos é recarregado. O ProductService parece ser uma instância compartilhada; portanto, o cache local de 'products: Product []' no ProductService é mantido durante a navegação e as chamadas subseqüentes a "GetProducts ()" retornam o valor em cache. Uma observação final, eu li comentários sobre como as observáveis / assinaturas precisam ser fechadas quando você terminar para evitar 'vazamentos de memória'. Eu não incluí isso aqui, mas é algo a ter em mente.
fonte
Suponho que @ ngx-cache / core possa ser útil para manter os recursos de armazenamento em cache para as chamadas http, especialmente se a chamada HTTP for feita nas plataformas de navegador e servidor .
Digamos que temos o seguinte método:
Você pode usar o
Cached
decorador de @ NGX-cache / núcleo para armazenar o valor retornado do método de fazer a chamada HTTP nacache storage
( ostorage
pode ser configurável, verifique a implementação em ng-semente / universal ) - à direita na primeira execução. Nas próximas vezes que o método for chamado (não importa no navegador ou na plataforma do servidor ), o valor será recuperado docache storage
.Há também a possibilidade de métodos de utilização de armazenamento em cache (
has
,get
,set
) usando o cache API .anyclass.ts
Aqui está a lista de pacotes, tanto para o cache do lado do cliente quanto do lado do servidor:
fonte
rxjs 5.3.0
Eu não fui feliz com
.map(myFunction).publishReplay(1).refCount()
Com vários assinantes,
.map()
executamyFunction
duas vezes em alguns casos (espero que seja executado apenas uma vez). Uma correção parece serpublishReplay(1).refCount().take(1)
Outra coisa que você pode fazer é simplesmente não usar
refCount()
e aquecer o Observable imediatamente:Isso iniciará a solicitação HTTP, independentemente dos assinantes. Não tenho certeza se o cancelamento da inscrição antes da conclusão do HTTP GET o cancelará ou não.
fonte
Meu favorito pessoal é usar
async
métodos para chamadas que fazem solicitações de rede. Os métodos em si não retornam um valor; em vez disso, atualizam umBehaviorSubject
no mesmo serviço, no qual os componentes se inscreverão.Agora, por que usar um em
BehaviorSubject
vez de umObservable
? Porque,onnext
.getValue()
métodoExemplo:
customer.service.ts
Então, sempre que necessário, podemos apenas assinar
customers$
.Ou talvez você queira usá-lo diretamente em um modelo
Portanto, agora, até que você faça outra chamada
getCustomers
, os dados são retidos nocustomers$
BehaviorSubject.E daí se você deseja atualizar esses dados? basta ligar para
getCustomers()
Usando esse método, não precisamos reter explicitamente os dados entre as chamadas de rede subsequentes, pois elas são manipuladas pelo
BehaviorSubject
.PS: Normalmente, quando um componente é destruído, é uma boa prática se livrar das assinaturas, para isso você pode usar o método sugerido nesta resposta.
fonte
Ótimas respostas.
Ou você pode fazer isso:
Esta é a versão mais recente do rxjs. Eu estou usando 5.5.7 versão do RxJS
fonte
Basta ligar para share () após o mapa e antes de qualquer assinatura .
No meu caso, eu tenho um serviço genérico (RestClientService.ts) que está fazendo a chamada restante, extraindo dados, verificando erros e retornando observável a um serviço de implementação concreto (por exemplo: ContractClientService.ts), finalmente essa implementação concreta retorna observável para ContractComponent.ts e este se inscreve para atualizar a exibição.
RestClientService.ts:
ContractService.ts:
ContractComponent.ts:
fonte
Eu escrevi uma classe de cache,
É tudo estático por causa de como o usamos, mas fique à vontade para torná-lo uma classe e um serviço normais. Não tenho certeza se angular mantém uma única instância o tempo todo (novo no Angular2).
E é assim que eu uso:
Suponho que poderia haver uma maneira mais inteligente, que usaria alguns
Observable
truques, mas isso era bom para meus propósitos.fonte
Basta usar essa camada de cache, ele faz tudo o que você precisa e até gerencia o cache para solicitações de ajax.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
É muito fácil de usar
A camada (como um serviço angular injetável) é
fonte
É
.publishReplay(1).refCount();
ou.publishLast().refCount();
desde que os observáveis do Angular Http sejam concluídos após solicitação.Essa classe simples armazena em cache o resultado para que você possa se inscrever em .value várias vezes e faz apenas uma solicitação. Você também pode usar .reload () para fazer nova solicitação e publicar dados.
Você pode usá-lo como:
e a fonte:
fonte
Você pode criar uma classe simples Cacheable <> que ajude a gerenciar os dados recuperados do servidor http com vários assinantes:
Uso
Declare o objeto Cacheable <> (presumivelmente como parte do serviço):
e manipulador:
Chamada de um componente:
Você pode ter vários componentes inscritos nele.
Mais detalhes e exemplo de código estão aqui: http://devinstance.net/articles/20171021/rxjs-cacheable
fonte
Você pode simplesmente usar o ngx-cacheable ! É melhor para o seu cenário.
Portanto, sua classe de serviço seria algo como isto -
Aqui está o link para mais referência.
fonte
Você já tentou executar o código que já possui?
Como você está construindo o Observável a partir da promessa resultante
getJSON()
, a solicitação de rede é feita antes que alguém assine. E a promessa resultante é compartilhada por todos os assinantes.fonte