Preâmbulo
Ouvimos cada vez mais sobre o uso crescente do conceito Redux com Angular (2+). Enquanto procurava vários artigos para me orientar sobre o assunto, percebi que muito poucos são acessíveis em nossa bela linguagem de Molière. O objetivo deste artigo é, portanto, fazer você entender o Redux e seu uso com um framework que não é seu campo favorito. Se você usa Angular e não está familiarizado com esse conceito, veio ao lugar certo.

Pré-requisitos
Este artigo é destinado a desenvolvedores que estão acostumados a usar Angular sem ter uma ideia clara do que é Redux. Conceitos como Observables, noções básicas de Typescript e Componentes serão dados como garantidos. Conhecimento em programação funcional é um diferencial.
Conheça
Redux é uma arquitetura que visa concentrar o estado da sua aplicação em um só lugar. As ações serão despachadas por eventos, que a cada vez retomarão o estado anterior, modificarão parte dele e retornarão um novo que será usado para gerenciar seus dados.
Não está muito claro? Não se preocupe, o objetivo deste artigo é desmistificar um conceito aparentemente complicado que acaba sendo simples e lógico.
Antes de chegar ao cerne da questão, gostaria de insistir no fato de que o Redux, muito usado com o React, não depende de forma alguma deste último. Já é hora de entendermos os benefícios de uma arquitetura muito prática para usar com qualquer framework/biblioteca orientado a componentes, e o Angular é um exemplo perfeito.
Quais são os problemas com o Angular que podem ser resolvidos por esse novo gerenciamento de dados de aplicativos?
Variáveis compartilhadas por diferentes componentes de diferentes níveis
Em um componente Angular em geral, é raro ter variáveis em readonly para algo diferente de constantes de serviço ou Observáveis. As variáveis exibidas diretamente no componente também são modificadas por este mesmo componente. Mas onde você pode ter um problema real é quando essas variáveis são passadas como entrada para um subcomponente e são modificadas por esse subcomponente. É possível e funciona porque a vinculação de dados do Angular é feita para isso. No entanto, torna-se muito difícil testá-lo e o código pode ter efeitos colaterais.
Imagine uma árvore como:
Componente0 ├── Componente1 └── Componente2 └── Componente3
Temos uma variável que queremos comum aos componentes 1 e 3. Então, temos que declarar esta variável como uma propriedade do Component0 , apenas para atualizá-la no Component2 assim como no Component3. Teremos que colocar uma saída no componente 1 se quisermos modificar a variável após um evento. E faça o mesmo, não só no Component3, mas também no Component2 para ir até o Component0 com EventEmitter.
Muito código, para simplesmente modificar uma variável usada em 2 lugares na mesma página.
Obviamente, o Angular já incorpora soluções que poderíamos usar neste caso. O uso particular de serviços, no qual armazenaríamos a variável compartilhada. Se quiséssemos que fosse vinculado em ambos os componentes, ou seja, mudasse em tempo real no Component1 quando foi alterado no Component3, teríamos que usar paradigmas de programação reativa, especialmente Observables com RXJS. Isso é factível e muitos aplicativos funcionam dessa maneira, mas pode se tornar difícil de manter quando o padrão se repete regularmente, em vários locais no aplicativo. No entanto, essa ideia não deve ser descartada completamente, pois servirá de base para nossa solução.
Os dados vinculados são muito voláteis e a depuração pode ser complicada
Os dados podem mudar o tempo todo e, a menos que você esteja trabalhando constantemente na imutabilidade, rastrear alterações nos valores de um objeto pode ser complicado. Também é difícil manter um histórico de ações e eventos realizados por um tempo.O Redux também oferece uma solução nesse nível com sua “depuração de viagem no tempo”. O Redux mantém cada vez que um novo estado é criado uma cópia de sua versão anterior, e tudo está disponível em as Dev Tools através da extensão

Pode ser difícil organizar o código que modifica variáveis de maneira pura.
Aqui, realmente entramos em parte da explicação da programação funcional, mas uma solução fornecida por este paradigma é o conceito de “função pura”. Uma função pura possui um ou mais argumentos, não modifica nenhuma variável fora de seu escopo e retorna um valor que sempre será o mesmo com os mesmos argumentos. É bastante simples na teoria, e nem sempre na prática.
Se você quiser se aprofundar na explicação e compreensão do paradigma de programação funcional, só posso recomendar um artigo de Yoan Ribeiro sobre o assunto.
Além disso, o uso de Angular muitas vezes nos leva a fazer OOP ao invés de programação funcional, no sentido de que modificamos os dados do componente (que é uma classe) que são por definição externos aos métodos de composição.
Um pouco de teoria antes da prática
Agora que levantamos alguns pontos inerentes ao desenvolvimento com Angular e que podem ser simplificados com Redux, vamos tentar entender bem o padrão, arquitetando uma aplicação Redux básica.

Às vezes você verá Store, às vezes State. As duas palavras cobrem globalmente o mesmo significado, ou seja, o estado do aplicativo e sua estrutura de dados.
Este esquema é muito simples. Do nosso componente, vamos despachar uma ação. Podemos traduzir isso pelo simples fato de enviar uma chamada, um evento que irá chamar uma função.
Então esta ação será aplicada a um redutor quem vai atuar no estado do aplicativo. Em última análise, é esse mesmo estado que será lido pelo(s) componente(s) na interface do usuário.
Em duas frases e um diagrama simples, poderíamos resumir o conceito com bastante facilidade. Mas, obviamente, as ações, os redutores e o Estado têm um papel. E embora possa parecer muito código e funções para fazer pequenas coisas, cada um deles tem um grande significado.
Como pode ser visto, o fluxo sempre terá que ser unidirecional, e este é um ponto importante que servirá como pedra angular da nossa arquitetura. As ações do usuário passarão pelo componente que despachará as ações. Essas ações serão despachadas para os redutores, nossas funções puras que retornarão um novo estado para a aplicação.
Anteriormente estávamos falando sobre uma preocupação com efeitos colaterais ao modificar variáveis compartilhadas por vários componentes. Redux oferece uma solução real neste nível ao se colocar no conceito de programação funcional, e não na orientação a objetos. Uma minimização dos efeitos de borda, cada componente, seja ele qual for, só atuará no estado que for necessário para ele. Despacharemos uma ação para atualizar uma propriedade do estado. Então em cada lugar onde esta propriedade for utilizada na forma de variável em um componente, ela será vinculada e portanto modificada, tudo em uma ação específica que somente terá modificado esta propriedade.
Exemplo de redutor
const reducer: Reducer<AppState> = (state: AppState, action: Action) => {
switch (action.type) {
case "IS_LOADING":
return {
...state,
isLoading: action.payload
};
default:
return state;
}
};
Abra caminho para a prática
Aqui está um diagrama um pouco mais complicado que traduz um caso de uso real dentro da estrutura do desenvolvimento de um aplicativo.

NDD: uma carga útil é o nome dado a uma variável de qualquer tipo que será enviada para a ação e utilizada pelo redutor.
Vamos pegar um caso em que construímos um aplicativo que se parece com um blog, mas lida com mangá (para alterar artigos).
Teremos um componente que exibirá os dados de um mangá através de seu ID (presente na URL). Vamos imaginar que o título do mangá, seu autor e seu número de volumes são dados carregados quando a página é exibida, mas não sua descrição. Este sendo muito longo, você deve clicar em um botão para exibi-lo e, portanto, carregá-lo.
Quando este botão é clicado, várias coisas vão acontecer. Nosso componente despachará uma ação que carregará a descrição do mangá através da função carregarMangaDescrição(). Esta função irá em primeiro lugar despachar a ação DESCRIPTION_IS_LOADING com um carga útil para verdadeiro. Queremos mostrar ao usuário que a informação está carregando. Podemos, por exemplo, desde que esse valor seja verdadeiro, exibir um spinner. O componente, portanto, recuperará esse valor do Estado, que foi atualizado pelo redutor chamado pela ação DESCRIPTION_IS_LOADING.
Uma vez que de forma totalmente síncrona, atualizamos nosso estado para dizer que a descrição está carregando, podemos seguir em frente. O componente não apenas despachou essa ação, mas também chamou um serviço que buscará a descrição por meio de uma chamada HTTP. Quem diz HTTP aqui diz assíncrono, então não sabemos quando a resposta chegará. Geralmente é por isso que os spinners são exibidos, como neste exemplo.
Quando a solicitação retornar, o serviço retornará o resultado ao componente que finalmente poderá iniciar as duas ações a seguir (conectando-se ao resultado por Promise ou Subscription). O componente poderá, portanto, despachar a ação novamente DESCRIPTION_IS_LOADING desta vez com uma carga em falso.
Mas isso não é tudo. O objetivo da nossa operação, a partir do componente, é recuperar a descrição do mangá. Não há problema em colocar um spinner durante o carregamento e removê-lo assim que os dados forem recebidos, mas ainda seria necessário usar esses dados. Para isso, despacharemos uma segunda ação após a primeira que define o carregamento como falso, que será LOAD_DESCRIPTION. Esta ação conterá em payload os dados que queremos enviar para o redutor e depois encontrar no estado. São esses dados que serão exibidos na interface do usuário, fornecida ao componente.
O código do produto para este exemplo pode ser encontrado neste Repositório do Github para obter uma compreensão mais profunda do uso do Redux além da teoria e dos esquemas. É relativamente simples e corresponde a uma startup NgRedux, com sobretudo o essencial para que a arquitetura funcione.
Os épicos
Outro conceito trazido pelo Redux é o de Epics. No caso anterior, gostaríamos, por exemplo, de não ser obrigados a especificar que, uma vez que a chamada de serviço tenha sido feita e a ação despachada, também queremos que o carregador desapareça despachando uma nova ação. Se tivéssemos que despachar a ação que preenche os dados para vários lugares, teríamos que duplicar o código para fazer o loader desaparecer. É aí que entram os épicos. Uma Epic é uma função que será acionada por um redutor e que levará um fluxo de ações para retornar outro fluxo de ações. No nosso caso, bastaria dizer que cada vez que a ação LOAD_DESCRIPTION é despachado, também queremos despachar a ação DESCRIPTION_IS_LOADING com o payload definido como false. Simples e eficiente, sem duplicação de código ou casos adicionais para gerenciar.
Mais informações sobre o conceito de Epics e seus casos de uso:
- https://medium.com/kevin-salters-blog/epic-middleware-in-redux-e4385b6ff7c6
- https://redux-observable.js.org/docs/basics/Epics.html
Angular-Redux
Até lá, resta uma pergunta. Sabemos como ouvir eventos no componente para despachar nossas ações. Sabemos como conectar um serviço a ele se precisarmos de dados assíncronos e como despachar com eficiência nossas ações para redutores para que as alterações de estado dos dados não colidam. Exceto que, uma vez que atualizamos nosso estado, como recuperar os elementos ainda não está claro.
Como você sabe quando uma variável mudou de valor? E como usar esse novo valor em nosso componente?
É para responder a esses tipos de perguntas que as bibliotecas como Angular-Redux. Este é um middleware que nos permitirá agir e recuperar elementos do nosso estado de forma estática ou dinâmica. Aqui está um código de exemplo para recuperar nossa descrição do mangá dinamicamente.
import { select } from '@angular-redux/store';
import { Observable } from 'rxjs/Observable';
import { MangaState } from '../store/state';
@Component({
selector: 'app-manga',
})
export class MangaComponent {
@select(['manga']) readonly manga: Observable;
constructor() {}
}
Extraído do código do repositório acima e simplificado, esse código nos permite ter uma visão geral de como recuperar nossos dados da loja.
A primeira palavra-chave na linha é o @selecionar que é um decorador fornecido pelo angular-redux e que nos permite recuperar nosso elemento mangá da loja e torná-lo um Observable. A que tipo de nossa variável corresponde, tomando como entrada a interface MangaState que corresponde ao tipo de nosso objeto.
A partir daí, será fácil exibir os dados em HTML. No entanto, há uma pequena alteração a ser feita para gerenciar o Observable.
<div *ngIf="manga | async; let manga">
Nom du manga : {{ manga.name }}
Auteur du manga : {{ manga.author }}
<div>LOADING DESCRIPTION</div>
<div>{{ manga.description }}</div>
</div>
Como você pode ver, você precisa gerenciar o Observable de forma assíncrona. O Observable é um stream que pode ter valores fixos em um determinado momento, mas para exibir esses valores no HTML, é preciso colocar um pipe | assíncrono que irá recuperar automaticamente o último valor. Segunda observação, por trás do pipe, você pode criar uma variável local para não precisar fazer um pipe assíncrono para cada valor a ser exibido (o que seria possível). Então o {{ manga.name }} na verdade exibirá a propriedade name da variável local do mangá criada, não a do Observable.
Comentários
Tendo trabalhado em aplicativos Angular do zero com e sem Redux, eu diria que existe um talento especial para “pensar Redux”, mas isso acontece muito rapidamente. Como ao passar de uma linguagem de objeto para uma linguagem funcional, você precisa se acostumar a codificar de maneira diferente. Mas o jogo definitivamente vale o esforço, e a combinação de Redux + digitação + teste é uma combinação vencedora para um aplicativo extremamente sólido, facilmente depurável e de fácil manutenção. Obviamente, sua implementação demora mais porque uma Store deve ser bem pensada de acordo com as necessidades da aplicação. A separação de dados entre diferentes redutores também pode ser um ponto que determinará a manutenibilidade dependendo da reutilização desses redutores por diferentes páginas. Redux é uma arquitetura aberta na qual você pode até adicionar padrões que correspondam às suas necessidades, como substituir os Epics por outro sistema de Dispatchers que terão a responsabilidade exclusiva de despachar ações, por exemplo.
Conclusão
Vemos que uma arquitetura como o Redux pode interagir com qualquer tipo de aplicação. Você só precisa entender completamente a utilidade do padrão para gerenciar dados e o estado global do seu aplicativo.
Você só precisa pensar em extras para integrá-lo ao Angular, como Angular-Redux para gerenciar a ligação entre as variáveis do componente e o estado global.
O uso do Typescript assume todo o seu significado, em particular através da digitação do armazenamento e dos dados que ele contém, que podem ser encontrados nos redutores, bem como nas ações, e até mesmo nos Observables nos componentes (ver código do repositório). Temos, portanto, em todo o código uma segurança trazida pela digitação para evite erros ao máximo.
Outro ponto importante: Redux não é mágico. Claro, você tem que perceber que o Redux nada mais é do que uma arquitetura adicionada à sua aplicação, mas não elimina magicamente todos os problemas de padrão que sua aplicação já pode ter. Mesmo que a biblioteca forneça uma solução forte para o gerenciamento de dados de aplicativos, o fato é que sua implementação requer tempo. Devemos pensar com cuidado sobre a estrutura dos dados e sua normalização, pois isso terá um impacto significativo na arquitetura das ações, no armazenamento e nos redutores. Você precisa ter tempo para digitar e testar esses dados, bem como as funções que os modificam para manter um aplicativo saudável, funcional e sem surpresas.
escrito por Guilherme Barranco
