Angular4 Module Based Services and Global Service

We are currently working on an Angular4 application and are looking for architecture feedback for the services.

Five "modules" of the application:

  • one
  • two
  • three
  • four
  • five

We currently have one data service specific to one

, however the abstract class ApiService

can be imported through the other four modules (see code below).

Here are some variations on what I think:

Option 1: Move the abstract class ApiService

to our shared folder module, that is, the shared folder module will be imported into each of the five modules.

Then create a service specific to each module that inherits from ApiService. This will simplify the management of each individual service.

Option 2: Move the abstract class to our public folder and create a global service containing all api calls for all five modules. This way we have one service to manage all API connections. However, the file can get a little large and difficult to manage. Thoughts on organization?

Option 3: Share observable services all together and go with something like ngrx / store to handle the state.

I'm looking for feedback on the architecture of a data service.

Module-one-data-service.ts

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';

import { IPosition } from './position.model';
import { IPositionReference } from '../shared/side-pane/position-reference.model';
import { Subject } from 'rxjs/Subject';


export abstract class ApiService {
  protected BASE_URL = 'http://justtheurl.com';
  protected baseAndModuleUrl: string;

  private OPTIONS = new RequestOptions({
      headers: new Headers({
        'Authorization' : 'Basic thisIsJustForTesting'
      }),
      withCredentials: true
  });

  constructor(private http: Http, private module: string) {
    this.baseAndModuleUrl = `${this.BASE_URL}${module}`;
  }

  public getBaseModuleUrl() { return this.baseAndModuleUrl; }

  protected fetch(apiAction: string): Observable<any> {
    return this.http
      .get(`${this.baseAndModuleUrl}${apiAction}`, this.OPTIONS)
      .map((res: Response) => res.json().data);
  }

  protected post(apiAction: string, positions: Object[]): Observable<any> {
    return this.http
      .post(`${this.baseAndModuleUrl}${apiAction}`, positions, this.OPTIONS)
      .map((res: Response) => res.json());
  }

  protected upload(apiAction: string, file: FormData): Observable<any> {
    return this.http
      .post(`${this.baseAndModuleUrl}${apiAction}`, file, this.OPTIONS)
      .map((res: Response) => res.json());
  }

}

@Injectable()
export class ModuleOneDataService extends ApiService {
  public editableValues = new Subject<any>();

  constructor(http: Http) { super(http, '/api/module/one'); }

  public fetchTree(): Observable<IPosition[]> { return this.fetch('/tree'); }

  public fetchList(): Observable<IPosition[]> { return this.fetch('/list'); }

  public fetchIndividual(id: string): Observable<IPositionReference> { return this.fetch(`/node/${id}`); }

  public savePositionsToSubgraph(positions: Object[]): Observable<any> { return this.post('/subgraph/upsert', positions); }

  public mergeSubraphToMaster(): Observable<Object> { return this.post('/subgraph/merge', [{}]); }


}

      

+3


source to share


1 answer


ApiService

probably shouldn't be abstract at all. Looking back at what you posted, it actually acts as a wrapper around the Angular HTTP service management. An Angular HTTP service wrapper is often needed because it has such a terrible API.

Service classes that need access to wrapped HTTP nodes should implement this API service rather than inherit from it.

The reason for this is that they are not logical descendants of the base class, and using inheritance to simply exchange codes leads to a confusing code base. There are better ways.

Here's what I would recommend

app / module-one / data-service.ts

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';

import ApiServiceFactory, {ApiService} from 'app/shared/api';

@Injectable() export class DataService {
  constructor(apiServiceFactory: ApiServiceFactory) {
    this.api = apiServiceFactory.create('/api/module/one');
  }

  api: ApiService;

  fetchTree(): Observable<IPosition[]> { 
    return this.api.fetch('/tree');
  }

  fetchList(): Observable<IPosition[]> {
    return this.api.fetch('/list');
  }

  fetchIndividual(id: string): Observable<IPositionReference> {
    return this.api.fetch(`/node/${id}`);
  }
}

      



app / general / api.ts

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable() export default class ApiServiceFactory {
  constructor(readonly http: Http) {}

  create(moduleApiSubpath: string): ApiService {
    return new ApiServiceImplementation(this.http, moduleApiSubpath);
  }
}

export interface ApiService {
  fetch(url): Observable<{}>;

  post(url:string, body: {}): Observable<{}>;

  // etc.
}

const baseUrl = 'http://justtheurl.com';

// there actually no need to make this a class at all.
// It could be as simple as a function that returns an object literal.
// It doesn't matter much since it not exported.
class ApiServiceImplementation implements ApiService {
  constructor(readonly http: Http, readonly moduleApiSubpath: string){}

  get baseModuleUrl() {
    return `${baseUrl}${this.moduleApiSubpath}`;
  }

  fetch(apiAction: string): Observable<{}> {
    return this.http
      .get(`${this.baseModuleUrl}${apiAction}`, this.options)
      .map(res => res.json().data);
  }

  // etc.
}

      

Using this approach, the only joint injection would be ApiServiceFactory

. You can provide it in a generic module, or you can provide it yourself in each of the modules that have a service that implements it. You don't have to worry about extra instances or anything like that since the service is stateless and the actual objects returned by the factory are effectively transient.

Note that it would be nice if Angular provided native support for this pattern as transient injection is fairly common and has many use cases. While it is currently possible to achieve transient dependency behavior at the component level, there is no way at this service level to do it without creating such a factory.

Unlike frameworks like Aurelia, they can be decorated with a simple one @transient

, and consumers explicitly request a new instance in the same simple way.

+1


source







All Articles