0

New to Angular and wondering how to correctly initialize variables in an service.ts file. The purpose being to dry up my code a bit.

export class CollectionsService {
  private urlBase: string;
  private token: string;
  constructor(
    private authorized: AuthService,
    private http: Http,
    private httpHelper: HttpHelperService
  ) {
    this.urlBase = `/app/${this.authorized.user.appdomainid}`; 
    this.token = this.authorized.user.token;
    // this.authorized = undefined
  }
  // ...
}

If I just put the url base directly in a method it's fine:

getCollections(): Observable<any> {
    return this.http
      .get(
        `/app/${this.authorized.ampUser.appdomainid}/collections`,
        this.httpHelper.getRequestOptions(this.authorized.ampUser.token)
      )
      .pipe(map((response) => <any>response.json()))
      .pipe(map((response) => response.Items))
      .pipe(
        catchError((err) => {
          return throwError(err.json());
        })
      );
  }

1 Answer 1

1

There's nothing wrong with keeping the base URL and token as instance variables on your service. Redefining them in each method seems inefficient and repetitive, but it makes sense if you want to react to changes in the AuthService.

If that is the concern, you might consider using property getters instead:

@Injectable({
  providedIn: 'root'
})
export class CollectionsService {

  private get urlBase(): string {
    return `/app/${this.authorized.ampUser.appdomainid}`;
  }

  private get token(): string {
    return this.httpHelper.getRequestOptions(this.authorized.ampUser.token);
  }

  constructor(
    private http: HttpClient,
    private authorized: AuthService,
    private httpHelper: HttpHelperService
  ) {}

  getCollections(): Observable<any> {
    return this.http
      .get(
        `${this.urlBase}/collections`,
        this.token
      )
      .pipe(map((response) => <any>response.json()))
      .pipe(map((response) => response.Items))
      .pipe(
        catchError((err) => {
          return throwError(err.json());
        })
      );
  }
}

If you're really looking to DRY, you could make it an InjectionToken that's provided to your whole application. Any number of services could rely on these tokens without needing to instantiate/maintain their own instance variables.

You can make sure you react to changes in AuthService by providing the tokens with the useFactory strategy.

In your app.module.ts:

import { AuthService } from 'auth.service.ts';
import { HttpHelperService } from 'http-helper.service.ts';

const BASE_URL = new InjectionToken<string>('BaseUrl');
const API_TOKEN = new InjectionToken<string>('ApiToken');

@NgModule({
  providers: [
    { 
      provide: BASE_URL, 
      useFactory: (authorized: AuthService) => `/app/${authorized.ampUser.appdomainid}`,
      deps: [AuthService] 
    },
    { 
      provide: API_TOKEN,
      useFactory: (authorized: AuthService) => authorized.ampUser.token,
      deps: [AuthService]
    }
  ],
})
export class AppModule {}

Then in your collections.service.ts:

import { BASE_URL, API_TOKEN } from 'app.module.ts';

@Injectable({
  providedIn: 'root'
})
export class CollectionsService {

  constructor(
    private http: HttpClient,
    private httpHelper: HttpHelperService,
    @Inject(BASE_URL) private baseUrl: string,
    @Inject(API_TOKEN) private apiToken: string
  ) {}

  getCollections(): Observable<any> {
    return this.http
      .get(
        `${this.baseUrl}/collections`,
        this.httpHelper.getRequestOptions(this.apiToken)
      )
      .pipe(map((response) => <any>response.json()))
      .pipe(map((response) => response.Items))
      .pipe(
        catchError((err) => {
          return throwError(err.json());
        })
      );
  }

  deleteCollection(listId: unknown): Observable<any> {
    const body = { foo: 1, bar: 2 };
    return this.http
      .delete(
        `${this.baseUrl}/collections/${listId}/rows`,
        this.httpHelper.getRequestOptionsWBody(this.apiToken, body)
      )
      .pipe(map((response) => <any>response.json()))
      .pipe(map((response) => response))
      .pipe(
        catchError((err) => {
          return throwError(err.json());
        })
      );
  }
}

As an aside, if you're using Angular 14+, you can simplify the code a little using the inject() method instead of the @Inject decorator. This allows you to remove the constructor if all it was doing was declaring dependencies.

@Injectable({
  providedIn: 'root'
})
export class CollectionsService {

  private http = inject(HttpClient);
  private httpHelper = inject(HttpHelper);
  private baseUrl = inject(BASE_URL);
  private apiToken = inject(API_TOKEN);

  getCollections(): Observable<any> {
    return this.http
      .get(
        `${this.baseUrl}/collections`,
        this.httpHelper.getRequestOptions(this.apiToken)
      )
      // ...
  }

  deleteCollection(listId: unknown): Observable<any> {
    const body = { foo: 1, bar: 2 };
    return this.http
      .delete(
        `${this.baseUrl}/collections/${listId}/rows`,
        this.httpHelper.getRequestOptionsWBody(this.apiToken, body)
      )
      // ...
  }

I'm not sure what the Angular team's stance on inject() in useFactory is. They show it as an example in the usage notes. For your case:

import { AuthService } from 'auth.service.ts';
import { HttpHelperService } from 'http-helper.service.ts';

const BASE_URL = new InjectionToken<string>('BaseUrl');
const API_TOKEN = new InjectionToken<string>('ApiToken');

@NgModule({
  providers: [
    { 
      provide: BASE_URL, 
      useFactory: () => `/app/${inject(AuthService).ampUser.appdomainid}`
    },
    { 
      provide: API_TOKEN,
      useFactory: () => inject(AuthService).ampUser.token
    }
  ],
})
export class AppModule {}

On the other hand, the useFactory documentation still declares the dependencies in the signature...

Sign up to request clarification or add additional context in comments.

3 Comments

The first less DRY solution works well. I'd like to use the super DRY version, but there are too many moving parts in this app and I get circular warnings. Another method in collections.service uses getRequestOptionsWBody on` httpHelper` like this .delete( ${this.urlBase}/collections/${listId}/rows, this.httpHelper.getRequestOptionsWBody(this.token, body) )
@KirkRoss Are you importing your AppModule into other modules? You shouldn't be getting circular references with this. I'd look at extracting whatever it is in AppModule that's causing you to import it into other feature modules. Feel free to ask another question if you have a sample and I'll take a look at it.
If you use different methods on HttpHelperService depending on the endpoint, then adding it to the factory method like I did is probably not the right call. I've edited the sample to provide just the token. Each endpoint can decide how to consume that token in conjunction with the HttpHelperService.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.