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

angularrxjsrxjs5

I have a component that asks my service for an Observable object (that is, the underlying http.get returns one single object).

The object (an observable) is used in conjunction with an async pipe in my template.
Unfortunately, I get an error:

Cannot read property 'lastname' of null

I have been breaking my head on this one. A similar type of code works correctly on a list of objects (in conjunction with *ngFor).

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

Method in my service:

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
}

In my component:

//... 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();
  }
}

Apparently the observable is/returns a null object value in the pipe.
I have checked if I am really getting a nonempty Observable from my service and I can confirm that there exists an (underlying) object returned from my service.

Of course, I could subscribe to the observable in my component after having retrieved the Observable from the service, but I would really like to use the async construct.

Btw, another question: Is there already a pattern on how to handle errors that occur in the async pipe? (A downside of using async pipes…. errors are delayed until rendering time of the view.

Best Answer

The first time your view renders, person is not defined, since that component property only gets created asynchronously, when the route params subscription fires. You need to initially create an empty observable. Instead of

person:Observable<Person>;

try

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

I normally handle errors in a service by

  1. return an empty observable
  2. report the error to some app-wide (singleton) service. Some (singleton) component then displays those errors (if appropriate) somewhere on the page.
Related Topic