Saturday, June 13, 2015

How to Create AngularJS Configuration Files for Multiple Websites using Gulp

Many companies and developers have websites that consist of several subsections divided by subdirectories. Some of those subdirectories could contain entirely separate Angular apps, where some might use HTML5 mode and others might not. Consider large organizations that have several different web development groups managing a number of sections of a growing website, if these "websites" or subsections use Angular and want to enable HTML5 mode then this document might help.

Angular Setup

In the configuration below I've set up a 'home' route and enabled HTML5 mode in the $locationProvider. I've also enabled the hashPrefix for a future article that will describe what that does, but for now let's keep it enabled. If the Angular app can't find the path requested then it'll redirect to the /error path. Normally we would create a state for that path as well, and provide a template, however to keep the article simple I've ignored that code. The full working Angular example code will be linked at the end of this article.

// Declare App
var app = angular.module('testApp',[
    'ui.router' //this app uses ui.router, also consider ngRoute
]);

//create config
app.config(function testAppConfig($urlRouterProvider, $locationProvider, $stateProvider){

    // Routes for URLs that go nowhere
    // our default route is /home
    $urlRouterProvider.when('/', '/home').otherwise('/error');

    // use the HTML5 History API
    $locationProvider.html5Mode(true)
        // Use ! for spiders and old browsers, looks like /#!/path
        .hashPrefix('!');

    // add necessary states
    $stateProvider.state('home', {
        url: '/home',
        controller: 'mainController',
        templateUrl: 'home.tpl.html'
    });
});

Nginx configuration

The directory structure of our sample website is /var/www/html with a subdirectory of /var/www/html/test. We want the /test directory to be an HTML5 Angular app with its own unique files.

root /var/www/html;

# This is the default location, or the root
location / {
 # First attempt to serve request as file, then
 # as directory, then fall back to displaying a 404.
 try_files $uri $uri/ index.html =404;
}

# This is the subdirectory configuration for the new Angular app
location /test/ {
 # First attempt to serve request as file, then
 # as directory, then fall back to the Angular app.
 try_files $uri $uri/ /test/index.html;
}

Gulp-webserver Proxy Configuration

In this example we use Gulp with gulp-webserver to proxy our web requests to our API servers. The same configuration can be made with Grunt, however I prefer Gulp for its ease of use.

gulp.task('startWebserver', function(){
    var stream = gulp.src('build')
        .pipe(webserver({
            host      : '0.0.0.0',
            port      : 8080,
            livereload: {enable: true, port: 8081},
            fallback  : 'index.html',
            https     : true,
            key       : 'config/grunt-connect/server.key',
            cert      : 'config/grunt-connect/server.crt',
            proxies   : [
                {
                    // redirect API requests to our DEV environment
                    // since we don't run the servers on our local machine
                    source: '/api',
                    target: proxyDomainURL + '/api' // proxyDomainURL is your API server location 
                },
                {
                    source: '/test', // the proxy path for the subdirectory
                    target: 'https://0.0.0.0:8080' // is our running website
                }
            ]
        }));
});

Complete Angular Code

As promised, here's the sample app.

index.html

<!DOCTYPE html>
<html ng-app="testApp">
<head>
  <title>Test App</title>

  <base href="/test/" />
</head>

<body ng-controller="testController as test">
  <div>
   
    <h1>{{title}}</h1>

    <!-- Angular UI Router Directive for template insertion -->
    <div id="content" ui-view></div>
  
</div>

  <!-- AngularJS -->
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.14/angular-ui-router.min.js"></script>

  <script>
    (function(){
      // Declare App
      var app = angular.module('testApp',[
        'ui.router'
      ]);
      app.config(function testAppConfig($urlRouterProvider, $locationProvider, $stateProvider){

        // Routes for URLs that go nowhere
        $urlRouterProvider.when('/', '/home').otherwise('/error');

        // use the HTML5 History API
        //TODO: turned off because we're using the /secondaryFlow path in DEV.
        $locationProvider.html5Mode(true).hashPrefix('!');

        $stateProvider.state('home', {
          url: '/home',
          controller: 'mainController',
          templateUrl: 'home.tpl.html'
        });
      });
      
      // Create Controller
      app.controller('testController', function($scope, $rootScope){
        console.log('testController');
        // Data  
        $rootScope.title = "Hello, World!";
      // End Controller  
      });

      app.controller('mainController', function($scope, $rootScope){
        console.log('mainController');
        // Data  
        $rootScope.title = "Home";
      });
      
    // End App  
    })();
  </script>
</body>
</html>

home.tpl.html

<h2>You are now on the home page.</h2>