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