Como configurei o NgRx em Angular 16 com componentes autônomos (2023)

Um guia sobre como implementar NgRx em Angular 16

Como configurei o NgRx em Angular 16 com componentes autônomos (1)

Publicado em

pedaços

·

(Video) Use NgRx with Standalone Components in Angular 15

13 minutos de leitura

·

3 dias atrás

Como configurei o NgRx em Angular 16 com componentes autônomos (3)

O NgRx é um dos sistemas de gerenciamento de estado mais populares em todo o ecossistema Angular. Mas o que é realmente um sistema de gerenciamento de estado e por que preciso de um em meu aplicativo?

Vamos começar com uma explicação rápida:

  • Estado — conjunto de dados, que está sendo usado em nosso aplicativo
  • Sistema de gerenciamento—conjunto de ferramentas centralizadas para simplificar/organizar/estruturar os dados usados ​​por nosso aplicativo

Por definição, os dados do sistema de gerenciamento de estado têm escopo global, ou seja, podem ser usados/acessados ​​de qualquer lugar dentro de nosso aplicativo. Ao contrário do Angular Service simples, ele não pode ser definido para uma parte/recurso específico do aplicativo.

NgRx oferece adicionalmente@ngrx/component-storebiblioteca, especificamente para fornecer funcionalidade de escopo. O Component Store pode ser carregado lentamente e está sendo vinculado ao ciclo de vida do componente (inicializado com a criação do componente e destruído junto) — será abordado em outro artigo.

Mas estamos aqui para focar mais em uma abordagem de escopo global. A biblioteca NgRx fornece implementação Redux da Flux Architecture, o que significa fluxo unidirecional e previsibilidade.

Nem todo conjunto de dados precisa do uso do sistema de gerenciamento de estado! Você não precisa colocar tudo na loja NgRx! Se seus dados precisam ser compartilhados/reutilizados em diferentes recursos de aplicativos/componentes não relacionados ou contêm dicionários, faz sentido usar o NgRx! Caso contrário, você pode não precisar!

A arquitetura Flux fornece uma clara separação de preocupações, o que torna nosso código limpo, mas também há compensações, como a necessidade de algum código clichê para implementar. Demora um pouco mais para configurar tudo e funcionar, mas vale cada segundo do seu tempo. E quanto mais tempo você estiver usando, você estará aproveitando seus recursos com facilidade.

  • Estado— representa o objeto simples JSON que é uma fonte única de verdade de dados de aplicativo com escopo global.
  • Seletores—funções memorizadas permitem pegar um pedaço específico do estado e ouvir/subscrever suas mudanças.
  • redutores— o único lugar onde diretamente o objeto de estado está sendo atualizado de forma imutável. Garante atualizações previsíveis. Você poderia usar umadaptadorde@ngrx/biblioteca de entidadespara garantir algumas das operações imutáveis ​​mais genéricas prontas para uso.
  • efeitos— classes (agora até funções!) para lidar com operações assíncronas, como busca de dados. Este é o lugar onde podemos ouvir o despacho de ação específica. Na maioria dos casos, os efeitos estão retornando uma ação, mas não é essencial.
  • Ações— são operações/comandos específicos que estão sendo despachados, como atualizações de dados (operações CRUD). Esta é a única maneira de atualizar o estado do aplicativo NgRx.

Além disso, especificamente em aplicações Angular, faz sentido aplicar a técnica Facade Services — uma classe que implementa o padrão de design de fachada está sendo usada como a única possibilidade de acessar dados/acionar ações da loja.

Além disso, confiraessas ferramentas de desenvolvimento Angular para simplificar o desenvolvimento Angular em 2023.

Exemplo de uso:quando os dados da loja precisam ser acessados, precisamos injetar via Dependency Injection, Facade Service no componente que precisa usar os dados/métodos/atributos de lá. Nenhuma injeção direta de dependência de armazenamento no componente é permitida. Isso é crucial no caso de fornecer uma única implementação, que pode ser reutilizada em outro lugar (diferentes áreas do aplicativo).

Para iniciar basta executar o comando abaixo:

de adicionar @ngrx/store@latest

A loja NgRx será inicialmente adicionada ao seupacote.jsonarquivo, também adicionaleslintpacote será instalado, se você estiver usando o eslint em seu aplicativo. Se você navegar para o seumain.tsarquivo, você notará queprovideStore()foi adicionado ao seu array de provedores automaticamente.

Em seguida, precisamos instalar algumas bibliotecas NgRx adicionais com o seguinte comando:

npm install @ngrx/{effects,entity,store-devtools} --save

Estamos planejando montar:

(Video) Componentes avançados com Angular, passo a passo

  • @ngrx/store-devtools, a fim de conectar nosso aplicativo com as ferramentas de desenvolvimento do navegador e ter essa boa experiência de observar nossas alterações de estado/acionamento de ações, etc.
  • @ngrx/efeitosbiblioteca é necessária, pois estará fazendo operações assíncronas.
  • @ngrx/entidadebiblioteca nos ajudará a gerenciar nossos dados de maneira escalável, usando uma abordagem semelhante a um dicionário, onde nossas entidades estão sendo armazenadas dentroRegistro— onde a chave é o identificador único da nossa entidade e o valor é a própria entidade. Dessa forma, mesmo se lidarmos com centenas de milhares de entidades, nosso mecanismo de armazenamento será tão eficiente quanto com apenas alguns (algoritmo O grande) aqueles. Atrevido certo?

Agora, temos que criar nossoações,redutoreseefeitos, mas antes disso vamos nos concentrar na estrutura real da nossa loja. Prefiro uma abordagem baseada em recursos, em que cada tipo de entidade é tratado separadamente. Essa técnica garantirá boa separação, manutenção e abordagem atômica.

Nossa biblioteca @ngrx/store-devtools foi obtida com sucesso, portanto, podemos conectá-la ao nosso aplicativo independente, conforme abaixo:

import { enableProdMode, isDevMode } de '@angular/core';
importar { bootstrapApplication } de '@angular/platform-browser';

importar {ambiente} de './environments/environment';
importar { AppComponent } de './app/app.component';
importar { provideStore } de '@ngrx/store';
importar { provideStoreDevtools } de '@ngrx/store-devtools';

if (ambiente.produção) {
enableProdMode();
}

bootstrapApplication(AppComponent, {
provedores: [
provideStore(),
provideStoreDevtools({
maxAge: 25, // Retém os últimos 25 estados
logOnly: !isDevMode(), // Restrinja a extensão ao modo somente log
autoPause: true, // Pausa as ações de gravação e mudanças de estado quando a janela de extensão não está aberta
trace: false, // Se definido como true, incluirá rastreamento de pilha para cada ação despachada, para que você possa vê-lo na guia de rastreamento pulando diretamente para essa parte do código
traceLimit: 75, // máximo de quadros de rastreamento de pilha a serem armazenados (caso a opção de rastreamento tenha sido fornecida como verdadeira)
}),
],
}).catch(err => console.error(err));

Para poder utilizar totalmente o Store Devtools, preciso instalar o plug-in do navegador. Como eu uso o navegador Chrome, será o Redux Devtools que pode ser acessado no seguinte link:https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en

Prefiro criar uma pasta dedicada para manter toda a implementação da loja em um só lugar com a seguinte estrutura:

- loja
|-feature-store-1
|-feature-store-2
|-feature-store-2
| |-index.ts
| |-feature-store-1.actions.ts
| |-feature-store-1.effects.ts
| |-feature-store-1.facade.ts
| |-feature-store-1.reducers.ts
| |-feature-store-1.selectors.ts
| |-feature-store-1.state.ts
| |-... // não se esqueça dos arquivos de testes de unidade com .spec.ts! :)
|-index.ts

No meu caso, eu crieimensagenspasta de recursos dentro dolojae minha estrutura se parece com o abaixo:

- loja
|-mensagens
| |-index.ts
| |-messages.actions.ts
| |-mensagens.efeitos.ts
| |-messages.facade.ts
| |-mensagens.redutores.ts
| |-messages.selectors.ts
| |-messages.state.ts
|-index.ts

Vamos agora preencher os arquivos com algumas configurações básicas.

Veja como meumensagens.estado.tsaparência do arquivo:

importar { createEntityAdapter, EntityAdapter, EntityState } de '@ngrx/entity';
importar { Mensagem } de '../../messenger';

interface de exportação MessagesState extends EntityState {
carregando: [];
}

export const selectId = ({ id }: Mensagem) => id;

export const sortComparer = (a: Mensagem, b: Mensagem): número =>
a.publishDate.toString().localeCompare(b.publishDate.toString());

exportar adaptador const: EntityAdapter = createEntityAdapter(
{ selectId, sortComparer }
);

export const initialState: MessagesState = adapter.getInitialState(
{ carregando: [] }
);

eu estou usando o@ngrx/entidadebiblioteca para gerenciar convenientemente meus dados. Dentro da minha definição de estado de recurso, estendoEntidadeEstadogenérico forneceu a biblioteca com minha própria interface de dadosMensagem. Então estou declarando:

  • selectIdfunção para informar ao adaptador como criar um identificador exclusivo para o valor do meu objeto de entidade
  • sortComparerdefinição de função (o comparador não é necessário, você não precisa especificá-lo).

Finalmente, eu crio meuadaptadorinstância eEstado inicial, que será passado para o redutor como ponto de partida. Graças ao adaptador meuEstado inicialficará assim nas ferramentas de desenvolvimento do navegador:

{
mensagens: {
identificadores: [],
entidades: {},
carregando: [],
},
}

nós não adicionamosidentificadoreseentidadesatributos, esta é a mágica do adaptador :-). Assim, quando adicionamos uma nova entidade despachando uma ação, nosso estado ficará da seguinte forma:

{
mensagens: {
id: ['1'],
entidades: {
'1': {
... // meus atributos de entidade de mensagem
}
},
carregando: [],
},
}

Vamos adicionar agora ações aomensagens.ações.tsarquivo:

import { createAction, props } de '@ngrx/store';
importar { Mensagem } de '../../messenger';

export const messageKey = '[Mensagens]';

export const addMessage = createAction(
`${messagesKey} Adicionar mensagem`,
props<{ mensagem: Mensagem }>()
);

export const deleteMessage = createAction(
`${messagesKey} Excluir Mensagem`,
props<{ id: string }>()
);

A biblioteca NgRx nos permite criar ActionGroups, mas abordarei esse tópico em um artigo separado!

Em seguida, em nossa agenda está omensagens.redutores.tsarquivo. É assim que deve parecer:

import { ActionReducer, createReducer, on } de '@ngrx/store';
import { adaptador, initialState, MessagesState } de './messages.state';
importar { addMessage, deleteMessage } de './messages.actions';

export const messageReducers: ActionReducer = createReducer(
Estado inicial,
on(addMessage, (state: MessagesState, { message }) =>
adaptador.addOne(mensagem, estado)),
on(deleteMessage, (state: MessagesState, { id }) =>
adaptador.removeOne(id, estado))
);

Criamos messageReducer com a função createReducer de@ngrx/store library. Nós passamosEstado inicialdeclarado emmensagens.estado.tse usamos um já criadoadaptadorem todos os casos.

A biblioteca NgRx oferece a criação de recursos com experiência muito semelhante ao React Redux Toolkit ou Vuex, mas abordará isso em outro artigo!

Como um aparte, organize seu código NgRx com base nos módulos de recursos. Cada módulo Feature deve encapsular ações relacionadas, redutores, efeitos, seletores e modelos de estado. Você pode, então, empacotar, documentar automaticamente, compartilhar e reutilizá-los de forma independente usandoPedaço. Essa abordagem mantém sua base de código organizada e mais fácil de entender e manter.

Saber mais:

Agora é a hora de adicionar nossas queridas exportações de barris aoindex.tsarquivo dentro do nossomensagenspasta assim:

// local do arquivo: store/messages/index.ts
exportar * de './messages.actions';
exportar * de './messages.reducers';
exportar * de './messages.state';

Tudo o que precisamos fazer agora é conectar nossa loja de recursos de mensagens ao provedor da loja, como abaixo:

(Video) Todo lo que necesitas saber sobre los nuevos Componentes Autónomos (standalone) de Angular.

import { enableProdMode, isDevMode } de '@angular/core';
importar { bootstrapApplication } de '@angular/platform-browser';

importar {ambiente} de './environments/environment';
importar { AppComponent } de './app/app.component';
importar { provideStore } de '@ngrx/store';
importar { provideStoreDevtools } de '@ngrx/store-devtools';
importar { messageReducers } de './app/store/messages';

if (ambiente.produção) {
enableProdMode();
}

bootstrapApplication(AppComponent, {
provedores: [
provideStore({ message: messageReducers }), // <-- este é o lugar! :-)
provideStoreDevtools({
maxAge: 25, // Retém os últimos 25 estados
logOnly: !isDevMode(), // Restrinja a extensão ao modo somente log
autoPause: true, // Pausa as ações de gravação e mudanças de estado quando a janela de extensão não está aberta
trace: false, // Se definido como true, incluirá rastreamento de pilha para cada ação despachada, para que você possa vê-lo na guia de rastreamento pulando diretamente para essa parte do código
traceLimit: 75, // máximo de quadros de rastreamento de pilha a serem armazenados (caso a opção de rastreamento tenha sido fornecida como verdadeira)
}),
],
}).catch(err => console.error(err));

Neste ponto, se quisermos testar nossa conquista, podemos fazer uma verificação de sanidade, injetando a loja em nosso componente aleatório e despachando uma ação como esta:

import {Component, inject, OnInit} from '@angular/core';
importar { MessagesService } de '../../services';
importar { Mensagem } de '../../models';
importar { Store } de '@ngrx/store';
import { addMessage } from '../../../store/messages';

@Componente({
seletor: 'app-messenger',
templateUrl: './messenger.component.html',
styleUrls: ['./messenger.component.scss'],
autônomo: verdadeiro,
})
classe de exportação MessengerComponent implementa OnInit {
armazenamento privado somente leitura: Store = inject(Store);

addMessage(): void {
const message: Message = { /* objeto de mensagem com atributo id */ };
this.store.dispatch(addMessage({ mensagem: { conteúdo } }));
}

ngOnInit(): void {
this.addMessage();
}

Dentro do seu Chrome (ou outro navegador de sua escolha) Redux dev tools, você poderá ver a ação despachada / sua carga útil / diff / estado mais recente, conforme abaixo:

Como configurei o NgRx em Angular 16 com componentes autônomos (4)

Também podemos adicionar a interface principal do estado do aplicativo, que pode ser chamadaAppState, o local ideal para isso parece ser dentro doindex.tsarquivo localizado diretamente nolojapasta:

// local do arquivo: store/index.ts
importar { MessagesState } de './messages';

interface de exportação AppState {
mensagens?: MessagesState;
}

Agora, uma vez que tenhamos dados reais em nossa loja, podemos criar seletores em nossamensagens.seletores.tsarquivo (não se esqueça de adicionar exportação de barril ao respectivostore/messages/index.ts!):

importar { AppState } de '../index';
import { createFeatureSelector, createSelector, MemoizedSelector } de '@ngrx/store';
importar { MessagesState } de './messages.state';
importar { Mensagem } de '../../messenger';

export const selectMessagesFeature: MemoizedSelector =
createFeatureSelector('mensagens');

export const selectMessages: MemoizedSelector =
criarSeletor(
selectMessagesFeature,
({entities}: MessagesState): Message[] =>
Object.values(entities) as Message[]
);

Estamos quase lá, mas queremos ser profissionais e seguirPrincípios de programação DRY. nós não queremosinjetar lojasempre que precisamos acessar nossos dados de estado. Apresentaremos o Facade específico para nossosEstado das Mensagens! Lembre-se de adicionar a importação de fachada aostore/messages/index.tsarquivo!

import { inject, Injectable } de '@angular/core';
importar { Store } de '@ngrx/store';
importar { Mensagem } de '../../messenger';
importar { addMessage } de './messages.actions';
importar { selectMessages } de './messages.selectors';
importar { Observable } de 'rxjs';

@Injectable({ fornecidoIn: 'root' })
export class MessagesFacade {
armazenamento privado somente leitura: Store = inject(Store);

readonly message$: Observable = this.store.select(selectMessages);

addMessage(mensagem: Mensagem): void {
this.store.dispatch(addMessage({ mensagem }));
}
}

A próxima etapa é substituir nosso uso de teste pelo realMensagensFachada:

import {Component, inject, OnInit} from '@angular/core';
importar { AsyncPipe, NgFor, NgIf } de '@angular/common';
importar { MessagesService } de '../../services';
importar { Mensagem } de '../../models';
import { MessagesFacade } from '../../../store/messages';

@Componente({
seletor: 'app-messenger',
templateUrl: './messenger.component.html',
styleUrls: ['./messenger.component.scss'],
autônomo: verdadeiro,
})
classe de exportação MessengerComponent implementa OnInit {
private readonly messageFacade: MessagesFacade = inject(MessagesFacade);

addMessage(): void {
const message: Message = { /* objeto de mensagem com atributo id */ };
this.messagesFacade.addMessage(mensagem));
}

ngOnInit(): void {
this.addMessage();
}

Como resultado, não há referência para armazenar diretamente, tudo relacionado aEstado das Mensagensestá sendo navegado atravésMensagensFachada. Agora você é perfeitamente capaz de adicionar novas mensagens à nossa loja de qualquer lugar em nosso aplicativo! Só não se esqueça de adicioná-lo aoarquivo store/messages/index.ts:

// local do arquivo: store/messages/index.ts
exportar * de './messages.actions';
exportar * de './messages.reducers';
exportar * de './messages.state';
exportar * de './messages.selectors';
exportar * de './messages.facade';

Até agora, estamos lidando com nossas operações síncronas com nossa configuração de armazenamento NgRx, resta um bit relacionado a operações assíncronas!

Se quisermos persistir nossas mensagens em algum lugar dentro do banco de dados, precisamos chamar o respectivo endpoint e passá-lo para nossa implementação de back-end. Como essa operação leva tempo, ela está sendo tratada de forma assíncrona (não sabemos se a resposta chegará e, se chegar, não sabemos quando!). É aqui que a biblioteca @ngrx/effects entra em ação! Vamos configurar os efeitos em nossa implementação autônoma de gerenciamento de estado baseada em componentes!

Precisamos adicionar duas ações adicionais ao nossomensagens.ações.tsarquivo. É assim que o arquivo deve ficar:

import { createAction, props } de '@ngrx/store';
importar { Mensagem } de '../../messenger';

export const messageKey = '[Mensagens]';

export const addMessage = createAction(
`${messagesKey} Adicionar mensagem`,
props<{ mensagem: Mensagem }>()
);

export const deleteMessage = createAction(
`${messagesKey} Excluir Mensagem`,
props<{ id: string }>()
);

export const deleteMessageSuccess = createAction(
`${messagesKey} Excluir mensagem com sucesso`
);

export const deleteMessageError = createAction(
`${messagesKey} Excluir mensagem de erro`
);

Além disso, alguns ApiService são necessários para manter a camada de comunicação da API abstraída (../../shared/services/messages-api.service.ts):

import { inject, Injectable } de '@angular/core';
importar { HttpClient } de '@angular/common/http';
importar { Observable } de 'rxjs';

@Injectable({ fornecidoIn: 'root' })
classe de exportação MessagesApiService {
private readonly http: HttpClient = inject(HttpClient);

deleteMessage(id: string): Observable {
return this.http.delete(`/${id}`);
}
}

(Video) Angular 15: Breaking Changes You Need To Know

Existem duas opções para declarar efeitos, você pode usar uma abordagem de classe ou funcional. Como a abordagem funcional é relativamente nova, vamos utilizá-la em nosso exemplo (mensagens.efeitos.tsarquivo):

import { inject } de '@angular/core';
import { Actions, createEffect, ofType } de '@ngrx/effects';
importar { deleteMessage, deleteMessageError, deleteMessageSuccess } de './messages.actions';
importar { MessagesApiService } de '../../shared/services/messages-api.service';
import { catchError, map, mergeMap } de 'rxjs';

export const deleteMessage$ = createEffect(
(actions$: Actions = inject(Actions), messageApiService: MessagesApiService = inject(MessagesApiService)) => {
ações de retorno$.pipe(
ofType(deleteMessage),
mergeMap(({ id }) =>
mensagensApiService.deleteMessage(id).pipe(
map(() => deleteMessageSuccess()),
catchError(() => [deleteMessageError()])
)
)
);
},
{funcional: verdadeiro}
);

Para começar, usamos a função createEffect do@ngrx/biblioteca de efeitose passamos dois argumentos, um é um efeito real que queremos criar e o segundo é um objeto de configuração. Como estamos seguindo uma abordagem funcional, nossa configuração conterá umfuncionalbandeira definida paraverdadeiro.

Precisamos injetarações $e nossoAPIServicecomo argumentos a nosso favor, exatamente como no exemplo a seguir. Esta é a maneira preferida, pois esse efeito é definitivamente mais fácil de testar!

Nós ouvimos oAPIServicechame e reaja em caso de sucesso ou falha com ações específicas.

Como estamos usando umabordagem funcional, a exportação de efeitos precisaria parecer um pouco diferente. Basta adicionar este bit astore/messages/index.ts:

exportar * como messageEffects de './messages.effects';

Apenas mais um despacho de ação para adicionar dentro de nossoMensagensFachadae estamos prontos para testar nosso efeito:

deleteOne(id: string): void {
this.store.dispatch(deleteMessage({ id }));
}

Agora, você está pronto para chamar a mensagem deleteOne de qualquer lugar em seu aplicativo!

Voilá! Terminamos aqui! A implementação do NgRx nunca pareceu tão simples!

Como configurei o NgRx em Angular 16 com componentes autônomos (5)

Pedaçoferramenta de código abertoajude mais de 250.000 desenvolvedores a criar aplicativos com componentes.

Transforme qualquer interface do usuário, recurso ou página em umcomponente reutilizável— e compartilhe-o em seus aplicativos. É mais fácil colaborar e construir mais rápido.

Saber mais

Divida aplicativos em componentes para facilitar o desenvolvimento de aplicativos e aproveite a melhor experiência para os fluxos de trabalho que você deseja:

10 recursos úteis do Angular que você provavelmente nunca usou 10 recursos úteis do Angular que você pode ter perdido.blog.bitsrc.io
As 8 principais ferramentas para desenvolvimento angular em 2023blog.bitsrc.io
Começando com um novo projeto Angular em 2023blog.bitsrc.io
Como construímos micro frontendsConstruindo microfrontends para acelerar e dimensionar nosso processo de desenvolvimento web.blog.bitsrc.io
Como compartilhar componentes angulares entre projetos e aplicativosCompartilhe e colabore em componentes NG entre projetos para criar seus aplicativos mais rapidamente.blog.bitsrc.io
(Video) Estrategia de Reutilización de Rutas (RouteReuseStrategy) del Router de Angular.
Como construímos um sistema de design de componentesConstruindo um sistema de design com componentes para padronizar e dimensionar nosso processo de desenvolvimento de interface do usuário.blog.bitsrc.io
Criando um site de desenvolvedor com componentes BitComo construí meu portfólio usando componentes React independentes.blog.bitsrc.io

References

Top Articles
Latest Posts
Article information

Author: Kareem Mueller DO

Last Updated: 09/19/2023

Views: 5557

Rating: 4.6 / 5 (46 voted)

Reviews: 85% of readers found this page helpful

Author information

Name: Kareem Mueller DO

Birthday: 1997-01-04

Address: Apt. 156 12935 Runolfsdottir Mission, Greenfort, MN 74384-6749

Phone: +16704982844747

Job: Corporate Administration Planner

Hobby: Mountain biking, Jewelry making, Stone skipping, Lacemaking, Knife making, Scrapbooking, Letterboxing

Introduction: My name is Kareem Mueller DO, I am a vivacious, super, thoughtful, excited, handsome, beautiful, combative person who loves writing and wants to share my knowledge and understanding with you.