Introdução

Com o Redux ganhando popularidade generalizada dentro do ecossistema de front-end, o Angular e outros frameworks de front-end líderes o adotaram como uma de suas bibliotecas de gerenciamento de estado confiáveis.

Mas, infelizmente, a arquitetura Redux não fornece nenhuma funcionalidade integrada para lidar com alterações de dados assíncronas (também conhecidas como efeitos colaterais) para a árvore de estado Redux. Como resultado, quando essas ações assíncronas forem realizadas, a árvore de estado do Redux será afetada.

Este artigo apresentará a biblioteca @ ngrx/effects, um pacote especial para lidar com efeitos colaterais em aplicativos NgRx e como ela pode ser usada para lidar com efeitos colaterais em aplicativos NgRx.

Pré-requisitos

  1. Conhecimento de Angular
  2. Conhecimento de NgRx
  3. Conhecimento de TypeScript

Quais são os efeitos colaterais?

Os efeitos colaterais são operações como buscar dados de um servidor remoto, acessar armazenamento local, gravar eventos de análise e acessar arquivos que geralmente terminam em algum momento no futuro.

Sabendo o que são efeitos colaterais, digamos que queremos fazer uma solicitação a um endpoint de API para buscar uma lista de usuários em nosso aplicativo. Considerando o fato de que tal operação será assíncrona, serão contabilizados os seguintes casos:

  1. FETCHING_USERS
  2. USERS_FETCH_SUCCESSFUL
  3. ERROR_FETCHING_USERS

Vamos sujar as mãos praticando um pouco.

Configuração do ambiente

Crie um novo projeto Angular com o seguinte comando:

 ng novos efeitos colaterais

Execute o seguinte comando para instalar as dependências necessárias para este exercício:

 npm install--save @ ngrx/effects @ ngrx/store rxjs

A seguir, executaremos o seguinte comando para criar um módulo de recursos para os usuários:

 ng gerar usuário do módulo

Em seguida, criaremos um arquivo constants.ts para conter FETCHING USERS , USERS FETCH SUCCESSFUL e ERROR FETCHING USERS da seguinte forma:

//src/app/user/user.constants.ts
exportar const FETCHING_USERS="FETCHING_USERS";
export const USERS_FETCH_SUCCESSFUL="USERS_FETCH_SUCCESSFUL";
exportar const ERROR_FETCHING_USERS="ERROR_FETCHING_USERS";

Criadores de ação

Criadores de ações são funções auxiliares que criam e retornam ações. Sabendo disso, vamos criar um da seguinte maneira:

//src/app/user/user.actions.ts
import { USERS_FETCH_SUCCESSFUL, ERROR_FETCHING_USERS, FETCHING_USERS
} de"./user.constants";
export const usersFetchSuccessful=users=> ({ tipo: USERS_FETCH_SUCCESSFUL, carga útil: usuários
});
export const fetchError=error=> ({ tipo: ERROR_FETCHING_USERS, carga útil: erro
}); exportar const fetchingUsers=()=> ({tipo: FETCHING_USERS});

Aqui, exportamos usersFetchSuccessful , fetchError e fetchingUsers , que serão necessários nos componentes para interagir com o armazenamento NgRx.

O criador da ação fetchError () será chamado se ocorrer um erro, o criador da ação usersFetchSuccessful () será chamado assim que os dados forem retornados com sucesso de um endpoint, e o criador da ação fetchingUsers () será invocado assim que a solicitação da API for iniciada.

Criando o redutor

Redutores são funções puras que não alteram o estado. Em vez disso, eles produzem um novo estado. Um redutor especifica como o estado do aplicativo muda em resposta às ações acionadas.

Vamos criar nosso redutor da seguinte maneira:

//src/app/user/user.reducers.ts
import { USERS_FETCH_SUCCESSFUL, ERROR_FETCHING_USERS, FETCHING_USERS
} de"./user.constants";
import {User} de"./user.model";
importar {ActionReducerMap} de"@ ngrx/store/src/models";
const initialState={ carregando: falso, lista: [], erro: vazio 0
};
interface de exportação UserState { carregando: booleano; lista: Array ; erro: string;
}
interface de exportação FeatureUsers { usuários: UserState;
}
export const UserReducers: ActionReducerMap ={ usuários: UserReducer
};
função de exportação userReducer (state=initialState, action) { switch (action.type) { case USERS_FETCH_SUCCESSFUL: return {... estado, lista: action.payload, carregando: false}; caso ERROR_FETCHING_USERS: return {... estado, erro: action.payload, carregando: false}; case FETCHING_USERS: return {... estado, carregando: verdadeiro}; padrão: estado de retorno; }
}

Cada vez que uma ação é despachada de qualquer componente conectado à loja, o redutor recebe a ação e testa a propriedade de tipo da ação para cada um desses casos. Se o teste não atender a nenhum desses casos, ele retornará o estado atual.

Criando o efeito

Os efeitos nos permitem realizar uma tarefa específica e, em seguida, despachar uma ação assim que a tarefa for concluída.

Sabendo disso, vamos criar nosso efeito que tratará de todo o processo de enviar uma solicitação, receber uma solicitação e também receber a resposta de erro quando a solicitação falhar:

//src/app/user/user.effect.ts
importar {Actions, Effect, ofType} de"@ ngrx/effects";
importar {HttpClient} de"@ angular/common/http";
importar {FETCHING_USERS} de"./product.constants";
importar {injetável} de"@ angular/core";
import {Observable} de"rxjs/Observable";
import {delay, map, catchError, switchMap} de"rxjs/operadores";
import {usersFetchSuccessful, fetchError} de"./user.actions";
importar {Action} de"@ ngrx/store";
import {of} de"rxjs/observable/of";
@Injectable ()
export class UserEffects { @Efeito() usuários $: observável =this.actions $.pipe ( ofType (FETCHING_USERS), switchMap (ação=> this.http .get ("https://jsonplaceholder.typicode.com/users") .tubo( atraso (3000), map (usersFetchSuccessful), catchError (err=> of (fetchError (err))) ) ) ); construtor (ações privadas $: Ações , http: HttpClient privado) { console.log ("efeitos do usuário inicializados"); }
}

Aqui, o decorador @Injectable é usado para decorar a classe Effect .

O método ofType () nos permite ouvir uma ação enviada específica e, em nosso caso, FETCHING_USERS enquanto acionamos o switchMap () método nos permite converter nosso observável atual em um serviço AJAX. O método delay () nos permite mostrar o indicador de carregamento por algum tempo. O método map () nos permite despachar uma ação se a resposta AJAX for bem-sucedida.

Registrando o efeito

Existem duas maneiras de registrar efeitos. Podemos fazer isso no módulo raiz ou no módulo de recursos. A primeira abordagem torna o efeito acessível globalmente em todo o aplicativo, enquanto a última restringe sua acessibilidade a um módulo específico. Por motivos de reutilização do código, o último é preferível.

//src/app/user/user.module.ts
importar {NgModule} de"@ angular/core";
importar {UserComponent} de"./user.component";
importe {BrowserModule} de"@ angular/platform-browser";
importar {UserEffects} de"./user.effect";
importar {EffectsModule} de"@ ngrx/effects";
importar {StoreModule, Action} de"@ ngrx/store";
importar {UserReducers} de"./user.reducers";
importar {HttpClientModule} de"@ angular/common/http"; @NgModule ({ importações: [ BrowserModule, StoreModule.forFeature ("featureUsers", UserReducers), EffectsModule.forFeature ([UserEffects]), HttpClientModule ], exportações: [UserComponent], declarações: [UserComponent], provedores: []
})
export class UserModule {}

Para um módulo de recurso, usaríamos o método forFeature () no EffectsModule .

Agora que terminamos de criar e registrar o efeito, vamos acessar o efeito de nosso componente.

Criando seletores

Se você já usou o Vuex antes, estará familiarizado com os getters, que são semelhantes a NgRx s eleitores . Os seletores são usados ​​para derivar informações computadas do estado da loja. Podemos chamar getters várias vezes em nossas ações e em nossos componentes.

Sabendo disso, vamos criar nossos seletores:

//src/app/user/user.selector.ts
importar {AppState} de"../app-state";
export const getList=(estado: AppState)=> state.featureUsers.users.list;
export const getError=(state: AppState)=> state.featureUsers.users.error;
export const isLoading=(estado: AppState)=> state.featureUsers.users.loading;

Precisamos de um componente para exibir um indicador de carregamento enquanto aguardamos a conclusão da solicitação AJAX. O componente exibirá nossos dados se a solicitação for bem-sucedida ou exibirá uma mensagem de erro se não for.

Criação de componentes

Vamos criar um componente para exibir nossos dados, bem como um indicador de carregamento, enquanto nossa solicitação AJAX está pendente:

//src/app/user/user.component.ts
import {Component, OnInit} de"@ angular/core";
importe {AppState} de"../app-state";
importar {Store} de"@ ngrx/store";
import {fetchingUsers} de"./user.actions";
import {getList, isLoading, getError} de"./user.selector";
@Componente({ seletor:"usuários", template: ` Comercial: 
{{user.email}}
buscando usuários...
{{error}}
` }) export class UserComponent implementa OnInit { usuários $; carregando $; erro $; construtor (armazenamento particular: Store ) { this.users $=this.store.select (getList); this.loading $=this.store.select (isLoading); this.error $=this.store.select (getError); } ngOnInit () { this.store.dispatch (fetchingUsers ()); } }

Aqui, a loja é injetada por meio do construtor, para que possamos acessar as propriedades do objeto de estado da loja por meio do método select () da loja. O método select da loja retorna um observável que é renderizado no modelo usando o canal assíncrono.

Com isso, vamos atualizar o AppState :

//src/app/app-state.ts
import {FeatureUsers} de"./user/user.reducer";
interface de exportação AppState { featureUsers: FeatureUsers;
}

Como o AppState agora conhece a estrutura do objeto de usuário resultante, nosso componente é capaz de acionar o método store.select () .

Além disso, vamos atualizar o appModule :

 importe {BrowserModule} de"@ angular/platform-browser";
importar {NgModule} de"@ angular/core";
importar {EffectsModule} de"@ ngrx/effects";
importar {AppComponent} de"./app.component";
importar {StoreModule} de"@ ngrx/store";
import {UserModule} de"./user/user.module";
@NgModule ({ declarações: [AppComponent], importações: [ BrowserModule, EffectsModule, StoreModule.forRoot ({}), EffectsModule.forRoot ([]), UserModule ], provedores: [], bootstrap: [AppComponent]
})
exportar classe AppModule {}

Vamos ver o que estamos construindo até agora em nosso navegador com o seguinte comando:

 npm start

Conclusão

Neste artigo, demonstramos como lidar com os efeitos colaterais em nossos aplicativos NgRx usando a biblioteca @ ngrx/effects enquanto construímos alguns R edux conceitos como ações, redutores e constantes. Além disso, fomos capazes de criar efeitos para lidar com solicitações pendentes, erros em solicitações AJAX e solicitações AJAX bem-sucedidas.

Para obter mais informações sobre o NgRx, você pode verificar a documentação oficial do NgRx aqui . E aqui está o repositório GitHub para este tutorial.

Entre em contato na seção de comentários abaixo se tiver dúvidas ou sugestões.

A postagem Lidando com efeitos colaterais em um aplicativo Angular + Redux apareceu primeiro no LogRocket Blog .