DEV Community

Cliff Eby
Cliff Eby

Posted on • Edited on

Initial Nulls in Angular http – Recognition, Debugging and Solutions

I’ve struggled with displaying complex-object Observables in Angular. When I try to render a variable in my template, sometimes I get nothing, other times errors, combinations of the two, and on occasion, the value with no console errors. Dealing with null or undefined objects and properties is perplexing particularly with Observables. Lack of an opinionated way of displaying objects in your template in a desired format is often frustrating.

Handling asynchronous calls to back-end data-stores creates a new way of thinking for most. The evolution from callbacks to promises to observables and their variants (subjects and behavior subjects) has greatly improved the coding experience. I find that passing asynchronous data to components using a service and observable offers a reliable and easy to grasp concept. But often I know that I have data, it just doesn’t render.

Here are few approaches that can work if you have struggled like me. A working copy of the code below is on Stackblitz at https://stackblitz.com/edit/angular-7wojgn

Part I Auto subscribe

Let’s assume the you have a service injected in your component that returns an observable from an http get.

Consider a service method that gets

{ "name": "Bob", "roles": [ { "job": "carpenter" }, { "job": "woodworker" } ] }

after at least two seconds;

fetchMembers() {
  return this.http
      .get<any[]>(
        "https://nulls-d2af3.firebaseio.com/members.json"
      )
      .delay(2000)
}
Enter fullscreen mode Exit fullscreen mode

and, component code that calls service and logs the result.

  mdata: Observable<any>[];

  constructor(private _elementService: ElementService) {}

  ngOnInit(){
    this.mdata = this._elementService.fetchMembers();
    console.log(Members, this.mdata)
Enter fullscreen mode Exit fullscreen mode

When executed, and looking at the browser console, you see immediately that “mdata” is an observable, but it doesn’t yet have data because I have not yet subscribed to the observable. I can subscribe in the component, but you don’t need to. You can use the async pipe as an alternative which auto subscribes and unsubscribes for you.

Example A. Use async to auto subscribe and unsubscribe followed by the json pipe to convert the observable object to a json string. It will display the entire

<!—A. async and json pipe -->
{{mdata | async | json }}
Enter fullscreen mode Exit fullscreen mode

object and is generally an easy way to show that data is available to the template. Use of {{ mdata | json }} does not work because there is no subscription. Use of {{ mdata | async }} does not work because the object has not been converted to strings for display.

Example B. Use *ngif and async to display selected data

<!—B. *ngif and async -->
 <div *ngIf="mdata | async as mdata; else loading;">
  <h2>{{mdata.name}} {{mdata.roles[0].job}} {{mdata.roles}} </h2>
 </div>
<ng-template #loading>Loading User Data...</ng-template>
Enter fullscreen mode Exit fullscreen mode

Example B will check to see if “mdata” is not null and will display “Loading User Data…” until the observable is resolved. Note that the async pipe automatically subscribes and unsubscribes to the observable. Compared to method A, expression interpolation, method B offers better granularity, and if combined with an *ngfor property arrays could display your data in final form. For this case, the template will, after two seconds, render the following:

Bob carpenter [object Object],[object Object]

As shown, {{ mdata.roles }} is unable to display the json data without a property name.

If you plan to let the async pipe do the subscribing for you, this is probably a good point to write unit tests for your service and component.

Part II Self subscribe

Most tutorials on Observables subscribe in the component. Let’s see how that changes the data in the template.

Component code that calls the service, subscribes, and logs the result.

mdata: Observable<any>[];

  constructor(private _elementService: ElementService) {}

  ngOnInit(){
  this._elementService.fetchMembers().subscribe(res => {
  this.mdata = res
  console.log('Members', this.mdata);
  });

Enter fullscreen mode Exit fullscreen mode

The first thing to notice when executed is Members (in the browser console) shows the asynchronous call when Members’ properties populate after two seconds.

<!—C. async and json pipe -->
{{mdata | json }}

<!—D. *ngif and async -->

 <div *ngIf="mdata; else loading;">
  <h2 {{mdata.name}} {{mdata.roles[0].job}} {{mdata.roles[0]}} </h2>
<ng-template #loading>Loading User Data...</ng-template>
Enter fullscreen mode Exit fullscreen mode

For Examples C and D, we remove the async pipe and output identical values to Part I. But since we’re subscribed, we now have other options.

Part III – Dealing with nulls – Safe-Navigation and Elvis operators

<!—E. Value of an array property -->
{{mdata.name }}
Enter fullscreen mode Exit fullscreen mode

Example E accesses the observable’s description property. When executed Bob renders on the page after two seconds. Everything seems fine until you look at the console. It is littered with errors saying “ERROR TypeError: Cannot read property '0' of undefined.” These errors are produced before the observable is resolved and the console can’t say “Oh, never mind.”

To stop the errors, let’s try the Elvis operator. Use of the Elvis operator is saying get someProperty on someObject if someObject exists. It’s form is objectname?.property and can deal with deeply nested properties.

<!—F. Value of an array property with Elvis 
{{mdata?.name }}
Enter fullscreen mode Exit fullscreen mode

It too renders Bob, and the console errors are gone. But what about {{ mdata?roles?.job }}? Turns out that in Angular the Elvis operator works well on nested properties and stops errors when an array is present, but it doesn’t display any data or the "object, object" notation. The Elvis operator works well for non-arrayed complex objects but could be misleading. I find that most of my data starts as an array of objects.

<!—G. Value of an array property expression
{{  mdata && mdata.name }}
Enter fullscreen mode Exit fullscreen mode

Interpolation in Angular {{ expression }} evaluates the expression and we can use that evaluation to check for nulls or undefined. The table below shows some possibilities

HTML Result Comment
{{ mdata && mdata.roles }} [object Object] No console errors
{{ 1 && 2 }} 2 No console errors
{{ 1 OR 2 }} 1 No console errors
{{ 3 == 3 }} true No console errors
{{ “No data” && mdata.roles[1].job }} woodworker Console errors – Cannot read property [roles] - types do not match
{{ mdata && mdata.roles[1].job }} woodworker No console errors – types match

Part IV – Resources

A good article by Todd Moto

https://ultimatecourses.com/blog/angular-ngif-async-pipe

Top comments (0)