打字稿通用服务
Posted
技术标签:
【中文标题】打字稿通用服务【英文标题】:Typescript generic service 【发布时间】:2017-05-23 08:38:19 【问题描述】:我是 typescript 和 angular2/4 的新手,我正在构建一个具有两个基本实体的应用程序,即 Car 和 Driver,我所做的只是通过 API 调用列出它们。
我面临的问题是每个 CarService 和 DriverService 都有代码冗余,而其他实体服务的代码可能相同。
目前的实现如下,跳过其他方法进行说明:
@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');
DriverService 唯一改变的是 url 末尾的 Car/
和 Observable<Car[]>
中的数据类型和响应。
我想知道使用通用服务避免这种情况的最佳方法是什么,以及如何在 Typescript 中做到这一点。
【问题讨论】:
为什么还要为此提供服务?为什么不直接使用 http 服务作为通用服务呢? 或者您可能想查看 ngx-resource 库。 github.com/troyanskiy/ngx-resource 我的服务中有多个函数,但为简单起见,我隐藏了其他调用。 不要,它们可能是相关的。 :) 我用完整的服务更新了问题,驱动程序和其他实体服务是一样的,只有url和数据类型会改变,所以我想到了一个通用服务但我不知道如何实现它,我找不到一个完整且解释清楚的方法来使用服务来实现它 【参考方案1】:您可以创建一个抽象泛型类和两个继承自它的子类:
抽象类:
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);
驱动服务类
@Injectable()
export class DriverService extends AbstractRestService<Driver>
constructor(http:Http,configuration:Configuration)
super(http,configuration.serverWithApiUrl+"Driver/");
汽车服务等级
@Injectable()
export class CarService extends AbstractRestService<Car>
constructor(http:Http,configuration:Configuration)
super(http,configuration.serverWithApiUrl+"Car/");
请注意,只有具体类被标记为@Injectable()
,并且应该在模块内声明,而抽象类不应该。
Angular 4+ 更新
Http
类被弃用以支持HttpClient
,您可以将抽象类更改为类似的内容:
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>;
【讨论】:
是的,这更准确地说是我所期待的实现。然后我的问题是如何在其中一个子类中调用getAll()
?
我不明白这个问题,对不起...你想从哪里打电话给getAll()
?
我的意思是如果我在一个组件中调用 CarService 的getAll()
,它会是这样的:this._carService.GetAll().subscribe((data: Car[])...
对吗?我不会有数据类型的问题吧?
@danger89 *Angular 4+
你能用配置扩展答案吗?【参考方案2】:
以下是基于 Angular 7 和 RxJS 6 构建的基本示例。
ApiResponse<T>
代表任何服务器响应。服务器必须具有相同的结构,并且无论发生什么都返回它:
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,
...
通用服务:
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("&");
那么你可以从RestService<T>
派生:
export class OrderService extends RestService<Order>
constructor(http: HttpClient) super('order', http);
并使用它:
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());
);
您可以重新设计RestService<T>.ctor
并直接注入RestService<Order>
,而不是声明和注入OrderService
。
看起来RxJS 6
不允许重新抛出/返回键入的错误。因此RestService<T>
捕获所有错误并在强类型ApiResponse<T>
中返回它们。调用代码应该检查ApiResponse<T>.hasErrors()
而不是在Observable<T>
上捕获错误
【讨论】:
【参考方案3】:为您的应用提供基础服务。
使用 get
post
和 delete
方法并附加您的 base URL
。
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
);
这是一个有用的实现,因为它允许您轻松调试 WS 调用 - 所有调用最终都来自基础。
HOST_AND_ENDPOINT_START
可以被您想要扩展基础服务的任何模块覆盖。
让我们假设你的端点是这样的:
/myapp/rest/
如果你想实现一个HttpSearchBase
,你可以简单地扩展HttpServiceBase
并用类似的东西覆盖HOST_AND_ENDPOINT_START
:
/myapp/rest/search
例如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)
【讨论】:
我看到了背后的逻辑和我想要的那种。但在这种情况下,例如,在 CarDriverService 中获取 AllCars() 并将其映射到我的数据类型,我所要做的就是return this.getWebServiceDataWithPartialEndpoint('/Car').map((response: Response) => <Car[]>response.json())
?
是的。我忘了包括映射 - 我已经更新了 getAllCars
方法。
感谢您的回答@DanielCooke以上是关于打字稿通用服务的主要内容,如果未能解决你的问题,请参考以下文章