State Binding with Angular

AngularJS combined with UIRouter is a powerful and fast client-side combination for routing and rendering nested views. There’s one thing that needs some special attention though. Angular expects us to include the same, or all of our scripts and styles in the parent-most view. What if we want sylesheets or scripts coupled with specific states? Here’s a solution.

Lets start by creating an index.html file and preparing it for an angular application.

<html>
<head>
  <title></title>
  <a href="http://js/angular/angular.min.js" data-mce-href="http://js/angular/angular.min.js">http://js/angular/angular.min.js</a>
  <a href="http://js/angular/angular-ui-router.min.js" data-mce-href="http://js/angular/angular-ui-router.min.js">http://js/angular/angular-ui-router.min.js</a>

</head>

</div> http://js/jquery.js http://js/angular.app/angular.app.js

</html>

Note the div elements I’ve created for our stylesheets and scripts? Yeah? Good.

Now let’s hook up some states in angular.app.js, dude!

angular.module('app', [
  'ui.router'
])
.config(['$urlRouterProvider', '$stateProvider', 
  function($urlRouterProvider, $stateProvider) {

  $urlRouterProvider.otherwise('/');

  $stateProvider
  .state('login', {
    url: '/login',
    templateUrl: 'html/login.html',
    stylesheets: [
      "css/form.css"
    ],
    scripts: [
      "js/login.js"
    ],
  })
}]);

I’ve added two arrays to the login state to contain some script and stylesheet references that we’ll be adding to the document object model. This next part is my favourite, and makes use of the app’s rootScope.

var $scripts;
var $title;
var $styles;

angular.module('app').run(['$rootScope', function($rootScope) {

  $rootScope.$on('$stateChangeStart', function(event, toState){
    $scripts = toState['scripts'];
    $title = toState['title'];
    $styles = toState['stylesheets'];
  });

  $rootScope.$on('$viewContentLoading', function(event, viewConfig){
    rootScopeMechanism.SetTitleElement($title);
    rootScopeMechanism.SetStyleSheets($styles);
    rootScopeMechanism.SetStateScript($scripts);
  });

}]);

When the state begins to change, the global variables (above) will be assigned the said arrays we’ve coupled with the state. Just before the view content gets rendered by the browser, these calls to rootScopeMechanism will fire, and be passed the arrays as arguments – appending new HTML elements to the DOM.

var rootScopeMechanism = {

  SetStyleSheets: function(stylesheets) {

    $('#styles').empty();

    if(stylesheets !== null && stylesheets !== undefined)
    {
      stylesheets.forEach(function(entry) {
        $('#styles').append('<link rel="stylesheet" href="' + entry + '">');
      });
    }
  },
  SetStateScript: function(scripts) {

    $('#scripts').empty();

    if(scripts !== null && scripts !== undefined)
    {
      scripts.forEach(function(entry) {
        $('#scripts').append('http://'%20+%20entry%20+%20'');
      });
    }
  }
};

Boom. Suckas. This root scope mechanism will now bind scripts and stylesheets to our states!