How can I implement a 'category filter' in

2020-07-24 04:43发布

问题:

Scenario

In an Angular2 app, I have some code in a component's view parent.component.html looping through an array of items and outputting each item as a new component:

<div class="list-items">
  <!-- In the attached image, this is displayed as a coloured card -->
  <app-item *ngFor="let item of items" [item]="item"></app-list-slide>
</div>

Each item has a category key which is an array of ids (corresponding to the id of each category in a separate list).

// Item JSON
[
  { 
    "id": 1,
    "imageUrl": "https://via.placeholder.com/300x150/FF0000/FFFFFF?text=1",
    "title": "One",
    "categories": [ 1 ]
  }
]

// Category JSON
[
  { "id": 1, "title": "Red Cards" },
  { "id": 2, "title": "Blue Cards" }
]

Requirement

The app needs to have a filter, dynamically generated by the categories (I might make this into a separate component):

<div class="items-filter">
  <!-- In the attached image, this is displayed as a list of category buttons -->
  <div *ngFor="let category of categories">
    <a (click)="filterItemsByCategory(category)">{{ category.title }}</a>
  </div>
  <div class="btn-o">Reset</div>
</div>

When a category item is clicked, only the items corresponding to that category should be shown. Ideally, multiple categories could be clicked, but that can be for later.

All the examples I can find for filters, seem to use text input based filtering, and I'm not really sure where to start with this.

Final product should look like this:

Similar Example

Here is an example of something very similar to what I am trying to do (but the text input box would be replaced with the array of category buttons):

Article: http://www.freakyjolly.com/angular-4-5-typescript-create-filter-list-with-check-boxes-to-select-from-list/

Demo: https://freakyjolly.com/demo/AngularJS/Angular5FilterList/

Question

My question is either, does anyone know of any good working examples of what I am trying to do, or can anyone suggest a good place to start with this?

One option I can think of is to create classes on the component corresponding to the ids of the categories class="category-1 category-2" but this seems messy.

Another option is using something like Masonary or Isotope, but a lot of the Angular libraries for these seem to be outdated: https://github.com/search?q=angular+masonry

Thanks

回答1:

This can be achieved by create a new variable, an array representing the filtered items and using *ngFor with those filtered items. You'd use Array.prototype.filter along with Array.prototype.includes to filter items based on whether the id of the category is included in the item's categories array of id values:

Component:

import { Component } from "@angular/core";
import { Item } from "./item.ts";
import { Category } from "./category.ts";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  items: Item[] = [
    {
      id: 1,
      imageUrl: "https://via.placeholder.com/300x150/FF0000/FFFFFF?text=1",
      title: "One",
      categories: [1]
    }
  ];
  categories: Category[] = [
    { id: 1, title: "Red Cards" },
    { id: 2, title: "Blue Cards" }
  ];
  // Create a shallow copy of the items array
  filteredItems: Item[] = [...this.items];

  filterItemsByCategory(category: Category) {
    this.filteredItems = this.items.filter((item: Item) => {
      return item.categories.includes(category.id);
    })
  }

  reset() {
    this.filteredItems = [...this.items];
  }
}

Template:

<div class="items-filter">
    <!-- In the attached image, this is displayed as a list of category buttons -->
    <div *ngFor="let category of categories">
        <button type="button" (click)="filterItemsByCategory(category)">{{ category.title }}</button>
    </div>
    <button type="button" (click)="reset()" class="btn-o">Reset</button>
</div>

<div class="list-items">
    <!-- In the attached image, this is displayed as a coloured card -->
    <app-item *ngFor="let item of filteredItems" [item]="item">
    </app-item>
</div>

Here is an example in action. If your date is async, which it most likely is in your actual application, you can use *ngIf and/or defaults to empty arrays [] to avoid trying to perform array operations on undefined/null.

Also, it's recommend to avoid using <a> elements as buttons, and instead just use <button> elements. Also, as mentioned in a comment, the angular team recommends NOT using pipes for filtering and/or sorting, so I'd avoid doing what the articles you link suggest.