Integrating AngularJs with Legacy Server Controller

This week’s challenge was to integrate our existing Rails app with new AngularJS page to provide better user experience. We are reworking the existing processes a bit and leveraging web service calls to load data into the page. I’m amazed at how quickly we can get work done because of the power of AngularJS’s bindings and lack of DOM manipulation.

Purusing the documentation and Google uncovered little to help with integration. The current focus seems to be doing cool and sexy stuff in AngularJS and a ton of basic how-tos. However, not much press about how to work with legacy code. Hmmm… Do you blame them? I don’t

NoInit

The typical way to init an app is to serve out a bootstrap page and have the JavaScript code initialize itself from embedded, generated, or retrieved data. I can imagine a scenario where the back end server could serve out a page along with some bootstrap data from the outset. Then lazily load other data on demand. Although not ideal, it allows integrating with legacy server side code and can reduce some web server traffic, both speeding up app init time and reducing server load. This won’t be a huge savings, but it’s something and the sooner you can get the page loaded, the better the user experience.

The trick is how to get external data into AngularJS app. We were thinking of three main “integration patterns” and completely ruled out the fourth.

  1. URL
  2. Hidden Tags
  3. ng-init
  4. inline javascript

The first, adding parameters to the URL, is OK for a few items and is commonly used, but it makes for an ugly URLs and doesn’t scale well as the params grow. Cutting and pasting large urls is error prone and inconvenient. Potentially tripping Twitters character limit. LOL. There is a reason bit.ly exists. The second, Hidden Tags, seemed promising, but feels hacky and in-elegant. Hidden tags only exist as a temporary place holder that has no use after the page inits. The third, ng-init, was the most likely candidate and a few developers recommended this approach. Please, for the love of humanity, don’t use inline JavaScript, ’nuff said.

<div ng-init="myVar="Hello world"/>

Happy with our decision, we started to implement the solution and had to reference  the online docs, which is were we found an issue. The documentation strongly states the only use case for using ng-init is with in the ng-repeat tag.

The only appropriate use of ngInit is for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
https://docs.angularjs.org/api/ng/directive/ngInit

Hmmmm….. That was odd and not expected. Although several online posts pointed to this being the solution we were looking for, there were always those that were quoting the official docs that prophesize doom and gloom if you proceed. Our issue was passing several values into a function that we wired up to ng-init. We were using Rails erb scriptlets, which are make reading the code difficult by being verbose when embedded in a tag parameter.  Undeterred, we moved a bit further until we discovered a fatal error. The ng-init loads AFTER the controller. So we were left with undefined initialized scope variables. ARGH. Those Hidden tags were looking a lot nicer.

<div ng-init="init(<%= APPNAME::MYMODULE::MyFormater.format(
@someclass.my_value.capitalize).to_s%>)"></div>

It’s easy to see how unwieldy this is with only one attribute, imagine passing in two, three, or more. Yukk. Some people would argue that nice looking code isn’t important. For me, readability and clean code has lots of advantages. Other posts to follow on this topic.

The basic thought for using the hidden field contains a single tag for each value to be integrated. This makes the code a little easier on the eyes and readable. The tag initializes from the erb scripting tags which then bind to a Angular model. Ruby evaluates the scriptlet and output a string in the ng-init variable. Angular will load up the ng-init and assign it to the model on the hidden tag making it reachable via the $scope.

<input type="hidden" 
       ng-model="car.engine" 
       ng-init="car.engine = <%= @vechicle_options.engine %>"/>

This isn’t ideal, but it adds one param at a time in a nice “readable” format. It’s not too busy. I’m not happy with this approach, but it seems to be the decent solution.

We finally settled on a different approach. Solutions typically are designed for a given problem. When your trying to do something, you run into a problem and frame it in your mind a specific way, which presents certain solutions. Often I have implemented something only to have some one else ask why didn’t I do it this way or that way. Because I thought about it differently and this was the solution that worked. Reworking the problem space often yields different solutions.

And that is what happened. Thinking about the issue and exploring the data a bit more changed what we really needed to do. It turned out several items didn’t need to be pushed in to the app after all, which made ng-init a little more appealing. We cleaned up the code a bit by simplifying the original param. This was done by moving the logic in the scriptlet into the server and just returned a simple string. This init function was defined in the rootScope and updated a model object that was stored in the service. The view accessed the model via the controller which exposed the model.

The view

 <div ng-init="deliveryInit('<%= @campaign_source %>')">

And here is the initialization snippet from app.js, which injects my model as a service and sets the values.

myApp.run(function($location, $rootScope, $route, myModel) { 

$rootScope.deliveryInit = function(initializedValue) { 
  myModel.someProp = initializedValue
  //other init if needed.
};

The model is currently defined in the service.js. But I want to move elsewhere. Here it is defined as a value. I want to move this into a “domain.js” file which contains POJO’s (plain JavaScript object).

var myServices = angular.module("myServices", ['ngRoute']);
myServices.value('myModel', {
  someProp: '',
  someotherProp: ''
}

The controller exposes the model. The name is the same and can be confusing. Some might want to expose it slightly differently.

blastSendControllers.controller('subscribersController', 
                                 function ($scope, myModel){
$scope.myModel = myModel;
}

And the view that uses the value

 <input type="text" ng-model="myModel.someProp" />

I’m not suggesting this as a valid approach, but a potential solution. The trade off is AngularJS doesn’t seem to endorse this approach which could have some ramifications. The Hidden field approach may make more sense for your app. Anyone have a better, cleaner, more elegant solution?

 

Just some thoughts

Tom

This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

1 Response to Integrating AngularJs with Legacy Server Controller

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s