tag page navigationend change angular generics typescript service

angular - page - Servicio genérico mecanografiado



title angular (3)

A continuación se muestra un ejemplo básico construido en Angular 7 y RxJS 6 .

ApiResponse<T> representa cualquier respuesta del servidor. El servidor debe tener la misma estructura y devolverlo, pase lo que pase:

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

Servicio genérico:

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>(); Object.assign(result, err.error) // if err.error is not ApiResponse<TData> e.g. connection issue if (result.errors.length == 0) { result.errors.push({ code: ErrorCode.UnknownError, text: ''Unknown 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("&"); } }

entonces puede derivar de RestService<T> :

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

y úsalo:

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

Puede rediseñar RestService<T>.ctor e inyectar RestService<Order> directamente en lugar de declarar e inyectar OrderService .

Parece que RxJS 6 no permite volver a generar / devolver errores escritos. Por esta razón, RestService<T> captura todos los errores y los devuelve dentro de ApiResponse<T> fuertemente ApiResponse<T> . El código de llamada debe verificar ApiResponse<T>.hasErrors() lugar de detectar errores en Observable<T>

Soy nuevo en mecanografiado y angular2 / 4 y estoy creando una aplicación única que tiene dos entidades básicas, que es Coche y Conductor, y todo lo que hago es enumerarlas con una llamada de API.

El problema al que me enfrento es que tengo redundancia de código para cada Servicio de Coche y Servicio de Conductor, y podría tener el mismo código para el servicio de otras entidades.

La implementación está siguiendo hasta ahora, omitiendo otros métodos para la ilustración:

@Injectable() export class CarService { private actionUrl: string; private headers: Headers; constructor(private _http: Http, private _configuration: Configuration) { // Getting API URL and specify the root this.actionUrl = _configuration.serverWithApiUrl + ''Car/''; this.headers = new Headers(); this.headers.append(''Content-Type'', ''application/json''); this.headers.append(''Accept'', ''application/json''); } // Function to get all Cars - API CALL: / public GetAll = (): Observable<Car[]> => { return this._http.get(this.actionUrl) .map((response: Response) => <Car[]>response.json()) .catch(this.handleError); } // Function to get a Car by specific id - API CALL: /:id public GetSingle = (id: number): Observable<Car> => { return this._http.get(this.actionUrl + id) .map((response: Response) => <Car>response.json()) .catch(this.handleError); } // Function to add a Car - API CALL: /create public Add = (newCar: Car): Observable<Car> => { return this._http.post(this.actionUrl + ''/create'', JSON.stringify(newCar), { headers: this.headers }) .catch(this.handleError); } // Function to update a Car - API CALL: / public Update = (id: number, CarToUpdate: Car): Observable<Car> => { return this._http.put(this.actionUrl + id, JSON.stringify(CarToUpdate), { headers: this.headers }) .catch(this.handleError); } // Function to delete a Car - API CALL: /:id public Delete = (id: number): Observable<Response> => { return this._http.delete(this.actionUrl + id) .catch(this.handleError); } // Function to throw errors private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || ''Server error''); }

Lo que solo cambia con DriverService es el Car/ al final de la url y el tipo de datos en Observable<Car[]> y la respuesta.

Me gustaría saber cuál es la mejor manera de evitar esto con un servicio genérico y cómo hacerlo en Typescript.


Puede crear una clase genérica abstracta y dos clases hijos que se hereda de ella:

clase abstracta:

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

clase de servicio del conductor

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

clase de servicio de coche

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

Tenga en cuenta que solo las clases concretas están marcadas como @Injectable() y deben declararse dentro de un módulo, mientras que el resumen no debería estarlo.

actualización para Angular 4+

Http clase Http está en desuso en favor de HttpClient , puede cambiar la clase abstracta a algo así:

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


Tener un servicio de base para su aplicación.

Con los métodos get post y delete con su base URL adjunta.

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

Esta es una implementación útil, ya que le permite depurar fácilmente las llamadas WS, ya que todas las llamadas provienen de la base.

HOST_AND_ENDPOINT_START puede ser reemplazado por cualquier módulo que desee extender el servicio base.

Supongamos que su punto final es algo como: /myapp/rest/

Y si quieres implementar una HttpSearchBase , puedes simplemente extender HttpServiceBase y anular HOST_AND_ENDPOINT_START con algo como:

/myapp/rest/search

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