Marcos Vainer Loeff

Using PI Web API with AngularJS

Blog Post created by Marcos Vainer Loeff Employee on Nov 27, 2015

Introduction

 

One blog post that developers on this community refer to when developing HTML5 application with jQuery that will access PI data through PI Web API is Using PI Web API on HTML with jQuery. That blog post shows how to make HTTP requests against PI Web API using jQuery $.ajax function overcoming authentication and CORS issues by selecting the correct inputs for $.ajax() method.

 

Although jQuery is a great library for manipulating the DOM and making Ajax request , AngularJS maintained by Google has become an interesting option for performing similar tasks.

 

What is AngularJS?

 

According to the AngularJS official documentation:

 

AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML's syntax to express your application's components clearly and succinctly. Angular's data binding and dependency injection eliminate much of the code you would otherwise have to write. And it all happens within the browser, making it an ideal partner with any server technology.

 

Angular is not a single piece in the overall puzzle of building the client-side of a web application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a well-defined structure. This makes Angular opinionated about how a CRUD (Create, Read, Update, Delete) application should be built. But while it is opinionated, it also tries to make sure that its opinion is just a starting point you can easily change.

Given the description above, we conclude that AngularJS is a great option to integrate your web application with the PI System using also PI Web API.

 

Sample Application

 

On this blog post, we will convert the sample application developed using jQuery to a similar one using AngularJS. You can download the source code package from this GitHub repository. Please follow the following steps to create the application:

 

  • Open Visual Studio and create a new ASP.NET Web Application.

Figure 1 - Creating an ASP.NET Web Application on Visual Studio.

 

 

  • Select the empty template as no server side code will be used.

 

Figure 2 - Select the Empty template

 

  • Open Package Manager Console and type "Install-package AngularJS.Core" in order to download and add the core AngularJS file to your project. Make sure the NuGet Packager Manager is installed as an extension of your Visual Studio.
  • Create a new file on the root of your project named index.html with the following content:

 

<!DOCTYPE html>


<!--This is where you define the name of your AngularJS app-->
<html ng-app="PiWebApiSampleApp">
<head>
    <title>PI Web API Sample App with AngularJS</title>
    <!--The file below was copied from the blog post sample app using jQuery to style the DOM elements-->
    <link href="default.css" rel="stylesheet" />      
</head>


<!--The mainContrl is our main AngularJS controller-->
<!--Variables declared within $scope will manipulate the DOM within the body node-->
<body ng-controller="mainCtrl">
    <!--The div below will appear only if $scope.requestMode == true-->
    <div ng-show="requestMode == true">
        <h1>Request PI Data</h1>
        <p>
            Please fill in your details below and click  on &quot;Get PI
            Data&quot;. Fields marked with an asterisk (*) are required.
        </p>
        <div>
            <div style="width: 30em;">
                <label for="piServerName">PI Server Name *</label>
                <!--The value of the input below is bound to the value of the $scope.piServerName.-->
                <input type="text" name="piServerName" id="piServerName" value="" ng-model="piServerName" />
                <label for="piPointName">PI Point name *</label>
                <input type="text" name="piPointName" id="piPointName" value="" ng-model="piPointName" />
                <label for="startTime">Start time *</label>
                <input type="text" name="startTime" id="startTime" value="" ng-model="startTime" />
                <label for="endTime">End time *</label>
                <input type="text" name="endTime" id="endTime" value="" ng-model="endTime" />
                <label for="interval">Interval *</label>
                <input type="text" name="interval" id="interval" value="" ng-model="interval" />


                <label for="getsnap">Get Snapshot?</label>  
                <select name="getsnap" id="getsnap" size="1" ng-model="getSnap" ng-options="option.name for option in yesOrNoOptions"> </select>
                
                <label for="getrec">Get Recorded Data?</label>
                <select name="getrec" id="getrec" size="1" ng-model="getRec" ng-options="option.name for option in yesOrNoOptions"> </select>


                <label for="getint">Get Interpolated Data?</label>
                <select name="getint" id="getint" size="1" ng-model="getInt" ng-options="option.name for option in yesOrNoOptions"> </select>


                <div style="clear: both;">
                    <!--This function will make Http request and store the results-->
                    <input type="submit" id="submitButton" value="Get PI Data!" ng-click="getData()" />
                    <!--This function will change the values declared on $scope which are bound to some DOM elements-->
                    <input type="button" id="DefaultButton" ng-click="defaultValues()" value="Default Values" style="margin-right: 20px;" />
                </div>
            </div>
        </div>
    </div>
    <!--After running $scope.getData(), $scope.requestMode will become equal to false. As a result the second div will be shown-->
    <div ng-show="requestMode != true">
        <h1>Displaying SINUSOID data</h1>


        <h2>Connection information</h2>
        <br />
        <table border="0" style="width: 20em; border: 1px solid #666;">
            <tr>
                <th>Property</th>
                <th>Value</th>
            </tr>


            <tr>
                <td>PI Server</td>
                <!--Using { {} } also binds the DOM with variables declared under $scope-->
                <td id="PIServerNameValue">{{piServerName}}</td>
            </tr>
            <tr>
                <td>PI Point</td>
                <td id="PIPointNameValue">{{piPointName}}</td>
            </tr>
            <tr>
                <td>Start time</td>
                <td id="StartTimeValue">{{startTime}}</td>
            </tr>
            <tr>
                <td>End time</td>
                <td id="EndTimeValue">{{endTime}}</td>
            </tr>
            <tr>
                <td>Interval</td>
                <td id="IntervalValue">{{interval}}</td>
            </tr>


            <tr>
                <td>Does the PI Server exist?</td>
                <td id="PIServerExistValue">{{piServerExistsValue}}</td>
            </tr>
            <tr>
                <td>Does the PI Point exist?</td>
                <td id="PIPointExistValue">{{piPointExistsValue}}</td>
            </tr>
        </table>


        <div id="errors">
            <!--If errors are detected, they will be displayed here-->
            <p ng-repeat="error in snapshotError.Errors">{{error}}</p>
            <p ng-repeat="error in recordedErrorError.Errors">{{error}}</p>
            <p ng-repeat="error in interpolatedError.Errors">{{error}}</p>
            <p ng-repeat="error in piServerError.Errors">{{error}}</p>
            <p ng-repeat="error in piPointError.Errors">{{error}}</p>
            <!--ng-repeat will iterate through all arrays-->
        </div>
        <!--The div below will appear if a webId is found and if the user has selected to get the snapshot on the previous screen-->
        <div ng-show="getSnap.value == true && webId != null">
            <h2 class="snapshot">Snapshot Value of Sinusoid</h2>
            <br />
            <table class="snapshot" border="0" style="width: 20em; border: 1px solid #666;">
                <tr>
                    <th>Value</th>
                    <th>Timestamp</th>
                </tr>
                <tr>
                    <td>{{snapshotData.Value}}</td>
                    <td>{{snapshotData.Timestamp}}</td>
                </tr>
            </table>
        </div>
        <br />
        <br />


        <div ng-show="getRec.value == true && webId != null">
            <h2 class="recorded">Recorded Values of Sinusoid</h2>
            <br />
            <table class="recorded" border="0" style="width: 20em; border: 1px solid #666;">
                <tr>
                    <th>Value</th>
                    <th>Timestamp</th>
                </tr>
                <!--Iterating through all the values received-->


                <tr ng-repeat="item in recordedData.Items">
                    <td>{{item.Value}}</td>
                    <td>{{item.Timestamp}}</td>
                </tr>
            </table>
        </div>
        <br />
        <br />
        <div ng-show="getInt.value == true && webId != null">
            <h2 class="interpolated">Interpolated Values of Sinusoid</h2>
            <br />
            <table class="interpolated" border="0" style="width: 20em; border: 1px solid #666;">
                <tr>
                    <th>Value</th>
                    <th>Timestamp</th>
                </tr>
                <tr ng-repeat="item in interpolatedData.Items">
                    <td>{{item.Value}}</td>
                    <td>{{item.Timestamp}}</td>
                </tr>
            </table>
            <br /><br />
        </div>
    </div>
    <script src="Scripts/angular.min.js"></script>
    <script src="Scripts/app.js"></script>
    <script src="Scripts/piwebapi.js"></script>
</body>
</html>

 

 

  • Under the Scripts folder, create a javascript file called app.js with the following content:

 

//Make sure the name below is the same declared on  <html ng-app="PiWebApiSampleApp">


var piWebApiApp = angular.module("PiWebApiSampleApp", []);


piWebApiApp.controller("mainCtrl", function ($scope, piWebApiHttpService) {


    //declare and inicialize variables
    $scope.requestMode = true;
    $scope.getSnap = true;
    $scope.getRec = true;
    $scope.getInt = true;


    //options for the combobox on the initial page
    $scope.yesOrNoOptions = [{ "value": true, "name": "Yes" }, { "value": false, "name": "No" }];


    //update values when the default button is pressed
    $scope.defaultValues = function () {
        $scope.piServerName = "MARC-PI2014";
        $scope.piPointName = "SINUSOID";
        $scope.startTime = "*-1d";
        $scope.endTime = "*";
        $scope.interval = "1h";
        $scope.getSnap = $scope.yesOrNoOptions[0];
        $scope.getRec = $scope.yesOrNoOptions[0];
        $scope.getInt = $scope.yesOrNoOptions[0];
    }


    //get data by making http calls
    $scope.getData = function () {
        //switch div to display the results
        $scope.requestMode = false;
        //all HTTP requests are done through the  piWebApiHttpService factory object
        piWebApiHttpService.validPIServerName($scope.piServerName).then(function (response) {
            //this function will be executed in case of success
            $scope.piServerData = response.data;
            $scope.piServerExistsValue = true;
        }, function (error) {
            //this function will be executed in case of error
            $scope.piServerError = error.data;
            $scope.piServerExistsValue = false;
        });


        piWebApiHttpService.validPIPointName($scope.piServerName, $scope.piPointName).then(function (response) {
            $scope.piPointData = response.data;
            $scope.piPointExistsValue = true;
            //in case of success, we will get the webId of the PI Point which will be used by other requests
            $scope.webId = response.data.WebId;
            piWebApiHttpService.getSnapshotValue($scope.webId).then(function (response) {
                //Response of the snapshot is stored on the snapshotData
                $scope.snapshotData = response.data;
            }, function (error) {
                $scope.snapshotError = error.data;


            });
            //The following requests use the webId already stored
            piWebApiHttpService.getRecordedValues($scope.webId, $scope.startTime, $scope.endTime).then(function (response) {
                $scope.recordedData = response.data;
            }, function (error) {
                $scope.recordedError = error.data;
            });


            piWebApiHttpService.getInterpolatedValues($scope.webId, $scope.startTime, $scope.endTime, $scope.interval).then(function (response) {
                $scope.interpolatedData = response.data;
            }, function (error) {
                $scope.interpolatedError = error.data;
            });
        }, function (error) {
            $scope.piPointError = error.data;
            $scope.piPointExistsValue = false;
        });
    }
});

 

 

  • Still under the Scripts folder, create another javascript file named piwebapi.js with the following content:

 

'use strict'
piWebApiApp.factory('piWebApiHttpService', ['$http', '$q', function ($http, $q) {


    //This factory method works like a service in which all HTTP requests are made.
    var serviceBase = 'https://marc-web-sql.marc.net/piwebapi/';


    //Set withCredentials = true; if you need to type your credentais.
    $http.defaults.withCredentials = true;
    var piWebApiHttpServiceFactory = {};
    piWebApiHttpServiceFactory.validPIServerName = function (piServerName) {
        return $http.get(serviceBase + "dataservers?name=" + piServerName).then(function (response) {
            return response;
        });
    };


    piWebApiHttpServiceFactory.validPIPointName = function (piServerName, piPointName) {
        return $http.get(serviceBase + "points?path=\\\\" + piServerName + "\\" + piPointName).then(function (response) {
            return response;
        });
    };


    piWebApiHttpServiceFactory.getSnapshotValue = function (webId) {
        return $http.get(serviceBase + 'streams/' + webId + '/value').then(function (response) {
            return response;
        });
    };


    piWebApiHttpServiceFactory.getRecordedValues = function (webId, startTime, endTime) {
        return $http.get(serviceBase + 'streams/' + webId + '/recorded?starttime=' + startTime + '&endtime=' + endTime).then(function (response) {
            return response;
        });
    };


    piWebApiHttpServiceFactory.getInterpolatedValues = function (webId, startTime, endTime, interval) {
        return $http.get(serviceBase + 'streams/' + webId + '/interpolated?starttime=' + startTime + '&endtime=' + endTime + "&interval=" + interval).then(function (response) {
            return response;
        });
    };


    return piWebApiHttpServiceFactory;
}]);

 

  • Finally, If you want to improve the design and style, download the default.css file from the sample application developed with jQuery and paste it on the project root. You can download the sample application of this blog post directly as well.

 

The final look of the application on Google Chrome will look like Figure 3.

 

Figure 3 - Testing the sample web app on Google Chrome

 

Conclusions

 

I have found an interesting article with 10 reasons why web developers should learn AngularJS. I specially find it really useful in case you are developing Single Page Applications (this is actually the reason why I have learned it). Another great benefit is that it makes easier to manipulate the DOM when compared to jQuery. Just compare the source code from both sample applications and you will realize that the AngularJS version requires less lines of code.

 

I hope you have found this blog post informative! The following blog post will about Unit Testing JavaScript code.

Outcomes