angular, rest and u2 - the rocket software blog · pdf fileangular, rest and u2 blog post 2 ....
TRANSCRIPT
Brian Leach Consulting Limited
http://www.brianleach.co.uk
Angular, REST and U2
Blog Post 2
Angular, REST and U2 Page 2 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Contents
Introduction ....................................................................................................................... 3
Prerequisites ...................................................................................................................... 3
What is REST? .................................................................................................................... 4
Defining our First RESTful Service ....................................................................................... 5
Create a REST Server ....................................................................................................... 6
Create a Resource Folder ................................................................................................ 6
Create a Data Resource ................................................................................................... 7
Testing the Data Resource............................................................................................... 9
Creating your First Angular Component............................................................................ 10
Creating the Model ....................................................................................................... 10
Creating a Service ......................................................................................................... 11
Creating the Component ............................................................................................... 12
Updating the Routing .................................................................................................... 13
Creating the Component UI........................................................................................... 14
Running the Component ............................................................................................... 15
Adding Searching ............................................................................................................. 16
Adding a Selection Clause ............................................................................................. 16
Fetching a Value from the UI ......................................................................................... 17
Adding Pagination ............................................................................................................ 18
Connecting Components .................................................................................................. 21
Creating the Data Resource ........................................................................................... 21
Creating the TypeScript Model ...................................................................................... 21
Updating the Service ..................................................................................................... 22
Making the Connection ................................................................................................. 23
Creating a Maintenance Form .......................................................................................... 25
Creating the HTML Form ............................................................................................... 25
Adding Validation .......................................................................................................... 27
Types of Validation ....................................................................................................... 28
Putting Updates ............................................................................................................... 29
Calling a Subroutine ......................................................................................................... 32
Mapping the Subroutine Parameters ............................................................................ 34
Next Steps ........................................................................................................................ 37
Angular, REST and U2 Page 3 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Introduction
In the first post of this series, we looked at Angular - the Single Page Application framework
from Google - from the perspective of the web client. We delved into the template syntax
used to specify Angular components and modules, the routing and the data binding. We
introduced TypeScript, the highly typed and modular meta-language that transpiles to
JavaScript. We created a working sample project using Microsoft Visual Studio 2017 and
ASP.NET WebAPI. We even looked briefly at the http module and RxJS Observables and
Promises, the mechanisms required for bringing RESTful data into our project.
After all that scene-setting in the first post, we are ready to start building our own Angular
components and to start connecting these to our U2 systems. In this post, we will be using the
built-in U2 RESTful Web Services to provide and consume information, running queries and
calling stored procedures.
Prerequisites
Before we begin, there are some additional assets you will need to get up and running.
First and foremost, you will need a copy of UniVerse. All of this will work equally with UniData,
but the examples here will be UniVerse driven. If you do not have easy access to a copy on
your PC, you can download the free limited UniVerse Personal Edition from Rocket by visiting
http://www.rocketsoftware.com/u2 and clicking on the ‘Try it now’ button.
You will also need to install the U2 RESTful Web Services Developer from the Rocket U2
DBTools package, available from the same location as the UniVerse Personal Edition above.
We will be using the Book Store demonstration database that forms the backbone to the
Incubator Project, an open source project sponsored by Brian Leach Consulting for the
MultiValue World user group and available at:
http://www.mvdeveloper.com/public/files/u2_books_universe.zip
Angular, REST and U2 Page 4 Blog Post 2
Brian Leach Consulting Limited Version 1.0
What is REST?
REST (REpresentative State Transfer) is a series of protocols built around HTTP, XML and JSON.
It is better described as a set of conventions: an agreement to use these already mature
technologies in a more-or-less standard way. REST leans on the four primary HTTP verbs - GET,
PUT, POST and DELETE - and uses these in a predictable way. REST therefore needs no special
services on the client and HTTP is well handled through the layers that make up any modern
infrastructure: any browser can issue HTTP requests and support for JSON has been part of
the currency of the web, albeit in different guises, for over a decade.
REST describes a uniform approach to accessing resources through URIs. A URI might
represent a data source, but REST makes no assumptions about what that source might be or
how it should behave. The only requirements are that it should respect the HTTP verbs and
that any data provided must be capable of being represented in JSON, XML or HTTP format.
There is no requirement for normalization and so this fits with the modern world of web
development and the rise of JSON-oriented NoSQL and document oriented databases as
today’s platforms of choice, freeing developers from the constrictions imposed by the legacy
SQL model and the need to navigate proprietary protocols.
That is especially good news for MultiValue sites, since both XML and JSON happily
encapsulate nested entities and all the complexity involved in MultiValue data, and so it
means we can expose our native model without the need to dynamically normalize our files,
avoiding the overheads associated with using relational protocols such as ODBC or ADO.NET.
And because REST also makes no differential between URIs, tools like the Rocket U2 RESTful
Web Services can equally surface out data files and subroutines as end points.
REST defines a specific meaning for each HTTP verb:
GET List a collection or Fetch a specific element
PUT Replace a collection or Create/Update/Replace a specific element
POST Create a new entry in a collection
DELETE Delete a collection or delete a specific element
PATCH Update an element (will be supported by Rocket in the future)
In CRUD terms these can be seen as roughly equivalent to:
GET Read
PUT Update/Replace
POST Insert
DELETE Delete
PATCH Update
Because this is driven by convention, not all implementations necessarily obey all these rules.
Angular, REST and U2 Page 5 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Defining our First RESTful Service
For our first service, we are going to expose one of the data files in the u2_books account that
forms part of the Incubator Project database installed above. This represents a bookstore
containing regular and audio publications. We will use a RESTful service to retrieve two sets
of book information directly from a file: a summary view that will form a listing component in
Angular, and a detail view that we will use later down the track to create a maintenance
component.
Start the U2 RESTFul Web Services Developer (henceforth RWSD), and once it has loaded, right
click on ‘Servers’ in the top left panel and select ‘New Server’. If you have installed UniVerse
Personal Edition, enter localuv as the server name and localhost for the connection. If not you
will need to enter the name of your remote server.
Next, right click on the localuv entry and select ‘Connect’: this uses UniObjects (as UOJ) to
connect, so you will need to supply your database user name and password: for Personal
Edition, these are your Windows credentials.
This opens a list of accounts drawn from the UV.ACCOUNT file in the uv home account. If your
u2_books account is not there don’t worry, you can add it: right click the Accounts folder and
select ‘New Account’. You can enter the path to an existing account and it will just add the
UV.ACCOUNT entry. If you prefer you can go to TCL in the uv account and type:
INSERT INTO UV.ACCOUNT(@ID,PATH) VALUES (‘u2_books’,’path_to_u2_books’);
If all went well, you should now be able to open the u2_books folder in RWSD and see a list of
all the files (including Q pointers) in that account.
Angular, REST and U2 Page 6 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Create a REST Server
Before we can expose of any of the files for use, we need to create a REST server. This will be
the gateway through which we can access the various data and subroutine resources we will
define in this account. In the lower left panel, right click the REST Servers icon and select ‘New
REST Server’. Here you get to define the location and connection details for the server:
The RWSD will fill in the URL with your machine name and select a port number above 9190.
If you do not have a connection pooling licence, or are using Personal Edition, make sure to
uncheck the Connection Pooling option.
Create a Resource Folder
We first want to expose the U2_BOOKS file. RWSD is sometimes less than intuitive: to do this,
drag the U2_BOOKS from the U2_Resource panel above and drop it onto the u2_book_server
entry. This will ask if you want to create a resource folder: say Yes to the prompt. The resource
folder binds together services from the same account, and holds the login credentials that the
server will need to access that account.
Angular, REST and U2 Page 7 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Note that you will need to use a service account with a non-expiring password if you deploy
these for real. Use the Test Database Connection button to check you have entered the details
correctly before you continue.
Create a Data Resource
Now the resource folder has been created, you can add the Data Resource that will provide
access to the U2_BOOKS file. This presents you with a list of the fields, including any virtual
fields, that you want to make available. This is contextual - you can create multiple resources
based on the same file, so you do not need to map every field to cater for every possible use
as part of the same resource.
We will start with only a summary view suitable for a listing. The U2_BOOKS file holds details
on the publications sold by our demonstration bookstore, and so we will select the ID, TITLE,
AUTHOR_NAME, ISBN, SALE_PRICE and MEDIA fields to identify these.
Angular, REST and U2 Page 8 Blog Post 2
Brian Leach Consulting Limited Version 1.0
On the next page we get to name the resource elements. Change the Resource Name to
U2_book_summary and the fields as follows: title, ISBN, salePrice, media, id, authorName.
That’s it - you have created your first Data Resource. Let’s see this in action.
Angular, REST and U2 Page 9 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Testing the Data Resource
You will notice that the u2_book_server has a red square over the icon: this shows that this
RESTful server is currently stopped. You cannot change the properties of a server or add
resources to it whilst it is running.
Right click on the icon and select ‘Start REST Server’. The icon changes to show a green overlay.
If not, you will need to check the U2 REST Server Log panel to see why the server could not
start (did you uncheck the Connection Pooling option?).
The easiest way to test the resource is from within the RWSD itself. Double click on the
U2_book_summary entry under Data Resources (consistency would be a virtue) and this
displays the results of the call:
This shows the first 10 U2_BOOKS records in a suitable JSON format under the container name
U2_book_summary.
We will investigate some of the options available for querying later but for now you are ready
to create your first Angular component to display these.
Angular, REST and U2 Page 10 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Creating your First Angular Component
In the first post, you created a new project in Visual Studio 2017 using the SPA Template. This
included several sample components and a WebAPI base that we will be using in the next post.
For now, we are going to add our components into this project. You can clean up the sample
components later at your leisure.
The FetchDataComponent that we investigated in the first post, demonstrates how to call a
RESTful service to fetch and display data. We will be extending what we learned from this just
a little for our book list summary.
Creating the Model
To consume RESTful data, we first must define a matching class or interface that Angular can
use to map the JSON content. In the sample project the interface definition for the
WeatherForecast was held inside the FetchDataComponent component, but that scheme
effectively binds it to that one resource. A better option is to place it in its own model directory
so it can be more easily reused.
The Angular side of the sample web application is held in the ClientApp\app folder. Right click
in there and add a new folder called models. Right click on that in turn and select ‘New Item’.
You will need to add a new TypeScript file to define this model. You will find this under Web
in the ASP.NET Core section of the Add New Item dialog:
Our summary model will be simple, nothing more than a field map for the data returned from
our resource, so we can use an interface. Add the following TypeScript definition to your
bookSummary.model.ts file:
Angular, REST and U2 Page 11 Blog Post 2
Brian Leach Consulting Limited Version 1.0
export interface BookSummary { id: string; title: string; authorName: string; ISBN: string; salePrice: number; media: string; }
Creating a Service
In the first post, we learned about Angular Components and Modules. These form the major
blocks in an Angular application, but there are other tools you will need to create a working
real world solution. One such tool is a Service.
A service brings together a set of functionality and properties that are not bound to a single
component. Services are typically used for handling data, including RESTful calls, where that
data will be shared between a set of components: services thus provide the glue between
components. We will be needing that to bridge between our first two components: our listing
will identify candidate books and lead directly to our maintenance component to edit them.
Add a new services folder under the ClientApp\app folder, then add into that a new TypeScript
file called booklist.service.ts. Services have no visible interface so you won’t need an HTML
page as well.
Here is the new service definition in full:
import { Injectable, Inject } from '@angular/core'; import { HttpModule, Http } from '@angular/http'; import { BookSummary } from '../models/bookSummary.model'; import { Observable } from 'rxjs/Observable'; @Injectable() export class BookListService { books: BookSummary[]; editing: boolean; constructor(private _http: Http, @Inject('REST_URL') private restUrl:string) { } getBooksFromREST(searchTerm: string) { this._http.get(this.restUrl + "/U2_book_summary").subscribe( result => {this.books = result.json().U2_book_summary as BookSummary[]; }); } }
Angular, REST and U2 Page 12 Blog Post 2
Brian Leach Consulting Limited Version 1.0
You may notice the close resemblance this bears to the FetchDataComponent we examined
in part 1.
I have added an Injectable for the REST_URL so this can be stored once against the application
module in exactly the same way the FetchDataComponent injects the ORIGIN_URL, rather
than having to repeat it for every RESTful call. In order that Angular will inject this on demand,
this needs to be added to the application module in the app.module.client.ts file, substituting
the resource path for your U2 REST Server:
providers: [ { provide: 'ORIGIN_URL', useValue: location.origin }, { provide: 'REST_URL', useValue: 'http://WORKVM:9193/u2_books'} ]
Creating the Component
We have now completed the data plumbing ready for adding our book list component. This
will present a table with the rows fetched from the booklist service. We can use the Bootstrap
styling to make it look a little prettier.
We will follow the same conventions as those used by the sample components, so add a folder
under components named bookList and a new TypeScript file within that called
booklist.component.ts.
The work of fetching the data has been delegated to the booklist service, so the component
itself is suitably simple: we just need to add BookListService to the list of imports and to the
providers property of the component metadata to make sure Angular can find it.
Here is the template definition for the listing component:
import { Component, OnInit, Injectable, Input } from '@angular/core'; import { BookListService } from '../../services/booklist.service'; import { BookSummary } from '../../models/bookSummary.model'; @Component({ providers: [BookListService], templateUrl:'./booklist.component.html' }) export class BookListComponent { constructor(public bookListService: BookListService) { bookListService.getBooksFromREST(''); } }
Angular, REST and U2 Page 13 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Updating the Routing
The project sets up the routing to create each of the sample components as we saw in the
previous post, so we will need to add our new component into this routing scheme. We need
to tell the module how to create the component and we need to add it to the routing table.
For this you need to look into app.module.shared.ts.
First, import the component you just created:
import { BookListComponent } from './components/bookList/booklist.component';
Next, update the declarations on the module so Angular will create the component on
demand:
declarations: [ AppComponent, NavMenuComponent, CounterComponent, FetchDataComponent, HomeComponent, BookListComponent
Now you can add the following line to the routing table:
{ path: 'book-list', component: BookListComponent },
That’s all we need from the Angular perspective to create our component, but we will still
need to add an entry into the menus to let us select it. Copy and paste the last entry on the
navmenu.component.html and change it to show our list:
<li [routerLinkActive]="['link-active']"> <a [routerLink]="['/book-list']"> <span class='glyphicon glyphicon-th-list'></span> Book List </a> </li>
This will create the routing entry book-list when it is selected.
Angular, REST and U2 Page 14 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Creating the Component UI
Nearly there - the final piece is to just add the UI to display the content. Add an html file to
the booklist component folder and name it booklist.component.html. This will hold the UI
snippet for our listing component using Angular interpolation to bind the content. We will also
create the Edit button for each row so we can use that later.
Note the use of the *ngFor directive we met in the first post to run through each row.
<div class="container"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default panel-table"> <div class="panel-heading"> <div class="row"> <div class="col col-xs-6"> <h3 class="panel-title">Book Listing</h3> </div> </div> </div> <div class="panel-body"> <table class="table table-striped table-bordered table-list"> <thead> <tr> <th></th> <th>Id</th> <th>Title</th> <th>Author Name</th> <th>ISBN</th> </tr> </thead> <tbody> <tr *ngFor="let book of bookListService.books"> <td> <button (click)="onEdit(book)">Edit</button> </td> <td>{{book.id}}</td> <td>{{book.title}}</td> <td>{{book.authorName}}</td> <td>{{book.ISBN}}</td> </tr> </tbody> </table> </div> <div class="panel-footer"> </div> </div> </div> </div> </div>
Angular, REST and U2 Page 15 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Running the Component
Now before we run the component, it’s worth taking stock of where we are.
We needed to add a lot of things for Angular to work its magic. This is normal and is the flip-
side of using such a powerful toolkit. Just in case all those steps have left you confused, here
is what you have created:
The Navigation Menu now has a button to show the Book List.
The button will set the address to /book_list
The module will route this address and create the BookListComponent.
The component will receive an instance of the BookList service as a provider.
The service will make a call back to the U2 RESTful server using http.
The U2 RESTful server will query the U2_BOOKS file.
The results will be converted into an array of BookSummary objects.
Angular will bind the array to the HTML table.
If you followed all steps correctly (and remember to run your RESTful service), you can now
run your project and select the Book List from the menu. This will display the list of titles as
below.
It is worth keeping a pad and paper to jot down the pieces you need before adding any Angular
component, otherwise you may forget a step and Angular is not forgiving.
Angular, REST and U2 Page 16 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Adding Searching
Returning a whole data set consisting of every record in a file is not a suitable strategy for
most real-world situations, so we need to add some filtering and pagination to our listing.
Additionally, the list is not sorted, so the order of the records is not helpful.
Adding a Selection Clause
The U2 RESTful Web Services support several options for refining the list of records returned.
These are all supplied as parameters on the request. First, let us change the sort order so it
follows the IDs:
http://myServer:9131/u2_book_service/U2_book_summary?sort=BY ID
The sort order is specified using the sort QueryString parameter in the same format as
RetrieVe or UniQuery. You can use multiple BY and BY.DSND clauses.
Selections are also handled similarly to the native enquiry languages. You can create a search
against any field or combination of fields as you would for a regular query by adding a select
parameter:
select=DEPT=JUNIOR AND TITLE LIKE …Potter…
Note that this will need to be URL encoded if it contains special characters such as quotation
marks.
We will use this to add a simple selection to our listing. This will do a partial search on the title
and author name to filter the books returned. Did you notice that in the BookList Service we
already added a search term to the getBooksFromREST() method? We can now implement
that in our request:
getBooksFromREST(searchTerm: string) { var uri = this.restUrl + "/U2_book_summary?sort=BY ID"; if (searchTerm) { uri = uri + '&select=TITLE LIKE "...' + searchTerm + '..." OR AUTHOR_NAME LIKE "...' + searchTerm + '..."'; } this._http.get(uri).subscribe( result => {this.books = result.json().U2_book_summary as BookSummary[]; }); }
Angular, REST and U2 Page 17 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Fetching a Value from the UI
We can apply a search term to our RESTful request, but where will it come from? It would be
nice to add a search box to our list component, and have the content of that box automatically
passed into the book service to fetch the matching titles.
Fortunately, we have already done all the hard work in setting up the component and service
definitions, so adding a feature like this is relatively trivial.
First, we need a means of capturing a value typed into the UI - here being our search box - so
we can use it from the backing component class. For this, the easiest way is to bind a property
of our component to the UI using a new type of binding we have not met before. So far we
have only looked at interpolation and output binding, which we have used to display the
properties of our component and to hook up events respectively.
Model binding uses a special syntax to bind a UI element to the backing component in a two-
way fashion:
[(ngModel)]= 'propertyName'
ngModel is a reserved identifier that Angular uses to reference the current model: in this case,
it is just the content of the backing component. If you have used the previous AngularJS, you
will be familiar with ngModel: all its features have been added to Angular 2+.
After all the hassle you went through in building these components, you may be relieved to
learn that to add our search feature, you just need a simple two-step operation. First, you
need to add a public searchText property to your BookListComponent class in
bookList.component.ts. You can also add an onSearch() method that will call the
getBooksFromREST() method on your booklist service passing the search text:
export class BookListComponent { public searchText: string; constructor(public bookListService: BookListService) { bookListService.getBooksFromREST(''); } onSearch() { this.bookListService.getBooksFromREST(this.searchText); } }
That is all the coding requirements fulfilled.
The second step, is to add the search text box to the UI, bind it to the searchText property of
your BookListComponent class, and add a blur event to the box that uses output binding to
fire the onSearch() method you just added.
Angular, REST and U2 Page 18 Blog Post 2
Brian Leach Consulting Limited Version 1.0
In the booklist.component.html file, add an input control into to the panel-heading row next
to the Book Listing title as below:
<div class="col col-xs-6"> <h3 class="panel-title">Book Listing</h3> </div> <div class="col col-xs-4"> <input (blur)="onSearch()" placeholder="Search Text" name="searchBox" [(ngModel)]="searchText"> </div>
Now you are good to go. Type a value into your new search box and see it take effect when
you tab or click away:
Now you have a searchable list that queries the server for matching titles and authors.
Adding Pagination
Using a search term to narrow down our listing has reduced the size of the data returned, but
it would still be useful to add some pagination to our table. We can do this in two ways:
Fetch all the data and then add pagination at the client.
Tell the service which page of data we need.
In most cases, the latter is probably more efficient. We can add pagination to our queries by
adding two more parameters to the QueryString: pageno and pagesize.
pageno=3&pagesize=20
Angular, REST and U2 Page 19 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Unfortunately, whilst this seems a good idea, the server does not tell us how many pages there
would be in total, so from a user feedback perspective it is not the most helpful. People expect
to be told how many pages of results to step through: so we will use local pagination for this
example. You can adapt the methods for using the pageno option at your leisure, including
lazy and background loading.
We can add local pagination to our class by fetching capturing the entire set of results, and
then binding the UI to just a subset of the data. Let’s add some simple logic to our BookList
service:
export class BookListService { books: BookSummary[]; pageBooks: BookSummary[]; currentPage: number; allPages: number; constructor(private _http: Http, @Inject('REST_URL') private restUrl:string) { } getBooksFromREST(searchTerm: string) { var uri = this.restUrl + "/U2_book_summary?sort=BY ID"; if (searchTerm) { uri = uri + '&select=TITLE LIKE "...' + searchTerm + '..." OR AUTHOR_NAME LIKE "...' + searchTerm + '..."'; } this._http.get(uri).subscribe( result => { this.books = result.json().U2_book_summary as BookSummary[]; this.getPage(1); }); } backPage() { if (this.currentPage > 1) { this.getPage(this.currentPage - 1); } } nextPage() { if (this.currentPage < this.allPages) { this.getPage(this.currentPage + 1); } } getPage(apage: number) { const pageSize = 10; this.pageBooks = []; var row = (apage - 1) * pageSize; for (var i = 0; (i < pageSize) && (row < this.books.length); i++) { this.pageBooks.push(this.books[row++]); } this.currentPage = apage; this.allPages = Math.floor(this.books.length / pageSize); }
Angular, REST and U2 Page 20 Blog Post 2
Brian Leach Consulting Limited Version 1.0
This now fetches the set, but places only the first 10 BookSummaries into a shortlist called
pageBooks. The nextPage() and backPage() methods will navigate and refresh that list.
Now we can hook all this up to the UI. Change the *ngFor directive used for creating the
rows so that it now looks at the pageBooks short-list instead of the full list of books:
*ngFor="let book of bookListService.pageBooks"
We can provide pagination control by adding the following to the table footer:
<div class="panel-footer"> <div class="row"> <div class="col col-xs-4"> Page {{bookListService.currentPage}} of {{bookListService.allPages}} </div> <div class="col col-xs-8"> <ul class="pagination pull-right"> <li><a (click)="onBack()">«</a></li> <li><a (click)="onNext()">»</a></li> </ul> </div> </div> </div>
Now our initial view is more manageable:
Angular, REST and U2 Page 21 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Connecting Components
You now have a searchable listing onto the books available in the U2 Incubator Bookstore.
This can be adapted into many kinds of display content: cards, sequences, details, these are
design decisions and outside the scope of this series.
Where you have componentization, there is also a need to route information between
components. Also, none of the components you have met so far perform any editing of their
content. We will see both in action as we create a maintenance component.
Creating the Data Resource
You created a summary view of the books for the listing. We now need a more detailed view
for the maintenance aspect.
Go into the U2 Restful Web Services Designer and by following the steps you completed
earlier, add a second Data Resource, again on the U2_BOOKS file. If the service is running, you
will need to stop it before you can amend the details or add a new resource.
This time select the following fields and name the resource U2_book_detail:
ID
TITLE
AUTHOR_ID
AUTHOR_NAME
ISBN
DEPT
GENRE
MEDIA
PUBLISHER_ID
PUBLISHER_NAME
SALE_PRICE
STOCK_LEVEL
ON_ORDER
TAX_CODE
TAX_RATE
SUPPLIER_ID
SUPPLIER_NAME
PURCH_PRICE
Accept the default naming for each of the fields. Remember to start the u2_book_server again
once you have added the new resource.
Creating the TypeScript Model
You will then need to add the TypeScript model to reflect this and to parse the JSON returned
from the RESTful service. We will be adding functionality to this one in the next post, so we
will make this a class rather than an interface.
Add a new TypeScript file named bookDetail.model.ts under your models folder, and fill in the
details to match the names of the fields selected above:
Angular, REST and U2 Page 22 Blog Post 2
Brian Leach Consulting Limited Version 1.0
export class BookDetail { public id: number; public title: string; public author_id: string; public author_name: string; public isbn: string; public dept: string; public genre: string; public media: string; public publisher_id: string; public publisher_name: string; public sale_price: number; public stock_level: number; public on_order: number; public tax_code: string; public tax_rate: number; public supplier_id: string; public supplier_name: string; public purch_price: number; }
Updating the Service
The book detail, just like the book listing, will be retrieved using the booklist service. This gives
us a single point for handling the books, that will be shared between these components. First
you will need to import your BookDetail model:
import { BookDetail } from '../models/bookDetail.model';
and add a selectedBook property to the class along with an editing property:
selectedBook: BookDetail = new BookDetail(); editing: boolean;
We are going to explicitly use a Promise when handling the response. For more details on this
see the previous article. This means you need to add three things to the imports: import { HttpModule, Http, Response } from '@angular/http'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise';
You can now add the method to retrieve a single book detail by record id to your class. This
will call the new service, but with a slightly more complex syntax than before: getBookFromREST(id: any) { var book: BookDetail = null; this.selectedBook = new BookDetail(); // ensure no nulls for template var p = this._http.get(this.restUrl + "/U2_book_detail/" + id) .map((response: Response) => response.json()) .toPromise() .catch(this.handleError) p.then(result => { this.selectedBook = result }); }
Angular, REST and U2 Page 23 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Why the additional step of mapping this into a Promise? First, it illustrates the Promise
architecture: instead of subscribing to the events, we map the result and tell it what to do
once it completes using the then() method. It also allows us to add some simple error handling: private handleError(error: any): Promise<any> { console.error('An error occurred', error); // for demo purposes only return Promise.reject(error.message || error); }
Notice that this returns a single item, not a collection. To retrieve the set of books we needed
to parse response.json().u2_book_summary, which is the name of the container. Here we only
need response.json().
Making the Connection
In the previous example, you used the ngModel to capture the search text entered in the
display list header. We will be using the same technique for the maintenance form, and
extending this to add some simple validation.
Before we do any of this, however, we want to make sure the call to the service is running and
that we can make this from our book listing.
Create a new folder under components and name it bookEditor.
Start by creating a minimal component with the following bookeditor.component.html:
<div class="container"> <h1>Book Title</h1> <div> Book Id : {{book.id}} <br /> Book Title : {{book.title}} <br /> Book Author : {{book.author_name}} </div> </div>
Add the following component definition into a new bookeditor.component.ts. There should
be no real surprises here.
import { BookDetail } from '../../models/bookDetail.model'; import { BookListService } from '../../services/booklist.service'; @Component({ providers: [BookListService], selector: 'book-editor', templateUrl: './bookeditor.component.html' }) export class BookEditorComponent { @Input() book: BookDetail; constructor(public bookListService: BookListService) { } }
Angular, REST and U2 Page 24 Blog Post 2
Brian Leach Consulting Limited Version 1.0
This should be sufficient to illustrate the routing between our book listing and the new
component. We need to pass a book id in response to clicking the Edit button on the listing.
This will cause the book service to fetch the book detail. We need to then expose this to the
book editor component, but the book listing and the book editor know nothing about one
another. We need to tell Angular to join the two.
Add the following line to the bottom of the booklist.component.html:
<book-editor [book]="bookListService.selectedBook"></book-editor>
This will display the book editor underneath the listing (you can deal with the design issues
later). As it does so, Angular maps the book property of the editor component to the
selectedBook property of the service.
Now, finally you can code up the edit button event on the BookListComponent:
onEdit(abook: BookSummary) { this.bookListService.editing = true; this.bookListService.getBookFromREST(abook.id); }
Time to run your project and make sure that the Edit button now fills out the details of the
title below. If you hit any errors, go back carefully over the steps and make sure you have not
forgotten anything.
Angular, REST and U2 Page 25 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Creating a Maintenance Form
Now you have two components on your page: a listing to select a title and a display of the title
details. We will turn that display into a maintenance form. As a prerequisite we need to import
a new reference to our project: the Angular Forms package. This adds validation, model
binding, submission control and other features.
Import the modules into the app.module.shared.ts:
import { FormsModule } from '@angular/forms';
Add this to the imports section, before the RouterModule:
imports: [ FormsModule, RouterModule.forRoot([
Creating the HTML Form
The look and feel of the form will come from the Bootstrap theming, so we can be pretty lean
about how we want to render this. We need a form containing form-groups of controls.
Angular uses this group to append a number of implicit directives about how the form will
operate. Each control is placed in a form-group div.
Start by creating a simple form. Change the bookeditor.component.html to hold a set of fields
as below:
<div class="container"> <h1>Book Title</h1> <form (ngSubmit)="onSubmit()" #bookForm="ngForm"> <div class="form-group"><label>id:</label>{{book.id}}</div> <div class="form-group"> <label>title:</label> <input class="form-control" [(ngModel)]="book.title" name="title" placeholder="title" required #title="ngModel"> </div> <div class="form-group"> <label>author:</label> <span>{{book.author_name}}</span> </div> <div class="form-group"> <label>dept:</label> <select class="form-control" [(ngModel)]="book.dept" name="dept" placeholder="dept" required> <option value="ADULT">ADULT</option> <option value="JUNIOR">JUNIOR</option> </select> </div> <div class="form-group">
Angular, REST and U2 Page 26 Blog Post 2
Brian Leach Consulting Limited Version 1.0
<label>ISBN:</label> <input class="form-control" [(ngModel)]="book.isbn" name="isbn" placeholder="ISBN"> </div> <div class="form-group"> <label>price:</label> <input class="form-control" [(ngModel)]="book.sale_price" name="salePrice" placeholder="sale price"> </div> <div class="form-group"> <label>stock:</label> <input class="form-control" [(ngModel)]="book.stock_level" name="stockLevel" placeholder="stock"> </div> </form> </div>
You should now have a form that looks similar to the one below. I agree, it’s not the most
exciting but that is beside the point.
Notice that we hard coded the list of departments into the HTML template. That may be well
and good for a binary choice, but we should extract this from the UI for something more
flexible, such as the list of genres. We will add that to the BookListService class as a property:
genres: string[] = ['BIOGRAPHY', 'BUSINESS', 'CLASSIC', 'CRIME', 'DARK_ROMANCE', 'DRAMA', 'FANTASY', 'FICTION', 'HISTORY', 'HUMOUR', 'LANGUAGE'];
Now we can consume this in our template:
<div class="form-group"> <label>genre:</label> <select class="form-control" [(ngModel)]="book.genre" name="genre" placeholder="genre" required> <option *ngFor="let genre of bookListService.genres" value={{genre}}>{{genre}}</option> </select> </div>
Angular, REST and U2 Page 27 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Adding Validation
As with any form, validation is important and should be carried out both on the client and the
server (you are receiving data from outside, and that could come from anywhere).
First, let us start with the title. We cannot save a book without a title. We signal this by adding
the required keyword, and binding this to the model: note that this is a new property called
title and not book.title:
required #title="ngModel"
We can now provide some user feedback. If no title is provided, we will display a message.
This can be placed in a hidden div as follows:
<div [hidden]="title.valid || title.pristine" class="alert alert-danger"> Title is required. </div>
This binds the hidden property of the div to two aspects of that title: valid and pristine. Angular
tracks the field and applies three CSS classes, which we can use to identify the state as well as
providing visual feedback:
touched or untouched
valid or invalid
pristine or dirty
Now we can present this simple validation if you clear the title field:
Let’s see what also happens when we add Submit and Reset buttons to the base of the form.
<button type="submit" class="btn btn-default" [disabled]="!bookForm.form.valid">Submit</button> <button class="btn btn-default" (click)="onClose($event); bookForm.reset()">Close</button>
Angular tracks the validation state of the form as a whole through formName.form.valid,
allowing us to enable and disable the Submit button. Notice that the button is also disabled
when no data is present.
Angular, REST and U2 Page 28 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Types of Validation
So what can we add as validations? First, we can add HTML validations to the fields: these will
be parsed by Angular and validations applied.
Let us tell Angular that the ISBN must be 10 characters long:
<input class="form-control" [(ngModel)]="book.isbn" name="isbn" placeholder="ISBN" minlength="10" maxlength="10" #isbn="ngModel">
We can differentiate the validation messages based on the errors property of the field. Note
that the whole section is controlled by an *ngIf:
<div *ngIf="isbn.errors && (isbn.dirty || isbn.touched)" class="alert alert-danger"> <div [hidden]="!isbn.errors.minlength">Must be at least 10 characters </div> <div [hidden]="!isbn.errors.maxlength">Cannot be over 10 characters long</div> </div>
HTML validation is sparse, so what about those little things like ensuring the price is numeric
and the stock is integral? For this we can use the HTML 5 pattern validation. This accepts a
regular expression.
You can read all about regular expressions in your own time, for now we can add these to the
price and stock level fields respectively:
<input class="form-control" [(ngModel)]="book.sale_price" name="salePrice"
placeholder="sale price" pattern="^\d+\.\d{2}$" #salePrice="ngModel">
<input class="form-control" [(ngModel)]="book.stock_level" name="stockLevel" placeholder="stock" pattern="^\d+$" #stockLevel="ngModel">
What about other validations. Angular must make that easy, right?
Erm, no. Not really. I said Angular is cool, I didn’t say it is complete. For that we must enter
the hairy world of custom validators and there simply isn’t time in these posts.
Angular, REST and U2 Page 29 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Putting Updates
You now have a form that can edit the book details and provide some simple validation. The
only problem is that it doesn’t save the details back.
We have two approaches we can take.
The simpler approach is to just use the u2_book_details Data Resource you have already
defined. Any data resource is two-way: by specifying the http verb we can GET a single
resource or collection, or we can PUT our changes back. This is probably a bad idea but we
start with this anyway.
The PUT request, like the GET requests, will run from the booklist service. We need to add a
couple of things to our imports before we can make full use of this:
import { HttpModule, Http, Response,Headers,RequestOptions } from
'@angular/http';
The U2 RESTful Web Services use a simple form of optimistic control to ensure that updates
are captured. When you request a Data Resource, in addition to the fields mapped from the
file there is a u2version field appended. This holds a kind of timestamp to identify this instance
of the record in the collection:
The service will only update the book if the same version number is sent back, to make sure
that no intervening changes have been made. Unfortunately, for reasons best known to the
engineers, this is not provided as part of the return data but using a Match-If HTTP header.
Angular, REST and U2 Page 30 Blog Post 2
Brian Leach Consulting Limited Version 1.0
In any case, you first need to capture this version, so add this to the bottom of the
bookDetail.model.ts model:
public purch_price: number; public u2version: string; }
Next add a new method to the BookListService class. This will call the http.put() method
passing the book as a JSON string and adding in the new headers. The syntax is a little tortuous
but necessary.
writeBookToREST (book: BookDetail): Promise<BookDetail> { let headers = new Headers(); headers.append('Content-Type', 'application/json'); headers.append('If-Match', book.u2version); let options = new RequestOptions({ headers: headers }); var body = JSON.stringify(book); return this._http.put(this.restUrl + "/U2_book_detail/" + book.id, body, options) .toPromise() .then(() => book) .catch(this.handleError); }
Now this can be called from the submit button by wiring up the OnSubmit() method on the
BookEditorComponent. You can also handle the onClose event from the close button whilst
you are at it:
onSubmit() { this.bookListService.writeBookToREST(this.book); this.bookListService.editing = false; } onClose(event: any) { this.bookListService.editing = false; event.stopPropagation(); }
Finally, you should be able to run the editor and update the U2_BOOKS record on the server.
If not, see below for one possible reason.
Note that only the fields that make up the BookDetails are updated: this is unlike the regular
MultiValue model where the whole record is read, updated and then written back. Any fields
not part of the BookDetails are undisturbed.
Angular, REST and U2 Page 31 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Notice
If you are using Chrome, you may get an error regarding Cross Origin Resource Sharing (CORS).
Chrome sends an OPTIONS request before the real call to check whether the server allows
cross origin requests, signalled by a server response including an Access-Control-Allow-Origin
header. If this is not returned the browser will not perform the subsequent PUT or POST call.
The RESTful services does not respond to this request before version 4.2.0 and so Chrome
denies access. You may see this if you open the Developer Tools from the Settings menu and
go to the Console View.
If you hit this, the simplest option for now is to update your copy of the U2 Restful Web
Services or to disable CORS when starting Chrome from Visual Studio.
Add a new Browse With … entry to start Chrome with the options:
--disable-web-security --user-data-dir
Angular, REST and U2 Page 32 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Calling a Subroutine
As always, it should go without saying that directly updating a file from an external resource
is a really bad idea.
Instead, we will replace this with a call to a subroutine to perform the update. This allows us
to validate correctly and to add any further processing. Let’s start simple by just capturing
what we are sending back.
To illustrate the process of calling a subroutine, create the following subroutine in the
books.bp file in the u2_books account and make sure you compile and catalog it.
As you can see, this will only capture and log the incoming book details.
In the U2 Restful Web Services Developer, stop the u2_book_server and expand the list of
catalogued programs in the u2_books account in the U2 Resources panel above. You should
find your UpdateBookFromREST subroutine.
Drag and drop this onto the Subroutine Resources under the u2_book_server. This prompts
you to define the subroutine parameters as below:
Angular, REST and U2 Page 33 Blog Post 2
Brian Leach Consulting Limited Version 1.0
To call a subroutine we need to make a POST call to the URI:
{your_server}/u2_books/subroutine/{subroutine_name}
The post request body must include a JSON object that contains each of the input parameters
identified using the name in the definition.
To see this in practice, change the writeBookToREST on the BookListService to make this call.
Note that we no longer need to send down the If-Match header as we are not asking the
RESTful service to resolve any lock conflicts on our behalf:
writeBookToREST(book: BookDetail): Promise<BookDetail> { var headers = new Headers(); headers.append('Content-Type', 'application/json'); let options = new RequestOptions({ headers: headers }); var body = JSON.stringify({ 'BookDetails': book }); return this._http.post( this.restUrl + "/subroutine/UpdateBookFromREST", body, options) .toPromise() .then(() => book) .catch(this.handleError); }
Angular, REST and U2 Page 34 Blog Post 2
Brian Leach Consulting Limited Version 1.0
This time, when you hit the submit button it will call the subroutine to write the input data to
the REST_DEBUG entry in your VOC file:
>ED VOC REST_DEBUG
1 lines long.
----: p
0001: {"publisher_id":"018","author_name":"Alexander McCall
Smith","publisher_name":"Bolinda Audio","isbn":"1405500530","stock_level":"6",
"media":"TAPE","dept":"ADULT","title":"Friends, Lovers, Chocolate Bunnies of the
Apocalypse","purch_price":"4.99","u2version":"7929494882365574422",
"sale_price":"14.99","@uri":"http://workvm:9193/u2_books/U2_book_detail/3",
"on_order":"","genre":"FICTION","id":"3","author_id":"2","supplier_name":"NISBETT
AND NIFFLIN","supplier_id":"NN"}
Bottom at line 1.
----: EX
Mapping the Subroutine Parameters
This provides access to the data sent as part of the request - but as a string, which is not hugely
useful unless you already have your own JSON parsing routines (many sites do). However, the
U2 RESTful Services has two other ways to represent data passed to and from subroutines.
The default parameter type is simply string, which passes the content through with minimal
intervention. You can also specify a type of json, which will pass the data as a UDO (Universal
Data Object). This is most useful for complex, nested or otherwise unstructured entities. You
can read about UDOs in the UniVerse Basic Extensions guide.
As a more developer-friendly option, the U2 RESTful service can also parse the content of a
JSON-formatted parameter on your behalf into or out of a regular dynamic array using a
mapping definition similar to the TypeScript model you created on the client. The array
definitions are created as separate resources so that they can be shared between multiple
subroutines.
Under the Subroutine Resources section of your u2_book_server service you will find the
Dynamic Array Definitions.
Angular, REST and U2 Page 35 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Right click this folder and select New Dynamic Array. Here you can map JSON keys to locations
in the array. For each field, you define the location and MultiValue handling. You do not need
to map every field in the incoming JSON message, so complete this for only the local fields in
the BookDetail model that relate directly to the U2_BOOKS file.
Now you can change the subroutine definition to expect a dynamic array in this format.
Remember that you need to stop the service before you can amend any of the details.
Right click the UpdateBookFromREST subroutine and select Properties. Edit the BookDetails
input parameter and set the Type to dynamic array and the array name to BookDetailsArray:
Angular, REST and U2 Page 36 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Now, without changing the subroutine, this will store the details as a regular array.
>CT VOC REST_DEBUG
REST_DEBUG
0001 4
0002 The Legend of Spud Murphy
0003 3
0004 0141805293
0005 JUNIOR
0006 FANTASY
0007 CD
0008 003
0009 5.99
0010 4
0011
0012
0013 1.99
I will leave it to you to write the production logic to allow your UpdateBookFromREST
subroutine to update the U2_BOOKS file with these details: that is just bread-and-butter
Business Language coding.
Do be aware, of course, you would need to come up with a scheme for concurrency handling
were you to apply this to real world situations, whether merging or including a checksum in
your data or having some other means of handling and resolving conflicts. RESTful services,
like all web technologies, are stateless, and so you cannot rely on the safer but less scalable
pessimistic locking model that underpins traditional MultiValue applications.
Angular, REST and U2 Page 37 Blog Post 2
Brian Leach Consulting Limited Version 1.0
Next Steps
In this post, we have introduced the Rocket U2 RESTful Web Services and used these to fetch
data from regular files using dictionary items. We have also discovered how to write back data
using PUT requests, and how to call subroutines using POST.
On the browser side, we have discovered how to write new Angular components and to link
these together to form a workflow. Now that you have seen the progression, you can add
some sophistication to the UI by toggling between the list and editor views.
In the next and final post in this series, we will add some more features to the Angular UI, and
look at another model for creating RESTful services to surface your U2 data and subroutines.
Here we will look at how to leverage the Microsoft WebAPI to call into your server using the
UniObjects protocol.