Angular4:多路由出口下父组件向路由子组件异步传值问题
嘿,针对你这两个Angular 4的组件传数据问题,我来给你梳理下实用的解决办法,都是实际开发中验证过的:
路由加载的子组件没法直接用模板绑定的方式传值,下面这几种方法是最常用的:
路由路径参数传递
适合传递一些标识类的固定数据(比如用户ID),先在路由配置里定义参数:const routes: Routes = [ // 把uid作为路径参数 { path: 'user/:uid', component: UserheaderComponent } ];子组件里通过
ActivatedRoute订阅参数变化:import { ActivatedRoute } from '@angular/router'; constructor(private route: ActivatedRoute) {} ngOnInit() { this.route.paramMap.subscribe(params => { const uid = params.get('uid'); // 可以用uid去请求接口,或者直接赋值给组件属性 this.user.uid = uid; }); }Query参数传递
适合传递可选的、非核心的参数(比如筛选条件),跳转路由时带上query参数:import { Router } from '@angular/router'; constructor(private router: Router) {} // 跳转时携带参数 goToUser() { this.router.navigate(['/user'], { queryParams: { name: '张三', username: 'zhangsan' } }); }子组件里获取query参数:
this.route.queryParamMap.subscribe(queryParams => { this.user.name = queryParams.get('name'); this.user.username = queryParams.get('username'); });共享服务传递(最适合异步数据)
这是处理跨组件异步传值的黄金方案,先创建一个共享服务:import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class UserDataService { // BehaviorSubject会保留最新值,新订阅者能立即拿到 private userData$ = new BehaviorSubject<any>(null); // 对外暴露可观察对象,防止外部直接修改源数据 public currentUserData$ = this.userData$.asObservable(); // 父组件调用这个方法传递数据 updateUserData(data: any) { this.userData$.next(data); } }父组件异步获取数据后,调用服务的方法:
constructor(private userDataService: UserDataService) {} ngOnInit() { // 模拟异步请求数据 setTimeout(() => { const asyncData = { name: '张三', icon: 'avatar.png', username: 'zhangsan', uid: '1001' }; this.userDataService.updateUserData(asyncData); }, 1500); }子组件订阅服务的数据流:
constructor(private userDataService: UserDataService) {} ngOnInit() { this.userDataService.currentUserData$.subscribe(data => { if (data) { // 合并数据到组件的user对象 this.user = { ...this.user, ...data }; this.loading = false; } }); }路由Resolve守卫预加载数据
如果需要在路由激活前就准备好数据(避免子组件加载后再显示加载状态),可以用Resolve守卫:
先创建Resolve类:import { Resolve } from '@angular/router'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { UserDataService } from './user-data.service'; @Injectable({ providedIn: 'root' }) export class UserDataResolver implements Resolve<any> { constructor(private userDataService: UserDataService) {} // 这里可以调用异步接口获取数据 resolve(): Observable<any> { return this.userDataService.fetchUserDataFromApi(); } }配置路由时关联Resolve:
const routes: Routes = [ { path: 'user', component: UserheaderComponent, resolve: { userData: UserDataResolver } } ];子组件里直接获取预加载的数据:
ngOnInit() { this.route.data.subscribe(data => { this.user = data.userData; this.loading = false; }); }
首先得搞清楚为什么@Input没用:通过router-outlet加载的子组件,并不是在父组件模板里用<app-userheader [data]="userData"></app-userheader>这种方式声明的,Angular的@Input只对模板中直接绑定的组件实例生效,路由加载的组件相当于独立实例,所以输入属性根本不会被触发。
针对你的异步传数据场景,最推荐的还是共享服务方案,也就是刚才问题1里提到的BehaviorSubject方式,原因有两个:
- 不管你有多少个router-outlet,只要子组件注入了共享服务,就能收到数据;
- 异步数据的传递非常顺畅,BehaviorSubject会自动把最新数据推送给所有订阅的子组件,完美解决你说的异步传值需求。
另外补充个小细节:如果你的多个子组件对应不同的路由场景,可以在服务里给数据流加个标识(比如路由路径),这样能精准给对应子组件传值,避免数据混乱。比如:
// 服务里修改传递逻辑 updateUserData(data: any, outletKey: string) { this.userData$.next({ data, outletKey }); } // 子组件里根据标识过滤 this.userDataService.currentUserData$.subscribe(payload => { if (payload && payload.outletKey === 'headerOutlet') { this.user = payload.data; } });
内容的提问来源于stack exchange,提问作者Relm

