Angular 4 How to return multiple observables in resolver
Asked Answered
R

5

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
})
Retinoscope answered 8/1, 2018 at 9:1 Comment(2)
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 dataDoorstep
@VinodBhavnani thanks but I want to solve it using resolversRetinoscope
R
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.

Retinoscope answered 12/12, 2018 at 12:2 Comment(0)
S
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];
  }
}
Spheroidal answered 10/12, 2018 at 10:43 Comment(4)
thanks for sharing, but after testing your solution, I realized it's not always resolve all the data, but yea it worked at firstRetinoscope
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.Spheroidal
Added a Stackblitz example, fyi. Would appreciate you reviewing before down voting, tySpheroidal
I haven't down voted you, perhaps somebody else. Your answer actually helped me with my solution :)Retinoscope
R
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

Rola answered 8/1, 2018 at 9:23 Comment(2)
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.Retinoscope
Sorry, I don't use them myself!Rola
C
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.

Cabob answered 8/1, 2018 at 15:17 Comment(2)
I’m already using observable. The topic is about how to use multiple one in resolverRetinoscope
Can you give an example of resolver where you fetch one then based on that one you fetch other values for it ?Retinoscope
B
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]);

}

Bollworm answered 22/12, 2021 at 9:14 Comment(1)
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.Bernita

© 2022 - 2024 — McMap. All rights reserved.