Consume OData Restful Services in Angular 2
Today I want to speak about a feature which I think can be of a high importance in the near future, which is OData. And to create discussion, I will consume OData services with the oh so popular technology nowadays (and which I personally adore) Angular 2.
Introduction
As stated at the official OData website:
“OData (Open Data Protocol) is an OASIS standard that defines a set of best practices for building and consuming RESTful APIs. OData helps you focus on your business logic while building RESTful APIs without having to worry about the various approaches to define request and response headers, status codes, HTTP methods, URL conventions, media types, payload formats, query options, etc. …”
Now let’s take a look at what OData is from the perspective of the client (the application which is going to consume that service, and could be server or browser). For the client, OData standards allow to have access to truly Restful services but in predictable and standard way. So no more messy and custom URIs where client should know how the service is implemented before being able to connect to it, but it can use calls from standard and be sure that it's going to receive the correct responses for all requests (we can say that responses are predictable).
OData is just a normal web service which should be accessed through HTTP. And given that the we will not need to create anything special but just a simple set of methods which will perform HTTP requests, in this tutorial, I will show you a handy and scalable way to create a client for Angular 2 which will consume the OData web service.
Getting started
So, to start, I will use the following tools (you may use tools of your choice):
- Visual Studio Code, as Integrated Development Environment;
- Angular2 seed project, as a template to start;
- Node.Js, NPM, to make our life easier;
First let’s go to the following Github repo and download our template project: https://github.com/angular/angular2-seed.
Then you need to open that project in your local environment and run the command below from the project’s root folder.
npm install
Then, the code below will download all node modules necessary to work with our project. Pay attention that to have this step work correctly you must have node.js and NPM installed on your machine.
cd angular2-seed
Now if you run the npm start
command, the local server will be launched and you will be able to see the result in the browser. The default URL is http://localhost:3000
, and if everything goes well until now, you should see this homepage in your browser:
Now let’s go to the src -> app, and create additional folders there which will hold a component for our tests with OData requests. I will call that folder odata.
Inside that folder I'm going to create 4 files:
odata.component.ts
— which will be our component to interact with data;odata.component.html
— template for our component;odata.service.html
— service which will support us in calling the OData web services;odata.model.ts
— file to store our enums, classes, interfaces, and values for structure
The final result should look like this:
Implementation
Now let’s dive to the implementation. Put the following code into component:
import { Component, OnInit } from '@angular/core';
import { ODataService } from './odata.service';
import { RequestTypes } from './odata.model';
import { IUrlOptions } from './odata.model';
@Component({
selector: 'odata',
templateUrl: 'odata.component.html'
})
export class ODataComponent implements OnInit {
public requestResult: any;
constructor(
private odata: ODataService
) { }
ngOnInit() { }
testGet() {
let urlOptions: IUrlOptions = <IUrlOptions>{};
urlOptions.restOfUrl = "Products?$format=json";
this.odata.Request(RequestTypes.get, urlOptions).subscribe(
data => this.requestResult = data,
error => alert(error)
);
}
testPost() {
let urlOptions: IUrlOptions = <IUrlOptions>{};
this.odata.Request(RequestTypes.post, urlOptions).subscribe(
data => this.requestResult = data,
error => alert(error)
);
}
testPut() {
let urlOptions: IUrlOptions = <IUrlOptions>{};
this.odata.Request(RequestTypes.put, urlOptions).subscribe(
data => this.requestResult = data,
error => alert(error)
);
}
testPatch() {
let urlOptions: IUrlOptions = <IUrlOptions>{};
this.odata.Request(RequestTypes.patch, urlOptions).subscribe(
data => this.requestResult = data,
error => alert(error)
);
}
testDelete() {
let urlOptions: IUrlOptions = <IUrlOptions>{};
this.requestResult = this.odata.Request(RequestTypes.delete, urlOptions);
}
}
This is the simple component where we reference our template, inject the instance of our service and create methods which we will bind to the buttons on our template to test the action. Also, this contains an object which holds a response from the web service in JSON format, which we going to display in the template.
In the component’s template put the following code:
<div>
<h2>Welcome to OData Component</h2>
<button (click)="testGet()">Get Request</button>
<br />
<button (click)="testPost()">Post Request</button>
<br />
<button (click)="testPut()">Put Request</button>
<br />
<button (click)="testPatch()">Patch Request</button>
<br />
<button (click)="testDelete()">Delete Request</button>
<br />
<h3>Request results will be displayed here</h3>
<pre>{{requestResult | json}}</pre>
</div>
As explained previously, the template will have several buttons to perform different test requests and sections to display the result of the requests.
Now let’s take a look at the service itself. Please copy-paste the following code into the service file:
import { Injectable } from '@angular/core';
import { Http, RequestOptionsArgs, Response } from '@angular/http';
import { Observable } from 'rxjs';
import { IUrlOptions } from './odata.model';
import { RequestTypes } from './odata.model';
@Injectable()
export class ODataService {
constructor(
private host: string,
private http: Http
) { }
private constructUrl(urlOptions: IUrlOptions): string {
return this.host + urlOptions.restOfUrl;
}
//T specifies a generic output of function
public Request<T>(requestType: RequestTypes, urlOptions: IUrlOptions, body?: any, options?: RequestOptionsArgs) : Observable<T> {
let response: Observable<Response>;
//True in case of post, put and patch
if (body && options) {
response = this.http[RequestTypes[requestType]](
this.constructUrl(urlOptions),
body,
options);
}
//True in case of post, put and patch if options is empty
else if (body) {
response = this.http[RequestTypes[requestType]](
this.constructUrl(urlOptions),
body);
}
//True in case of get, delete, head and options
else if (options) {
response = this.http[RequestTypes[requestType]](
this.constructUrl(urlOptions),
options);
}
//True in case of get, delete, head and options, if options is empty
else {
response = this.http[RequestTypes[requestType]](
this.constructUrl(urlOptions),
options);
}
return response.map((res) => <T>res.json());
}
}
The first thing you'll notice here is that in our constructor, we have injected a host, which we will pass through the custom provider (we will create this in a moment).
Since OData is composed of three parts which are:
- Service root URL
- Resource path
- Query options
And since we know that service root URL will always be the same, we can inject it as a dependency to our service (private host: string), and concentrate mostly on constructing a resource path and working with query options.
The second important thing to notice here is the Request method. In Angular 2's HTTP module, we have different types of requests (get, post, put, options, delete, etc…) and all of them are almost identical and the only thing that could change is body and query options. So, that's why I prefer to have only one method, the other reason to have only one method is that with Odata, we don’t need to do any additional manipulation before making a request, because all manipulations necessary is done on the request’s URL.
For that purpose I’ve created a method called constructUrl
, which holds a responsibility to create the correct URL (I’ve made it very simple just to demonstrate you my idea, but it can be expanded in a generic way, just by adding new properties to urlOptions
and to use them inside the constructUrl
function).
The model file looks like this:
export enum RequestTypes {
get,
post,
put,
delete,
patch,
head,
options
}
export interface IUrlOptions {
restOfUrl: string,
}
Now let’s create one more additional file to hold our custom provider for ODataServive
class. I will call it odata.serviceProvider.ts
. Please put the following code inside this file:
import { Http } from '@angular/http';
import { ODataService } from './odata.service';
export function provideODataService(url: string) {
return {
provide: ODataService, useFactory: (http) => {
return new ODataService(url, http);
},
deps: [Http]
}
}
This is the way that we have to inject custom values into our service while instantiating it. So basically, what's going to happen here is that, when we will first request our service, it’s going to be created. During the creation, instead of using a default constructor, it will use a factory where we are going to inject our custom value for the host property.
Now go to the app.module.ts file and add: ODataComponent
to the declarations list, and the following line of code to the providers list:
provideODataService("http://services.odata.org/V3/(S(pq1lpmgz0kuok05ubqtx1c2g))/OData/OData.svc/")
Pay attention that before adding these lines of code, you should import the references to the ODataComponent
and provideODataService
.
Info: in this tutorial I will use the test services provided by www.odata.org: http://services.odata.org/V3/(S(pq1lpmgz0kuok05ubqtx1c2g))/OData/OData.svc/
Now we need to add the route to our routing definition in order to be able to reach the component and send requests to web services. For that, go to the app.routes.ts and add the following line of code to the array of routes:
{ path: 'odata', component: ODataComponent }
(Again: don’t forget to import ODataComponent
previously).
That it, work done!
Now hit npm start
in your command line, to allow everything to be recompiled again. Go to the http://localhost:3000/#/odata
URL and hit the Get Request button, and in the bottom of the page, you should see the response from the OData web service.
Conclusion
In this tutorial, I only showed how to work with get requests, but the other types of request will work in a very similar way. So, I believe that at this moment, you have a good starting (and scallable) point to start working with OData web services in your Angular 2 application.
Thank you very much for your time, and if you have any question about OData or Angular 2 you can reach me through here at Codementor or post your feedback on the comment section below.
I got CORS error, Please help
What is the .svc file? I have seen you can use a npm package for JaySvc. How is that file generated.
Excellent - thank you!! +1