我正在迁移角度应用程序以使用信号和输入信号,但发现了一个我不满意的情况。
在之前的
@Input()
中,我可以定义一个setter来在特定输入更改时触发“效果”,并且我可以为对服务器的请求设置一个可观察对象,例如
@Input()
set id(value: number) {
this.loading = true;
this.entity$ = this.service.findById(value).pipe(finalize(() => {
this.loading = false;
}));
}
我喜欢避免使用
ngOnChanges
,因为我可以避免验证哪个输入已更改。
我能找到的最接近信号是使用
effect
来触发查询,但文档并不真正推荐它
大多数应用程序代码中很少需要效果,但在特定情况下可能很有用
避免使用效果来传播状态变化。这可能会导致 ExpressionChangedAfterItHasBeenChecked 错误、无限循环更新或不必要的更改检测周期。
由于这些风险,默认情况下不允许设置信号,但如果绝对必要,可以启用。
因为我必须将
loading
信号设置为 true,所以我会破坏最后一部分
effect(() => {
this.loading.set(true);
this.entity$ = this.service.findById(value).pipe(finalize(() => {
this.loading.set(false);
}));
})
我还找到了一种在输入信号上使用
toObservable()
的方法,但它在幕后使用 effetc()
所以我想我也受到同样的限制。
有推荐的方法吗?对于这种情况,我是否依赖
ngOnChanges
?
解决这个问题的一种方法是使用
toObservable
将信号转换为可观察量,然后我们可以应用 switchMap 并进行 API 调用,因为可观察量没有限制,我们可以根据需要设置加载信号!
import { AsyncPipe, CommonModule } from '@angular/common';
import {
Component,
inject,
Injectable,
input,
InputSignal,
signal,
} from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { bootstrapApplication } from '@angular/platform-browser';
import { of, delay, Observable } from 'rxjs';
import { finalize, switchMap } from 'rxjs/operators';
import 'zone.js';
@Injectable({ providedIn: 'root' })
export class TestService {
findById(id: string): Observable<any> {
return of({ id: Math.random.toString(), name: 'test' }).pipe(delay(2000));
}
}
@Component({
selector: 'app-child',
standalone: true,
imports: [CommonModule],
template: `
{{loading()}}
@if(!loading() && (observable$ | async); as data) {
API DONE -> for ID {{id()}}: {{data | json}}
} @else {
Loading...
}
`,
})
export class Child {
service = inject(TestService);
id: InputSignal<string | null> = input<string | null>(null);
loading = signal(false);
observable$ = toObservable(this.id).pipe(
switchMap((id: string | null): any => {
this.loading.set(true);
return id
? this.service.findById(id).pipe(
finalize(() => {
this.loading.set(false);
})
)
: of(false);
})
);
ngOnInit() {}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [Child],
template: `
<app-child [id]="id"/>
`,
})
export class App {
id: string = Math.random().toString();
constructor() {
setInterval(() => {
this.id = Math.random().toString();
}, 5000);
}
}
bootstrapApplication(App);