ng2-rike v0.4.1
Rike: REST-like API for Angular2
Rike enhances Angular Http
service by supporting custom protocols and reporting request processing events.
This allows to report request status and errors in a generic way.
Rike defines resources as injectable Angular services. Operations performed on these resources are reported as events.
See the API documentation for more detailed description.
Usage
To use it import a RikeModule
to your application:
import {NgModule} from "@angular/core";
import {RikeModule} from "ng2-rike";
@NgModule({
imports: [RikeModule]
})
export class MyModule {
}
Targets And Operations
The target groups a set of operations. Only one operation on a given target can be performed at a time. Invoking another operation cancels already active one.
import {Component, OnInit} from "@angular/core";
import {Rike, JSON_PROTOCOL} from "ng2-rike";
@Component({
selector: "my-component",
template:
`
<div>{{content | json}}</div>
`
})
class MyComponent implements OnInit {
content: any;
constructor(private _rike: Rike) {
}
ngOnInit() {
this._rike.target("My Target")
.operation("load", JSON_PROTOCOL)
.get("/path/to/data.json")
.subscribe(content => this.content = content, error => console.log(error));
}
}
The Rike
service can be injected into any component or service. It contains methods for creating targets
(RikeTarget
). RikeTarget
has methods for creating operations (RikeOperation
). RikeOperation
contains a set of
methods for performing HTTP requests (GET
, POST
, PUT
, etc.)
Protocols
Operations are performed using protocols (Protocol
). Protocol can be configured globally
(using RikeOptions
, see below), set for target (Rike.target(id, protocol)
), or for particular operation
(RikeTarget.operation(name, protocol)
).
The protocol defines input and output data types. A value of input type is passed to operation when performing
POST
, PUT
, or PATCH
HTTP request. A value of output type is reported as operation result.
Rike contains a few predefined protocols:
HTTP_PROTOCOl
uses any input value asHttp
request body (Request.body
), and reports HttpResponse
as an output.JSON_PROTOCOL
uses any input value serialized to JSON as a request body, and reports deserialized JSON response as output.jsonProtocol()
is the same asJSON_PROTOCOL
, but specifies the types of input and output values.
Custom Protocols
Custom protocol can be constructed based on existing one:
import {RequestOptions, RequestOptionsArgs} from "@angular/http";
import {Protocol, HTTP_PROTOCOL} from "ng2-rike";
const TEXT_PROTOCOL: Protocol<string, string> = HTTP_PROTOCOL
.instead().writeRequest((request: string, opts: RequestOptionsArgs) => new RequestOptions(opts).merge({
body: request,
}))
.instead().readResponse(response => response.text());
The following methods could be used to customize protocol:
instead()
- perform operations instead of the ones defined in the original protocol:prepareRequest(prepare)
- prepare HTTP request with the given function.writeRequest(write)
- write HTTP request with the given function.readResponse(read)
- read HTTP response with the given function.handleError(handle)
- handle error with the given function.
prior()
/then()
- perform operations prior/after the ones defined in the original protocol respectively:prepareRequest(prepare)
- prepare HTTP request with the given function.updateRequest(update)
- update HTTP request options with the given function.handleError(handle)
- handle error with the given function.apply(protocol)
- do all of the above by the given protocol. This is used when constructing default target protocols based on default one, and operation protocol based on default target one.prior().input(convert)
- convert request from arbitrary type to the type of original protocol input with the given function.then().output(convert)
- convert response of original protocol output type to another type with the given function.
Handling Errors
Errors are reported as ErrorResponse
objects.
Protocols can also handle such errors in application-specific way. E.g. when server reports errors in a specific format.
This is typically done by adding custom fields to ErrorResponse
object.
import {RequestOptions, RequestOptionsArgs} from "@angular/http";
import {ErrorResponse, JSON_PROTOCOL} from "ng2-rike";
export interface CustomErrorResponse extends ErrorResponse {
errorMessage?: string;
}
const CUSTOM_PROTOCOL = JSON_PROTOCOL
.then().handleError(error => {
const customError = error as CustomErrorResponse;
customError.errorMessage = error.response.text();
return customError;
});
Field Errors
Rike contains an implementation of error handler, which treats JSON responses in a predefined format as input field errors, if possible. Otherwise it handles errors in a generic way.
The expected error response format is following JSON:
{
"field": [
{
"code": "error.code",
"message": "Error message"
},
...
],
...
}
Where field
is arbitrary input field name caused this error. There are multiple errors possible per field,
and multiple fields with errors could be reported. The special field named *
is reserved to report generic errors.
The error handler appends a fieldErrors
field to generic ErrorResponse
, thus effectively converting it to
FieldErrorResponse
object.
This error format is used by ErrorCollector
service and rikeErrors
component to display Rike operation errors.
Use the following module initialization code to apply this handler by default:
import {NgModule} from "@angular/core";
import {RikeModule, RikeOptions, BaseRikeOptions, HTTP_PROTOCOL, addFieldErrors} from "ng2-rike";
@NgModule({
imports: [RikeModule],
providers: [
{
provide: RikeOptions,
useValue: new BaseRikeOptions({
defaultProtocol: HTTP_PROTOCOL.instead().handleError(addFieldErrors)
})
}
]
})
export class MyModule {
}
See the explanation below.
Alternatively you can use addFieldErrors
function explicitly:
import {ErrorResponse, FieldErrors, addFieldErrors} from "ng2-rike";
function getFieldErrors(errors: ErrorResponse): FieldErrors {
return addFieldErrors(errors).fieldErrors;
}
Configuration
Rike
service defaults could be configured by binding to RikeOptions
class when configuring injector.
Or simply by using RikeModule.configure()
static method:
import {NgModule} from "@angular/core";
import {RikeModule, RikeOptions, BaseRikeOptions} from "ng2-rike";
@NgModule({
imports: [
RikeModule.configure({
baseUrl: "/application/base",
defaultProtocol: CUSTOM_PROTOCOL,
})
]
})
export class MyModule {
}
The following options supported:
baseUrl
All relative target and operation URLs will be relative to this one.defaultProtocol
DefaultProtocol
to use. Target and operation protocols will be based on this one. It may contain request preparation and error handling logic.defaultStatusLabels
- operation processing status labels used byStatusCollector
by default, and reported byrikeStatus
component. See below.
These defaults could be overridden in RikeTarget
. RikeTarget
options are in turn the defaults for RikeOperation
ones and could be overridden there too.
import {Component} from "@angular/core";
import {Headers} from "@angular/http";
import {Rike, RikeTarget, RikeOperation} from "ng2-rike";
export class MyComponent {
private _target: RikeTarget<any, any>;
private _operation: RikeOperation<any, any>
constructor(rike: Rike) {
this._target = rike
.target("My Target", TARGET_PROTOCOL)
.withBaseUrl("my-target"); // Relative to Rike base URL
this._operation = this._target
.operation("my-operation", OPERATION_PROTOCOL)
.withUrl("my-operation.json") // Relative to target base URL
.withOptions({
headers: new Headers({"X-Operation-Name": "my-operation"})
});
}
}
Resources
Rike resource is an injectable Angular service incorporating a single Rike target.
Such service should be registered with provideResource
function. Then all of events emitted by operations on resource
target will be reported to event consumers.
An examples of such consumers are StatusCollector
and ErrorCollector
- an injectable Angular services automatically
registered by provideResource
function, as well as at application level. These services are used by rikeStatus
and rikeErrors
components to report status and errors of all available resources. These services could be injected
into your component or service as well, and used directly.
import {Component, OnInit} from "@angular/core";
import {Rike, JSON_PROTOCOL, provideResource} from "ng2-rike";
@Component({
selector: "my-component",
template:
`
<span rikeStatus></span>
<span rikeErrors></span>
<div>{{content | json}}</div>
`,
providers: [
provideResource({provide: MyResource, useClass: MyResource}),
]
})
class MyComponent implements OnInit {
content: any;
constructor(private _resource: MyResource) {
}
ngOnInit() {
this._resource.load()
.subscribe(content => this.content = content, () => {});
}
}
It is possible to register arbitrary number of resources in the same component (or at the application level).
All of them will emit events to event consumers registered at the same level.
E.g. StatusCollector
and ErrorCollector
would contain information combined from all resources provided for the
same component.
Resources Implementations
The Resource
class is just an interface. Rike contains several abstract Resource
implementations, that could be
more convenient to use.
LoadableResource
is able to load arbitrary data from server. The default protocol is JSON. It contains method
load()
returning an Observable
on loaded data.
import {Injectable, Component, OnInit} from "@angular/core";
import {Rike, LoadableResource, provideResource} from "ng2-rike"
export interface ItemDescription {
item: string;
description: string;
price: string;
}
@Injectable()
export class ItemDescriptionsService extends LoadableResource<ItemDescription[]> {
constructor(rike: Rike) {
super(rike);
}
}
@Component({
selector: 'item-prices',
template:
`
<h3>Item Descriptions <span rikeStatus></span></h3>
<span rikeErrors></span>
<dl>
<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="itemName">
<dt>{{item.item}}</dt>
<dd>$ {{item.description}}</dd>
</template>
</dl>
`,
providers: [
provideResource({provide: ItemDescriptionsService, useClass: ItemDescriptionsService}),
]
})
export class ItemPricesComponent implements OnInit {
items: ItemDescription[] = [];
constructor(private _itemService: ItemDescriptionsService) {
}
ngOnInit() {
this._itemService.load().subscribe(items => this.items = items, () => {});
}
}
CRUDResource
is a basic CRUD resource implementation able to create()
, read()
, update()
, and delete()
some
RESTful resource with appropriate HTTP requests using JSON protocol by default.
RikeResource
is a base Resource
implementation. It can be customized to your needs. Other basic resource
implementations are based on it.
UI Components
RikeStatusComponent
The RikeStatusComponent
is an indicator of operation statuses combined from all registered resources.
It utilizes StatusCollector
service.
It is bound to rike-status
element, [rikeStatus]
and other attributes. The meaning of attributes is following:
[rikeStatus]
optionally accepts aStatusView
instance, that can be constructed byStatusCollector.view()
method.[rikeStatusLabels]
accepts aStatusLabelMap
instance(s) used to customize status indication.[rikeStatusLabelText]
function converts status label to text to display. By default supports string labels and labels of typeDefaultStatusLabel
, and converts everything else to strings.[rikeStatusLabelClass]
function returns status label CSS class according toStatusView
state. By default supports CSS classes provided byDefaultStatusLabel.cssClass
orDefaultStatusLabel.id
, and uses predefined classes.
By default CSS classes have a form like rike-status rike-status-XXX
.
Some predefined status CSS classes are:
rike-status-hidden
- used when there are no status labels known. Means that the status indicator should be hidden. Some labels may wish to hide status indicator. E.g. when operation completed successfully.rike-status-processing
- indicates the operation is in process.rike-status-cancelled
- indicates the operation has been cancelled.rike-status-failed
- indicated the operation failure.rike-status-succeed
- indicates the operation success.
Additional status CSS classes could be appended for operations defined in the base resource implementations. For example:
rike-status-loading
- forload
operation (ofLoadableResource
).rike-status-reading
- forread
operation (ofCRUDResource
).rike-status-sending
- forsend
operation.
You may call your operation similarly to apply these classes.
The generated HTML wood look like this:
<any-tag class="rike-status rike-status-CLASS...">
<span class="rike-status-icon"></span>
Status Text
</any-tag>
Status labels can be customized on a per-operation basis, or globally. For this an instance of StatusLabelMap
can be used, and provided either globally (RikeOptions.defaultStatusLabels
), or via component attribute
([rikeStatusLabels]
). See the API documentation for the details.
RikeErrorsComponent
The RikeErrorsComponent
is a list of all operation errors combined from all registered resources.
It utilizes ErrorCollector
service and uses FieldErrorResponse
to detect field errors. Even if addFieldErrors()
function is not used in operation protocol, this component applies it to error response.
The component is bound to rike-errors
element, [rikeErrors]
and other attributes. The meaning of attributes is
following:
[rikeErrors]
optionally accepts a field name. If not specified or*
is used, the component displays generic errors, and errors for fields for which error consumers are not registered, i.e. no corresponding[rikeErrors]
component.[rikeErrorsOf]
aErrorCollector
instance to be used instead of the injected one.
Note that you don't have to create a [rikeErrors]
component for each of the input fields. Then the errors will be
reported as generic ones. Also, if there is a [rikeErrors]
component for particular field, the errors for this
field won't be reported as generic ones.
The correct way to display errors for e.g. form is to place one <span rikeErrors></span>
component to report generic
errors, and several <span rikeErrors="field"></span>
components to report errors for each field.
The generated HTML would look like this:
<any-tag class="rike-errors">
<ul class="rike-error-list">
<li class="rike-error">Error message</li>
<li class="rike-error">Another error message</li>
...
</ul>
</any-tag>
When there are no errors to report the HTML would look like this:
<any-tag class="rike-errors rike-no-errors"></any-tag>
RikeDisabledDirective
This directive disables a control it is applied to while Rike operation is in progress. Additionally sets
rike-disabled
CSS class to host element when Rike operation is in progress.
This directive could be useful e.g. to disable submit button while submitting a form with Rike.
It utilizes StatusCollector
service.
The directive is bound to [rikeDisabled]
and other attributes. The meaning of attributes is following:
[rikeDisabled]
optionally accepts a boolean value. The component will be disabled when this value is true or when Rike operation is in progress.[rikeDisabledBy]
accepts aStatusCollector
instance to detect if the Rike operation is in progress. When omitted the injectedStatusCollector
instance will be used.
RikeReadonlyDirective
This directive makes a control read-only while Rike operation is in progress. Just like RikeDisabledDirective
. But it
sets readonly
attribute instead of disabled
one. Additionally sets rike-readonly
CSS class to host element when
Rike operation is in progress.
This directive could be useful e.g. to make form inputs read-only while submitting a form with Rike.
The directive is bound to [rikeReadonly]
and other attributes. The meaning of attributes is following:
[rikeReadonly]
optionally accepts a boolean value. The component will be made read-only when this value is true or when Rike operation is in progress.[rikeReadonlyBy]
accepts aStatusCollector
instance to detect if the Rike operation is in progress. When omitted the injectedStatusCollector
instance will be used.