Igor Simic
1 year ago

Angular - how to load data from backend before rendering component HTML on route change


How to hide HTML content until data from backend is loaded to the DOM in Angular 2+ for specific route. There are couple of solutions for this, but in this example we will use resolver. By using resolver we will preload data before the component is rendered.

So let's assume we have a list of posts and when user clicks on one of our posts we will change the route to post/:slug and render that specific post. So that would mean that we have single post component whit custom HTML and this component will make a call to backend to get the single post data. After the data is retrieved it will be populated inside of HTML. But the problem here is that Angular can render HTML before the data is retrieved and user will see first empty page for a split second until the data is loaded end then complete page  - which is not nice.

There are couple solutions for this, like using variable isDataLoaded which can be set by default to false, and when we get response from backend it can be set to true and we will just put it in our HTML:
<div *ngIf="isDataLoaded">
...
</div>
But, very often this approach will produce error:
Expression has changed after it was checked. Previous value: 'ngIf: false'. Current value: 'ngIf: true'

And by the way this is not se elegant solution. The best way will be if can preload content before  we call that route.

The first step will be to implement resolver in our routing module for our specific route. In our app-routing.module we will add third param to our single post route which will be resolve
export const routes: Routes = [
...
{
  path: ':slug',
  component: SinglePostComponent,
  resolve: {
    singlePost: SinglePostResolver
  }

}
...
];
As  you can see this resolve is calling SinglePostResolver, and this is where we will actually load our data. 
So let's create SinglePostResolver. Create new file single.post.resolver.ts and put it in same folder where your post component is (tor in some other folder - it is up to you)
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";
import {Injectable} from "@angular/core";
import {RestRequestService} from "../../Services/rest-request.service";

@Injectable()
export class SinglePostResolver implements Resolve<any>{

  constructor(
     
    private restRequest:RestRequestService,

  ){

  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
     // In my case i am using custom service for getting rest calls but you can use simple http.get()...
    return this.restRequest.sendGetRequest('post/'+route.params['slug'], false);

  }
}
And now inside of your component you can get this data by using Activated route data:
export class SinglePostComponent implements OnInit {
 

  constructor(
    private route: ActivatedRoute,
    ...

  ) {

  }

  ngOnInit(){
    
    this.route.data.subscribe( (data) => { 
      console.log(data)
    });
}

And now you can use this data in your HTML like this:

<h1>{{data.title}}</h1>
...
<div [innerHTML]="(data.HTMLContent)"></div>
...


And that was it. Now your data route will not be rendered before the data from backend is loaded
Easy - peasy.


Additional reading: 

How to show preloader on every route change in Angular