使用模板中的异步管道可观察不适用于单个值

Observable with Async Pipe in template is not working for single value

我有一个组件向我的服务请求一个 Observable 对象(即底层 http.get returns 一个对象)。

对象(可观察对象)在我的模板中与异步管道结合使用。 不幸的是,我得到一个错误:

Cannot read property 'lastname' of null

我一直在为这个问题伤脑筋。类似类型的代码可以在对象列表上正确运行(与 *ngFor 结合使用)。

<li>{{(person | async)?.lastname}}</li>

我服务中的方法:

getPerson(id: string): Observable<Person> {        
   let url = this.personsUrl + "/" + id;
   return this.http.get(url, {headers: new Headers({'Accept':'application/json'})})
              .map(r => r.json())
              .catch(this.handleError); //error handler
}

在我的组件中:

//... imports omitted

@Component({
  moduleId: module.id,
  selector: 'app-details-person',
  templateUrl: 'details-person.component.html',
  styleUrls: ['details-person.component.css'],
})
export class DetailsPersonComponent implements OnInit, OnDestroy 
{
   person: Observable<Person>;
   sub: Subscription;

   constructor(private personService: PersonService, private route: ActivatedRoute) {
   }

  ngOnInit() {
     this.sub = this.route.params.subscribe(params => {
                 let persId = params['id'];
                 this.person = this.personService.getPerson(persId);
                });
  }

  ngOnDestroy(): void {
     this.sub.unsubscribe();
  }
}

显然,observable is/returns 管道中的空对象值。 我已经检查了我是否真的从我的服务中获得了一个非空的 Observable,并且我可以确认存在一个从我的服务返回的(底层)对象。

当然,我可以在从服务中检索 Observable 后订阅组件中的 observable,但我真的很想使用异步构造。

顺便说一句,另一个问题:是否已经有关于如何处理异步管道中发生的错误的模式? (使用异步管道的缺点....错误会延迟到视图的渲染时间。

您的视图第一次呈现时,person 未定义,因为该组件 属性 仅在路由参数订阅触发时异步创建。您首先需要创建一个空的可观察对象。而不是

person:Observable<Person>;

尝试

person = Observable.of<Person>(Person());  // or however you create an empty person object
    // the empty object probably needs a lastname field set to '' or null

我通常通过

处理服务中的错误
  1. return 一个空的可观察对象
  2. 向某个应用范围(单例)服务报告错误。某些(单例)组件随后会在页面某处显示这些错误(如果适用)。

请尝试换行

person:Observable< Person>

进入

person:Observable< any>;

如果这不起作用,请打印出此 {{ person | async | json }} 当然请进行上述更改,然后看看您得到什么。

您无需订阅ngOnInit中可观察到的路由数据。

与其在每次路由数据可观察对象发出时都订阅并创建一个全新的可观察对象,不如将路由数据与 personService 可观察对象链接在一起,形成一个新的可观察对象。这就是函数式编程的本质:

The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration.

考虑到这一点,您可以在声明时声明有关 "person" 的所有内容:

person: Observable<Person> = this.route.params.pipe(
  map(params => params.id),
  mergeMap(persId => this.personService.getPerson(persId)),
);

这样做有很多好处:

  • Observable 在订阅它之前不会真正触发(比如异步管道)。这意味着除非您在模板中使用异步管道,否则它不会向此人发出请求。
  • 您无需处理退订。异步管道会为你做到这一点。
  • 通过查看代码很容易看出 "Person" 的构成。全部定义在一处。
  • 您不需要创建一个空的初始化可观察对象,这可能不是有效的 "Person"。

话虽如此,异步管道仍然可以默认为 null,您可以在模板中使用 ngIf 来处理它:

 <li *ngIf="(person | async) as person">{{person.lastname}}</li>

如果您使用 Subject<T> observable 而不是 ReplaySubject<T>BehaviorSubject<T>,就会发生这种情况。

发生在 Subject<T> 上的是它 'broadcasts' 它的值只对当时注册的任何听众 一次

因此,如果您在可观察对象 message: Subject<string> 上调用 next('hello word'),它只能更新 UI,前提是 UI 的 'piece' 存在于那个时刻时间。

因此在此示例中,消息不会出现 - 除非您调用 next 两次!

<div *ngIf="message | async">
   here is the message {{ message | async }}
</div>

最简单的解决方案是使用 ReplaySubject<string>(1),其中 1 是它记住的值的数量(我们这里只需要一个)。