Angular 4 Component ngOnInit not called on each ro

2019-02-17 04:27发布

问题:

I just recently started diving into Angular 4 coming from Angular 1.5. I'm working with user routes and pulling API data from a service into a user component.

The component seems to stay static unlike controllers in 1.* where they were refreshed on each request.

Is there anyway to have the ngOnInit function called on each new route request?

My user component class:

// url structure: /user/:id
export class UserComponent implements OnInit {

    constructor(private route: ActivatedRoute) {}

    ngOnInit() {

      // doesn't log new id on route change
      let id = this.route.snapshot.params['id'];
      console.log(id);

      this.route.params
        .switchMap(params => this.userService.getUser(params['id']))
        .subscribe(user => {
          // logs new id on route change
          let id = this.route.snapshot.params['id'];
          console.log(id);

          this.user = user
        });
    }
}

UPDATE:

Found a solution that I think works best for my scenario. Based on several answers and comments to my question and further investigation, subscribing to the route seems to be the way to go. Here is my updated component:

export class UserComponent {

  user;
  id;

  constructor(
    private userService: UserService,
    private route: ActivatedRoute
  ) {}


  ngOnInit() {
    this.route.params
      .subscribe(params => this.handleRouteChange(params));
  }

  handleRouteChange(params) {
    this.id = params['id'];

    switch (this.id) {
      case '1':
        console.log('User 1');
        break;

       case '2':
        console.log('User 2');
        break;

       case '3':
        console.log('User 3');
        break;
    }

    this.userService.getUser(this.id).
      subscribe(user => this.user = user);
  }

}

回答1:

Angular prefers -by default- to reuse the same component instead of instanciating a new one if the route is the same but the parameters change.

Good news ! this is a customizable behavior, you can force instanciating a new component instance by implementing your own RouteReuseStrategy:

export class MyRouteReuseStrategy extends DefaultRouteReuseStrategy {
  shouldReuseRoute(next: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    let name = next.component && (next.component as any).name;
    return super.shouldReuseRoute(next, current) && name !== 'UserComponent';
  }
}

in your module:

@NgModule({
  ...
  providers:[{
      provide: RouteReuseStrategy,
      useClass: MyRouteReuseStrategy}]
  ...
})
export class AppModule {}

(this code is more or less taken from this article, especially the check for the name property which might not be very accurate...)

Note that ther might be some performance drawback when reinstanciating the same component. So maybe you'd better use the observables properties instead of the snapshot.



回答2:

Does the url look like "...?id=1" or "../:id" and you want the component to log the id each time the GET param changes? The problem is talked about here. A better solution I found is here. I haven't tested it myself but this should work.

The second link shows how to subscribe to a route change within a component, which I think is essentially what you're trying to do. It will allow you to handle a GET param change within ngOnInit. I'm not sure if the route subscribe will run when you initally navigate to the URL, so you might want to have the function that handles the route event call a doCheck(), and change ngOnInit to ngAfterContentInit

ngOnInit only operates on change detection, and a GET change seems not to trigger it, which is surprising to me.

Tl;dr - Subscribe to the route change event in the constructor and create a function that handles an emitted event. The function should hold the logic you have in ngOnInit().



回答3:

Hi you have to use switchMap like this:

this.sub = this.route.paramMap
  .switchMap((params: ParamMap) =>
  this.firebaseService.getProductsByCategory(params.get('category'))).subscribe(products => {
  this.products = products;
  this.count = products.length;
});

This work for me.