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