Typescript generic service Typescript generic service angular angular

Typescript generic service


You can create an abstract generic class and two children class that inherits from it :

abstract class:

export abstract class AbstractRestService<T> {  constructor(protected _http: Http, protected actionUrl:string){  }  getAll():Observable<T[]> {    return this._http.get(this.actionUrl).map(resp=>resp.json() as T[]);  }  getOne(id:number):Observable<T> {    return this._http.get(`${this.actionUrl}${id}`).map(resp=>resp.json() as T);  }} 

driver service class

@Injectable()export class DriverService extends AbstractRestService<Driver> {  constructor(http:Http,configuration:Configuration){    super(http,configuration.serverWithApiUrl+"Driver/");  }}

car service class

@Injectable()export class CarService extends AbstractRestService<Car> {  constructor(http:Http,configuration:Configuration) {    super(http,configuration.serverWithApiUrl+"Car/");  }}

Note that only the concrete classes are marked as @Injectable() and should be declared inside a module while the abstract one should not.

update for Angular 4+

Http class being deprecated in favor of HttpClient, you can change the abstract class to something like that:

export abstract class AbstractRestService<T> {  constructor(protected _http: HttpClient, protected actionUrl:string){  }  getAll():Observable<T[]> {    return this._http.get(this.actionUrl) as Observable<T[]>;  }  getOne(id:number):Observable<T> {    return this._http.get(`${this.actionUrl}${id}`) as Observable<T>;  }} 


Below is a basic example built on Angular 7 and RxJS 6.

ApiResponse<T> represents any server response. Server must have the same structure and return it whatever happens:

export class ApiResponse<T> {  constructor() {    this.errors = [];  }  data: T;  errors: ApiError[];  getErrorsText(): string {    return this.errors.map(e => e.text).join(' ');  }  hasErrors(): boolean {    return this.errors.length > 0;  }}export class ApiError { code: ErrorCode; text: string; }export enum ErrorCode {  UnknownError = 1,  OrderIsOutdated = 2,  ...}

Generic service:

export class RestService<T> {  httpOptions = {    headers: new HttpHeaders({ 'Content-Type': 'application/json',        'Accept': 'application/json'})  };  private _apiEndPoint: string = environment.apiEndpoint;  constructor(private _url: string, private _http: HttpClient) { }  getAll(): Observable<ApiResponse<T[]>> {    return this.mapAndCatchError(      this._http.get<ApiResponse<T[]>>(this._apiEndPoint + this._url         , this.httpOptions)    );  }  get(id: number): Observable<ApiResponse<T>> {    return this.mapAndCatchError(      this._http.get<ApiResponse<T>>(`${this._apiEndPoint + this._url}/${id}`         , this.httpOptions)    );  }  add(resource: T): Observable<ApiResponse<number>> {    return this.mapAndCatchError(      this._http.post<ApiResponse<number>>(        this._apiEndPoint + this._url,        resource,        this.httpOptions)    );  }  // update and remove here...  // common method  makeRequest<TData>(method: string, url: string, data: any)                                    : Observable<ApiResponse<TData>> {    let finalUrl: string = this._apiEndPoint + url;    let body: any = null;    if (method.toUpperCase() == 'GET') {      finalUrl += '?' + this.objectToQueryString(data);    }    else {      body = data;    }    return this.mapAndCatchError<TData>(      this._http.request<ApiResponse<TData>>(        method.toUpperCase(),        finalUrl,        { body: body, headers: this.httpOptions.headers })    );  }  /////// private methods  private mapAndCatchError<TData>(response: Observable<ApiResponse<TData>>)                                         : Observable<ApiResponse<TData>> {    return response.pipe(      map((r: ApiResponse<TData>) => {        var result = new ApiResponse<TData>();        Object.assign(result, r);        return result;      }),      catchError((err: HttpErrorResponse) => {        var result = new ApiResponse<TData>();        // if err.error is not ApiResponse<TData> e.g. connection issue        if (err.error instanceof ErrorEvent || err.error instanceof ProgressEvent) {          result.errors.push({ code: ErrorCode.UnknownError, text: 'Unknown error.' });        }        else {          Object.assign(result, err.error)        }        return of(result);      })    );  }  private objectToQueryString(obj: any): string {    var str = [];    for (var p in obj)      if (obj.hasOwnProperty(p)) {        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));      }    return str.join("&");  }}

then you can derive from RestService<T>:

export class OrderService extends RestService<Order> {  constructor(http: HttpClient) { super('order', http); }}

and use it:

this._orderService.getAll().subscribe(res => {  if (!res.hasErrors()) {    //deal with res.data : Order[]  }  else {    this._messageService.showError(res.getErrorsText());  }});// orthis._orderService.makeRequest<number>('post', 'order', order).subscribe(r => {  if (!r.hasErrors()) {    //deal with r.data: number  }  else    this._messageService.showError(r.getErrorsText());});

You can redesign RestService<T>.ctor and inject RestService<Order> directly instead of declaring and injection OrderService.

It looks like RxJS 6 doesn't allow to rethrow/return typed errors. For this reason RestService<T> catches all errors and returns them within strongly typed ApiResponse<T>. The calling code should check ApiResponse<T>.hasErrors() instead of catching errors on Observable<T>


Have a base service for your app.

With get post and delete methods with your base URL attached.

export class HttpServiceBase {    HOST_AND_ENDPOINT_START : string = 'you/rD/efa/ult/Url' ;    public getWebServiceDataWithPartialEndpoint(remainingEndpoint: string): Observable<Response> {        if (!remainingEndpoint) {            console.error('HttpServiceBase::getWebServiceDataWithPartialEndpoint - The supplied remainingEndpoint was invalid');            console.dir(remainingEndpoint);        }        console.log('GET from : ' , this.HOST_AND_ENDPOINT_START + remainingEndpoint);        return this.http.get(            this.HOST_AND_ENDPOINT_START + remainingEndpoint        );    }

This a useful implementation as it allows you to easily debug WS calls - all calls end up coming from the base.

HOST_AND_ENDPOINT_START can be overriden by any module that you want to extend the base service.

Lets pretend your endpoint is something like:/myapp/rest/

And you want to implement a HttpSearchBase you can simply extend HttpServiceBase and override HOST_AND_ENDPOINT_START with something like:

/myapp/rest/search

Example CarDriverService

@Injectable()export class CarDriverService extends HttpServiceBase{    //here we are requesting a different API    HOST_AND_ENDPOINT_START : string = '/myapp/rest/vehicle/;    getAllCars() : Observable<Car[]>{    return this.getWebServiceDataWithPartialEndpoint('/Car')           .map(res => <Car[]>res.json())    }    getAllDrivers(){    return this.getWebServiceDataWithPartialEndpoint('/Driver')    }    addNewDriver(driver: Driver){    return this.postWebServiceDataWithPartialEndpoint('/Driver/',driver)    }}