Angular RxJS管道中console.log未打印到控制台问题排查
问题描述
开发Angular spinner-button组件时,实现了如下交互逻辑:
- 监听模板中按钮原生的点击事件
- 接收父组件传入的
Observable<boolean>类型输入属性refreshState$ - 按钮点击时读取
refreshState$的最新状态,当状态值为false时,对外发射自定义点击事件
调试过程中发现,RxJS管道内编写的console.log()始终没有向控制台打印任何输出。
组件完整实现代码如下:
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from "@angular/core"; import { combineLatest, fromEvent, Observable, Subject } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, map, takeUntil, withLatestFrom } from 'rxjs/operators'; @Component({ selector: "spinner-button", templateUrl: "./spinner-button.component.html", styleUrls: ["./spinner-button.component.css"] }) export class SpinnerButtonComponent { destroy$ = new Subject(); constructor() {} @ViewChild("clicker", { static: true }) clickerBtn: ElementRef; @Input() refreshState$: Observable<boolean>; @Input() label: string; @Input() refreshLabel: string; @Output() clicked = new EventEmitter(); ngAfterViewInit() { fromEvent(this.clickerBtn.nativeElement, 'click') .pipe(takeUntil(this.destroy$), debounceTime(300), withLatestFrom(this.refreshState$), map(([a, b]) => { console.log('bump',a,b); // 始终无输出 if (!b) { this.clicked.emit(); } })) .subscribe(c => this.clicked.emit()); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
父组件中该组件的使用方式:
<spinner-button [refreshState$]="refreshing$"></spinner-button>
父组件中传入的refreshing$定义与赋值逻辑:
refreshing$: Observable<boolean>; // 赋值逻辑 this.refreshing$ = this.priceStore$.pipe(map(data => data.refreshing));
根本原因
问题核心是withLatestFrom操作符的固有工作约束:
withLatestFrom的作用是:当源Observable(此处为按钮点击生成的事件流)发射值时,取传入的目标Observable(此处为refreshState$)的最新值,将两个值合并为数组传给下游- 硬性前提:如果目标Observable在源Observable发射值之前,从未发射过任何值,那么源Observable发来的所有事件都会被直接丢弃,不会传递到后续的管道操作符
结合上下文场景:父组件传入的refreshing$是从priceStore$映射得到的派生流,如果用户第一次点击按钮前,priceStore$还没有发射过任何数据,refreshing$就不存在已发出的最新值,此时点击事件会被withLatestFrom直接拦截丢弃,后续map操作符中的console.log自然永远不会执行。
除此之外原代码还存在一个逻辑缺陷:map操作符中已经根据状态判断调用过this.clicked.emit(),subscribe回调中又无条件再次调用发射方法,会导致符合触发条件时点击事件重复向外发射。
修复方案
调整流逻辑,给refreshState$补充默认初始值,保证点击事件触发前目标流一定存在可用值,同时修复重复发射事件的问题,修改后的ngAfterViewInit代码如下:
// 注意需要额外从rxjs/operators导入startWith操作符 ngAfterViewInit() { fromEvent(this.clickerBtn.nativeElement, 'click') .pipe( takeUntil(this.destroy$), debounceTime(300), // 给refreshState$设置默认初始值false,保证流始终存在已发射的有效值 withLatestFrom(this.refreshState$.pipe(startWith(false))), // 直接过滤掉刷新状态为true的点击事件,不需要在map中做判断 filter(([_, refreshState]) => !refreshState) ) .subscribe(() => { this.clicked.emit(); }); }
内容的提问来源于stack exchange,提问作者Matt W
相关产品推荐
相关产品推荐

