AngularJS in A Groovy World

Craig Burke

About Me

  • Groovy/Grails Developer at Carnegie Mellon

  • Creator of Various AngularJS Related Build Tools

  • Creator of AngularJs/Grails Lazybones Template

  • www.craigburke.com

  • @craigburke1

Introduction

Let’s Build An Angular App

What We’ll Need

  • Gradle (and various plugins)

  • Asset Pipeline (and various modules)

  • John Papa’s Angular Style Guide

  • That’s About It

Starting Simple

plugins {
  id 'com.bertramlabs.plugins.asset-pipeline' version '2.4.2'
}

task build(dependsOn: ['assetClean', 'assetCompile'])
assetCompile.shouldRunAfter 'assetClean'

Issue #1

Client-Side Dependencies

Solutions

  • Download and Manage Dependencies Ourselves

  • Use Npm or Bower with Grunt or Gulp

  • Use Gradle with the Bower Installer Plugin

Bower Installer Plugin

Getting Started

plugins {
  id 'com.moowork.node' version '0.10'
  id 'com.craigburke.bower-installer' version '1.3.3'
}

Bower Installer Tasks

bowerInstall installs dependencies

bowerClean removes dependencies

bowerRefresh removes and reinstalls dependencies

Bower Installer Configuration

bower {
  dependencies = [
    bootstrap: '3.3.x'
  ]

  sources = [
    bootstrap: ['dist/css/bootstrap.min.css', 'dist/fonts/**']
  ]

  excludes = ['jquery']
}

Adding A New Dependency

  • Add dependency to dependencies list

  • bowerRefresh -PbowerDebug=true

  • Limit files with the sources properties

  • Limit transitive dependencies with excludes

  • bowerRefresh

Issue #2

JavaScript Minification

Understanding the Problem

Our original source
function ListController($log) {
  $log.error('HELP!');
}
myApp.controller('ListController', ListController);
Our minified source
function a(b) {
  b.error('HELP!');
}
c.controller('ListController', a);

Minification Safe Version

Using $inject
function ListController($log) {
  $log.error('HELP!');
}
ListController.$inject = ['$log'];
myApp.controller('ListController', ListController);
Inline annotation
function ListController($log) {
  $log.error('HELP!');
}
myApp.controller('ListController', ['$log', ListController]);

Solutions

  • Manually use $inject or inline annotations

  • Add special @ngInject comments to our functions

  • Use the Angular Annotate Asset Pipeline plugin

The @ngInject annotation

Set angularPass option for the Asset Pipeline
apply plugin: 'asset-pipeline'

assets {
  minifyOptions = [
    angularPass: true
  ]
}
Add a comment before any function we want annotated
// @ngInject
function ListController($log) {
  $log.error('HELP!');
}
myApp.controller('ListController', ListController);

Angular Annotate Asset Pipeline Plugin

buildscript {
  dependencies {
    classpath 'com.craigburke.angular:angular-annotate-asset-pipeline:2.1.2'
  }
}

Issue #3

Global Scope

JavaScript Closure

Wrap Angular components in an Immediately Invoked Function Expression (IIFE)

JavaScript Closure

(function() {
  angular.module('myApp', []);
})();

Solutions

  • Manually add a closure to every file

  • Use CoffeeScript

  • Use Js Closure Wrap Asset Pipeline Plugin

Js Closure Wrap Asset Pipeline Plugin

buildscript {
  dependencies {
    classpath 'com.craigburke:js-closure-wrap-asset-pipelin:1.1.0'
  }
}

Wrapping Our JavaScript

//= wrapped
angular.module('myApp', []);

Issue #4

Managing Templates

Angular Template Resolution

<div ng-include=" 'my-template.html' "></div>

If my-template.html isn’t in Angular’s template cache then an HTTP GET request is done for my-template.html

Adding to The Template Cache

Template in a script tag
<script type="text/ng-template" id="my-template.html">
  <h1>My template</h1>
</script>
Add directly to $templateCache in a run block
myApp.run(function($templateCache) {
  $templateCache.put('my-template.html', '<h1>My template</h1>');
});

Solutions

  • Just use plain html template files

  • Add to $templateCache via a script tag or run block

  • Use the Angular Templates Asset Pipeline plugin

Angular Template Asset Pipeline Plugin

buildscript {
  dependencies {
    classpath 'com.craigburke.angular:angular-template-asset-pipeline:2.2.1'
  }
}

Example

file located at src/assets/js/my-app/users/list.tpl.html
<p>Hello</p>
Becomes something like this:
angular.module('myApp.users').run(function($templateCache) {
  $templateCache.put('/my-app/users/list.html', '<p>Hello</p>');
});

Example #2

Set moduleNameBase property
assets {
  configOptions = [
    angular : [ moduleNameBase: 'myApp' ]
  ]
}
file located at src/assets/js/users/list.tpl.html
<p>Hello</p>
Becomes something like this:
angular.module('myApp.users').run(function($templateCache) {
  $templateCache.put('/users/list.html', '<p>Hello</p>');
});

Issue #5

Running JavaScript Tests

Solutions

  • Don’t write tests

  • Run test from Java using Nashorn or Rhino

  • Use the Jasmine Gradle Plugin

The Jasmine Gradle Plugin

Getting Started

plugins {
  id 'com.moowork.node' version '0.10'
  id 'com.craigburke.jasmine' version '1.0.1'
}

Jasmine Tasks

jasmineRun Runs jasmine tests

jasmineWatch Runs jasmine tests in watch mode

jasmineClean Removes generated karma config file

Jasmine Configuration

jasmine {
  files = [
    'bower/angular/angular.js',
    'bower/**/*.js',
    '**/*.module.js',
    '**/!(*.spec).js',
    '**/*.spec.js'
  ]
}

Server-Side Frameworks

Spring Boot, Grails and Ratpack

Spring Boot

buildscript {
  dependencies {
	// Asset pipeline modules set as build dependencies
	classpath 'com.craigburke.angular:angular-annotate-asset-pipeline:2.1.2'
	classpath 'com.craigburke:js-closure-wrap-asset-pipeline:1.1.0'
	classpath 'com.craigburke.angular:angular-template-asset-pipeline:2.2.1'
  }
}

dependencies {
  compile 'com.bertramlabs.plugins:asset-pipeline-spring-boot:2.4.2'

  // Asset pipeline modules set as runtime dependencies
  runtime 'com.craigburke.angular:angular-annotate-asset-pipeline:2.1.2'
  runtime 'com.craigburke:js-closure-wrap-asset-pipeline:1.1.0'
  runtime 'com.craigburke.angular:angular-template-asset-pipeline:2.2.1'
}

Spring Boot (2)

package angular

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan

@SpringBootApplication
@ComponentScan('asset.pipeline.springboot')
class Application {

    static void main(String[] args) {
        SpringApplication.run Application, args
    }
}

Grails 3

Good News! Everything works out of the box!

Any configuration settings should be in both the build.gradle and application.yml

Ratpack

Asset Pipeline works well there too:

Resources

Books

Presentations

  • Asset-Pipeline by THE David Estes (later today)

Thank You