2

In Angular, what is a robust way to display data in a view, taking into account loading-state and error-case?

Let's say we are getting a collection of documents from our backend and want to display those documents in our view using Angular. There are 3 states I want to take into account. For each of those states, I want to show a message to the user:

  1. Loading your data...
  2. Error trying to retrieve your data!
  3. Data retrieved successfully!

At the moment, I am doing the following. The issue I am facing is that both the "loading" and "error" messages are briefly displayed during the loading stage.

I am wondering how others go about solving this common use-case? Is it possible to do this by assigning different values to the myDocs variable, or do I have to introduce another variable?

TS

export class MyDocsComponent implements OnInit {

    myDocs: IMyDoc | null;

    ngOnInit() {

        // get myDocs
        this.myDocsService.getMyDocs()

        // if success
        .then( myDocs => { this.myDocs = myDocs; })

        // if error: could not get myDocs from service
        .catch( error => {
            // what should go in here
            // and how should this be checked in the view
            // to display an error message to the user
            // if the data could not be retrieved
            // while also being able to display a loading message beforehand

            // currently, I am doing:
            this.myDocs = null;
            // however, this leads to the error message 
            // incorrectly being displayed 
            // while loading the data
        });
    }
}

HTML

<!-- loading -->
<div class="loading" *ngIf="!myDocs">
    Loading your data...
</div>

<!-- error -->
<div class="error" *ngIf="myDocs==null">
    Error trying to retrieve your data!                         
</div>

<!-- success -->
<div class="success" *ngIf="myDocs">
    Data retrieved successfully!
    <div class="list" *ngFor="let d of myDocs">{{ d.title }}</div>
</div>
0

2 Answers 2

2

Try something like this

component.ts

export class MyDocsComponent implements OnInit {

  myDocs: IMyDoc | null;

  state: 'loading' | 'loaded' | 'error' = 'loading';

  isLoading() {
    return this.state === 'loading';
  }

  isError() {
    return this.state === 'error';
  }

  isLoaded() {
    return this.state === 'loaded';
  }

  ngOnInit() {
    this.myDocsService.getMyDocs()
      .then(myDocs => {
        this.myDocs = myDocs;
        this.state = 'loaded';
      })
      .catch(error => {
        console.log(error);
        this.state = 'error';
      });
  }
}

component.html

<div *ngIf="isLoading"  class="loading">
  Loading your data...
</div>

<div *ngIf="isError" class="error">
  Error trying to retrieve your data!
</div>

<div *ngIf="isSuccess" class="success" >
  Data retrieved successfully!
  <div class="list" *ngFor="let d of myDocs"> {{ d.title }}
  </div>
</div>
Sign up to request clarification or add additional context in comments.

6 Comments

This is helpful, thank you! In the first version, before your edit, does it still have to be myDocs: IMyDoc | null; or could it now simply be myDocs: IMyDoc;? Why?
It is null before the promise resolves so it should stay as myDocs: IMyDoc | null;
Thanks, that helps my understanding! I am currently trying this out, but thought it would be nice to use an enum for the states, i.e. enum DataState { loading, success, error } and then state: DataState = DataState.loading; and then adapting the isX() functions accordingly.
I would use enums as well. I wanted to keep it as short as possible as not to confuse the reader so I used string union.
Done. Glad I could help :)
|
0

This is another approach of handling the same problem. I'm moving it to another answer as requested by the OP. Do not upvote.

This uses some more advanced angular APIs like

  • async pipe,
  • <ng-container>
  • as keyword in the *ngIf expression
  • else loading combined with <ng-template #loading>

component.ts

  export class MyDocsComponent implements OnInit {
    myDocs$: Promise < IMyDoc | string > ;

    ngOnInit() {
      this.myDocs$ = this.myDocsService.getMyDocs()
        .catch(error => {
          console.log(error);
          return 'there was an error';
        });
    }

    isError(resp: IMyDoc | string) {
      return typeof resp === 'string';
    }


    isSuccess(resp: IMyDoc | string) {
      return typeof resp !== 'string';
    }
  }

component.html

<div *ngIf="myDocs$ | async as myDocs; else loading">
  <ng-container *ngIf="isSuccess(myDocs)" >
    <div class="success"
       Data retrieved successfully!
       <div class="list" *ngFor="let d of myDocs"> {{ d.title }}       
       </div>
    </div>
  </ng-container>

  <ng-container *ngIf="isError(myDocs)">
    <div *ngIf="isError" class="error">
      Error trying to retrieve your data!
    </div>
  </ng-container>
</div>

<ng-template #loading>
  <div *ngIf="isLoading" class="loading">
    Loading your data...
  </div>
</ng-template>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.