2

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

dc_Bita98
  • 851
  • 2
  • 17
  • 35

2 Answers2

2

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
  }
}

Bertramp
  • 376
  • 1
  • 15
  • *I don't know where you get the dynamic ids* --> here's the problem, cause those ids should change at runtime. They represents **primary key** of some model in the backend database. They would be part of the url being queried. I really don't know how to deal with them. Maybe the only possible solution is refactoring and pass them as parameters through each and every service method. But this sounds cumbersome – dc_Bita98 Aug 11 '20 at 17:42
  • I don't quite think i understand, where are the ids kept/generated? – Bertramp Aug 11 '20 at 19:53
  • Actually I parse the ids from the url. For example, a user is in the page **/item1/1/item2/1** . He wants to create an item3 which belongs to item2. The service would be created with id1=1 and id2=1 passed in its constructor. Then the service will send a POST request to the backend url **/item1/1/item2/1/item3/** – dc_Bita98 Aug 11 '20 at 20:07
  • 2
    I see. Then you should get them from the router and then just create a method in the service for setting them – Bertramp Aug 11 '20 at 20:18
  • Thank you sir. That was actually what I meant. On the other hand, I encourage you anyone who comes here to read @h0ss answer. From a software engineering perspective, **it is better for a service to be stateless**. The task of a service is to provide data from the backend, it is not its concern to keep information about the current user page url – dc_Bita98 Aug 12 '20 at 06:36
  • 1
    In this case yes, it is better for the service to be stateless. But as a general statement it is very wrong. Services as state management has many valid use cases – Bertramp Aug 12 '20 at 08:02
  • @Bertramp Agreed, there are many use cases where you'd need that, but more often than not you're better off separating your "state" services from your "business logic" services. – h0ss Aug 13 '20 at 19:36
2

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.

h0ss
  • 643
  • 6
  • 7
  • You're right. I was missing the point and trying to carry unwanted concerns on my service. I'll probably need 2 if not 3 services of this type in my project. so I'm going to create a base *parametrized* service class which can takes a variable number of parameter for each of its method and it will be able to build the correct url to query the backend – dc_Bita98 Aug 12 '20 at 06:40