Angular 4中如何将服务Ajax请求的数据传递到组件?
问题分析与解决方案
这个问题我太熟了,本质是异步HTTP请求的处理逻辑错误导致的,咱们一步步拆解问题,再给出可行的解决方案:
为什么会返回undefined?
你遇到的问题有两个核心原因:
- 你的
getMenuItems()方法没有任何返回值,默认就会返回undefined,哪怕HTTP请求后续拿到了数据,也没法直接通过这个方法返回给组件。 - HTTP请求是异步操作,当你在组件的
constructor里调用this.sidebarService.getMenuItems()时,请求还没完成,服务里的menuItems和menuCategories还没被赋值,自然拿不到数据。
解决方案:用Observable或BehaviorSubject处理异步数据
方案1:让服务返回Observable,组件订阅获取数据
这种方式适合单次获取数据的场景,修改你的SidebarService和SidebarComponent如下:
修改SidebarService
import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { UserService } from '../../services/user.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class SidebarService { menuItems: any; menuCategories: any; constructor(private http: HttpClient, private userService: UserService) {} getMenuItems(): Observable<any> { if (this.userService.getAuth() != null) { return this.http.get('https://dev.mysite.com/api/calendar/auth', { params: new HttpParams() .set('token', this.userService.getAuth().__token) }).pipe( map(data => { console.log('data', data); // 把数据存到服务的变量里,方便后续复用 this.menuItems = data.menuItems ?? []; this.menuCategories = data.menuCategories ?? []; return { menuItems: this.menuItems, menuCategories: this.menuCategories }; }) ); } // 如果没有权限,返回空数据的Observable return new Observable(observer => { observer.next({ menuItems: [], menuCategories: [] }); observer.complete(); }); } }
修改SidebarComponent
把请求逻辑放到ngOnInit里(不要在constructor里做异步操作,ngOnInit是组件初始化完成后的生命周期钩子,更合适),然后订阅服务返回的Observable:
import { Component, OnInit } from '@angular/core'; import { UserService } from '../../services/user.service'; import { SidebarService } from './sidebar.service'; @Component({ selector: 'app-sidebar', templateUrl: './sidebar.component.html', styleUrls: ['./sidebar.component.scss'], }) export class SidebarComponent implements OnInit { menuItems: any; menuCategories: any; constructor(private userService: UserService, private sidebarService: SidebarService) {} ngOnInit() { this.sidebarService.getMenuItems().subscribe(result => { this.menuItems = result.menuItems; this.menuCategories = result.menuCategories; console.log('拿到菜单数据了', this.menuItems, this.menuCategories); }); } objectKeys(obj) { return Object.keys(obj); } }
方案2:用BehaviorSubject实现数据共享(更推荐)
如果你的菜单数据需要在多个组件之间共享,或者组件可能在请求完成后才初始化,用BehaviorSubject会更灵活,它能保存最新的数据,新订阅的组件也能立即拿到最新值:
修改SidebarService
import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { UserService } from '../../services/user.service'; import { BehaviorSubject, Observable } from 'rxjs'; @Injectable() export class SidebarService { private menuItemsSubject = new BehaviorSubject<any[]>([]); private menuCategoriesSubject = new BehaviorSubject<any[]>([]); // 对外暴露Observable,防止外部直接修改Subject的值 menuItems$: Observable<any[]> = this.menuItemsSubject.asObservable(); menuCategories$: Observable<any[]> = this.menuCategoriesSubject.asObservable(); constructor(private http: HttpClient, private userService: UserService) {} loadMenuItems() { if (this.userService.getAuth() != null) { this.http.get('https://dev.mysite.com/api/calendar/auth', { params: new HttpParams() .set('token', this.userService.getAuth().__token) }).subscribe(data => { console.log('data', data); const items = data.menuItems ?? []; const categories = data.menuCategories ?? []; // 更新Subject的值,所有订阅的组件都会收到新数据 this.menuItemsSubject.next(items); this.menuCategoriesSubject.next(categories); }); } } }
修改SidebarComponent
import { Component, OnInit } from '@angular/core'; import { UserService } from '../../services/user.service'; import { SidebarService } from './sidebar.service'; @Component({ selector: 'app-sidebar', templateUrl: './sidebar.component.html', styleUrls: ['./sidebar.component.scss'], }) export class SidebarComponent implements OnInit { menuItems: any[] = []; menuCategories: any[] = []; constructor(private userService: UserService, private sidebarService: SidebarService) {} ngOnInit() { // 加载菜单数据 this.sidebarService.loadMenuItems(); // 订阅菜单数据的变化 this.sidebarService.menuItems$.subscribe(items => { this.menuItems = items; }); this.sidebarService.menuCategories$.subscribe(categories => { this.menuCategories = categories; }); } objectKeys(obj) { return Object.keys(obj); } }
额外注意点
- 永远不要在组件的
constructor里执行异步操作,ngOnInit或ngAfterViewInit才是合适的时机。 - 记得在组件销毁时取消订阅Observable,防止内存泄漏(可以用
takeUntil操作符或者async管道,用async管道的话Angular会自动帮你取消订阅)。比如在模板里用*ngFor="let item of menuItems$ | async",这样组件里甚至不需要手动订阅。
内容的提问来源于stack exchange,提问作者Zabs
相关产品推荐
相关产品推荐

