web-dev-qa-db-fra.com

Comment appliquer des filtres à * ngFor?

Apparemment, Angular 2 utilisera des tuyaux au lieu de filtres comme dans Angular1 en conjonction avec ng-for pour filtrer les résultats, bien que la mise en œuvre semble encore vague, sans documentation claire.

À savoir ce que j'essaie de réaliser pourrait être vu du point de vue suivant

<div *ng-for="#item of itemsList" *ng-if="conditon(item)"></div>

Comment mettre en œuvre de manière à utiliser des tuyaux?

216
Khaled

En principe, vous écrivez un tuyau que vous pouvez ensuite utiliser dans la directive *ngFor.

Dans votre composant:

filterargs = {title: 'hello'};
items = [{title: 'hello world'}, {title: 'hello kitty'}, {title: 'foo bar'}];

Dans votre modèle, vous pouvez transmettre une chaîne, un nombre ou un objet à votre canal à utiliser pour filtrer sur:

<li *ngFor="let item of items | myfilter:filterargs">

Dans ta pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'myfilter',
    pure: false
})
export class MyFilterPipe implements PipeTransform {
    transform(items: any[], filter: Object): any {
        if (!items || !filter) {
            return items;
        }
        // filter items array, items which match and return true will be
        // kept, false will be filtered out
        return items.filter(item => item.title.indexOf(filter.title) !== -1);
    }
}

N'oubliez pas d'enregistrer votre pipe dans app.module.ts; vous n'avez plus besoin d'enregistrer les tuyaux dans votre @Component

import { MyFilterPipe } from './shared/pipes/my-filter.pipe';

@NgModule({
    imports: [
        ..
    ],
    declarations: [
        MyFilterPipe,
    ],
    providers: [
        ..
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Voici un Plunker qui explique l'utilisation d'un filtre personnalisé et du tube de découpe intégré pour limiter les résultats.

Veuillez noter (comme plusieurs commentateurs l'ont souligné) que il y a une raison pourquoi il n'y a pas de tuyaux de filtrage intégrés dans Angular.

321
phuc77

Bon nombre d'entre vous ont d'excellentes approches, mais l'objectif ici est d'être générique et de définir un canal de matrice extrêmement réutilisable dans tous les cas en relation avec * ngFor.

callback.pipe.ts (n'oubliez pas d'ajouter ceci dans le tableau de déclaration de votre module)

import { PipeTransform, Pipe } from '@angular/core';

@Pipe({
    name: 'callback',
    pure: false
})
export class CallbackPipe implements PipeTransform {
    transform(items: any[], callback: (item: any) => boolean): any {
        if (!items || !callback) {
            return items;
        }
        return items.filter(item => callback(item));
    }
}

Ensuite, dans votre composant, vous devez implémenter une méthode avec la signature suivante: (item: any> boolean) ==, dans mon cas par exemple, je l'ai appelée filterUser, qui filtre l'âge des utilisateurs qui sont plus grands moins de 18 ans.

Votre composant

@Component({
  ....
})
export class UsersComponent {
  filterUser(user: IUser) {
    return !user.age >= 18
  }
}

Et le dernier mais non le moindre, votre code html ressemblera à ceci:

Votre HTML

<li *ngFor="let user of users | callback: filterUser">{{user.name}}</li>

Comme vous pouvez le constater, ce tuyau est assez générique dans tous les tableaux, comme les éléments devant être filtrés via un rappel. Dans mon cas, je l’ai trouvé très utile pour les scénarios similaires à * ngFor.

J'espère que cela t'aides!!!

codematrix

74
code5

Manière simplifiée (utilisée uniquement sur les petites baies en raison de problèmes de performances. Dans les grandes baies, vous devez créer le filtre manuellement via le code):

Voir: https://angular.io/guide/pipes#appendix-no-filterpipe-or- orderbypipe

@Pipe({
    name: 'filter'
})
@Injectable()
export class FilterPipe implements PipeTransform {
    transform(items: any[], field : string, value : string): any[] {  
      if (!items) return [];
      if (!value || value.length == 0) return items;
      return items.filter(it => 
      it[field].toLowerCase().indexOf(value.toLowerCase()) !=-1);
    }
}

Usage:

<li *ngFor="let it of its | filter : 'name' : 'value or variable'">{{it}}</li>

Si vous utilisez une variable comme second argument, n'utilisez pas de guillemets.

C'est ce que j'ai implémenté sans utiliser de pipe.

composant.html

<div *ngFor="let item of filter(itemsList)">

composant.ts

@Component({
....
})
export class YourComponent {
  filter(itemList: yourItemType[]): yourItemType[] {
    let result: yourItemType[] = [];
    //your filter logic here
    ...
    ...
    return result;
  }
}
25
Thang Le

Je ne sais pas quand il est entré, mais ils ont déjà fabriqué un tube en tranches qui le fera. C'est bien documenté aussi.

https://angular.io/docs/ts/latest/api/common/index/SlicePipe-pipe.html

<p *ngFor="let feature of content?.keyFeatures | slice:1:5">
   {{ feature.description }}
</p>
16
SpaceBeers

Vous pouvez également utiliser les éléments suivants:

<template ngFor let-item [ngForOf]="itemsList">
    <div *ng-if="conditon(item)"></div>
</template>

Cela affichera seulement la div si vos articles correspondent à la condition

Voir le documentation angulaire pour plus d'informations. Si vous avez également besoin de l'index, utilisez les éléments suivants:

<template ngFor let-item [ngForOf]="itemsList" let-i="index">
    <div *ng-if="conditon(item, i)"></div>
</template>
9
Jeroen

les tuyaux dans Angular2 sont similaires aux tuyaux sur la ligne de commande. La sortie de chaque valeur précédente est introduite dans le filtre après le tuyau, ce qui facilite la chaîne de filtres comme ceci:

<template *ngFor="#item of itemsList">
    <div *ngIf="conditon(item)">{item | filter1 | filter2}</div>
</template>
7
Ben Glasser

Pour cette exigence, j'implémente et publie un composant générique . Voir

https://www.npmjs.com/package/w-ng5

Pour utiliser ces composants, avant, installez ce paquet avec npm:

npm install w-ng5 --save

Après, importer le module dans app.module

...
import { PipesModule } from 'w-ng5';

Dans l'étape suivante, ajoutez la section declare de app.module:

imports: [
  PipesModule,
  ...
]

Exemple d'utilisation

Filtrage d'une chaîne simple

<input type="text"  [(ngModel)]="filtroString">
<ul>
  <li *ngFor="let s of getStrings() | filter:filtroString">
    {{s}}
  </li>
</ul>

Filtrage de chaînes complexes - champ 'Valeur' ​​de niveau 2

<input type="text"  [(ngModel)]="search">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.valor2', value: search}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtrage d'une chaîne complexe - champ central - 'Valeur' ​​au niveau 1

<input type="text"  [(ngModel)]="search3">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.valor1', value: search3}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtrage d'un tableau complexe simple - champ 'Nome' niveau 0

<input type="text"  [(ngModel)]="search2">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'nome', value: search2}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtrage dans les champs de l'arborescence - champ 'Valor' au niveau 2 ou 'Valor' au niveau 1 ou 'Nome' au niveau 0

<input type="text"  [(ngModel)]="search5">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.valor2', value: search5}, {field:'n1.valor1', value: search5}, {field:'nome', value: search5}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtrage du champ inexistant - 'Valeur' ​​dans un niveau inexistant 3

<input type="text"  [(ngModel)]="search4">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.n3.valor3', value: search4}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Ce composant fonctionne avec un niveau d'attribut infini ...

J'ai créé un plunker basé sur les réponses ici et ailleurs.

De plus, j'ai dû ajouter un @Input, @ViewChild et ElementRef du <input> et créer et subscribe() à un observable de celui-ci.

Filtre de recherche Angular2: PLUNKR (UPDATE: plunker ne fonctionne plus)

4
Nate May

Le tuyau serait la meilleure approche. mais en dessous on travaillerait aussi.

<div *ng-for="#item of itemsList">
  <ng-container *ng-if="conditon(item)">
    // my code
  </ng-container>
</div>
3
Hardik Patel

Une autre approche que j'aime utiliser pour les filtres spécifiques à une application consiste à utiliser une propriété personnalisée en lecture seule sur votre composant, ce qui vous permet d'encapsuler la logique de filtrage de manière plus propre que d'utiliser un tuyau personnalisé (IMHO).

Par exemple, si je veux lier à albumList et filtrer sur searchText:

searchText: "";
albumList: Album[] = [];

get filteredAlbumList() {
    if (this.config.searchText && this.config.searchText.length > 1) {
      var lsearchText = this.config.searchText.toLowerCase();
      return this.albumList.filter((a) =>
        a.Title.toLowerCase().includes(lsearchText) ||
        a.Artist.ArtistName.toLowerCase().includes(lsearchText)
      );
    }
    return this.albumList;
}

Pour lier le code HTML, vous pouvez ensuite lier la propriété en lecture seule:

<a class="list-group-item"
       *ngFor="let album of filteredAlbumList">
</a>

Je trouve que, pour les filtres spécialisés spécifiques à une application, cela fonctionne mieux qu'un tuyau car il conserve la logique liée au filtre avec le composant.

Les tuyaux fonctionnent mieux pour les filtres réutilisables dans le monde entier.

1
Rick Strahl

Je sais que c'est une vieille question, cependant, j'ai pensé qu'il pourrait être utile de proposer une autre solution.

équivalent de AngularJS de cette

<div *ng-for="#item of itemsList" *ng-if="conditon(item)"></div>

dans Angular 2+, vous ne pouvez pas utiliser * ngFor et * ngIf sur un même élément, le résultat sera le suivant:

<div *ngFor="let item of itemsList">
     <div *ngIf="conditon(item)">
     </div>
</div>

et si vous ne pouvez pas utiliser comme conteneur interne, utilisez plutôt ng-container. ng-container est utile lorsque vous souhaitez ajouter de manière conditionnelle un groupe d'éléments (par exemple, en utilisant * ngIf = "foo") dans votre application, mais ne souhaitez pas les envelopper avec un autre élément.

1
tgralex

Idéalement, vous devriez créer un tuyau 2 angulaire pour cela. Mais vous pouvez faire ce tour.

<ng-container *ngFor="item in itemsList">
    <div*ngIf="conditon(item)">{{item}}</div>
</ng-container>
1
sh977218

Une solution simple qui fonctionne avec Angular 6 pour filtrer un ngFor, c'est la suivante:

<span *ngFor="item of itemsList"  >
  <div *ngIf="yourCondition(item)">
    
    your code
    
  </div>
</span

Les portées sont utiles car elles ne représentent rien par nature.

1
Michael V

Ceci est mon code:

import {Pipe, PipeTransform, Injectable} from '@angular/core';

@Pipe({
    name: 'filter'
})
@Injectable()
export class FilterPipe implements PipeTransform {
    transform(items: any[], field : string, value): any[] {
      if (!items) return [];
      if (!value || value.length === 0) return items;
      return items.filter(it =>
      it[field] === value);
    }
}

Échantillon:

LIST = [{id:1,name:'abc'},{id:2,name:'cba'}];
FilterValue = 1;

<span *ngFor="let listItem of LIST | filter : 'id' : FilterValue">
                              {{listItem .name}}
                          </span>
1
Pàldi Gergő

J'ai créé le tuyau suivant pour obtenir les éléments souhaités d'une liste.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {

  transform(items: any[], filter: string): any {
    if(!items || !filter) {
      return items;
    }
    // To search values only of "name" variable of your object(item)
    //return items.filter(item => item.name.toLowerCase().indexOf(filter.toLowerCase()) !== -1);

    // To search in values of every variable of your object(item)
    return items.filter(item => JSON.stringify(item).toLowerCase().indexOf(filter.toLowerCase()) !== -1);
  }

}

La conversion en minuscules est juste pour correspondre de manière insensible à la casse. Vous pouvez l'utiliser dans votre vue comme ceci: -

<div>
  <input type="text" placeholder="Search reward" [(ngModel)]="searchTerm">
</div>
<div>
  <ul>
    <li *ngFor="let reward of rewardList | filter:searchTerm">
      <div>
        <img [src]="reward.imageUrl"/>
        <p>{{reward.name}}</p>
      </div>
    </li>
  </ul>
</div>
1
Sanchit Tandon

La première étape consiste à créer un filtre à l'aide de @Pipe dans votre fichier composant.ts:

your.component.ts

import { Component, Pipe, PipeTransform, Injectable } from '@angular/core';
import { Person} from "yourPath";

@Pipe({
  name: 'searchfilter'
})
@Injectable()
export class SearchFilterPipe implements PipeTransform {
  transform(items: Person[], value: string): any[] {
    if (!items || !value) {
      return items;
    }
    console.log("your search token = "+value);
    return items.filter(e => e.firstName.toLowerCase().includes(value.toLocaleLowerCase()));
  }
}
@Component({
  ....
    persons;

    ngOnInit() {
         //inicial persons arrays
    }
})

Et structure de données de l'objet Personne:

person.ts

export class Person{
    constructor(
        public firstName: string,
        public lastName: string
    ) { }
}

Dans votre vue en fichier HTML:

your.component.html

    <input class="form-control" placeholder="Search" id="search" type="text" [(ngModel)]="searchText"/>
    <table class="table table-striped table-hover">
      <colgroup>
        <col span="1" style="width: 50%;">
        <col span="1" style="width: 50%;">
      </colgroup>
      <thead>
        <tr>
          <th>First name</th>
          <th>Last name</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let person of persons | searchfilter:searchText">
          <td>{{person.firstName}}</td>
          <td>{{person.lastName}}</td>
        </tr>
      </tbody>
    </table>
0
Piotr R

Après quelques recherches sur Google, je suis tombé sur ng2-search-filter. In prendra votre objet et appliquera le terme recherché à toutes les propriétés d'objet recherchant une correspondance.

0
alindber

Voici un exemple que j'ai créé il y a quelque temps et sur lequel j'ai blogué, qui inclut un travail en réseau. Il fournit un filtre qui peut filtrer n’importe quelle liste d’objets. En gros, vous spécifiez simplement la propriété et la valeur {key: value} dans votre spécification ngFor.

Ce n'est pas très différent de la réponse de @NateMay, sauf que je l'explique de manière relativement détaillée.

Dans mon cas, j'ai filtré une liste non ordonnée sur du texte (filterText) que l'utilisateur a entré par rapport à la propriété "label" des objets de mon tableau avec ce type de balise:

<ul>
  <li *ngFor="let item of _items | filter:{label: filterText}">{{ item.label }}</li>
</ul>

https://long2know.com/2016/11/angular2-filter-pipes/

0
long2know

Ceci est votre tableau

products: any = [
        {
            "name": "John-Cena",
                    },
        {
            "name": "Brock-Lensar",

        }
    ];

Ceci est votre boucle ngFor Filtrer par:

<input type="text" [(ngModel)]='filterText' />
    <ul *ngFor='let product of filterProduct'>
      <li>{{product.name }}</li>
    </ul>

Là j'utilise filterProduct instantané des produits, parce que je veux préserver mes données d'origine. Ici, le modèle _filterText est utilisé comme zone de saisie. Lorsqu'il y a toujours une fonction de modification de changement, elle sera appelée. Dans setFilterText, performProduct est appelé, le résultat ne sera renvoyé que par ceux qui correspondent à l'entrée. J'utilise des minuscules pour les majuscules.

filterProduct = this.products;
_filterText : string;
    get filterText() : string {
        return this._filterText;
    }

    set filterText(value : string) {
        this._filterText = value;
        this.filterProduct = this._filterText ? this.performProduct(this._filterText) : this.products;

    } 

    performProduct(value : string ) : any {
            value = value.toLocaleLowerCase();
            return this.products.filter(( products : any ) => 
                products.name.toLocaleLowerCase().indexOf(value) !== -1);
        }
0
Gajender Singh

Sur la base de la très élégante solution de conduite de rappel proposée ci-dessus, il est possible de la généraliser un peu plus en permettant la transmission de paramètres de filtre supplémentaires. Nous avons alors:

callback.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'callback',
  pure: false
})
export class CallbackPipe implements PipeTransform {
  transform(items: any[], callback: (item: any, callbackArgs?: any[]) => boolean, callbackArgs?: any[]): any {
    if (!items || !callback) {
      return items;
    }
    return items.filter(item => callback(item, callbackArgs));
  }
}

composant

filterSomething(something: Something, filterArgs: any[]) {
  const firstArg = filterArgs[0];
  const secondArg = filterArgs[1];
  ...
  return <some condition based on something, firstArg, secondArg, etc.>;
}

html

<li *ngFor="let s of somethings | callback : filterSomething : [<whatWillBecomeFirstArg>, <whatWillBecomeSecondArg>, ...]">
  {{s.aProperty}}
</li>
0
Blablalux