• 12 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 20/06/2022

Implémentez la recherche

Dans un component de type list-view, comme la liste des candidats dans votre application, une fonctionnalité qui est très souvent demandée est la recherche en temps réel.

Il y a deux grandes variantes de cette fonctionnalité : une où la recherche passe par une requête serveur, et l'autre où la recherche se fait en local.

Avec un state management réactif, les deux versions sont beaucoup plus simples à implémenter que si on passait par des tableaux simples, par exemple, car tout le comportement à l'intérieur de ces fonctionnalités est réactif :

  • l'utilisateur rentre ses critères de recherche – il s'agit d'un flux de données, modélisable sous forme d'Observable, facilité par les Observables  valueChanges  d'Angular ;

  • soit ce flux est envoyé au serveur, et le flux qui en retourne est le flux final de données ;

  • soit ce flux est mélangé à l'Observable déjà présent pour filtrer les données en local.

Je vous propose maintenant d'implémenter la recherche en local.

Ajoutez les contrôles

Vous allez permettre aux utilisateurs d'effectuer des recherches par nom, prénom et entreprise.

Il vous faudra donc deux contrôles : un  input  de type  text , et un dropdown pour sélectionner le type de recherche.

searchCtrl!: FormControl;
searchTypeCtrl!: FormControl;

Vous allez initialiser les contrôles dans une méthode appelée dansngOnInit , en utilisant le FormBuilder que vous allez injecter dans leconstructor :

constructor(private candidatesService: CandidatesService,
            private formBuilder: FormBuilder) { }

ngOnInit(): void {
    this.initForm();
    this.initObservables();
    this.candidatesService.getCandidatesFromServer();
}

private initForm() {
    this.searchCtrl = this.formBuilder.control('');
}

InitialisersearchCtrl est facile, mais comment faire pour que les valeurs émises par  searchTypeCtrl soient typées strictement pour être les clés lastName ,  firstName et company de la classe Candidate ?

Plusieurs solutions s'offrent à vous. Je vous propose d'utiliser un enum . Créez un dossier  enums dans le module et créez-y un fichiercandidate-search-type.enum.ts :

export enum CandidateSearchType {
  LASTNAME = 'lastName',
  FIRSTNAME = 'firstName',
  COMPANY = 'company'
}

Ça vous permet de créer, dans le component, un tableau d'options pour le dropdown :

searchTypeOptions!: {
    value: CandidateSearchType,
    label: string
}[];

Cet objet associe des valeurs valides pour la recherche à un label pour l'affichage dans le dropdown.

Vous pouvez maintenant initialiser ce tableau, puis initialisersearchTypeCtrl avec une valeur par défaut valide :

private initForm() {
    this.searchCtrl = this.formBuilder.control('');
    this.searchTypeCtrl = this.formBuilder.control(CandidateSearchType.LASTNAME);
    this.searchTypeOptions = [
        { value: CandidateSearchType.LASTNAME, label: 'Nom' },
        { value: CandidateSearchType.FIRSTNAME, label: 'Prénom' },
        { value: CandidateSearchType.COMPANY, label: 'Entreprise' }
    ];
}

Pour rester dans des contrôles Material, ajoutez MatSelectModule auxexports de MaterialModule :

@NgModule({
  exports: [
    MatToolbarModule,
    MatCardModule,
    MatListModule,
    MatButtonModule,
    MatIconModule,
    MatFormFieldModule,
    MatInputModule,
    MatCheckboxModule,
    MatRadioModule,
    MatProgressSpinnerModule,
    MatSelectModule
  ]
})
export class MaterialModule {}

Et, côté template, ajoutez une div au  mat-title-group  :

<mat-card>
    <mat-card-title-group>
        <mat-card-title>
            Candidats
        </mat-card-title>
        <div class="form">
            <mat-form-field appearance="fill">
                <input matInput type="text" [formControl]="searchCtrl">
                <mat-icon matSuffix>search</mat-icon>
            </mat-form-field>
            <mat-form-field appearance="fill">
                <mat-select [formControl]="searchTypeCtrl">
                    <mat-option *ngFor="let option of searchTypeOptions" [value]="option.value">{{ option.label }}</mat-option>
                </mat-select>
            </mat-form-field>
        </div>
    </mat-card-title-group>
    <!-- ... -->
</mat-card>

Ici :

  • vous ajoutez un input avec un mat-icon en matSuffix  , ce qui affichera l'icône à l'intérieur du champ ;

  • vous créez un mat-select – qui fonctionne comme un select HTML classique – et vous itérez sur le tableau d'options pour générer un dropdown avec des valeurs et des labels valides.

Visuellement, ça donne :

Des contrôles de recherche
Des contrôles de recherche

La suite : écouter les changements de valeur de ces champs pour filtrer les candidats affichés !

Observez les contrôles : effectuez la recherche

Pour utiliser les entrées utilisateur dans le champ de texte et le dropdown pour filtrer la liste des candidats, il faudra changer l'implémentation decandidates$ .

D'abord, dans initObservables() , créez deux Observables à partir des FormControls :

const search$ = this.searchCtrl.valueChanges.pipe(
    startWith(this.searchCtrl.value),
    map(value => value.toLowerCase())
);
const searchType$: Observable<CandidateSearchType> = this.searchTypeCtrl.valueChanges.pipe(
    startWith(this.searchTypeCtrl.value)
);

Ici, comme précédemment, vous ajoutez un opérateur startWith pour faire émettre les Observables au moment de la souscription – ils émettront la valeur par défaut des champs.

Pour le champ de texte, la transformation en minuscules va permettre de créer une recherche qui n'est pas sensible à la casse (où "snapface", "SNAPFACE" et "SnaPFaCe" sont tous les trois reconnus, par exemple).

Vous allez maintenant combiner ces Observables avec l'Observable du service :

this.candidates$ = combineLatest([
    search$,
    searchType$,
    this.candidatesService.candidates$
]).pipe(
    // filter candidates here
);

L'opérateur combineLatest prend un tableau d'Observables en argument.

Il attend que chaque Observable ait émis au moins une fois, et puis, à chaque émission d'un des Observables, émet les dernières émissions de tous les Observables sous forme de tuple.

Ici, vous aurez d'abord un tuple de cette forme :

['', 'lastName', [ tableau de tous les candidats ]]

Du coup, vous pouvez utiliser la fonction Array.filter pour filtrer le tableau :

this.candidates$ = combineLatest([
    search$,
    searchType$,
    this.candidatesService.candidates$
    ]
).pipe(
    map(([search, searchType, candidates]) => candidates.filter(candidate => candidate[searchType]
        .toLowerCase()
        .includes(search as string))
    )
);

Dans le filter ici :

  • vous regardez  candidate[searchType] , ce qui est l'équivalent de  candidate.lastName si  searchType === 'lastName' , par exemple ;

  • vous le passez en minuscules pour que la recherche ne soit pas sensible à la casse ;

  • vous vérifiez si l'attribut sélectionné contient la chaîne de caractères passée dans le champ de recherche.

Avec cet Observable finalement très simple, la recherche fonctionne !

Recherche implémentée
Recherche en cours

En résumé

  • Un enum est l'une des multiples manières de typer strictement des chaînes de caractères.

  • Tout transformer en minuscules permet de créer une recherche insensible à la casse.

  • combineLatest émet les dernières émissions de tous ses Observables sous forme de tuple à chaque fois que l'un d'entre eux émet.

Dans le dernier chapitre du cours, vous donnerez aux utilisateurs la possibilité de modifier et supprimer les données. Vous êtes prêt ?

Exemple de certificat de réussite
Exemple de certificat de réussite