Angular作物与地区复选框筛选功能实现及问题解决
解决Angular中复选框筛选的重复显示与逻辑问题
看起来你遇到了两个核心问题:一是复选框因为数据重复而重复显示,二是筛选逻辑不符合需求。我会一步步帮你重构代码,实现你想要的功能。
问题根源分析
- 数据结构不合理:你的
CROPS数组里有两个同名的"Rice"条目,直接遍历渲染会导致复选框重复。 - 筛选逻辑未区分层级:作物筛选应该是全局隐藏该作物的所有卡片,地区筛选只隐藏对应地区的该作物卡片,而你的当前逻辑没有区分这两种筛选的作用范围。
解决方案步骤
1. 重构数据模型与数据源
首先我们拆分数据结构,分离唯一的作物列表、地区列表,以及作物-地区的关联数据:
// crop.model.ts export interface Crop { name: string; checked: boolean; } export interface District { name: string; checked: boolean; } export interface CropItem { cropName: string; districtName: string; subCategory: Subcategory[]; } export interface Subcategory { id: number; name: string; checked: boolean; }
// crop.data.ts import { Crop, District, CropItem } from "./crop.model"; // 存储作物与地区的关联数据 export const CROP_ITEMS: CropItem[] = [ { cropName: "Rice", districtName: "Thane", subCategory: [ { id: 1, name: "Basmati", checked: true }, { id: 2, name: "Ammamore", checked: true } ] }, { cropName: "Rice", districtName: "Nashik", subCategory: [ { id: 1, name: "Basmati", checked: true }, { id: 2, name: "Ammamore", checked: true } ] }, { cropName: "Wheat", districtName: "Nashik", subCategory: [ { id: 1, name: "Durum", checked: true }, { id: 2, name: "Emmer", checked: true } ] }, { cropName: "Barley", districtName: "Ratnagiri", subCategory: [ { id: 1, name: "Hulless Barley", checked: true }, { id: 2, name: "Barley Flakes", checked: true } ] } ]; // 提取唯一作物列表并初始化选中状态 export const CROPS: Crop[] = Array.from(new Set(CROP_ITEMS.map(item => item.cropName))) .map(name => ({ name, checked: true })); // 提取唯一地区列表并初始化选中状态 export const DISTRICTS: District[] = Array.from(new Set(CROP_ITEMS.map(item => item.districtName.trim()))) .map(name => ({ name, checked: true }));
2. 修改服务层实现筛选逻辑
我们用BehaviorSubject来管理状态,自动处理筛选逻辑:
// crop.service.ts import { Injectable } from "@angular/core"; import { Observable, BehaviorSubject } from "rxjs"; import { Crop, District, CropItem } from "../shared/crop.model"; import { CROPS, DISTRICTS, CROP_ITEMS } from "../shared/crop.data"; @Injectable({ providedIn: "root" }) export class CropService { private cropsSubject = new BehaviorSubject<Crop[]>(CROPS); private districtsSubject = new BehaviorSubject<District[]>(DISTRICTS); private cropItemsSubject = new BehaviorSubject<CropItem[]>(CROP_ITEMS); crops$ = this.cropsSubject.asObservable(); districts$ = this.districtsSubject.asObservable(); filteredCropItems$ = this.cropItemsSubject.asObservable(); constructor() { this.applyFilters(); // 初始化筛选 } // 更新作物选中状态 updateCropChecked(name: string, checked: boolean) { const updatedCrops = this.cropsSubject.value.map(crop => crop.name === name ? { ...crop, checked } : crop ); this.cropsSubject.next(updatedCrops); this.applyFilters(); } // 更新地区选中状态 updateDistrictChecked(name: string, checked: boolean) { const updatedDistricts = this.districtsSubject.value.map(district => district.name === name ? { ...district, checked } : district ); this.districtsSubject.next(updatedDistricts); this.applyFilters(); } // 核心筛选逻辑 private applyFilters() { const activeCrops = this.cropsSubject.value.filter(c => c.checked).map(c => c.name); const activeDistricts = this.districtsSubject.value.filter(d => d.checked).map(d => d.name); // 过滤出同时满足作物和地区都选中的条目 const filteredItems = CROP_ITEMS.filter(item => activeCrops.includes(item.cropName) && activeDistricts.includes(item.districtName.trim()) ); this.cropItemsSubject.next(filteredItems); } }
3. 更新组件逻辑
分离作物和地区的复选框事件处理:
// all-trades.component.ts import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { Crop, District, CropItem } from 'src/app/shared/crop.model'; import { CropService } from '../crop.service'; @Component({ selector: 'app-all-trades', templateUrl: './all-trades.component.html', styleUrls: ['./all-trades.component.css'], }) export class AllTradesComponent implements OnInit { crops$: Observable<Crop[]>; districts$: Observable<District[]>; filteredCropItems$: Observable<CropItem[]>; constructor(private cropService: CropService) { } ngOnInit(): void { this.crops$ = this.cropService.crops$; this.districts$ = this.cropService.districts$; this.filteredCropItems$ = this.cropService.filteredCropItems$; } onCropChange(event: any, crop: Crop) { this.cropService.updateCropChecked(crop.name, event.checked); } onDistrictChange(event: any, district: District) { this.cropService.updateDistrictChecked(district.name, event.checked); } }
4. 修改模板渲染
使用唯一的作物/地区列表渲染复选框,直接遍历筛选后的结果渲染卡片:
<app-header></app-header> <div fxLayout="row" fxLayout.lt-md="column" fxLayoutAlign="space-between start" fxLayoutAlign.lt-md="start stretch" > <div class="container-outer" fxFlex="20"> <div class="filters"> <section class="example-section"> <span class="example-list-section"> <h1>Select Crop</h1> </span> <span class="example-list-section"> <ul> <li *ngFor="let crop of crops$ | async"> <mat-checkbox [checked]="crop.checked" (change)="onCropChange($event, crop)" > {{ crop.name }} </mat-checkbox> </li> </ul> </span> </section> <section class="example-section"> <span class="example-list-section"> <h1>Select District</h1> </span> <span class="example-list-section"> <ul> <li *ngFor="let district of districts$ | async"> <mat-checkbox [checked]="district.checked" (change)="onDistrictChange($event, district)" > {{ district.name }} </mat-checkbox> </li> </ul> </span> </section> </div> </div> <div class="content container-outer" fxFlex="80"> <mat-card class="crop-card" style="min-width: 17%" *ngFor="let item of filteredCropItems$ | async" > <a [routerLink]="[item.cropName]"> <mat-card-header> <img mat-card-avatar class="example-header-image" src="/assets/icons/crops/{{ item.cropName }}.PNG" alt="crop-image" /> <mat-card-title>{{ item.cropName }}</mat-card-title> <mat-card-subtitle>100 Kgs</mat-card-subtitle> </mat-card-header> </a> <mat-card-content> <p>PRICE</p> </mat-card-content> <mat-card-content> <p>{{ item.districtName }}</p> </mat-card-content> </mat-card> </div> </div> <app-footer></app-footer>
功能说明
- 作物筛选:取消勾选"RICE"时,所有关联RICE的条目都会被过滤,对应的地区卡片全部隐藏。
- 地区筛选:取消勾选"Thane"时,只有Thane地区的RICE条目会被过滤,其他地区的RICE卡片正常显示。
- 复选框重复问题:通过提取唯一的作物和地区名称,彻底解决了重复显示的问题。
内容的提问来源于stack exchange,提问作者faisal moinuddin
相关产品推荐
相关产品推荐

