Criando Auto-Save com Angular Reactive Forms e RxJS
Uma das maiores vantagens ao utilizar o Angular se dá pela integração com o RxJS, onde podemos usufruir de vários recursos da programação reativa ao construir nossas aplicações.
Neste artigo demonstrarei como podemos desenvolver um Auto-Save com mensagens personalizadas (funcionalidade semelhante ao do Google Docs), onde conforme digitamos, automaticamente o texto vai sendo salvo.
Para este feito iremos utilizar o Reactive Forms do Angular em conjunto com várias funções e operadores do RxJS, desfrutando ao máximo de todo o poder que temos em mãos na utilização de Observables.
Primeiramente vamos definir nosso HTML, vinculando o textarea a um FormControl e um Observable simples chamado saveIndicator$ para indicar qual o status atual da operação. Foi utilizado o operador of para emitir um texto inicial para o componente. Observe que estamos nos inscrevendo neste Observable diretamente no template com o pipe async.
Agora vamos ouvir todas as mudanças que forem feitas no formulário com o valueChanges. Mas antes filtraremos os valores com os seguintes operadores:
- debounceTime: passamos 0.3 segundos de delay. muito útil para quando o usuário digita num intervalo muito rápido, pois assim somente o último valor será emitido.
- distinctUntilChanged: se o texto digitado for igual ao último então também iremos filtrar
- share: transformamos nosso Observable em um multicast para que nas próximas etapas podemos utilizá-lo de forma compartilhada
Passamos esses procedimentos para a variável inputToSave$ e demos um subscribe nela. Agora conforme vamos digitando já recebemos no console o texto atual!
Na próxima etapa vamos criar mais dois Observables com base no anterior que criamos.
O Observable savesInProgress$ tem a responsabilidade de pegar os valores de inputToSave$ e retornar um outro Observable com o texto “Salvando…” através do mapTo. Depois ele executa uma ação através do operador tap que irá somar mais 1 na variável saveCount. Este contador foi criado para termos um controle de quando atualizar o status para o usuário. Na próxima etapa o seu uso ficará mais claro.
O Observable savesCompleted$ pegará os valores de inputToSave$ e chamará a função saveChanges. No nosso exemplo essa função apenas retornará o mesmo observable com um delay de 1.5 segundos com o intuito de simular uma requisição para o servidor. Em uma aplicação real esta função poderia ser substituída por um HttpRequest para salvar o valor em uma API. O retorno dessa função é passado adiante por meio do mergeMap. Por fim executamos novamente o tap mas agora para subtrair 1 na variável saveCount.
Vamos encadear mais dois operadores no Observable savesCompleted$: Iremos filtrar (filter) todos os valores onde a nossa variável saveCount for diferente de zero. Desta forma garantimos que vamos atualizar o status para “Salvo!” somente se todos os valores já foram passados devidamente pela função saveChanges.
Lembra do Observable saveIndicator$ que criamos lá no início para setar uma mensagem inicial? Agora vamos mudar ele de lugar. Faremos ele receber a união dos dois Observables anteriores (savesInProgress$ e savesCompleted$) com a função merge. Já o valor inicial dele foi transferido para o operador startWith.
Agora as coisas começam a ficar um pouco mais complexas. Se você reparar, tanto o savesInProgress$ como o savesCompleted$ retornam um Observable de um outro Observable! Desta forma não conseguiremos renderizar os valores deles no HTML. Mas fique tranquilo que temos um operador para resolver exatamente este problema. O switchAll tem esse papel de se inscrever em um Observable de Observables (Mais conhecidos como “Higher-Order Observable”). Ele sempre irá se inscrever no Observable interno mais recente e irá cancelar as outras inscrições. Agora o saveIndicator$ sempre receberá um dos textos informativos para o usuário.
Para finalizar, vamos melhorar ainda mais a experiência do usuário. Ao passar 2 segundos sem novas alterações, o status deverá ser trocado para a data e hora da última atualização.
Para isso utilizaremos a função concat que executa vários Observables, um de cada vez. Primeiro emitimos a mensagem “Salvo!”, depois emitimos um Observable vazio (empty) com o delay de 2 segundos, e por fim chamamos a função defer para retornar o Observable com a data e hora atual. Como o próprio nome já diz, ele irá adiar a inscrição deste valor, pois como o Observable é compartilhado então não conseguiríamos alterar a data e hora por estar no mesmo escopo.
Conclusão
No fim deste exemplo conseguimos utilizar várias funções e operadores do RxJS. Existem muitos outros que podem ser úteis em situações específicas. Quanto mais recursos aprendemos desta poderosa lib, maior o nosso leque de ferramentas e soluções para resolver problemas utilizando programação reativa. O site Learn RxJS é um ótimo guia de aprendizado, inclusive o código deste artigo foi transportado de um de seus exemplos.
O código completo está disponível pelo StackBlitz: