Crafting an Angular 2 application using auto-generated APIs

angular-1.png

The new Angular 2 address book app is a great way to get started with Angular 2 and learn how easy it is to call DreamFactory’s REST API (to see how to put a REST API on AWS Redshift, check this post). In this post, I'll go over some important things to know about the Angular 2 example app.

The Angular 2 address book is a boilerplate application for getting started with Angular 2 and DreamFactory. The GitHub repo contains all the required setup to communicate with your DreamFactory instance.

The code has been organized using components and modules and uses Typescript instead of Javascript (Typescript is a typed superset of Javascript which gets compiled to plain Javascript before being served to the client). Typescript syntax contains all the features of ECMAScript2015, such as classes and modules. A benefit of Typescript is its tooling, including auto completion, easy refactoring, and code navigation. Expressing your application architecture with interfaces and classes is cleaner than using Javascript prototypes and its inheritance architecture.

To get started with Angular 2, start by reading the 5 Minute Quickstart from angular.io. The quickstart shows you how to organize code and create components in Angular 2. Note that Typescript is not compulsory for Angular 2. You can still write Angular 2 apps in Javascript, ES6 or Dart. Angular 1.5 also has a new feature called components for upgrading your existing Angular app to Angular 2.

Code architecture

It’s always better to first think about the architecture of an application before jumping into the code. Our address book application is broken into pieces like components, services, and models. Components handle the UI, handling how the app interacts with the user. Components have limited ability to handle the data coming from the user. Knowledge of data in our application resides in services. Models give definition to your data and have attributes that are used by both services and components. For instance, you can have your own private fields, getters and setters, constraints etc. in your model.

Code examples

To start, we need to configure the application during bootstrap. Consider the following code sample from bootstrap.ts.

bootstrap(AppCmp, [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: HashLocationStrategy }),
provide(RequestOptions, { useClass: DfRequestOptions }),
provide(ExceptionHandler, { useClass: CustomExceptionHandler }),
provide(Window, { useValue: window })
]);

We bootstrap the application with an initial component called AppCmp which is defined in app.ts. We need to provide necessary dependencies like ROUTER_PROVIDERS, HTTP_PROVIDERS. etc. We use ‘provide’ to configure various modules. For example, we configure the ExceptionHandler to use the custom class called CustomExceptionHandler. This class checks for any exceptions that occur in the application. For example, if we receive 401 or 403 at any point we want to route the app to the login page.

export class CustomExceptionHandler extends ExceptionHandler {
constructor() {
super(new _ArrayLogger(), true);
}
call(exception: any, stackTrace: any, reason: string): void {
if (~[401, 404].indexOf(exception.status)) {
window.location.hash = '/login';
} else {
super.call(exception, stackTrace, reason);
}
}
}

Session handling

Similarly, we use the DfRequestOptions class to tell our application to use a session token from local storage in the headers of any outgoing request.

export class DfRequestOptions extends BaseRequestOptions {
constructor () {
super();
this.headers.set('X-Dreamfactory-API-Key' ,constants.DSP_API_KEY);
var token = localStorage.getItem('session_token');
if (token) {
this.headers.set('X-Dreamfactory-Session-Token', token);
}
}
}

It’s a bit difficult to write interceptors in Angular 2 like we did in Angular 1.x versions. Mutable global state is discouraged in Angular 2. There are a couple approaches to passing the session token in every request and regularly updating the token. One approach is to create a custom request class which takes care of all the GET, PUT, POST and DELETE requests.

This class will be used by all the other classes to communicate with the server. This custom class is just a wrapper around the http service in Angular 2, which attaches/updates the header each time a request is made. Another approach is a workaround. See this stackoverflow answer for both approaches. We will go for the second approach.

base-http.ts

@Injectable()
export class BaseHttpService {
static token:string = '';
http: Http;
constructor(http: Http) {
this.http = http;
}
}

login.ts

this.httpService.http._defaultOptions.headers.set('X-Dreamfactory-Session-Token', data && data.session_token);
localStorage.setItem('session_token', data.session_token);

UI component

contact.ts

import {Component} from 'angular2/core';
import {RouteParams, Router} from 'angular2/router';

@Component({
selector: 'contact',
templateUrl: './components/contact/contact.html',
styleUrls: ['./components/contact/contact.css'],
providers: [ContactService, BaseHttpService, ContactGroupService, GroupService],
directives: [FORM_DIRECTIVES]
})

export class ContactCmp { }

Getting records

We will use the same BaseHttpService class used above to make API calls.

Let’s take an example:

contactservice

query (params?:URLSearchParams): Observable<Contact[]> {
return this.httpService.http
.get(this.baseResourceUrl, { search: params })
.map((response) => {
var result: any = response.json();
let contacts: Array<Contact> = [];
result.resource.forEach((contact) => {
contacts.push(Contact.fromJson(contact));
});
return contacts;
});
};

We have created a query method which makes the API call and returns on observable to be subscribed. The above code takes search params as an argument. This search param object is passed on to the server in the API call. This method gets the wrapped response, unwraps it, makes proper instances out it (in this case Contact instance), and then returns it as an input for the subscriber. Now let’s call the method and get the response.

getList () {
let self = this;
let params: URLSearchParams = new URLSearchParams();
params.set('order', 'last_name+ASC');
this.contactService.query(params)
.subscribe((contacts: Contact[]) => {
self.contacts = contacts
});
};

The ‘getList’ method is defined in the component. As I mentioned before, we don’t want to make API calls directly from a component. We delegated that responsibility to the service and here in the component we just subscribe to the API call. ‘URLSearchParams’ is a class from the angular2/http module. It takes care of expressing url params.

In this example, we set a parameter called ‘order’ with value ‘last_name+ASC’, which tells the server to sort the list in ascending order by ‘last_name’. Similarly, we delegate the task of posting any data to the service and we call the method from the component.

save (contact: Contact) {
if (contact.id) {
return this.httpService.http.put(constants.DSP_INSTANCE_URL + '/api/v2/db/_table/contact/' + contact.id, contact.toJson(true))
.map((data) => {
return data;
});
} else {
return this.httpService.http.post(constants.DSP_INSTANCE_URL + '/api/v2/db/_table/contact', contact.toJson(true))
.map((data) => {
return data;
});
}
}

And in the component, we have:

save () {
var self = this;
var isNew = !!this.contact.id;

this.contactService.save(this.contact)
.subscribe((response) => {
if (isNew)
alert('New contact created');
else
alert('Contact updated');

self.back();
})
}

I hope this blog post helps you get started with Angular 2 and DreamFactory. More demo code that shows database CRUD operations as well as file CRUD operations are available at the GitHub repo. To get the app up and running, just follow the directions in the repo README.

Related reading: Building an AngularJS Application using DreamFactory