image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image
Diego Nunes
Diego Nunes05/12/2023 16:16
Compartilhe

NgRx ComponentStore — Gerenciamento de estado no Angular

  • #Angular

Hoje, falaremos um pouco sobre gerenciamento de estado no Angular. Na Pricefy, empresa onde eu trabalho, utilizamos o ComponentStore, do NgRx, para gerenciar os estados das nossas aplicações.

Nós dividimos o nosso gerenciador em duas partes:

  1. Effects: Responsável por fazer as requisições na API e enviar os dados para o gerenciador de estado.
  2. Store: Local onde ficam as lógicas e transformações das informações vindas do backend em interfaces que serão lidas pelos componentes.

Agora vamos para um exemplo prático

Primeiro, criaremos um novo projeto usando o Angular CLI:

ng new movies-app

Com o projeto criado, vamos instalar duas bibliotecas que nos ajudarão com o servidor fake, o json-server, que será o backend, e o concurrently, que nos permitirá executar o json-server e a nossa aplicação Angular com apenas um comando.

json-server:

npm install json-server

concurrently:

npm install concurrently

Após as instalações, vamos no package.json alterar a chave start para:

"concurrently \"json-server --watch db.json\" \"ng serve\""

Com essa configuração, quando iniciarmos nossa aplicação com o comando npm start, tanto o servidor quanto nossa aplicação estarão em execução.

Nosso próximo passo é criar a estrutura do nosso projeto. Para isso, criaremos uma pasta chamada services e, dentro dela, o nosso serviço movies.service.ts, que terá quatro funções para o nosso CRUD:

private url: string = `http://localhost:3000/movies`;

constructor(private http: HttpClient) { }

insertMovie(movie: Movie): Observable<Movie> {
return this.http.post<Movie>(this.url, movie);
}

updateMovie(movie: Movie, id: number): Observable<Movie> {
return this.http.put<Movie>(`${this.url}/${id}`, movie);
}

deleteMovie(id: number) {
return this.http.delete(`${this.url}/${id}`);
}

getAllMovies(): Observable<Movie[]> {
return this.http.get<Movie[]>(this.url);
}

Agora, vamos criar dois componentes que mostrarão os dados do store:

ng g c componente1
ng g c componente2

No nosso arquivo app.component.html, colocamos os dois componentes:

<div class="container">
<div class="col-6">
  <app-componente1></app-componente1>
</div>
<div class="col-6">
  <app-componente2></app-componente2>
</div>
</div>

Ah, vale lembrar que coloquei dentro do nosso index.html o style bootstrap, apenas para dar um pouco de estilo aos nossos componentes.

Bom, com nosso projeto estruturado, vamos ao que interessa. Para usarmos o ComponentStore, teremos de adicioná-lo a nossa aplicação com o comando:

ng add @ngrx/component-store@latest

Após a instalação, devemos inseri-lo dentro do nosso app.module como provider:

providers: [ ComponentStore ],

A seguir, criaremos uma pasta chamada state e, dentro dela, um arquivo injectable chamado movies.store.ts, que herdará de ComponentStore. Também criei duas interfaces neste mesmo arquivo.

export interface Movie {
id?: number;
name: string;
}

export interface MoviesState {
movies: Movie[];
}

@Injectable({ providedIn: 'root' })
 export class MoviesStore extends ComponentStore<MoviesState> {
}

No construtor, enviaremos o nosso array para o construtor do ComponentStore.

constructor() {
super( { movies: [] } )
}

Com o método select, podemos recuperar os dados de nosso estado como no exemplo abaixo:

readonly movies$: Observable<Movie[]> = this.select(state =>    state.movies);

O método updater é utilizado para atualizar o estado. Ele recebe uma função pura com o estado atual e com o valor que a ser atualizado e retorna o estado alterado. Abaixo temos o exemplo de adicionar, remover e editar um filme.

readonly addMovie = this.updater((state, movie: Movie) => ({
movies: [...state.movies, movie]
}));

readonly editMovie = this.updater((state, movie: Movie) => {
let actualMovie = state.movies.find(item => item.id === movie.id);
actualMovie.name = movie.name;
return state;
});

readonly removeMovie = this.updater((state, movie: Movie) => {
let movies = state.movies.filter(item => item.id !== movie.id);
state.movies = movies;
return state;
});

No nosso próximo passo, criaremos um arquivo movies.effects.ts e injetamos nele o nosso serviço e o store.

constructor(
  private moviesStore: MoviesStore,
  private moviesService: MoviesService
) {}

Com o código abaixo, buscaremos os dados na API e enviaremos para o gerenciador de estado.

readonly getMovies = this.moviesStore.effect((trigger$:   Observable<{}>) => {
return trigger$.pipe(
  switchMap(() => {
    return this.moviesService.getAllMovies().pipe(
      map((httpResponse) => {
        Object.keys(httpResponse).forEach((item) => {
        this.moviesStore.addMovie(httpResponse[item])
      });
     }),
     catchError((error: any) => {
       console.log(error);
       return EMPTY;
     })
    );
   }));
});

Com nosso gerenciador de estado configurado, podemos buscar os dados no store e apresentar em nossos dois componentes.

movies$ = this.moviesStore.movies$;

<tr *ngFor="let movie of (movies$ | async)">
  <th>
    {{ movie.id }}
  </th>
  <th>
    {{ movie.name }}
  </th>
</tr>

image

No componente 1, também coloquei funções para adicionar, editar e deletar um filme:

// Edit movie

this.moviesService.updateMovie(movie, id).subscribe(httpResponse  => {
this.moviesStore.editMovie(httpResponse);
this.showFormNewMovie = false;
this.form.reset();

// New movie
this.moviesService.insertMovie(movie).subscribe(httpResponse => {
this.moviesStore.addMovie(httpResponse);
this.showFormNewMovie = false;

// Delete movie
this.moviesService.deleteMovie(movie.id).subscribe(() => {
this.moviesStore.removeMovie(movie);
});

image

Pronto, agora ao adicionar, editar ou remover um filme, tanto o componente 1 quanto o componente 2 mostrarão as mesmas informações.

image

O código fonte se encontra no GitHub.

Até a próxima.

Compartilhe
Comentários (0)