Skip to content

Comprendre et Utiliser les Sélecteurs dans NgXs

Introduction:

NgXS offre une méthode déclarative pour accéder aux états grâce aux sélecteurs. Ces sélecteurs sont des fonctions qui peuvent être utilisées pour récupérer des portions de l'état. Nous allons détailler le fonctionnement des sélecteurs et comment les utiliser dans un composant Angular.

Pour suivre ce contenu, il faut déjà avoir créer l'état et l'action (voir Créer un état et Créer une action). Sinon, regardez le code en entier à la fin de ce contenu 😃

1. Qu'est-ce qu'un sélecteur ?

Un sélecteur est une méthode statique, décorée avec @Selector(), à l'intérieur d'une classe State. Il permet de récupérer une partie ou l'ensemble des données stockées dans un état. Les sélecteurs sont optimisés pour éviter des recalculs inutiles, rendant l'accès à l'état performant.

2. Création et utilisation du sélecteur getUsersList:

2.1. Définir le sélecteur:

Dans notre UserState (src/app/store/users.state.ts), nous avons le sélecteur getUsersList:

typescript
@Selector()
static getUsersList(state: UserStateModel): User[] {
    return state.usersList
}
@Selector()
static getUsersList(state: UserStateModel): User[] {
    return state.usersList
}

Ce sélecteur retourne simplement la liste des utilisateurs depuis l'état.

3. Comment utiliser le sélecteur dans un composant:

3.1. Utilisez le décorateur @Select:

NgXS fournit un décorateur pratique, @Select, qui peut être utilisé pour injecter directement un Observable représentant une partie de l'état dans votre composant.

Dans UsersComponent, nous utilisons ce décorateur:

typescript
@Select(UserState.getUsersList) users$!: Observable<User[]>
@Select(UserState.getUsersList) users$!: Observable<User[]>

Cela crée un Observable users$ qui émettra la liste des utilisateurs chaque fois qu'elle est mise à jour dans l'état.

3.2. Utilisation dans le template:

Dans le fichier users.component.html, vous pouvez utiliser users$ avec la pipe async pour itérer sur la liste des utilisateurs:

html
<div *ngFor="let user of users$ | async">
    <div class="user">
        <div class="user__name">{{ user.name }}</div>
        <div class="user__email">{{ user.email }}</div>
    </div>
</div>
<div *ngFor="let user of users$ | async">
    <div class="user">
        <div class="user__name">{{ user.name }}</div>
        <div class="user__email">{{ user.email }}</div>
    </div>
</div>

3.3. Charger des données:

La méthode ngOnInit est utilisée pour déclencher l'action GetUsersAction qui récupère les utilisateurs:

typescript
ngOnInit() {
    this.store.dispatch(new GetUsersAction())
}
ngOnInit() {
    this.store.dispatch(new GetUsersAction())
}

Code en entier

ts
import { Injectable } from "@angular/core";
import { Action, State, StateContext, Selector } from "@ngxs/store";
import { Observable, tap } from "rxjs";
import { User } from "src/app/core/interfaces/user";
import { UserService } from "src/app/core/services/user.service";
import { GetUsersAction } from "./users.action";

export interface UserStateModel {
    usersList: User[]
}

@State({
    name: 'users',
    defaults: {
        usersList: []
    }
})
@Injectable()
export class UserState {
    constructor(
        private userService: UserService
    ) {}

    @Selector()
    static getUsersList(state: UserStateModel): User[] {
        return state.usersList
    }

    @Action(GetUsersAction)
    getUsers(context: StateContext<UserStateModel>, action: GetUsersAction): Observable<any> {
        return this.userService.getAll(action.sort)
            .pipe(
                tap((users: User[]) => {
                    context.patchState({
                        usersList: users
                    })
                })
            )
    }
}
import { Injectable } from "@angular/core";
import { Action, State, StateContext, Selector } from "@ngxs/store";
import { Observable, tap } from "rxjs";
import { User } from "src/app/core/interfaces/user";
import { UserService } from "src/app/core/services/user.service";
import { GetUsersAction } from "./users.action";

export interface UserStateModel {
    usersList: User[]
}

@State({
    name: 'users',
    defaults: {
        usersList: []
    }
})
@Injectable()
export class UserState {
    constructor(
        private userService: UserService
    ) {}

    @Selector()
    static getUsersList(state: UserStateModel): User[] {
        return state.usersList
    }

    @Action(GetUsersAction)
    getUsers(context: StateContext<UserStateModel>, action: GetUsersAction): Observable<any> {
        return this.userService.getAll(action.sort)
            .pipe(
                tap((users: User[]) => {
                    context.patchState({
                        usersList: users
                    })
                })
            )
    }
}
ts
export class GetUsersAction {
    static readonly type = '[Users] Get Users';
    constructor(public sort: string) {}
}
export class GetUsersAction {
    static readonly type = '[Users] Get Users';
    constructor(public sort: string) {}
}
ts
import { Component, OnInit } from '@angular/core'
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { User } from 'src/app/core/user';
import { GetUsersAction } from 'src/app/store/users.action';
import { UserState } from 'src/app/store/users.state';

@Component({
    selector: 'app-users',
    templateUrl: 'users.component.html'
})
export class UsersComponent implements OnInit {
    @Select(UserState.getUsersList) users$!: Observable<User[]> 
   
    constructor(
        private store: Store
    ) { }

    ngOnInit() {
        this.store.dispatch(new GetUsersAction())
    }
}
import { Component, OnInit } from '@angular/core'
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { User } from 'src/app/core/user';
import { GetUsersAction } from 'src/app/store/users.action';
import { UserState } from 'src/app/store/users.state';

@Component({
    selector: 'app-users',
    templateUrl: 'users.component.html'
})
export class UsersComponent implements OnInit {
    @Select(UserState.getUsersList) users$!: Observable<User[]> 
   
    constructor(
        private store: Store
    ) { }

    ngOnInit() {
        this.store.dispatch(new GetUsersAction())
    }
}
html
<div *ngFor="let user of users$ | async">
    <div class="user">
        <div class="user__name">{{ user.name }}</div>
        <div class="user__email">{{ user.email }}</div>
    </div>
</div>
<div *ngFor="let user of users$ | async">
    <div class="user">
        <div class="user__name">{{ user.name }}</div>
        <div class="user__email">{{ user.email }}</div>
    </div>
</div>
ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { User } from "src/app/core/interfaces/user";

@Injectable({
    providedIn: 'root'
})
export class UserService {
    readonly url: string = 'https://jsonplaceholder.typicode.com/users'

    constructor(
        private http: HttpClient
    ) {}

    getAll(): Observable<User[]> {
        return this.http.get<User[]>(this.url)
    }
}
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { User } from "src/app/core/interfaces/user";

@Injectable({
    providedIn: 'root'
})
export class UserService {
    readonly url: string = 'https://jsonplaceholder.typicode.com/users'

    constructor(
        private http: HttpClient
    ) {}

    getAll(): Observable<User[]> {
        return this.http.get<User[]>(this.url)
    }
}
ts
export interface User {
    id: number;
    name: string;
    username?: string;
    email: string;
    address?: {
        street: string;
        suite: string;
        city: string;
        zipcode: string;
        geo: {
            lat: string;
            lng: string;
        }
    };
    phone?: string;
    website?: string;
    company?: {
        name: string;
        catchPhrase: string;
        bs: string;
    };
}
export interface User {
    id: number;
    name: string;
    username?: string;
    email: string;
    address?: {
        street: string;
        suite: string;
        city: string;
        zipcode: string;
        geo: {
            lat: string;
            lng: string;
        }
    };
    phone?: string;
    website?: string;
    company?: {
        name: string;
        catchPhrase: string;
        bs: string;
    };
}