如何使用管道来过滤游戏

129 阅读5分钟

你可以考虑通过使用管道来过滤游戏。在这种情况下,你必须建立你自己的自定义管道,因为Angular没有内置的过滤或排序管道。使用自定义管道可能只适用于少量的数据,因为这些类型的管道通常性能不高。与其使用管道,不如将过滤逻辑移到组件本身。


创建过滤器

我们首先需要的是一个过滤后的游戏列表,以便与之绑定。我们将为其创建一个属性,就像这样。

game-list.component.ts

import {Component, OnInit} from '@angular/core';
import {IGame} from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    listItem = 'Mario';
    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

下一步是添加一个属性,让我们知道用户何时改变过滤标准。当然,这将是当用户在文本框中输入一个字符串时,就像我们看到的那样。虽然过滤框中的文本更新了,但游戏列表还没有被过滤。

我们可以像这样添加这个属性。
game-list.component.ts

import {Component, OnInit} from '@angular/core';
import {IGame} from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

在这个属性上使用下划线的原因是我们实际上可以实现一个getter和setter。当数据绑定需要该值时,会调用getter。每当用户修改该值时(在过滤器文本框中输入),数据绑定就会调用setter,同时传入改变或更新的值。这个语法看起来像这样。
game-list.component.ts

import {Component, OnInit} from '@angular/core';
import {IGame} from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    get listFilter(): string {
        return this._listFilter;
    }
    set listFilter(value: string) {
        this._listFilter = value;
    }
    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

为数据绑定获取和设置值是很好的,但这并不能更新我们的列表。我们需要在用户在文本框中输入一些文本时执行过滤逻辑。因此,我们需要在setter中增加一些代码。被过滤的游戏数组应该被设置为被过滤的游戏列表,像这样。
game-list.component.ts

import {Component, OnInit} from '@angular/core';
import {IGame} from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    get listFilter(): string {
        return this._listFilter;
    }

    set listFilter(value: string) {
        this._listFilter = value;
        this.filteredGames = this.listFilter ? this.doFilter(this.listFilter) : this.games;
    }

    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

上面的代码使用三元操作符,根据列表中的过滤器字符串是否为空、空、未定义或填充了一个有效的过滤器字符串来执行一些逻辑。如果有一个过滤器的值,该 **doFilter()**方法运行。如果没有过滤值,那么过滤后的游戏属性将被分配给整个游戏集。换句话说,如果没有过滤器,就显示我们拥有的所有游戏。


添加过滤逻辑

doFilter()方法还没有定义,所以我们现在来创建它。
game-list.component.ts

import { Component, OnInit } from '@angular/core';
import { IGame } from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    get listFilter(): string {
        return this._listFilter;
    }

    set listFilter(value: string) {
        this._listFilter = value;
        this.filteredGames = this.listFilter ? this.doFilter(this.listFilter) : this.games;
    }

    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    doFilter(filterBy: string): IGame[] {
        filterBy = filterBy.toLocaleLowerCase();
        return this.games.filter((game: IGame) =>
            game.gameName.toLocaleLowerCase().indexOf(filterBy) !== -1);
    }

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

在上面突出显示的代码中,我们做的第一件事是将过滤器字符串设置为小写。这是在过滤数据时常用的技术,因为它使每个项目都能进行相似的比较。换句话说,它提供了一个不分大小写的比较。接下来,过滤后的游戏列表被返回。请注意数组过滤方法的使用,它创建了一个新的数组,其中的元素通过了函数中的测试。对于列表中的每个游戏,它首先被转换为小写字母,然后使用indexOf()方法来查看是否在游戏名称中找到了过滤文本。如果是,那么该游戏就被添加到过滤的游戏列表中。


配置构造函数

为了使其正常工作,我们需要在构造函数中设置一些默认数据。构造函数在组件被初始化时自动运行。这与任何面向对象语言中的构造函数的想法相同。在构造函数中,游戏列表应该被分配到过滤的游戏中开始。过滤器字符串的默认值将只是一个空字符串,因为我们不希望在用户在过滤器文本框中输入东西之前发生任何过滤。
game-list.component.ts

import { Component, OnInit } from '@angular/core';
import { IGame } from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    get listFilter(): string {
        return this._listFilter;
    }

    set listFilter(value: string) {
        this._listFilter = value;
        this.filteredGames = this.listFilter ? this.doFilter(this.listFilter) : this.games;
    }

    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    constructor() {
        this.filteredGames = this.games;
        this.listFilter = '';
    }

    doFilter(filterBy: string): IGame[] {
        filterBy = filterBy.toLocaleLowerCase();
        return this.games.filter((game: IGame) =>
            game.gameName.toLocaleLowerCase().indexOf(filterBy) !== -1);
    }

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

更新组件模板

为了使其发挥作用,我们还需要对组件模板进行一些调整。请看突出显示的几行。
game-list.component.html

<div class='card'>
    <div class='card-header'>
        {{pageTitle}}
    </div>
    <div class='card-body'>
        <div class="row">
            <div class="col-3">
                <input [(ngModel)]="listFilter" type="text" class="form-control" id="filterInput" placeholder="Type to filter">
            </div>
            <div class="col">
                <div *ngIf='listFilter' class="form-text text-muted">Filtered by: {{listFilter}}</div>
            </div>
        </div>
        <div class='table-responsive'>
            <table class='table' *ngIf='games && games.length'>
                <thead>
                <tr>
                    <th>
                        <button class='btn btn-primary'
                                (click)='toggleImage()'>
                            {{showImage ? 'Hide ' : 'Show'}} Image
                        </button>
                    </th>
                    <th style="color:firebrick">Game</th>
                    <th>Part#</th>
                    <th>Release Date</th>
                    <th>Cost</th>
                    <th>5 Thumb Rating</th>
                </tr>
                </thead>
                <tbody>
                <tr *ngFor='let game of filteredGames'>
                    <td>
                        <img *ngIf='showImage'
                             [src]='game.imageUrl'
                             [title]='game.gameName'
                             [style.width.px]='imageWidth'
                             [style.margin.px]='imageMargin'>
                    </td>
                    <td>{{ game.gameName }}</td>
                    <td>{{ game.gameCode | lowercase }}</td>
                    <td>{{ game.releaseDate }}</td>
                    <td>{{ game.price | currency:'USD':'symbol':'1.2-2' }}</td>
                    <td>{{ game.thumbRating }}</td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
    <div class="card-footer text-muted text-right">
        <small>Powered by Angular</small>
    </div>
</div>

过滤器开始工作了!


总结

我们的游戏列表组件现在已经相当完整了。通过添加基于用户输入的游戏过滤功能,我们看到了如何向Typescript组件添加自定义逻辑,在用户每次更新过滤条件时触发。这正是Angular所设计的类型,即创建响应用户输入的丰富用户界面。