Aplicações Angular: Porque usar NGXS ao invés de NGRX?

Johnny Trentin
OPANehtech
Published in
8 min readAug 25, 2022

No ecossistema Angular, existem algumas soluções no mercado quando se trata de gerenciamento de estado da aplicação. Neste artigo, irei abordar a biblioteca NGXS e exemplificar seu uso e funcionamento.

Por que utilizar essa biblioteca dentre tantas alternativas no mercado?

A premissa da biblioteca é muito simples: Aproveitar-se de algumas features do TypeScript (classes e decorators) para evitar boilerplate* no código, tornando a implementação e a curva de aprendizado menor do que o consolidado framework NGRX, sendo o mais simples e direto possível.

Boilerplate na programação se refere a blocos de código que tem que ser incluidos em vários lugares diferentes porém com pouca ou nenhuma alteração.

Conceitos Básicos

Caso você ainda não esteja muito familiarizado com os conceitos de gerenciamento de estado, abaixo darei uma breve explicação de cada conceito principal, porém recomendo pesquisar e se aprofundar no assunto procurando as documentações oficiais do Redux, NGRX e NGXS.

Store

Store é um container global que gerencia todo o estado da aplicação.

É aqui onde todos os seus dados ficarão armazenados quando você passar a utilizar os conceitos e implementações de gerenciamento de estado.

Selectors

Funções puras que selecionam/buscam uma “parte” do dado da store.

São essas funções que você utilizará para efetuar a busca dos seus dados atualizados. Logo após a chamada de uma API, ou após efetuar a edição de um dado na store, por exemplo.

Reducers

Funções que irão modificar (CRUD) o estado global.

Funções que você irá criar para fazer todo o gerenciamento do seu estado, como por exemplo, após a chamada de uma API que devolve todos os carros que um usuário possui. Você irá salvar o retorno desta API na store, para que você não precise mais fazer a mesma chamada sempre, e sim, só faça uma busca pelos dados atualizados e imutáveis* na store.

Dados imutáveis são objetos no qual seu estado não pode ser modificado após criado.

Um outro exemplo: Você precisa agora remover um carro que estava cadastrado nesse usuário. Pelo Reducer você irá remover esse dado diretamente na store, podendo ou não fazer isso atualizando o seu DB através de uma API. Tudo depende do contexto do seu caso.

Deu para sacar a ideia? Qualquer coisa que irá modificar o estado atual da sua aplicação (store), você irá fazer pelos reducers.

Actions

Expressa os eventos que acontecem no estado da aplicação, como por exemplo: “[Projects] AddProject”, “[Projects] UpdateProject”, “[Projects] DeleteProject”.

Com as Actions, você explicitamente irá declarar e exemplificar em qual fluxo e contexto as ações dentro da sua aplicação estão sendo realizadas. Actions e Reducers funcionam juntos. Você primeiramente descreve em qual contexto da aplicação que a action irá atuar, e depois qual a ação que você vai realizar.

Por exemplo, “[User] Create new user”, sendo “User” o contexto e “Create new user” a ação. Essas ações poderão conter um payload ou não, que será usado pelos reducer como metadados* adicionais.

Metadados são dados sobre outros dados. Informações que estendem os dados afim de informar-nos sobre eles.

Após a definição, você criará reducers de acordo com as actions, fazendo com que a cada dispatch/envio de uma action, um reducer associado a essa action irá ser chamado, realizando modificações baseadas na sua implementação.

Effects

“Middlewares” que ficam entre a camada da aplicação e os reducers, funcionam com o intuito de isolar os side-effects* que ações como interação com API’s geram nos componentes.

Qualquer operação que não é diretamente relacionada a saída de uma função é chamado de Side-effect.

Em uma aplicação Angular, é comum se usar services para interagir com recursos externos a aplicação, como interação com API’s, web sockets, etc.

Effects é uma maneira de isolar a implementação e o conhecimento externo dessas tarefas nos componentes.

Entitys

Permite criar e gerenciar entidades no estado (state).

Entitys são ideais para quando se tem uma grande quantidade de dados (objetos, arrays) para serem manipulados.
As entitys transformam os arrays em objetos simples com chave e valor, contendo um id para referência e o valor do dado, não precisando inteirar em todo o array para buscar valores específicos.

A API das entitys também fornecem alguns benefícios na hora do desenvolvimento:

  • Redução do boilerplate na criação de reducers que gerenciam a coleção dos seus modelos de dados.
  • CRUDs performáticos para gerenciar as coleções de entidades.
  • Entitys usam objetos JavaScript simples no gerenciamento das suas coleções de dados*.

O uso de objetos JavaScript simples como forma de armazenar seus dados, trazem algumas garantias e seguranças na hora de persistir esses dados na store:

Garantem que a estrutura de dados salva na store, não contenha nada que faça com que os dados se mutem entre sí, garantindo também que o estado (state) da aplicação sempre seja serializável e debugavel usando o devtools do redux.

Principais Diferenças

— NGRX

Como acabamos de ver anteriormente nos conceitos de gerenciamento de estado, estes que o NGRX aplica rigorosamente, temos:

  • Store
  • Selectors
  • Reducers
  • Actions
  • Effects
  • Entitys

— NGXS

Já na biblioteca NGXS, os conceitos são diminuidos e resumidos em quatro, sendo eles:

  • Store
  • State
  • Actions
  • Select

Store, State e Select para ambas as soluções funcionam da mesma maneira, o que difere aqui são as Actions.

No NGXS, as Actions são a junção de Actions e Reducers, elas tem a função de expressar/mostrar para a aplicação o que está acontecendo no estado global, ao mesmo tempo que elas também efetuam a modificação dela (CRUD).

Implementação

Não somente nos conceitos que o NGXS é mais prático que o NGRX. Na codificação iremos ver que ele se sai tão bem como na teoria.

Para ficar um exemplo mais didático, iremos criar um caso de uso simples, digamos que precisamos desenvolver uma aplicação que irá exibir um carro com suas características (modelo, ano, cor), e nossa aplicação permitirá que o usuário possa deletar, criar e editar um carro.

Com isso em mente, podemos abordar de forma simples, todos os conceitos apresentados acima via código.

Estruturação de pastas

o NGXS segue um style-guide próprio que nos sugere aonde iremos organizar nossos states.

States globais devem ficar na estrutura:

|-- src
|-- shared
|-- state

States de features devem ficar na própria estrutura da feature:

|-- src
|-- app
|-- your-feature

Você pode também pode criar uma lib que irá conter todos os states, dependendo novamente, muito do seu caso de uso.

Instalação de dependências

Para instalar o core da biblioteca NGXS iremos instalar 2 pacotes, o “@ngxs/store” e o “@ngxs/devtools-plugin”.

O segundo pacote se refere a possibilidade de se usar a extensão do browser Redux Devtools, é recomendado instalar ela também, irá auxiliar bastante na leitura dos logs e na visualização da estrutura do seu estado, permitindo também efetuar o debug da store se necessário.

npm install @ngxs/store --save
npm install @ngxs/devtools-plugin --save-dev
ouyarn add @ngxs/store
yarn add @ngxs/devtools-plugin --dev

Importação das dependências

Após as devidas instalações das dependências, iremos adicionar elas ao nosso projeto angular.

Note a linha 8, onde temos um array vazio, esse array deverá conter todas as stores criadas da sua aplicação.

Definição Model, Actions e State

Após importar os módulos do NGXS no módulo root da nossa aplicação, iremos definir o model do nosso state.

O model é uma interface TypeScript com o a convenção de prefixo NomeState seguido do sufixo Model

Aqui criamos o nosso model que contem as 4 propriedades que correspondem ao modelo, ano e cor do carro e id.

Agora vamos criar as actions, para que possamos disparar eventos para o estado da aplicação e que vão modificar os dados na store.

Exemplo de actions com payload

Como mencionamos no artigo anteriormente as actions podem ou não conter um payload, que é usado como metadados pelas proprias actions para modificar a store. No NGXS passamos payloads para as actions por meio de parâmetros publicos no construtor da classe.

Caso uma action não necessite de um payload, podemos definir uma action somente criando uma classe com a propriedade type, conforme exemplo abaixo:

Exemplo de actions sem payload

Agora com a cereja do bolo, vamos definir nosso state.

Exemplo de State com exemplos de Selectors e Actions

A biblioteca provê um decorator@State”, é com ele que iremos setar os metadados do nosso state, como interface, nome, e valores default.

Após definirmos o nome da nossa classe seguindo o style-guide do NGXS, com o Nome + State, precisamos usar o decorator @Injectabledo angular em nossa classe.

Logo em seguida utilizamos outros dois decorators do NGXS, o “@Selector” e o@Actionpara definir respectivadamente nossos Selectors e Actions. Os selectors sempre serão métodos estáticos que retornam alguma parte da sua store, no nosso exemplo acima, criei 3 selectors que irão retornar o modelo, ano e cor do carro diretamente da store.

Poderiamos ter feito somente um selector que retornaria todos os dados da store, mas para ser mais didático, preferi separar em 3 isoladamente.

A sintaxe das Actions é bem simples, usamos o decorator “@Action” passando um parâmetro com o nome da classe que definimos a Action. O método tem 1 parâmetro do tipo StateContext contendo um ponteiro para o seu state e funções para definir o state, podendo também ter outro parâmetros caso sua action contenha um payload.

Com isso usamos ou o setState ou o patchState para setar e modificar valores na store, e o getState para pegar o estado mais atualizado do state.

Utilizando a store em nossos components e services

Para utilizarmos nosso state basta criarmos uma propriedade do tipo Store no nosso construtor do componente ou serviço, e usar o método dispatch passando por parâmetro a uma nova instância das classe das actions que criamos.

Ambos os métodos são do import { Select, Store } from ‘@ngxs/store’;

E se precisarmos buscar nossos dados da store, utilzamos o decorator@Select”, no qual nos retorna um Observable do tipo do dado que definimos no selector, com isso, basta dar um subscribe e pegar o valor retornado da stream de dados.

Imaginando que temos uma página HTML que contém 3 botões no qual um adiciona um carro, o outro edita e o último remove o carro, como ficaria as actions e nossa store?

Vamos observar utilizando a extensão do Redux DevTools:

Ao clicar no botão de adicionar um novo carro.
Ao clicar no botão de editar as informações do carro.
E na opção de deletar o carro.

E nosso app module com o state importado no array de dependências:

Conclusão

Com este artigo espero que tenham conseguido compreender os conceitos básicos de como funciona um gerenciamento de estado em aplicações web, as sútis, porém importantes diferenças entre o principal framework do ecossistema Angular, o NGRX e a biblioteca NGXS, e usar e implementar os conceitos e códigos necessários para usar o gerenciamento de estado NGXS em suas aplicações Angular.

--

--