我在订阅组件生命周期挂钩中的可观察对象时遇到问题。内容在模板中正确显示。然而,通过组件方法更改 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 更改,额外的页面刷新就会在短时间内显示旧状态。
对此问题的任何指导表示赞赏。
首先,我认为在这种情况下你不需要“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>
希望这有帮助。