@elemental-concept/grappa v17.1.0
Grappa
Decorator-powered REST client for Angular 13+ and its HttpClient, plus RxJs 6+.
| Last version | Angular Versions | Node |
|---|---|---|
17.0.0 | 13 up to 17 (included) | 18 |
16.0.0 | 13 up to 16 (included) | 16 |
1.1.1 | 13 up to 15 (included) | 14 |
🛠 Installation
- With npm:
npm i --save @elemental-concept/grappa
Add GrappaModule to your main AppModule to imports section.
@NgModule({
declarations: [ ... ],
imports: [
...,
GrappaModule
],
providers: [ ],
bootstrap: [ ... ]
})
export class AppModule {
}📖 Introduction
Grappa minimises boilerplate code required for REST clients and streamlines request and response modifications with filters. Simply define a list of methods which reflect REST API:
@Injectable()
@RestClient('http://example.com/api/')
export class UserService {
@GET('/users')
list: () => Observable<User[]>;
@GET('/users/{0}')
find: (id: number) => Observable<User>;
@PATCH('/users/{0}')
update: (id: number, user: User) => Observable<User>;
@POST('/users')
create: (user: User) => Observable<User>;
@PUT('/users/{0}')
update: (id: number, user: User) => Observable<User>;
}Grappa will auto-generate required class methods which can be easily called from any component:
@Component({
// ...
})
export class AppComponent {
constructor(private userService: UserService) {
userService.find(42).subscribe(user => console.log(user));
}
}Define @BeforeRequest() filter methods to uniformly modify data being sent to the API.
A good example could be JWT injection, but we are covering that with a separate library
Grappa JWT
Custom header injection is a good example:
@Injectable()
@RestClient('http://example.com/api/')
export class UserService {
// ...
constructor(private authService: AuthService) {
}
@BeforeRequest()
private customHeaders(request: RestRequest) {
request.headers = {
...request.headers,
'X-Xid': id,
'X-Client-Id': clientId,
'X-User-Id': userId,
};
}
}Use @AfterRequest() to transform responses and inject global error handlers:
@Injectable()
@RestClient('http://example.com/api/')
export class UserService {
// ...
@AfterRequest()
tranformResponse(response: Observable<HttpResponse<any>>) {
return response.map(item => User.fromResponse(item.body));
}
@AfterRequest()
handleErrors(response: Observable<HttpResponse<any>>) {
return response.catch(error => {
alert('Error!');
return Observable.of(error);
});
}
}API
Decorators on a class and its properties define how a request will be handled.
@RestClient(baseUrl: string = '')
Optional decorator which allows to define base URL for all REST methods in a class. If decorator is not present
or baseUrl argument is empty string, null or undefined, then it is assumed that property decorators will contain
full URLs.
Example with @RestClient:
@Injectable()
@RestClient('http://example.com/api/')
export class UserService {
@GET('/users')
list: () => Observable<User[]>; // List method will call http://example.com/api/users
}Example without @RestClient:
@Injectable()
export class UserService {
@GET('http://somedomain.org/users')
list: () => Observable<User[]>; // List method will call http://somedomain.org/users
}@GET(endpoint: string, options: RequestOptions = {})
Makes HTTP GET request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Example:
@GET('/users/{0}/posts?page={1}')
getUserPosts: (userId: number, page: number) => Observable<Post[]>;{0} will be replaced with userId value and {1} will be replaced with page value.
@PATCH(endpoint: string, options: RequestOptions = {})
Makes HTTP PATCH request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Last function argument will be used as a PATCH body.
@PATCH('/users/{0}', options: RequestOptions = {})
update: (userId: number, user: User) => Observable<User>;@POST(endpoint: string, options: RequestOptions = {})
Makes HTTP POST request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Last function argument will be used as a POST body.
@POST('/users')
create: (user: User) => Observable<User>;@PUT(endpoint: string, options: RequestOptions = {})
Makes HTTP PUT request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Last function argument will be used as a PUT body.
@PUT('/users/{0}', options: RequestOptions = {})
update: (userId: number.user: User) => Observable<User>;@DELETE(endpoint: string, options: RequestOptions = {})
Makes HTTP DELETE request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Example:
@DELETE('/users/{0}')
remove: (userId: number) => Observable<User>;@BeforeRequest(applyTo: OptionalList<string> = null)
Runs decorated method before every request in a class.
@BeforeRequest()
beforeFilter(request: RestRequest) {
request.headers[ 'X-Dummy' ] = 'Abcde';
}@AfterRequest(applyTo: OptionalList<string> = null)
Runs decorated method after every request in a class.
@AfterRequest()
afterFilter(response: Observable<HttpResponse<any>>) {
return response.map(r => r.body.value);
}RequestOptions
Configuration for specific GET, POST, PATCH, PUT and DELETE requests.
export interface RequestOptions {
observe?: ObserveOptions;
query?: number | boolean;
emptyBody?: boolean;
}
export enum ObserveOptions {
Body = 'body',
Response = 'response'
}Given
@GET('/users/{0}/posts?page={1}')
getUserPosts: (userId: number, page: number) => Observable<Post[]>;you'll have {1} as the page attribute, but if instead you have multiple query params, the best approach is to have a
single object and then use the query option:
@GET('/users/{0}/posts', { query: true })
getUserPosts: (userId: number, { page: number, search: string, sort: string, hideOutOfStock: boolean }) => Observable<Post[]>;This way Grappa will translate the object into a list of query params, like this:
`/users/{0}/posts?page=${ page }&search=${ search }&sort=${ sort }&hideOutOfStock=${ hideOutOfStock }`If for any reason you need to send a PUT or a POST without a body (which is not a good practise), we added a new
flag emptyBody, that allow that. So you could send something like this:
@PUT('/users/{0}', { emptyBody: true })
getUserPosts: (userId) => Observable<Post[]>;If for any reason you need to send a PUT or a POST url params as well as query params as well body object,
you need to specify which arg index is the query params
@PUT('/users/{0}', { query: 1 })
getUserPosts: (userId, queryParams: { name: string }, body: User) => Observable<Post[]>;OptionalList
Allows to specify the names of request methods specific filter should apply.
export type OptionalList<T> = T | T[] | null;Usage:
@GET('/users')
get: () => Observable<User[]>;
@POST('/users')
create: (user: User) => Observable<User>;
@BeforeRequest('create')
beforeFilter(request: RestRequest) {
// This filter function will only apply to create() calls
}1 year ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago