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

Posted

技术标签:

【中文标题】在模板中使用异步管道可观察到的不适用于单个值【英文标题】:Observable with Async Pipe in template is not working for single value 【发布时间】:2017-01-10 20:07:23 【问题描述】:

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

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

无法读取 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,并且我可以确认存在从我的服务返回的(基础)对象。

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

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

【问题讨论】:

这应该可以。请显示返回 observable 的服务代码,以及定义 person 的组件代码。或者更好的是,创建一个plunker。 嗨,马克,我已经更新了上面的问题部分。您可以阅读上面的服务片段和组件代码。谢谢。 【参考方案1】:

第一次渲染视图时,person 未定义,因为该组件属性仅在路由参数订阅触发时异步创建。您最初需要创建一个空的 observable。而不是

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

我通常通过

处理服务中的错误
    返回一个空的可观察对象 将错误报告给某些应用程序范围的(单例)服务。然后某些(单一)组件会在页面某处显示这些错误(如果适用)。

【讨论】:

嗨,马克,感谢您的回复。事实上,我同意关于人员和路由参数代码的异步行为。我听从了你的建议,但我仍然看到同样的错误。似乎视图是在定义人员之前呈现的(使用服务调用)。我是否处于错误的生命周期? 如果person = Observable.of&lt;Person&gt;(Person()); is defined before your constructor, it should exist when the view is first rendered. You can test this by commenting out your this.person = ...`代码。 嗨,马克,(之前)做过,但徒劳无功。这是一个大脑破坏者:-(我开始考虑我是否在库和兼容性方面存在问题。正如你之前所说......这应该有效。直截了当但仍然是一个问题。【参考方案2】:

请尝试换行

person:Observable< Person>

进入

person:Observable< any>;

如果这不起作用,请打印出这个 person | async | json ,当然请使用上面的更改,看看你会得到什么。

【讨论】:

你试过 flatMap 而不是 map 吗?【参考方案3】:

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

您可以将路由数据与 personService 可观察对象链接在一起,形成一个新的可观察对象,而不是在每次发出路由数据 observable 时都订阅和创建一个全新的 observable。这就是函数式编程的精髓:

函数式反应式编程的本质是指定 声明时值的动态行为。

考虑到这一点,您可以在声明时声明关于“人”的所有内容:

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

这样做有很多好处:

直到有东西订阅它(如异步管道),observable 才会真正触发。这意味着除非您在模板中使用异步管道,否则它不会向该人发出请求。 您不需要处理取消订阅。异步管道将为您完成这项工作。 通过查看代码很容易看出是什么构成了“Person”。这一切都在一个地方定义。 您不需要创建一个空的初始化 observable,它可能不是一个有效的“Person”。

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

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

【讨论】:

这应该是公认的答案。它避免了创建一个空的人对象,当有正确的方法时,它被用作一种变通方法,并避免了不必要的订阅。【参考方案4】:

如果您使用 Subject&lt;T&gt; observable 而不是 ReplaySubject&lt;T&gt;BehaviorSubject&lt;T&gt;,则可能会发生这种情况。

Subject&lt;T&gt; 发生的情况是,它只向当时注册的任何听众“广播”一次其值。

因此,如果您在可观察的message: Subject&lt;string&gt; 上调用next('hello word'),它只能在那个“部分”UI 存在的情况下及时更新UI。

所以在这个例子中消息不会出现 - 除非你调用next 两次!

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

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

【讨论】:

以上是关于在模板中使用异步管道可观察到的不适用于单个值的主要内容,如果未能解决你的问题,请参考以下文章

使用异步管道从可观察对象中读取对象字段

如何使用来自html的异步管道获取可观察对象的嵌套值

使用角度异步管道以完全反应式方式刷新数据

Angular/Rxjs 管道异步不适用于 s-s-r?

使用HTTP模块时出错 - 检测到的ASP.NET设置不适用于集成管理管道模式

如何对这个从管道可观察到的错误捕获的 Angular 打字稿 Http 错误拦截器进行单元测试?