Blog

- August 05, 2015

For the Summer of API, I’m implementing an Angular application (see my first post on the subject) to display open data on maps in a convenient way. I want to use all the classical tools provided by the community to make my development life easier.

Concretely, I’m using Yeoman with the Angular generator. This means that the project structure was generated for me. In addition Yeoman leverages tools like NPM and Bower for dependencies and also Grunt for the build script. Yeoman also configured the project to be able to use LiveReload and build / optimize it for production.

In this context, implementing the application is really convenient with its local server. To deploy the application within an APISpark Web API, by using the built-in file store, we need to make some adjustments to follow the required structure of the file store, as it supports a flat folder structure, and mandates all files to be in a folder.

This means that I need to define several folders for the following contents:

  • html for the HTML elements
  • images for the images
  • scripts for the JavaScript files
  • styles for the CSS files

I dont have problems for the last two ones, since Grunt will gather code within a minimum set of files and no sub folders will be required in them. Here is a sample content for these folders:

dist/
  scripts/
    scripts.e5b45497.js
    vendor.59008981.js
  styles/
    main.9dcbb5ce.css
    vendor.8fe9e3e1.css

For the images folder, if we are a bit careful (not use sub folders), there is also no problem.

For the html folder things are a bit trickier. As a matter of fact, the files are present at the root folder. So we need to copy them into the html folder and update the links to related JS and CSS files. For this, we will show how to implement and use a very simple Grunt task apisparkify.

You can also notice that there is also a views folder in the dist folder We will use Angular template preloading in order not to have to use it after deploying.

Preloading Angular views

To preload Angular views, we can use the tool grunt-html2js to integrate such processing directly within the build chain. The installation of this tool can be simply done within the file package.json, as described below:

{
    (...)
    "devDependencies": {
        (...)
        "grunt-html2js": "0.3.2",
        (...)
    }
    (...)
}

Simply type then the following command to install the tool within your project:

npm install

Then you need to configure this tool within your Gruntfile file. This consists of loading the tool, defining its configuration within the grunt.initConfig function and finally add it within the task processing flow.

module.exports = function (grunt) {
    // Define the configuration for all the tasks
    grunt.initConfig({
        (...)

        html2js: {
            options: {
                base: 'app',
                module: 'mapManager.templates',
                singleModule: true,
                useStrict: true,
                htmlmin: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true,
                    removeComments: true,
                    removeEmptyAttributes: true,
                    removeRedundantAttributes: true,
                    removeScriptTypeAttributes: true,
                    removeStyleLinkTypeAttributes: true
                }
            },
            main: {
                src: ['app/views/**/*.html'],
                dest: 'app/scripts/templates.js'
            },
        }
    });

    grunt.loadNpmTasks('grunt-html2js');

    (...)

    grunt.registerTask('test', [
        'clean:server',
        'wiredep',
        'concurrent:test',
        'autoprefixer',
        'html2js',
        'connect:test',
        'karma'
    ]);

    (...)

    grunt.registerTask('build', [
        'clean:dist',
        'wiredep',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'html2js',
        'concat',
        'ngAnnotate',
        'copy:dist',
        'cdnify',
        'cssmin',
        'uglify',
        'filerev',
        'usemin',
        'htmlmin'
    ]);

    (...)
};

You can notice that we only add the task html2js task within the build one. As a matter of fact, we only need to compile views into JavaScript when executing the application within APISpark. It’s not necessary for development with command grunt serve.

As you can see, we specify a module name for the generated template source code. To actually use this source code, we need not to forget to do two things.

The first one is to add the generate JavaScript file (here app/scripts/templates.js) within our file index.html, as described below:

<html>
    (...)
    <body>
        (...)
        <!-- build:js({.tmp,app}) scripts/scripts.js -->
        (...)
        <script src="scripts/templates.js"></script>
        <!-- endbuild -->
    </body>
</html>

The second one is to define the module as dependency of our Angular application within our file app/scripts/app.js, as described below:

angular
    .module('mapManagerApp', [
        (...)
        'mapManager.templates'
    ])
    .config(function ($routeProvider) {
        (...)
    });

Now this is completed, we need to tackle HTML files to be able correctly load and use the Angular application.

Configuring HTML files

As described previously, we need to implement the following processing within the build chain:

  • Create a folder: dist/html
  • Copy the index.html file from folder dist to dist/html
  • Update the references on files JS and CSS within the copied file index.html

For this, we will create a custom Grunt task. We will leverage the utility functions of Grunt relative to files and folders and the library Cheerio to parse and update links within the HTML file. Following snippet describes the implementation of this custom task:

grunt.registerTask('apisparkify', function() {
    var fs = require('fs');
    var $ = require('cheerio');

    // Create an html directory
    grunt.file.mkdir('dist/html');
    grunt.log.ok('Created folder dist/html');

    // Copy the index.html file into the created folder
    grunt.file.copy('dist/index.html', 'dist/html/index.html', {});
    grunt.log.ok('Copied file dist/index.html to folder dist/html');

    // Update links in it
    var indexFile = fs.readFileSync('dist/html/index.html').toString();
    var parsedHTML = $.load(indexFile);

    parsedHTML('script').each(function(i, elt) {
        var wElt = $(elt);
        var srcAttr = wElt.attr('src');
        if (srcAttr != null) {
            wElt.attr('src', '../' + srcAttr);
        }
    });
    grunt.log.ok('Updated script tags');

    parsedHTML('link').each(function(i, elt) {
        var wElt = $(elt);
        var hrefAttr = wElt.attr('href');
        if (hrefAttr != null) {
            wElt.attr('href', '../' + hrefAttr);
        }
    });
    grunt.log.ok('Updated link tags');

    fs.writeFileSync('dist/html/index.html', parsedHTML.html());
    grunt.log.ok('Written updated file dist/index.html');
});

Now the Grunt task is implemented, we need to put it within the task processing chain. We put it at the very end of the task build, as described below:

grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'html2js',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin',
    'apisparkify'
]);

Let’s deal now with how to deploy the Angular application within the APISpark platform.

Deploying the frontend application

Now we did the work, installing the application on the APISpark platform is quite easy! We simply need to upload files within corresponding folders. We use the same names as those generated by Grunt when creating our Web API. Uploading can be done either using the file brower of the underlying file store used or directly using the Web API with paths corresponding to folders.

Summary

In this post, we’ve learned how to automate, transform and serve an Angular.JS application from APISpark’s built-in file store. Another interesting approach as well would be to use APISpark’s Github file store, as explained by Guillaume in his API fireworks demo.

In the next article, we’ll come back to the theme of visualizing Open Data sets, explaining how to define the various data layers, (we’ll use D3.js for that purpose)! Stay tuned!

What about your Summer of APIs participation, how is it going? Tell us how it goes!

Article initially posted on Thierry’s blog.