为什么在生命周期钩子中订阅observable会在页面刷新时短时间内显示错误的数据?

问题描述 投票:0回答:1

我在订阅组件生命周期挂钩中的可观察对象时遇到问题。内容在模板中正确显示。然而,通过组件方法更改 DOM 会在页面刷新时产生奇怪的结果; DOM操作之前的值会短暂显示(忽略模板中的条件语句),然后正确显示数据。

由于某种原因,当订阅被某种方法触发时,它可以正常工作,例如元素上的单击事件

<p (click)="changeContent()">initial content</p>
。我在开发模式下使用 Angular v17 (ngserve)。

组件代码

import { Component, OnInit, AfterViewInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { delay, of } from "rxjs";
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-dummy',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './dummy.component.html',
  styleUrl: './dummy.component.sass'
})
export class DummyComponent implements OnInit, OnDestroy, AfterViewInit {

  displayValue: boolean = true;
  showExtraContent: boolean = false;
  subscription;


  constructor(
    private ref: ChangeDetectorRef
   ){}

  ngOnInit(){
    console.log("init");
  };
  
  ngAfterViewInit(){
    console.log("view init");
    this.sub();
  }

  sub(){
    let obs = of(1,4,6,9,10);
    this.subscription = obs.pipe(delay(500)).subscribe(
      res => {
        this.displayValue = false;
        this.showExtraContent = true;
      },
      err => {},
      () => {

      }
    );
  }

  removeContent($event: any){
    $event.target.remove();
    this.ref.detectChanges();
  }

  changeContent(){
    this.sub();
  }

  ngOnDestroy(){
    if(this.subscription){
      this.subscription.unsubscribe(); 
    }
  }

}

模板代码

@if(displayValue){
    <p (click)="changeContent()">initial content</p>
}@else{
    <p id="modified-content">modified content</p>
    @if(showExtraContent){
        <p (click)="removeContent($event)">Additional Content (Click to remove)</p>
    }
}

<p (click)="changeContent()">Change content</p>

为了说明行为,我使用以下 gif 显示页面刷新订阅按预期工作,但一旦 DOM 更改,额外的页面刷新就会在短时间内显示旧状态。

更改dom后页面刷新

对此问题的任何指导表示赞赏。

angular
1个回答
0
投票

首先,我认为在这种情况下你不需要“this.subscription”。只需订阅 observable 就足够了。

回答你的问题,当页面重新加载时,角度将采用默认值(displayValue = true,showExtraContent = false),直到可观察值被解析,这是异步发生的。

所以,我要做的就是添加一个“isLoading”属性,如下所示:

import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { delay, of } from "rxjs";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit {
  displayValue: boolean = true;
  showExtraContent: boolean = false;
  isLoading: boolean = true;

  ngOnInit(){
    console.log("[LOG] init");
    this.sub();
  };

  ngAfterViewInit() {
    console.log("[LOG] view init");
  }

  sub(){
    let obs = of(1,4,6,9,10);

    obs.pipe(delay(500)).subscribe( {
      next: (res) => {
        this.displayValue = false;
        this.showExtraContent = true;
      },
      complete: () => {
        console.log("[LOG] complete");
        this.isLoading = false;
      }
    });
  }

  removeContent($event: any){
    this.showExtraContent = false;
  }

  changeContent(){
    this.sub();
  }

  ngOnDestroy(){
  }
}

@if(!isLoading) {
  @if(displayValue) {
    <p (click)="changeContent()">initial content</p>
  }@else{
    <p id="modified-content">modified content</p>
    @if(showExtraContent) {
      <p (click)="removeContent($event)">Additional Content (Click to remove)</p>
    }
  }

  <p (click)="changeContent()">Change content</p>
}

这样做的缺点是组件在响应返回之前不会显示任何内容。您必须选择使用默认值或隐藏内容直到响应返回。没有其他选择,因为 Angular 在解析之前无法知道该值,这需要时间。

另外,我建议使用 *ngIf 而不是 @if,这是在模板中使用 if 的更现代的方式:

<div *ngIf="!isLoading">
  <p *ngIf="displayValue" (click)="changeContent()">initial content</p>
  <p *ngIf="!displayValue" id="modified-content">modified content</p>
  <p *ngIf="!displayValue && showExtraContent" (click)="removeContent($event)">Additional Content (Click to remove)</p>
  <p (click)="changeContent()">Change content</p>
</div>

希望这有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.