3

I am on Angular 2.3.1 and I am fairly new to both Angular and event based programming. I have two subscriptions, route.params.subscribe and engineService.getEngines(). In my onInit I want to call getEngineName after this.route.params.subscribe and this.engineService.getEngines().subscribe complete.

Reason for this: getEngineName functions depends on the engineId from the queryParams and the engines array which is populated after the completion of getEngines() call.

I did look at flatMap and switchMap but I did not completely understand them.

This is the code in the component:

export class ItemListComponent implements OnInit {
  items: Item[];

  engines: Engine[];
  private engineId: number;

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private itemService: ItemService,
    private engineService: EngineService
   ) {}

  ngOnInit() {
    this.route.params.subscribe((params: Params) => {
      this.engineId = +params['engineId'];

      // itemService is irrelevant to this question 
      this.itemService.getItems({engineId: this.engineId})
        .subscribe((items: Item[]) => {
          this.items = items;
        });
    });

    this.engineService.getEngines()
      .subscribe(engines => this.engines = engines);

    // This should only run after engineId and engines array have been populated.
    this.getEngineName(this.engineId);
  }

  getEngineName(engineId: number) {
    this.engines.find((engine) => {
      return engine.id === engineId;
    })
  }
}
eko
  • 39,722
  • 10
  • 72
  • 98
Yathi
  • 1,011
  • 1
  • 12
  • 21
  • you could use combineLatest. Your problem is similar to this one: http://stackoverflow.com/questions/40872357/waiting-for-ngrx-action-before-loading-page-with-url-parameter/40905330#40905330 With combineLatest you don't need to nest subscriptions. – chrigu Jan 09 '17 at 20:32

2 Answers2

4

Why don't you just move the logic inside the route.params callback?

this.route.params.subscribe((params: Params) => {
      this.engineId = +params['engineId'];

      // itemService is irrelevant to this question 
      this.itemService.getItems({engineId: this.engineId})
        .subscribe((items: Item[]) => {
          this.items = items;
        });

     //this.engineId is defined here (1)
     this.engineService.getEngines()
      .subscribe((engines) => {
         this.engines = engines;
         //this.engines is defined here (2)
         this.getEngineName(this.engineId);
     });
});

with flatMap and forkJoin:

this.route.params.flatMap((params: Params) => {
      this.engineId = +params['engineId'];    

      return Observable.forkJoin( 
         this.itemService.getItems({engineId: this.engineId}),
         this.engineService.getEngines()
      )          
}).subscribe((data)=>{
   let items = data[0];
   let engines = data[1];
   this.items = items;
   this.engines = engines;
   this.getEngineName(this.engineId);
});
eko
  • 39,722
  • 10
  • 72
  • 98
  • 1
    Ah! Yes. This makes so much sense! Thanks a lot. This did it for me. – Yathi Jan 09 '17 at 20:38
  • @Yathi glad I could help – eko Jan 09 '17 at 20:44
  • 1
    just wondering as I don't know it any better: Are nested subscriptions ok in rxjs? I personally try to avoid them as I think the code is easier to read, but maybe I was mistaken. – chrigu Jan 09 '17 at 20:48
  • @chrigu I'm not the right person to say that it is ok :-) but they are definitely not great. As you have mentioned you should use operators like `flatMap` or `forkJoin`. I'll update the question with the `flatMap` version also but since I don't know your whole architecture I didn't want to recommend anything. – eko Jan 09 '17 at 20:56
  • 1
    @chrigu I've updated my answer with `flatMap` + `forkJoin` I might have messed up somewhere so be sure to check the return parameter (`data`). Hope it helps ;) – eko Jan 09 '17 at 21:09
  • 1
    @echonax I admit that I had to look `forkJoin` up :). This should work if the forked streams terminate. – chrigu Jan 09 '17 at 21:23
0

switchMap is recommended in this scenario.

    this.route.params.pluck('engineId') //pluck will select engineId key from params
        .switchMap(engineId => {
            this.getItems(engineId);
            return this.engineService.getEngines().map(engines => {
                /*this.engineService.getEngines() emits the list of engines.
                  Then use map operator to convert the list of engines to engine we are looking for
                 */
                return engines.find((engine) => {
                    return engine.id === engineId;
                })
            })
        }).subscribe(engine => {
          //engine  
    })

    getItems(engineId) {
      this.itemService.getItems({engineId: engineId})
        .subscribe((items: Item[]) => {
          this.items = items;
        });
    }

Suppose the engineId in the params changes, the first observable this.route.params.pluck('engineId') will emit data, which will cause the next observable this.engineService.getEngines() to get fired. Now suppose the route changes before this observable emits the data. Here you need to cancel getEngines observable to prevent error. This is done by switchMap.

switchMap cancels inner observable if outer observable is fired.

PS: I have avoided keeping any states like this.engineId etc.

Gaurav Mukherjee
  • 6,205
  • 2
  • 23
  • 33
  • I don't follow this completely. Can you add some more comments in the code to make it more clear? Also, where would I be calling my `itemService.getItems()`? I assume it is before the return statement in the switchMap. Also why is this better than the first answer? – Yathi Jan 11 '17 at 15:47