Angular:如何用Observable实现嵌套查询树获取评论子列表?
实现Hacker News嵌套评论树
要构建完整的评论树,核心是通过递归方式获取每个评论的子评论并填充到children属性中,以下是具体实现步骤:
1. 修正接口类型(可选但推荐)
统一TypeScript类型规范,将Comments接口中的大写String改为小写string:
export interface Comments { by: string; id: number; parent: number; text: string; time: number; deleted?: boolean; dead?: boolean; kids?: number[]; children?: Comments[]; type: string; }
2. 扩展服务层,实现递归获取嵌套评论
在StorieCommentService中添加递归方法,获取单条评论及其所有子评论,同时过滤无效评论(已删除/标记为dead的):
@Injectable({ providedIn: 'root', }) export class StorieCommentService { private apiBaseUrl: string = 'https://hacker-news.firebaseio.com/v0'; constructor(private http: HttpClient) {} // 原有方法保持不变 getObservableDataStory(id: number): Observable<Story> { const urlReq = `${this.apiBaseUrl}/item/${id}.json`; return this.http.get<Story>(urlReq); } getObservableDataComment(id: number): Observable<Comments> { const urlReq = `${this.apiBaseUrl}/item/${id}.json`; return this.http.get<Comments>(urlReq); } // 新增:获取单条评论及其所有嵌套子评论 private getCommentWithChildren(id: number): Observable<Comments | null> { return this.getObservableDataComment(id).pipe( // 过滤已删除或无效的评论 map(comment => { if (comment.deleted || comment.dead) return null; return comment; }), concatMap(comment => { if (!comment || !comment.kids || comment.kids.length === 0) { return of({ ...comment, children: [] } as Comments); } // 递归获取所有子评论 return forkJoin(comment.kids.map(kidId => this.getCommentWithChildren(kidId))).pipe( map(children => ({ ...comment, children: children.filter(child => child !== null) // 过滤无效子评论 })) ); }) ); } // 新增:批量获取带嵌套子评论的评论列表 getDataCommentsWithChildren(ids: number[]): Observable<Comments[]> { if (!ids || ids.length === 0) { return of([]); } return forkJoin(ids.map(id => this.getCommentWithChildren(id))).pipe( map(comments => comments.filter(comment => comment !== null) as Comments[]) ); } }
3. 修改组件层,调用新的评论获取方法
替换原有loadCommentsData方法的调用逻辑,同时处理评论ID为空的边界情况:
@Component({ selector: 'app-main-comment', templateUrl: './main-comment.component.html', styleUrls: ['./main-comment.component.css'], }) export class MainCommentComponent implements OnInit { story!: Story; comments!: Comments[]; isLoading = false; // 新增加载状态 constructor( private activatedRoute: ActivatedRoute, private storieCommentService: StorieCommentService ) {} ngOnInit() { this.loadStoryData(); } loadStoryData() { this.activatedRoute.params .pipe( switchMap(({ id }) => this.storieCommentService.getObservableDataStory(id) ) ) .subscribe({ next: story => { this.story = story; if (story.kids && story.kids.length > 0) { this.loadCommentsData(story.kids); } else { this.comments = []; } }, error: err => console.error('加载文章失败:', err) }); } loadCommentsData(ids: number[]) { this.isLoading = true; this.storieCommentService .getDataCommentsWithChildren(ids) .pipe(finalize(() => this.isLoading = false)) .subscribe({ next: comments => this.comments = comments, error: err => console.error('加载评论失败:', err) }); } }
关键逻辑说明
- 递归获取:
getCommentWithChildren方法会在获取到当前评论后,自动遍历其kids数组,递归调用自身获取每个子评论的完整树结构。 - 无效评论过滤:在每一层递归中都会过滤已删除或标记为
dead的评论,避免无效节点出现在评论树中。 - 批量处理:使用
forkJoin批量发起子评论请求,提升请求效率,同时保证所有子评论加载完成后再组装到父评论的children属性中。
内容的提问来源于stack exchange,提问作者SoleeL
相关产品推荐
相关产品推荐

