Let's write an Angular 1.x app with a Caché REST backend - Part 1 of Many

So, one day you're working away at WidgetsDirect, the leading supplier of widget and widget accessories, when your boss asks you to develop the new customer facing portal to allow the client base to access the next generation of Widgets..... and he wants you to use Angular 1.x to read into the department's Caché server.   

There's only one problem:  You've never used Angular, and don't know how to make it talk to Caché.

This guide is going to walk through the process of setting up a full Angular stack which communicates with a Caché backend using JSON over REST.  

Part 1 - Setup

To start fresh, we will create a Namespace for our new application - WIDGETDIRECT, and set this up with Code and Data databases, and appropriate Security roles.  

Our next step is to set up 2 Applications to serve web content; one for the Angular web content and one to serve the REST content

Our web content application is a standard CSP application, with a location on the local storage to the static web content

However, our REST application is set up a little different, and has a Dispatch class specified rather than a CSP Files Physical Path. This application is entirely driven by Classes

Our final set up step is to create our REST.Dispatch class, so that our application can serve some content.  Create a Caché class and have it extend  from %CSP.REST.  

     

An empty REST class won't do anything useful, so we need to add a Route which maps a URL expression (and an HTTP verb, but we'll come back to this later)  to a ClassMethod.  Any expression beginning with : signifies a parameter to the classmethod, by position.

 

This will now take any request to widgetsdirect/rest/<name> and will return a personalised Hello World message based on the Name value passed in.  We can test this by accessing the URL using a browser (which will use an HTTP Get to retrieve the content)

 

Congratulations you have just created your first Caché REST service!

 

Part 2 - Creating our Web Front End 

We usually don't want to have our customers directly interacting with a REST service, so we now need to add a page to our Web application.  Create a standard CSP page.  We'll give it the Widgets Direct title, and add some script references.  The first is the Angular runtime, to allow us to utilise the Angular framework, and the second is our own Module and Controller code.  Finally, we will display the value of "message" in the Angular scope, so we put {{message}} in the body of the page, then we save as "Welcome.csp".  

When we view as HTML, we get...

This clearly isn't what we want, so we are clearly needing some more setup.  First, we need our Module and Controller code, so we need to create the javascript we mentioned in our Welcome page.  We will define our Angular Module as "WidgetsDirect" and attach our first Controller "PageController" to the module.  We will also pass in some useful Angular functionality to the controller, such as the $scope and the $http methods to allow us to send and receive HTTP content.  When our controller is referenced, it will set the value of $scope.message (which we are displaying on the page) to the string "Hello Scope!"

 

We will need to tell our Welcome.csp page that it is using both the Module and the Controller, in order to allow the page to see the correct $scope.   We specify the ng-app at the html top level, and specify the controller at the body level in this example.  Everything inside of the <body> tags will now be able to reference our Controller data and code.

If we now reload our Welcome.csp page in a browser, we should see

This is great!  We now have our page talking to our Angular scope.  However, we're not talking to Caché yet.  Let's complete the chain, and have the controller look up our REST service, and assign the data returned to the message variable.  To do this, we use the $http service, which provides us with an easy way to send and consume the results of an HTTP Get.   We will pass in our own name for the parameter in the URL request. We have 2 functions following the return of the request, the first deals with Success return codes (where we expect valid data), and the second deals with any error conditions)

 

We will look a little bit closer at the response objects in later articles, but for now we will just copy the value of the data element into the message storage.  If we hard refresh our browser to get the latest version of the Javascript, we should now see:

 

SUCCESS!  You have now implemented a page which incorporates a full Angular stack to Caché.  In a rush of excitement, you send the link to your boss to review this fantastic page! (next lesson)

 

This article is part of a multi-part series on using Angular on top of Caché REST services.  The listing of the full series can be found at the Start Here page

  • + 14
  • 1
  • 2497
  • 19

Comments

This is very helpful, thank you!  One problem is that a few of the images seem to be missing from the post (the standard "image not found" icon is shown).

That's weird.  I've reuploaded the images and resaved, so hopefully they should stay uploaded now.  I will double check Pts 2 and 3 also

Hi Chris 

This is great that there is a sample project that is angularjs specific. I have tried running the sample and I am running into an issue. Here is what is working. The REST service works. The csp page only displays {{message}} I can not seem to get it to find the JavaScript. I am also trying to get this to work on my server which is not localhost.(typos will get you every Time) I have it resolved 

If you could provide the code so it could be downloaded or have the ability to cut and paste that wouldhelp.

thanks

Joe

Hi Joe

My next article was going to be a debugging session, so this is a great question.  The first thing to do when Angular misbehaves is to open your browser debugger using F12, and seeing if there are any errors being logged.  The Angular errors very helpfully include a degree of self diagnosis. 

I will get the codebase up publicly (probably on github), and will post a link back on the articles

Glad you're getting some benefit from my rambling

Chris

Also, you will want to make sure that the ng-app is defined on the html tags, and that the ng-controller is defined for the body.  Importing the javascript is only half the battle, the hints in the html tell the runtime where to look for the logic

Hi Chris 

i now have all 3 parts working.

I know there were significant changes to JSON from Cache 2016.1 to Cache 2016.2.

will this tutorial work on a Cache 2016.1 system? I am hoping to get the company I working with to upgrade but there is a real possibility they won't? 

Thanks 

Joe

Hi Joe

The sytnax is different and incompatible unfortunately.  However, I've actually been doing all of my development against 2016.1, so the code I'm writing here is all feature compatible (I purposefully didn't explore any of the more advanced JSON functionality once I heard about the changes in the pipeline).  Essentially, the only difference is that the % calls are instead $ calls in 2016.1.  Since I've kept the logic basic, this means you should only need to convert a small amount of the code to make it compatible with 2016.1.  Stefan wrote a great summary of the functionality here, and this contains the 2016.1 versions of the syntax.  If I start referring to any new JSON functionality that does not have an equivilant syntax in 2016.1, I will make that very clear in the article

HTH

Chris

Using the macros is a bad idea.  The changes to the JSON, especially in this case, are simple.   Any source control system worth its salt is going to be able to make nearly all of the changes for you automatically.  As far as I know the only change that can't be caught by find-and-replace is the one requiring COS expressions embedded within JSON to have parentheses.  In this case, the new syntax is supported in 2016.1 JSON, so you wouldn't even need to change that.  Because of the ease of change, the fact that the change only needs to happen once, and the code obfuscation that occurs when adding  macros, I do not think using them is a good idea for anyone planning to use Caché's native JSON implementation.

 

Edit: It occurs to me that the macros would be useful if you have to switch back and forth between 2016.1 and later versions over and over again, but to do the conversion once is too simple to bother with macros.

"As far as I know the only change that can't be caught by find-and-replace is the one requiring COS expressions embedded within JSON to have parentheses"


This was my understanding of the differences too, so I've (probably at my own expense) been very careful to only use the methods that I know I can do a basic Find-Replace on in the future.  So, I've been assigning the results of any COS expressions to variables, then binding these to the Dynamic objects.  It's a little more verbose, but it should allow an easier translation path between .1 and .2

 

Kyle,

The macros are intended to prevent developers from having to refactor code at the same time as they perform an upgrade, as well as make it easier for application providers who have code running on a number of versions.

I've learned from experience that it is always best to have the fewest moving parts when possible when doing an upgrade so you can quickly find the cause of any issues that pop up.  Therefore, I always try to write forward compatible code and only after all of my systems for a given codebase have been upgraded and are stable do I start to introduce backwards incompatible changes.  These macros allow that very nicely.  In addition, using the macros means that you have more flexibility to upgrade without having to schedule a concurrent refactoring project (even if it is just a find and replace refactoring project :) ).

All that being said, the macros are not intended for long-term use with-in an application.  Once the 2016.1 > 2016.2 hurdle has been cleared then my recommendation would be to pull out the macros (find & replace) and stick with Caché's native JSON access going forward.  But that can then be a project that takes place post-upgrade, thus simplifying the upgrade and lowering risk.

Thanks Chris, this is very helpful!  

I had to make a few adjustments due to my configuration, but I'm posting here in case it's useful to others who are new to some of these areas.

Setting this up in HealthShare (or other environments with normal security)

Since I set this up in a HealthShare environment, which is installed with normal security by default, I needed to adjust the web applications.  I created a new namespace and database for WIDGETDIRECT, along with a new security resource.  The resource (%DB_WIDGETDIRECT) has no public permissions set.  Note, however, the system automatically creates a role (with the same name) that has RW privileges on that resource. So to get the REST service to work, I changed the web application by going to the Application Roles tab and adding the %DB_WIDGETDIRECT role to the application roles.

I know this isn't the correct way to handle security in a production environment, but I needed to do this to get this example to work.

Getting REST to work with an Apache web server

I am using a full Apache web server on my machine (Mac OS X) to service several instances of HealthShare.  So, for example, to access the Management Portal using Apache my URL looks something like this:

http://localhost/infoexchange/csp/sys/%25CSP.Portal.Home.zen

There were two things required to get this entire exercise to work correctly:

1. In my httpd.config file I had something like this to force the CSP Gateway to handle all files:

<Location "/infoexchange/">
     CSPFileTypes *
</Location>

When I tried calling the REST service directly using this:

/infoexchange/widgetsdirect/rest/jill

I kept getting "URL not found errors".  However, when I tried this it worked correctly:

/infoexchange/widgetsdirect/rest/jill.txt

It turns out that using CSPFileTypes is valid only for URLs with an extension (obvious now and to those who are expert in this area, but that's not me).  So I added one line to my httpd.conf file to force the CSP Gateway to evaluate any URL with this location and all worked perfectly!

<Location "/infoexchange/">
    CSPFileTypes *
    CSP On
</Location>

 

2. Since my URL for the REST service includes information about my instance, I needed to update the widgetmaster.js file to include that in the $http.get() command:

$http.get('/infoexchange/widgetsdirect/rest/Jill')

 

Hi Jill

You busted me!  I deliberately left off the Caché Security portion of the equation because it would have gotten very complex very fast.  

The apache config info is great.  I was lucky enough to inherit a fully configured httpd instance on my dev system

Many thanks

Chris

P.S.  Chasing up why the images keep disappearing from the post, hopefully will be resolved soon

Note that some of attribute values are case sensitive.

INCORRECT

<Route Url="/:name" Method="GET" Call="HelloWorld" Cors="False" />

CORRECT

<Route Url="/:name" Method="GET" Call="HelloWorld" Cors="false" />

Chris, 

Great tutorial.  Was able to get this all working fairly easy against an InterSystems IRIS for Health environment.  Looking forward to the next section.

Ken