14

As the title states, I need to return multiple observables or maybe results. The goal is basically to load let's say a library list and then load books based on that library IDs. I don't want to call a service in components, instead I want all the data to be loaded before the page load.

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { UserService } from './../_services/index';

@Injectable()
export class LibraryResolver implements Resolve<any> {
    constructor(private _userService: UserService) {}

    resolve(route: ActivatedRouteSnapshot) {
        return this._userService.getLibraryList();
    }
}

How can I load library list first and then load book info for each library and return to my component?

PS: My service got this method to load by Id

this.userService.getLibraryBooks(this.library["id"]).subscribe((response) => {
 // response processing
})
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
hxdef
  • 393
  • 2
  • 6
  • 15
  • If you need data to be available before the component loads, you can actually do your HTTP calls in the service constructor. Now when the component is loaded, since there is a dependency injection, the service object will be provided to the component with the required data – Vinod Bhavnani Jan 08 '18 at 09:20
  • 2
    @VinodBhavnani thanks but I want to solve it using resolvers – hxdef Jan 08 '18 at 09:21

5 Answers5

14

I found a solution for this issue, maybe will help somebody, so basically I've used forkJoin to combine multiple Observables and resolve all of them.

resolve(route: ActivatedRouteSnapshot): Observable<any> {
        return forkJoin([
                this._elementsService.getElementTypes(),
                this._elementsService.getDepartments()
                .catch(error => {

                    /* if(error.status === 404) {
                        this.router.navigate(['subscription-create']);
                    } */

                    return Observable.throw(error);
                })
        ]).map(result => {
            return {
                types: result[0],
                departments: result[1]
            };
        });
    };

Now it works correctly, as intended.

hxdef
  • 393
  • 2
  • 6
  • 15
7

You can easily resolve multiple observables using withLatestFrom.

Here's a working demo in Stackblitz: https://stackblitz.com/edit/angular-xfd5xx

Solution below.

In your resolver, use withLatestFrom to combine your observables:

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Library } from '../models/library.model';
import { LibraryBook } from '../models/library-book.model';
import { LibraryService } from '../services/library.service';
import { Observable } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';

@Injectable()
export class LibraryDisplayResolver implements Resolve<[Library, LibraryBook[]]> {

  constructor(
    private _libraryService: LibraryService,
  ) { }

  resolve (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<[Library, LibraryBook[]]> {
    const libraryId = route.params['id'];
    return this._libraryService.getLibrary(libraryId).pipe(
      withLatestFrom(
        this._libraryService.getBooksFromLibrary(libraryId)
      )
    );
  }
}

Make sure your resolver is set in your route with an appropriate identifier:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LibraryDisplayComponent } from './library-display.component';
import { LibraryDisplayResolver } from '../resolvers/library-display.resolver';

const routes: Routes = [
  {
    path: ':id',
    component: LibraryDisplayComponent,
    resolve: {
      libraryResolverData: LibraryDisplayResolver
    }
  },
  {
    path: '',
    redirectTo: '1',
    pathMatch: 'full'
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class LibraryDisplayRoutingModule { }

In the receiving component, you can access snapshots of both observables like so:

import { Component, OnInit } from '@angular/core';
import { LibraryBook } from '../models/library-book.model';
import { Library } from '../models/library.model';
import { ActivatedRoute } from '@angular/router';

@Component({
  // tslint:disable-next-line:component-selector
  selector: 'library-display',
  templateUrl: './library-display.component.html',
  styleUrls: ['./library-display.component.scss']
})
export class LibraryDisplayComponent implements OnInit {

  library: Library;
  libraryBooks: LibraryBook[];

  constructor(
    private route: ActivatedRoute
  ) { }

  ngOnInit() {
    this.library = this.route.snapshot.data['libraryResolverData'][0];
    this.libraryBooks = this.route.snapshot.data['libraryResolverData'][1];
  }
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
Gorgant
  • 381
  • 5
  • 7
  • thanks for sharing, but after testing your solution, I realized it's not always resolve all the data, but yea it worked at first – hxdef Dec 12 '18 at 12:00
  • Hmm it's been working fine for me in my application (I've only used this pattern for that use case). I welcome any corrections. – Gorgant Dec 13 '18 at 02:39
  • Added a Stackblitz example, fyi. Would appreciate you reviewing before down voting, ty – Gorgant Dec 13 '18 at 10:12
  • I haven't down voted you, perhaps somebody else. Your answer actually helped me with my solution :) – hxdef Dec 13 '18 at 10:14
0

I have a similar situation, but I approached it slightly differently.

In your case, my understanding is you want to get the query parameter, and then based on the response, call a few more services.

The way I do that is by having a component that wraps around the others, and passes the objects they need. I subscribe to them through the route paramMap and then wrap all my other calls in a. Observalbe.forkJoin

In my wrapper component I do the following:

ngOnInit() {

    this.route.params.subscribe((params) => {
        if (params.hasOwnProperty('id') && params['id'] != '') {
            const slug = params['slug'];
            Observable.forkJoin([
                this.myService.getData(id),
                this.myService.getOtherData(id),
            ]).subscribe(
                ([data, otherData]) => {
                    this.data = data;
                    this.otherData = otherData;
                },
                error => {
                    console.log('An error occurred:', error);
                },
                () => {
                    this.loading = false;
                }
                );
        }
    });

Not entirely what you're after but I hope it points you in the right direction

Evonet
  • 3,600
  • 4
  • 37
  • 83
  • thanks man, yea thats what I'm looking for, thanks for your solution but I hope there is a way to do it using just resolvers since my knowledges not enough on that. – hxdef Jan 08 '18 at 09:44
  • Sorry, I don't use them myself! – Evonet Jan 08 '18 at 10:06
0

I would suggest you use Observables, they will make your life a lot easy. Check these two articles for more details on how to achieve that using observables:

http://www.syntaxsuccess.com/viewarticle/combining-multiple-rxjs-streams-in-angular-2.0

http://blog.danieleghidoli.it/2016/10/22/http-rxjs-observables-angular/

I personally use flatMap

Good Luck.

Nadhir Falta
  • 5,047
  • 2
  • 21
  • 43
  • I’m already using observable. The topic is about how to use multiple one in resolver – hxdef Jan 08 '18 at 16:05
  • Can you give an example of resolver where you fetch one then based on that one you fetch other values for it ? – hxdef Jan 08 '18 at 16:06
0
  resolve(route: ActivatedRouteSnapshot) {
let id = route.params['id'];

const getUser = new Promise((resolve, reject) => {
  this.customerService.getSingle(id).subscribe((res) => {
    resolve(res);
  }, (err) => {
    console.log('err', err);
  });
});

const getDocument = new Promise((resolve, reject) => {
  this.customerService.getDocument().subscribe((res) => {
    resolve(res);
  });
});

return Promise.all([getUser, getDocument]);

}

  • Please don't post only code as an answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Tyler2P Dec 22 '21 at 10:14