Working with XML Data in NativeScript Angular 2

I recently wrote a tutorial for using RESTful API data in a NativeScript Angular 2 application. This tutorial included everything from Angular 2 providers, HTTP requests with RxJS, and data persistence with a NoSQL database called Couchbase. In most scenarios, remote web services will return JSON data to be consumed in your client facing application, but there are often scenarios where you receive XML data instead. JSON data is easily converted into JavaScript objects, but can the same be said about XML?

We're going to expand upon the previous article and cover the scenario where the API returns XML data. Lucky for us the Markit on Demand service gives the option to choose between XML or JSON, making this example smooth between the two tutorials.

You can download a zip of the source code for the project here.

Starting a NativeScript Project

For convenience, we're going to work with a fresh NativeScript project. While the result will be similar, we won't be covering data persistence or Angular 2 providers as seen in the previous.

From the Terminal (Linux and Mac) or Command Prompt (Windows), execute the following commands:

tns create ParsingProject --ng
cd ParsingProject
tns platform add android
tns platform add ios

The --ng flag indicates that our project will be an Angular 2 project. It is important to note that even though we're adding the iOS build platform, we can't actually build for iOS unless we're using a Mac.

The goal of this project is to create something like the following animated image.

NativeScript Stock Quote App

We are building a single page application that queries a remote web service for stock quote data. The data queried will be presented in a NativeScript list on the screen. In this particular application, the response data from the remote web service will be in XML format rather than JSON.

Include XML Parsing Support through NativeScript Plugins

While it is not impossible to parse XML data with vanilla JavaScript, it isn't necessarily easy. I wouldn't want to write my own JavaScript parser. Lucky for us there is a NativeScript plugin available called nativescript-xmlobjects that will do all the heavy lifting for us.

From the command line, execute the following to install the plugin:

tns plugin add nativescript-xmlobjects

So how do you use this plugin in your application? It might be best to first come up with a sample scenario. Take the following API data in XML format:

<StockQuote>
    <Status>SUCCESS</Status>
    <Name>Microsoft Corp</Name>
    <Symbol>MSFT</Symbol>
    <LastPrice>60.17</LastPrice>
    <Change>-0.3</Change>
    <ChangePercent>-0.4961137754</ChangePercent>
    <Timestamp>Wed Nov 9 00:00:00 UTC-05:00 2016</Timestamp>
    <MSDate>42683</MSDate>
    <MarketCap>467842869670</MarketCap>
    <Volume>48081591</Volume>
    <ChangeYTD>55.48</ChangeYTD>
    <ChangePercentYTD>8.4534967556</ChangePercentYTD>
    <High>60.59</High>
    <Low>59.2</Low>
    <Open>60</Open>
</StockQuote>

The root node of the above XML is StockQuote and it doesn't really have too much value – what matters is every node name and node value that follows.

Per the plugin documentation, the XmlObjects.parse method will consume the XML data. The root node is available in the result of that parsing like the following:

let doc = XmlObjects.parse(xml);
let rootElement = doc.root;
let allNodes = rootElement.nodes();

Once we have all nodes that reside under the root, we can loop through them and get the names as well as values. This will be further explained throughout the rest of the guide.

Developing the Single Page Application

Let's apply our knowledge towards the application. We're going to use the stock quote data in XML format like previously seen. This is actual data that was returned from the Markit on Demand API.

Before we develop our page, let's include the appropriate modules in the project's @NgModule block. Open the project's app/main.ts file and include the following:

import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { NgModule } from "@angular/core";
import { AppComponent } from "./app.component";

@NgModule({
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    imports: [
        NativeScriptModule,
        NativeScriptHttpModule
    ]
})
class AppComponentModule {}

platformNativeScriptDynamic().bootstrapModule(AppComponentModule);

Notice that we've imported the NativeScriptHttpModule and added it to the imports array of the @NgModule block. Now we're able to make HTTP requests in our application.

Now we can start developing our single page. Open the project's app/app.component.ts file and include the following TypeScript code:

import { Component } from "@angular/core";
import { Http } from "@angular/http";
import * as Dialogs from "ui/dialogs";
import * as XmlObjects from "nativescript-xmlobjects";
import "rxjs/Rx";

@Component({
    selector: "my-app",
    templateUrl: "app.component.html",
})
export class AppComponent {

    public stocks: Array<any>;

    public constructor(private http: Http) {
        this.stocks = [];
    }

    public addTicker() {
        let options = {
            title: "Ticker Symbol",
            okButtonText: "Add",
            cancelButtonText: "Cancel",
            inputType: Dialogs.inputType.text
        };
        Dialogs.prompt(options).then((result: Dialogs.PromptResult) => {
            this.getQuote(result.text);
        });
    }

    public getQuote(ticker: string) {
        this.http.get("http://dev.markitondemand.com/MODApis/Api/v2/Quote/xml?symbol=" + ticker)
            .do(result => console.log("XML: ", result.json()))
            .map(result => this.xmlToJson(result.json()))
            .do(result => console.log("JSON: ", JSON.stringify(result)))
            .subscribe(result => {
                this.stocks.push(result);
            }, error => {
                console.log("ERROR: ", error);
            });
    }

    private xmlToJson(xml: string): any {
        let result: any = {};
        let doc = XmlObjects.parse(xml);
        var rootElement = doc.root;
        var allNodes = rootElement.nodes();
        for (var i = 0; i < allNodes.length; i++) {
            var node = allNodes[i];
            if (node instanceof XmlObjects.XElement) {
                var allAttributes = node.attributes();
                result[<any> node.name] = node.value;
            }
        }
        return result;
    }

}

What is going on in the massive chunk of code above? Let's break it down.

We'll start by importing various Angular 2 and NativeScript dependencies:

import { Component } from "@angular/core";
import { Http } from "@angular/http";
import * as Dialogs from "ui/dialogs";
import * as XmlObjects from "nativescript-xmlobjects";
import "rxjs/Rx";

The Http component will allow us to make requests against remote APIs using RxJS and observables. The Dialogs component will allow us to present a prompt for data collection and the XmlObjects will let us use the plugin that we installed for parsing XML data into JavaScript objects.

public constructor(private http: Http) {
    this.stocks = [];
}

In the constructor we initialize our public array that will be bound to the UI. We are also injecting the Http component for use throughout the page.

Jumping ahead we have our parsing method:

private xmlToJson(xml: string): any {
    let result: any = {};
    let doc = XmlObjects.parse(xml);
    var rootElement = doc.root;
    var allNodes = rootElement.nodes();
    for (var i = 0; i < allNodes.length; i++) {
        var node = allNodes[i];
        if (node instanceof XmlObjects.XElement) {
            var allAttributes = node.attributes();
            result[<any> node.name] = node.value;
        }
    }
    return result;
}

The above method is where the magic happens. We take a string of XML data, parse it, and loop through each node. For every node that is an XElement, we gather the node name and the node value and store them in a JavaScript object. This freshly crafted object is something we can use easily and is comparable to the JSON data we would have received.

public getQuote(ticker: string) {
    this.http.get("http://dev.markitondemand.com/MODApis/Api/v2/Quote/xml?symbol=" + ticker)
        .do(result => console.log("XML: ", result.json()))
        .map(result => this.xmlToJson(result.json()))
        .do(result => console.log("JSON: ", JSON.stringify(result)))
        .subscribe(result => {
            this.stocks.push(result);
        }, error => {
            console.log("ERROR: ", error);
        });
}

In our getQuote method, we make the HTTP request. This request is an observable which is a stream of data. As the stream comes in, we print out the raw result using the do operation which will print out XML. Using the map operation, we can transform the XML data into a JSON compatible JavaScript object with our xmlToJson method. After the transformation, we print out the current state using the do operation. Finally, we subscribe to the end of the stream so that we can work with only the result that we want.

public addTicker() {
    let options = {
        title: "Ticker Symbol",
        okButtonText: "Add",
        cancelButtonText: "Cancel",
        inputType: Dialogs.inputType.text
    };
    Dialogs.prompt(options).then((result: Dialogs.PromptResult) => {
        this.getQuote(result.text);
    });
}

The addTicker will show a prompt dialog and the user input will be passed into the getQuote method. The user input should be a valid stock symbol.

With the TypeScript out of the way, we can focus on the HTML UI. Open the project's app/app.component.html file and include the following markup:

<ActionBar title="Stock Ticker">
    <ActionItem text="Add" ios.position="right" (tap)="addTicker()"></ActionItem>
</ActionBar>
<GridLayout>
    <ListView [items]="stocks" class="list-group">
        <Template let-stock="item">
            <GridLayout rows="auto, auto" columns="*, auto" class="list-group-item">
                <Label class="h2" text="{{ stock.Symbol }}" row="0" col="0"></Label>
                <Label text="{{ stock.Name }}" row="1" col="0"></Label>
                <Label text="{{ stock.LastPrice }}" row="0" col="1"></Label>
            </GridLayout>
        </Template>
    </ListView>
</GridLayout>

In the above markup, we have a NativeScript action bar with a button that when pressed, will show the prompt. The ListView will show the data found in the stocks array that was created in the TypeScript code. This data is formatted using NativeScripts CSS theme and a GridLayout to make it more like a table.

Conclusion

We just saw how to work with XML data that was returned from a RESTful API. This is a followup to a previous tutorial I wrote on the subject of remote web services. In the previous tutorial, we were working with JSON data, but not all APIs return JSON data. XML isn't particularly easy to work with in JavaScript without the use of the plugin demonstrated in this guide.

Comments