dcfg
dcfg

Reputation: 891

Angular 9 - how to inject dynamic parameters into a service constructor

I need to make requests to a backend url which comes in this form:

localhost:8000/myapp/item1/:id1/item2/:id2/item3

where id1 and id2 are dynamic numbers. I've thought using a service that takes 2 arguments in the constructor, something like this

export class Item3Service {

  private id1: number;
  private id2: number;

  constructor(
    id1: number,
    id2: number
  ) {
    this.id1 = id1;
    this.id2 = id2;
  }

  getList() {/**** implementation here ****/}
  getDetail(id3: number) {/**** implementation here ****/}
  create() {/**** implementation here ****/}
  update(id3: number) {/**** implementation here ****/}
  delete(id3: number) {/**** implementation here ****/}

}

I really don't know how to inject parameters into the constructor. I also need to use this service inside a resolver and again, how can I pass parameters to it in a resolver? Creating injection token sounds useless in this case because the token value should change every time. I've run out of ideas

Upvotes: 2

Views: 7755

Answers (2)

h0ss
h0ss

Reputation: 643

It's better for your Service to be stateless, it will bring down the complexity of your app and spare you some issues and debugging and it's just not necessary in your case because you can always get item1Id and item2Id from your activated route, so let the activated route hold the state of your application (in this case the state is what Item1Id and Item2Id are selected) and create a stateless service that you can call from anywhere and that holds the logic of your Item API.

Here is how I envision your service to be (Keep in mind that this is just an example to take into consideration since I don't know exactly your semantics and use cases)

ItemService

export class ItemService {
  constructor(private http: HttpClient) {}

  getList(item1Id: string, item2Id: string) {
    /* Call to Get List endpoint with Item1Id and Item2Id */
  }

  getDetails(item1: string, item2: string, item3: string) {
    /* Call to Get Details endpoint with Item1Id and Item2Id and Item3Id */
  }
}

Then you can use this service everywhere, as long as you have access to the ActivatedRouteSnapshot or ActivatedRoute

Example Use in Resolver for Route item1/:item1Id/item2/:item2Id

export class ItemResolver implements Resolve<any> {
  constructor(private itemService: ItemService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {
    return this.itemService.getList(route.params['item1Id'], route.params['item2Id']);
  }
}

Example Use in Component for Route item1/:item1Id/item2/:item2Id to get Item 3 details

export class HelloComponent  {

  constructor(private route: ActivatedRoute, private itemService: ItemService) {}

  getDetails(item3Id) {
    this.route.params.pipe(
      take(1),
      map(({ item1Id, item2Id }) => {
        console.log(this.itemService.getDetails(item1Id, item2Id, item3Id))
      })
    ).subscribe();
  }
}

Here is a working StackBlitz demonstrating this : https://stackblitz.com/edit/angular-ivy-h4nszy

You should rarely use stateful services (unless it's really necessary and even in that case I recommend using something like ngrx library to manage your state) with the information that you have given though, you really don't need to be passing parameters to the constructor of your service, you should keep it stateless and pass the parameters to your methods.

Upvotes: 2

Bertramp
Bertramp

Reputation: 385

I don't know where you get the dynamic ids, but you could actually maybe put them in the providers array and use dependency injection like you would with injection tokens. If it is possible to create a factory method for the ids of course

Service

export class Item3Service {

  constructor(
    @inject(LOCALE_ID) private locale: string) {}

}

app.moudle.ts

@NgModule({
  providers: [
    { provide: LOCALE_ID, useFactory: () => window.navigator.language}
  ]
})

Edit

Since the ids are part of your route, I would do it like this

Component

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { MyServiceService } from '../my-service.service';

@Component({
  selector: 'app-routed',
  templateUrl: './routed.component.html',
  styleUrls: ['./routed.component.scss']
})
export class RoutedComponent implements OnInit {

  constructor(private route: Router, private myService: MyServiceService) { }

  ngOnInit(): void {
    this.myService.setUrl(this.route.url)
  }

}

Service

import { Injectable } from '@angular/core';
import { ReplaySubject, Observable } from 'rxjs';
import { share, switchMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class MyServiceService {
  private _url$: ReplaySubject<string> = new ReplaySubject<string>(1);

  private _mydata$: Observable<string>;
  get myData$() { return this._mydata$.pipe(share()); }


  constructor() {
    this._mydata$ = this._url$.pipe(
      switchMap(url => {
        const parsedUrl = this.parseUrl(url);
        return this.callBackend(parsedUrl)
      })
    )
  }

  setUrl(url: string) {
    this._url$.next(url);
  }

  private callBackend(parsedUrl): Observable<string> {
    // call backend 
  }

  private parseUrl(url: string): number[] {
    // parse ids
  }
}

Upvotes: 2

Related Questions