【问题标题】:Angular .subscribe not updating dataAngular .subscribe 不更新数据
【发布时间】:2021-07-16 12:12:22
【问题描述】:

我在使用 .subscribe() 时遇到问题,我用来更新 UI 的值本身并没有更新。在我的 Inspect Element Network 标签中,我可以看到数据是正确的。在任何人提到它之前,嵌套的.subscribe() 不是问题,第一次订阅总是正确的,因为用户必须登录才能查看此屏幕。

ngOnInit(): void {
    this.service.isLoggedIn().subscribe( u => {
        this.user = u;
        this.id = this.location.selected.value.id
        this.location.getSelected().subscribe( selectedLocation => {
          if (selectedLocation) {
            this.showTable = true;
            if (selectedLocation.members) {
              this.member = true;
            }
            if (selectedLocation.schedules) {
              this.schedules = true;
            }
            if (selectedLocation.team) {
              this.team = true;
            }
          }
        });
    });
getSelected(): Observable<Location> {
    if (this.selected && this.selected.value) {
      return of(this.selected.value);
    } else {
      return this.selected.asObservable();
    }
  }

这是检查元素中我的网络选项卡中显示的内容:

{
    "id": 4,
    "member": true,
    "schedules": true,
    "team": false
}

但是,例如,当我转到此页面时,if(selectedLocation) 被触发,但其他if()s 没有,因此我的变量没有改变,因此 UI 也没有。

【问题讨论】:

  • 如果请求是异步的,您可能需要自己触发变更检测。
  • 1. 嵌套订阅可以替换为forkJoin 2. 这与错误有关:你如何获得当您显示的函数getSelected() 清楚地返回BehaviorSubject 或者它是.value 而不是HTTP 请求时,浏览器控制台网络中的响应?
  • @martin 嗨,谢谢您的回复,我之前看到有人提到过更改检测,markForCheck()detectChanges() 但几乎没有解释如何使用它们,或者它们是如何工作的.
  • @MichaelD 我不完全确定你的第二点,我刚离开大学并开始在一家新公司工作,我没有建立这个项目,我只是被要求修复这个错误.

标签: angular rxjs observable subscribe behaviorsubject


【解决方案1】:

我认为我们在这里看到的不够多,无法真正解决问题所在。如果是关于角度的变化检测,以下可能会有所帮助。

// Angular will perform this dependency injection for you
constructor(private ngZone: NgZone) { }

/*****
 * Wrap every emission of an observable (next, complete, & error)
 * With a callback function, effectively removing the invocation
 * of these emissions from the observable to the callback.
 *
 * This isn't really too useful except for as a helper function for
 * our NgZone Operators where we leverage this wrapper to run an
 * observable in a specific JavaScript environment.
 *****/
callBackWrapperObservable<T>(
  input$: Observable<T>, 
  callback: (fn: (v) => void) => void
): Observable<T> {
  const callBackBind = fn => (v = undefined) => callback(() => fn(v));
  const bindOn = (ob, tag) => callBackBind(ob[tag].bind(ob));
  return new Observable<T>(observer => {
    const sub = input$.subscribe({
      next: bindOn(observer, "next"),
      error: bindOn(observer, "error"),
      complete: bindOn(observer, "complete")
    });
    return { unsubscribe: () => sub.unsubscribe() };
  });
}

/*****
 * If we've left the angular zone, we can use this to re-enter
 * 
 * If a third party library returns a promise/observable, we may no longer be in
 * the angular zone (This is the case for the Google API), so now we can convert such
 * observables into ones which re-enter the angular zone
 *****/
ngZoneEnterObservable<T>(input$: Observable<T>): Observable<T> {
  return this.callBackWrapperObservable(input$, this.ngZone.run.bind(this.ngZone));
}

/*****
 * This is a pipeable version of ngZoneEnterObservable
 *****/
ngZoneEnter<T>(): MonoTypeOperatorFunction<T> {
  return this.ngZoneEnterObservable;
}

/*****
 * Any actions performed on the output of this observable will not trigger 
 * angular change detection. 
 *****/
ngZoneLeaveObservable<T>(input$: Observable<T>): Observable<T> {
  return this.callBackWrapperObservable(input$, this.ngZone.runOutsideAngular.bind(this.ngZone));
}

/*****
 * Pipeable version of ngZoneLeaveObservable
 *****/
ngZoneLeave<T>(): MonoTypeOperatorFunction<T> {
  return this.ngZoneLeaveObservable;
}

// How I would use this to try to solve your problem.
ngOnInit(): void {
  this.service.isLoggedIn().pipe(

    mergeMap(u => {
      this.user = u;
      // The logic of the following line makes no sense to me, 
      // you're assuming selected has a value and then right after
      // you're calling a function that explicitly doesn't assume this.
      this.id = this.location.selected.value.id;
      return this.location.getSelected();
    }),

    ngZoneEnter()

  ).subscribe(selectedLocation => {

    if (selectedLocation) {
      this.showTable = true;
      if (selectedLocation.members) {
        this.member = true;
      }
      if (selectedLocation.schedules) {
        this.schedules = true;
      }
      if (selectedLocation.team) {
        this.team = true;
      }
    }
    
  });
}

【讨论】:

  • 你是什么意思我假设 selected 有一个值,然后在调用一个假设它没有的函数之后?
  • 查看您为 this.location.getSelected 提供的代码。它有一个 if-else 分支来判断该值是否已设置(这很愚蠢,因为 BehaviourSubject 会在它到达时返回该值,但是嘿)。
【解决方案2】:

我在这里为你整理了一个堆栈闪电战:https://stackblitz.com/edit/angular-posts-behavior-subject-procedural-vs-declarative-zsbs9e

希望它足够接近来代表您想要实现的目标。

以下是关键代码:

  // Stream that emits the logged in user
  loggedInUser$ = this.isLoggedIn();

  // Stream that emits each time the user selects a different location
  locationChangedSubject = new BehaviorSubject<number>(0);
  locationChanged$ = this.locationChangedSubject.asObservable();

  // Combined stream to ensure both streams emit before processing
  selectedLocation$ = combineLatest([
    this.loggedInUser$,
    this.locationChanged$
  ]).pipe(
    switchMap(([user, locationId]) =>
      this.getSelected(locationId).pipe(
        tap(selectedLocation => {
          this.showTable = Boolean(selectedLocation);
          this.member = Boolean(selectedLocation?.members);
          this.schedules = Boolean(selectedLocation?.schedules);
          this.team = Boolean(selectedLocation?.team);
        })
      )
    )
  );

一些注意事项:

  1. 我为每个流创建了单独的声明属性。这为您提供了更大的灵活性,更容易订阅/取消订阅(如果您不使用异步管道)或直接绑定到属性(如果您使用异步管道)。

  2. 我使用 combineLatest 来组合流,而不是使用嵌套订阅。 (不推荐使用嵌套订阅。)

  3. 我修改了if 条件集以设置真或假。目前,您似乎只是将值设置为 true,而从未设置为 false 来清除它们。

  4. 由于switchMap 是一个高阶映射运算符,它会自动订阅它的内部可观察对象(从this.getSelected() 返回的那个。

【讨论】:

    猜你喜欢
    • 2019-11-30
    • 2016-10-10
    • 2017-06-23
    • 1970-01-01
    • 2021-02-17
    • 2020-11-16
    • 2018-09-22
    • 1970-01-01
    • 2019-07-19
    相关资源
    最近更新 更多