brians69
brians69

Reputation: 485

Stop reloading the whole content on route change

I have a route like the following: page/:id, where it loads a different content for each id. However, every time I go to a different page, it reloads the entire content. I'd like to reload only the data coming from the API.

Navigation:

<ul>
 <li [routerLink]="['/page', 1]">Page 1</li>
 <li [routerLink]="['/page', 2]">Page 2</li>
 <li [routerLink]="['/page', 3]">Page 3</li>
</ul>

Template: <h1 *ngIf="data">{{data.name}}</h1>

Component:

export class DataComponent implements OnInit {

  data: any;
  error: any;

  constructor(
    private route: ActivatedRoute,
    private service: DataService
  ) {}

  ngOnInit() {
    this.getData();
  }

  getData() {
    this.service.getData()
      .subscribe(
        res => this.data = res,
        error => this.error = error
      );
  }

}

If you go to this Plunker, you'll see the black rectangle blinks every time the route changes. Ideally, the black part would remain intact, only the data inside would change.

I also noticed this flickering behavior happens only if I'm using an HTTP call. If I'd have other things in that component, this wouldn't happen when changing routes.

Any ideas on how to solve this?

EDIT: Here's a Plunkr showing the expected behavior. However, I was using former ROUTER_DIRECTIVES, which are deprecated now.

Upvotes: 4

Views: 5941

Answers (3)

brians69
brians69

Reputation: 485

Extending @Rob's answer, I did manage to solve this issue using the Resolve guard. However, I was making a few mistakes in its implementation.

I've created a new data-resolve where I resolve the HTTP call before initializing the route:

@Injectable()
export class DataResolve implements Resolve {

  constructor(private service: DataService, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot):
    Observable<any> | Promise<any> | any {
      return this.service.getData().then(data => {
        if (data) {
          return data;
        } else {
          this.router.navigate(['/page/1']);
         return false;
       }
    });
  }
}

Then, I needed to add that service to my module's providers:

providers: [DataService, DataResolve]

And also tell the routing to solve that before loading the route:

resolve: { pages: DataResolve }

After that, I'll have an array I can use in my component to get the data:

this.route.data.forEach((data: {pages}) => {
  this.data = data.pages;
});

I've also updated my Plunker with a working example.

Upvotes: 0

BeetleJuice
BeetleJuice

Reputation: 40886

Your blinking occurs because when you navigate, DataComponent is destroyed and re-built, but it cannot be shown until new data arrives from the server because you block its display with *ngIf="data". On this plunker, I've added a 1 second delay to the server response to make the problem obvious. Note that only the DataComponent blinks. The rest of PagesComponent does not.

On this plunker, there is no visible blinking because the data is hard-coded so there is no wait for the server's response.

To alleviate the blinking, don't use *ngIf since that removes the element from the dom entirely. On this plunker, no blinking occurs even while we wait for data to arrive from the server.

Basically, as long as you choose to hide the element until data arrives, whenever the component is re-initialized you will see blinking.

Upvotes: 1

Rob
Rob

Reputation: 12872

You are already only reloading the content coming from the API. The black box is flickering because you are styling the h1 and the h1 is changing. If you want the box there for every page then move it up to the AppComponents template.

<div class="box">
    <router-outlet></router-outlet>
</div>

Then move the styles to the .box selector rather than the h1.

Another issue will be the height of the box will collapse and expand while the content is removed and refreshed. You can fix this by using a resolve guard on your routes.

https://angular.io/docs/ts/latest/guide/router.html#!#resolve-guard

Upvotes: 0

Related Questions