masaj salonu masaj salonları
Home » Advertising » Developing a Web Application Using Angular (Part 3)

Developing a Web Application Using Angular (Part 3)

In the previous article, we laid the foundation of our implementation by developing the resource layer, including automated unit tests that carefully exercised the functionality of our Order resource class. In this article, we will continue the implementation of our web application by creating the service layer that will interface with our order management web service. Along the way, we will explore the Dependency Injection (DI) and Hypertext Transfer Protocol (HTTP) functionality of Angular.

Implementing the Service Layer

The ultimate purpose of our service layer is to abstract the web service that manages our orders and to allow other components (namely the UI layer) to obtain Order resources and commit changes to these Orderresource objects. In order to accomplish this, we must understand the Representational State Transfer (REST) endpoints that the web service provides and how to interface with these endpoints. As designed in Creating a REST Web Service With Java and Spring (Part 1), our order management web service has the following endpoints:

Therefore, in order for our service layer to be complete, it must be able to do the following:

  1. Obtain a list of all orders.
  2. Obtain a single order.
  3. Create a new order.
  4. Update an existing order.
  5. Delete an existing order.

While this appears to produce a simple service layer, there are a few issues that must be addressed prior to implementing our service. Primary among these is the asynchronous nature of the HTTP calls that our order service must execute.

Asynchronism in the Service Layer

When we make an HTTP request to a REST endpoint provided by our order management web service, the web service may respond at any future time. This creates a serious problem for synchronous applications: Blocking until a response is received ties up a thread (possibly indefinitely) until the web service responds. This issue is compounded in a User Interface (UI) driven application, where a halted thread may cause the application to hang, leaving the user with the appearance that the application has frozen.

In order to allow our application to continue functioning while we await a response from our backend web service, we must introduce asynchronism. The primary asynchronous mechanism in TypeScript (as well as JavaScript) is the promise, which allows us to register callback functions that can be executed when a call completes at a future time. For example, the usage for a promise may resemble the following:

// Some promise that returns an Order when completed
const promise: PromiseOrder;

promise.then(order = console.log("An order was received: " + order));

When the promise completes (such as when a response is received from the order management web service), the lambda expression passed to the then method is executed. In essence, we are providing the promise with a callback function that it should execute when it determines that the asynchronous call we are anticipating has completed. The importance of this mechanism cannot be overstated: We are no longer able to assume the functionality in our web application is synchronous and will execute in some determinate order. Instead, we must use promises to ensure that functions we want to execute after one another do in fact execute in the expected order. For example, given the following code:

class OrderService {

    public getOrders(): PromiseOrder[] {
        // Obtain the orders from our web service
    }
}

const service = new OrderService();
service.getOrders();
console.log("We got the orders");

the log output of We got the orders may be printed to the console prior to actually obtaining the orders. This occurs because the getOrders() method returns a promise that is tied to the asynchronous call to the web service, but the promise object is returned immediately and the execution of the program continues to the next sequential call. For example, the execution order of the above snippet may resemble the following:

Create the order service
Return a promise that will complete when the orders are obtained
Log "We got the orders" to the console
Complete the promise [the orders are now available]

In order to remedy this situation, we can adjust the above snippet to the following:

class OrderService {

    public getOrders(): PromiseOrder[] {
        // Obtain the orders from our web service
    }
}

const service = new OrderService();
service.getOrders().then(orders = console.log("We got the orders: " + orders));

By providing a callback function to the then method, we are now assured that the log message will only be printed to the console after the orders are obtained (the promise attached to obtaining the orders completes). Although asynchronism can be difficult to wrap the mind around, it is an essential facet of modern web applications and a solid understanding is required in order to avoid difficult-to-debug temporal errors. For more information on promises and asynchronism in web applications, see the Google Developers Primer on Promises.

Implementing the Order Service

With a foundational understanding of promises established, we are now ready to implement our service layer. Since we have only one resource in our system (namely, the Order resource), we will only have one service in our service layer. In many cases, there is a one-to-one correspondence between services and resources, where the service provides the create, read, update, and delete (CRUD) operations that correspond to the resource. In our case, the OrderService class is defined as follows:

@Injectable()
export class OrderService {

    private ordersUrl = 'http://localhost:8080/order';
    private headers = new Headers({'Access-Control-Allow-Origin': '*', 'Accepts': 'application/json', 'Content-Type': 'application/json'});

    public constructor(private http: Http) {}

    public getOrders(): PromiseOrder[] {
        return this.http.get(this.ordersUrl, {headers: this.headers})
             .toPromise()
             .then(response = response.json().map(jsonOrder = Order.fromJson(jsonOrder)))
             .catch(this.handleError);
    }

    private handleError(error: any): Promiseany {
        console.error('Could not complete requested service operation', error);
        return Promise.reject(error.message || error);
    }

    public deleteOrder(order: Order): Promisevoid {
        return this.http.delete(order.links.delete.href, {headers: this.headers})
             .toPromise()
             .then(response = null)
             .catch(this.handleError);
    }

    public getOrder(id: number): PromiseOrder {
        return this.http.get(`${this.ordersUrl}/${id}`, {headers: this.headers})
             .toPromise()
             .then(this.convertResponseToOrderResource)
             .catch(this.handleError);
    }

    private convertResponseToOrderResource(response: any): Order {
        return Order.fromJson(response.json());
    }

    public updateOrder(order: Order): PromiseOrder {
        return this.http.put(order.links.update.href, JSON.stringify(order.serialize()), {headers: this.headers})
             .toPromise()
             .then(this.convertResponseToOrderResource)
             .catch(this.handleError);
    }

    public createOrder(order: Order): PromiseOrder {
        return this.http.post(`${this.ordersUrl}`, JSON.stringify(order.serialize()), {headers: this.headers})
             .toPromise()
             .then(this.convertResponseToOrderResource)
             .catch(this.handleError);
    }
}

Starting from the top, the @Injectable decorator informs the Angular framework that our class is a candidate for DI. We will see shortly that we must also register the service before it can be injected into another component. For more information on the @Injectable decorator, see the official documentation for @Injectable.

Moving into the body of our class, we define the base URL that will be used to contact our web service. This URL corresponds to the URL that was established in Part 1 of this tutorial when we started the order management web service. Next, we define the HTTP headers that will accompany each of the HTTP calls that we will execute in the various methods of our service. The rationale behind each header is enumerated below:

  • Access-Control-Allow-Origin: allows for cross-origin support between the web service and our web application. For more information on cross-origin support in web applications, see the Mozilla Developer Network (MDN) article on HTTP access control (CORS).
  • Accepts: denotes the type of content that is accepted by the web application. This type, application/json, corresponds to the Content-Type header included in the responses received from the order management web service (the web service supports sending JavaScript Object Notation, JSON, data and our web application supports receiving JSON data).
  • Content-Type: denotes the type of content that is sent in the request. This type corresponds to the Accepts header supported by the order management web service (the web service supports receiving JSON data and our web application supports sending JSON data).

The constructor, although innocuous, is a very important facet of our service. The parameter of the constructor informs the Angular DI framework that our service expects an Http object to be passed in when a new OrderService is created by the DI framework. In essence, we are informing the Angular DI framework that we are requesting an Http object to be supplied by constructor injection. This supplied Http object is then stored as a private field and will later be used to make HTTP requests to the web service.

The getOrdersmethod represents the first method in our OrderServiceclass that executes an HTTP request to the order management web service:

public getOrders(): PromiseOrder[] {
    return this.http.get(this.ordersUrl, {headers: this.headers})
        .toPromise()
        .then(response = response.json().map(jsonOrder = Order.fromJson(jsonOrder)))
        .catch(this.handleError);
}

private handleError(error: any): Promiseany {
    console.error('Could not complete requested service operation', error);
    return Promise.reject(error.message || error);
}

In order to execute the HTTP GET call to our backend web service, we call the get method on our Http object and supply the URL and headers that should be used to contact the web service. We then convert the response from this call to a promise (using the toPromise method using the ReactiveJS library) and convert the response (when the promise completes) to Order resource objects. Looking at the specification for our order management web service, we know that the response from obtaining a list of all orders is a JSON list containing Order resources. For example:

[
    {
        "description": "Some order",
        "costInCents": 465,
        "complete": false,
        "_links": {
            "self": {
                "href": "http://localhost:8080/order/2"
            },
            "update": {
                "href": "http://localhost:8080/order/2"
            },
            "delete": {
                "href": "http://localhost:8080/order/2"
            }
        },
        "id": 2
    },
    {
        "description": "Another order",
        "costInCents": 7684,
        "complete": true,
        "_links": {
            "self": {
                "href": "http://localhost:8080/order/3"
            },
            "update": {
                "href": "http://localhost:8080/order/3"
            },
            "delete": {
                "href": "http://localhost:8080/order/3"
            }
        },
        "id": 3
    }
]

By calling the map method on the JSON response data from our GET call, we map the raw JSON response data for each of the resources into an Order object using the static helper method we defined in Part 2 of this tutorial. Using the example above, our getOrders method would return a promise that resolves to an array containing two Order objects whose data corresponds to the data from the above JSON response resources, respectively. In essence, the map function allows us to process each JSON resource and convert it to an Order object. For more information on the map method, see the MDN map documentation.

Lastly, we register an error handler using the catch method of our promise. This method simply logs the error to the console and rethrows the error to allow for further processing by the object that calls our getOrders method. For more information on the catch method, see the MDN catch documentation.

The remainder of the methods within our OrderService class match the same basic structure as our getOrdersmethod, with some important caveats:

  1. The deleteOrder method does not return a PromiseOrder or PromiseOrder[] like the other methods. Instead, it returns a Promisevoid, which means that the completed promise does not return any data. This promise is simply used to notify a caller that the promise completed. The caller can register a callback, but the callback function takes no arguments, such as:
  2. orderService.deleteOrder(someOrder).then(() = console.log("Order was deleted"))
  3. The updateOrder and deleteOrder methods do not use the orderUrl field when sending their respective HTTP requests. Instead, they utilize the links provided with the order to make the call. This ensures that if the links supplied by the backend change, we will not have to make any manual adjustments to our service (since the link URLs supplied by the backend are used to make the update and delete calls).
  4. The updateOrder and createOrder methods supply a JSON string that represents an Order resource object as a request body when making their respective HTTP calls. We create this JSON string by serializing the supplied Order resource (using the serialize method we created in Part 2) and then using the built-in JSON.stringify method to convert this JSON object to a JSON string. For more information on the stringify method, see the MDN stringify documentation.
  5. The getOrder, createOrder, and updateOrder methods all convert the raw JSON order resources in the response body of the HTTP call to an Order resource object; therefore, we create the convertResponseToOrderResource method as a helper method that can be passed as a callback to all three service methods. This helper method simply consumes an HTTP response that contained a body holding JSON data representing an order and produces an Order resource object that corresponds to the JSON data.

The simplicity of the OrderService class is due in large part to the Http object that the Angular DI framework supplies to our service. In order for us to complete the various HTTP calls, we simply delegate to this Http object, supplying a URL, HTTP headers, and any request body data, and handle the response. In order for this scheme to work, though, we must wire our application together using the Angular DI framework.

Wiring the Application Together

In order for our OrderService to be injected into other components, we must declare to the Angular DI framework that it can be used as a provider. To do this, we create a NgModule configuration and enumerate our providers within this configuration:

@NgModule({
    declarations: [
        AppComponent,
        OrdersComponent,
        EditOrderComponent,
        CreateOrderComponent,
        OrderDescriptionPipe
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        AppRoutingModule
    ],
    providers: [
        OrderService
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

For the moment, a majority of the lines of this decorator can be ignored (we will revisit the other sections in a later part in this tutorial series). Within the providers section, we simply supply the name of our injectable service that we wish to register with the Angular DI framework. Once we have done so, other components in our project are now able to have our OrderService injected and use the methods provided by that service. Since we use the Http object in our OrderService, we must also declare this import in the imports section of the NgModule (for more information, see the Tour of Heroes tutorial). Although more advanced DI in Angular can become a confusing topic, a wealth of information about the Angular DI framework can be found in the official Angular DI documentation.

In the following part of this series, we will continue with the implementation of our web service, focusing on the last of our layers: the UI layer. Once we have completed our UI layer, we will be ready to put our web application to the test and use it to view orders, create orders, delete orders, and update orders stored in the order management web service.

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*

cover letter