typescript 打字稿通用服务
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/44129817/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Typescript generic service
提问by Imad El Hitti
I'm new to typescript and angular2/4 and I'm building a single app that have two basic entities which is Car and Driver and all I do is to list them with an API call.
我是 typescript 和 angular2/4 的新手,我正在构建一个具有两个基本实体的应用程序,即 Car 和 Driver,我所做的就是通过 API 调用列出它们。
The problem I'm facing is that I have code redundancy for each CarService and DriverService, and I might have the same code for other entities service.
我面临的问题是我对每个 CarService 和 DriverService 都有代码冗余,而我可能对其他实体服务有相同的代码。
The implementation is following so far, skipping other methods for ilustration :
到目前为止,实现如下,跳过其他 ilustration 方法:
@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');
}
What only change with the DriverService is the Car/
at the end of the url and the data type in Observable<Car[]>
and the response.
DriverService 唯一的变化是Car/
url 的末尾以及数据类型Observable<Car[]>
和响应。
I would like to know what is the best way to avoid this with a generic service and how to do it in Typescript.
我想知道使用通用服务避免这种情况的最佳方法是什么,以及如何在 Typescript 中做到这一点。
回答by n00dl3
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.
请注意,只有具体类被标记为@Injectable()
并应在模块内声明,而抽象类则不应。
update for Angular 4+
Angular 4+ 的更新
Http
class being deprecated in favor of HttpClient
, you can change the abstract class to something like that:
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>;
}
}
回答by AlbertK
Below is a basic example built on Angular 7and RxJS 6.
下面是一个基于Angular 7和RxJS 6的基本示例。
ApiResponse<T>
represents any server response. Server must have the same structure and return it whatever happens:
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,
...
}
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>
:
那么你可以从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());
}
});
// or
this._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
.
您可以直接重新设计RestService<T>.ctor
和注入RestService<Order>
,而不是声明和注入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>
看起来RxJS 6
不允许重新抛出/返回输入错误。出于这个原因,RestService<T>
捕获所有错误并在强类型中返回它们ApiResponse<T>
。调用代码应该检查ApiResponse<T>.hasErrors()
而不是捕捉错误Observable<T>
回答by Daniel Cooke
Have a base service for your app.
为您的应用程序提供基础服务。
With get
post
and delete
methods with your base URL
attached.
用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
);
}
This a useful implementation as it allows you to easily debug WS calls - all calls end up coming from the base.
这是一个有用的实现,因为它允许您轻松调试 WS 调用 - 所有调用最终都来自基础。
HOST_AND_ENDPOINT_START
can be overriden by any module that you want to extend the base service.
HOST_AND_ENDPOINT_START
可以被您想要扩展基本服务的任何模块覆盖。
Lets pretend your endpoint is something like:
/myapp/rest/
让我们假设你的端点是这样的:
/myapp/rest/
And you want to implement a HttpSearchBase
you can simply extend HttpServiceBase
and override HOST_AND_ENDPOINT_START
with something like:
并且你想实现一个HttpSearchBase
你可以简单地扩展HttpServiceBase
和覆盖HOST_AND_ENDPOINT_START
的东西:
/myapp/rest/search
/myapp/rest/search
Example CarDriverService
例子 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)
}
}