Angular中含routerOutlet的组件间传值问题求助
Angular 跨组件数据传递解决方案:Category → Products(路由加载场景)
问题背景
Dashboard 组件左侧为 Category 组件(带多选分类复选框),右侧通过 routerOutlet 加载 Products 组件。需要实现:选中分类后,将选中的分类 ID 传递给 Products 组件,用于筛选产品列表。
现有代码
dashboard.component.html
<div class="columns"> <div class="column is-2"> <app-category></app-category> </div> <div class="column is-10"> <router-outlet ></router-outlet> </div> </div>
category.component.html
<div class="container"> <input type="checkbox" id="menu-toggle"> <label for="menu-toggle" class="hamburger-menu" (click)="showCategoryList()"><span></span></label> <div class="content"> <p class="menu-title title is-4" style="font-weight: normal">Categories</p> <ul class="menu-list"> <li class="subtitle category-list" *ngFor="let category of categories"> <input type="checkbox" [id]="category.id" (change)="filterProductsByCategory($event)"> <label [htmlFor]="category.id">{{category.name}}</label> </li> </ul> <div class="filter mb-3"><button (click)="getFilterData()">Filter</button></div> </div> </div>
category.component.ts
export class CategoryComponent implements OnInit{ categories: Category[] = []; activatedCategory?: Category; dataLoaded = false; currentCategories: Category[] = []; selectedCategory: string[] = []; // 修正类型:存储分类ID字符串 constructor(private categoryService: CategoryService, private filterService: FilterService) { } ngOnInit(): void { this.getCategories(); } getCategories() { this.categoryService.getCategories().subscribe(response => { this.categories = response.data; this.dataLoaded = true; }) } getActivatedCategory(category: Category) { this.activatedCategory = category; } setCurrencyCategory(category: Category) { this.currentCategories.push(category); } showCategoryList() { const content = document.querySelector(".content") as HTMLElement; content.style.transition = "0.5s ease"; content.style.display = content.style.display === "block" ? "none" : "block"; } filterProductsByCategory(event: any){ const categoryId = event.target.id; if(event.target.checked){ this.selectedCategory.push(categoryId); } else { const index = this.selectedCategory.indexOf(categoryId); if(index !== -1) this.selectedCategory.splice(index, 1); } } getFilterData(){ const parsedIds = this.selectedCategory.map(id => parseInt(id)); this.filterService.setSelectedCategories(parsedIds); } }
products.component.html
<div id="products"> <div *ngIf="!dataLoaded" class="lds-ring"><div></div><div></div><div></div><div></div></div> <h1 class="title">Products</h1> <div id="search-box"> <input class="input mb-4" type="text" placeholder="Filter by product name" [(ngModel)]="searchText"> <button class="button is-dark" type="button">Search</button> </div> <table *ngIf="dataLoaded && products" class="table is-hoverable is-fullwidth is-striped" > <thead> <tr> <th>ID</th> <th>Product Name</th> <th>Category ID</th> <th>Price</th> <th>Unit In Stock</th> <th>Unit On Order</th> <th>Reorder Level</th> </tr> </thead> <tbody> <tr *ngFor="let product of products | filterProducts: searchText"> <td>{{ product.id }}</td> <td>{{ product.name }}</td> <td>{{ product.categoryID }}</td> <td>{{ product.unitPrice| currency: "TR" : '₺' : '2.2-2' }}</td> <td>{{ product.unitsInStock }}</td> <td>{{ product.unitsOnOrder }}</td> <td>{{ product.reorderLevel }}</td> </tr> </table> <div *ngIf="!products">No Products!</div> </div>
products.component.ts
import { Subscription } from 'rxjs'; export class ProductsComponent implements OnInit, OnDestroy{ products: Product[] = []; searchText = ''; dataLoaded = false; private filterSubscription?: Subscription; constructor( private productService: ProductService, private activatedRoute: ActivatedRoute, private filterService: FilterService ) { } ngOnInit(): void { // 监听筛选条件变化 this.filterSubscription = this.filterService.getSelectedCategories().subscribe(categoryIds => { categoryIds.length > 0 ? this.getProductsByCategoryIds(categoryIds) : this.getProducts(); }); // 兼容原路由参数逻辑 this.activatedRoute.params.subscribe(params => { if(params["id"]){ this.filterService.setSelectedCategories([params["id"]]); } }) } getProducts() { this.productService.getProducts().subscribe(response => { this.products = response.data; this.dataLoaded = true; }) } getProductsByCategoryIds(categoryIds: number[]) { // 前端筛选:若后端支持批量分类查询,替换为对应接口调用 this.productService.getProducts().subscribe(response => { this.products = response.data.filter(p => categoryIds.includes(p.categoryID)); this.dataLoaded = true; }) } ngOnDestroy(): void { this.filterSubscription?.unsubscribe(); } }
核心解决方案:共享 Service(推荐)
针对路由加载的兄弟组件,使用 BehaviorSubject 创建单例共享服务是最便捷、低耦合的方案:
1. 创建 FilterService
import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class FilterService { private selectedCategories$ = new BehaviorSubject<number[]>([]); setSelectedCategories(categoryIds: number[]): void { this.selectedCategories$.next(categoryIds); } getSelectedCategories(): Observable<number[]> { return this.selectedCategories$.asObservable(); } }
2. 关键修改说明
- Category 组件:注入
FilterService,在点击「Filter」时将解析后的分类 ID 传递给服务。 - Products 组件:订阅服务的分类 ID 流,根据接收的数组筛选产品;组件销毁时取消订阅,避免内存泄漏。
- 修复原代码问题:修正
filterProductsByCategory中的删除逻辑(原代码错误地将字符串当作对象处理)。
可选方案:URL QueryParams 传递
若需要将筛选条件暴露在 URL 中(支持刷新保留状态),可使用路由参数传递:
Category 组件修改
import { Router } from '@angular/router'; constructor(private router: Router, ...) {} getFilterData(){ const parsedIds = this.selectedCategory.map(id => parseInt(id)); this.router.navigate(['/dashboard/products'], { queryParams: { categoryIds: parsedIds.join(',') } }); }
Products 组件修改
ngOnInit(): void { this.activatedRoute.queryParams.subscribe(params => { if(params['categoryIds']){ const categoryIds = params['categoryIds'].split(',').map(id => parseInt(id)); this.getProductsByCategoryIds(categoryIds); } else { this.getProducts(); } }); }
内容的提问来源于stack exchange,提问作者Jaguar
相关产品推荐
相关产品推荐

