• Stars
    star
    117
  • Rank 301,828 (Top 6 %)
  • Language
    JavaScript
  • Created over 10 years ago
  • Updated over 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

AngularJS seed for Google Closure Compiler

A radical new approach to developing AngularJS apps

Build Status

An AngularJS seed for Google Closure Compiler.

Concept

This is not a 'one size fits all' solution. There is no CoffeeScript/etc. compiler or CSS preprocessor involved. That's up to you. It's already complicated enough without them :)

What you get is a clean and scalable project structure for your AngularJS apps.

Google Closure Compiler

Most of you might use uglifyjs for minification. At least that's what I did for a long time and there is nothing wrong with it. It is an awesome tool.

However Closure Compiler can do more than that.

  1. Minify code knowing about AngularJS
  2. Inject dependencies automatically
  3. Create namespaces
--angular-pass
--angular_pass

Generate $inject properties for AngularJS for functions annotated with @ngInject

Example controller.

/**
 * Controller.
 *
 * @param {angular.$window} $window
 * @param {angular.$http} $http
 * @ngInject
 */
var Ctrl = function($window, $http) {
  // ...
};

The above example after minification.

var a = function(b, c) {
  // that's beautiful
};
a.$inject = ["$window", "$http"];

No more

angular.module('app').controller('Ctrl', ['one', 'two', 'three', 'four', 'five', 'six', function(one, two, three, four, five, six) {
  // ^ that's quite a long line
}])
--externs
--externs closure/externs/angular.js

The file containing JavaScript externs. You may specify multiple

Example

angular.module('app', []);

The above example after minification without --externs.

angular.d('app', []);

That does not work as angular does not have a d() method. module(), directive(), service() and so on have to stay as they are. That's why we need an AngularJS externs file with the AngularJS global namespace.

--generate_exports
--generate_exports

Generates export code for those marked with @export

Closure Compiler throws away all unnecessary code. So if you declare a function but never call it the compiler doesn't include it in app.min.js. Problem here is that we declare our controllers and controller properties in our xyz-controller.js files and use them in your template files xyz.html. Closure Compiler doesn't look into our HTML template and doesn't know which methods we'd like to keep.

@export and @expose to the rescue. Simply use these annotations in your code to prevent variable names from being mangled.

/**
 * Controller.
 *
 * @export
 */
var Ctrl = function() {

  /**
   * `text` model cannot by used in template because it is not exposed.
   *
   * @type {String}
   */
  this.text = 'Hello world!';

  /**
   * `info` can be used in template because it is exposed.
   *
   * @type {String}
   * @expose
   */
  this.info = '"info" can be used in the template';
};
<!-- empty span -->
<span>{{ ctrl.text }}</span>

<!-- span with '"info" can be used in the template' -->
<span>{{ ctrl.info }}</span>

So what's the difference between @export and @expose?

  • use @export for constructor functions
  • use @expose for properties inside constructor functions

Important!

The style guide from Google says that

if you are using @export for properties, you will need to add the flags:

--remove_unused_prototype_props_in_externs = false
--export_local_property_definitions

Both of them didn't work for me because the Closure Compiler threw Errors.

Namespaces

Closure Compiler modularization works very similar to Node.js modules. You can create namespaces by using goog.provide() and goog.require(). It is a bit like module.exports and require() from Node.js.

xyz-controller.js

/**
 * Create namespace.
 */
goog.provide('my.Ctrl');

/**
 * Controller.
 *
 * Assign constructor function to namespace.
 */
my.Ctrl = function() {
  // ..
};

xyz-module.js

/**
 * Require controller.
 */
goog.require('my.Ctrl');

/**
 * Module.
 */
angular.module('app', [])
  .config(function() { /*...*/ })
  .controller('Ctrl', my.Ctrl);

No more, what's the difference between angular.module('app', []) and angular.module('app') ?!?

Be careful when providing and requiring submodules. Inside our main app.js we can require submodules and inject them into our main module. However you must use the name and not the whole module.

/**
 * Require submodule
 */
goog.require('my.first.module');

/**
 * Main app.
 *
 * Inject submodule by using its `name`.
 */
my.app = angular.module('app', [
  'ui.router',
  my.first.module.name
])

'controller as' syntax

Since Angular version 1.1.5 you can use an alternative syntax for your controllers. The old way was simply using the controller name. Either in your HTML

<div ng-controller="MyCtrl">
</div>

or inside the router.

$routeProvider.when('/first', {
  templateUrl: 'first.html',
  controller: 'MyCtrl'
});

By using the alternate so called 'controller as' syntax you have fine grained control over your app.

$stateProvider
.state('third', {
  url: '/third',
  templateUrl: 'states/third/third.html',
  controller: 'ThirdCtrl as third'
})
.state('third.one', {
  url: '/one',
  templateUrl: 'states/third/one/one.html',
  controller: 'ThirdOneCtrl as thirdOne'
})
.state(...);

You can now reference the controllers in your templates.

<p>{{ third.label }}</p>
<p>{{ thirdOne.label }}</p>

As opposed to the standard syntax.

<!-- where might 'label' come from? Parent or child controller? -->
<p>{{ label }}</p>

The 'controller as' syntax provides a much better overview in templates. It is easy to see where things come from.

Everything is a JavaScript class

Yes, with prototype and new.

  • Controller

    /**
     * First controller.
     */
    var FirstCtrl = function() {
      this.text = 'Hello world!';
    };
    
    /**
     * Write value of `text` model to stdout.
     */
    FirstCtrl.prototype.log = function() {
      console.log(this.text);
    };
    
    angular.module('app', [])
      .controller('FirstCtrl', FirstCtrl);
  • Service

    Use module.service instead of module.factory or module.provider.

    /**
     * Version service.
     */
    var Version = function() {
      this.version = '0.0.1';
    };
    
    /**
     * Return the current version.
     */
    Version.prototype.get = function() {
      return this.version;
    };
    
    angular.module('app', [])
      .service('version', Version);
  • Directive

    /**
     * @constructor
     */
    var Directive = function(version) {
      this.version = version;
      this.link = this.link.bind(this);
    
      this.scope;
      this.elem;
      this.attrs;
    };
    
    /**
     * Version directive factory. Entry point and used in `module.directive`.
     */
    Directive.factory = function(version) {
      var dir = new Directive(version);
      return {
        link: dir.link
      };
    };
    
    /**
     * Linking function.
     */
    Directive.prototype.link = function(scope, elem, attrs) {
      this.scope = scope;
      this.elem = elem;
      this.attrs = attrs;
      this.elem.text(this.version.get());
    };
    
    angular.module('app', [])
      .directive('version', Directive.factory);
  • Filter

    /**
     * @constructor
     */
    Filter = function() {
      this.checkmark = '\u2714';
      this.cross = '\u2718';
      this.convert = this.convert.bind(this);
    };
    
    /**
     * Check filter factory. Entry point and used in `module.filter`.
     *
     * @return {function}
     */
    Filter.factory = function() {
      var filter = new Filter();
      return filter.convert;
    };
    
    /**
     * Actual filter function.
     */
    Filter.prototype.convert = function(input) {
      return input ? this.checkmark : this.cross;
    };
    
    angular.module('app', [])
      .filter('check', Filter.factory);

Angular UI Router

nuff said

Everything is grouped in 'states' and 'components'

Directives, Filters and Services belong into the components/ folder. Each file should have a -directive, -filter or -service suffix. As you can see we have a version Directive and a version Service. With the suffixes they are easy to differentiate.

All states belong into the states/ folder. Give each folder the appropriate state name, i.e. home/ for home state. Put child states inside their parent directories, i.e. one/ inside third for third.one state.

As all components and states have their unit tests right next to them the test/unit/ folder only contains our karma.conf.js and the Istanbul code coverage reports. The same goes for the unit/e2e/ folder. It only contains the test scenarios for our main app.js file.

Gruntfile.js
package.json
node_modules/
  ...
closure/
  library/
    base.js
    deps.js
  externs/
    angular.js
  compiler.jar
app/
  index.html
  img/
  css/
  js/
    app.js
    app.min.js
    lib/
      angular.js
      angular-ui-router.js
      ...
  components/
    directives/
      version-directive.js
      version-directive.spec.js
    filters/
      check-filter.js
      check-filter.spec.js
    services/
      version-service.js
      version-service.spec.js
  states/
    first/
      first-controller.js               // controller
      first-controller.spec.js          // controller unit tests (karma)
      first-module.js                   // module initialization with Router
      first.html                        // partial template
      first.pageobject.js               // Page Object for e2e tests (protractor)
      first.scenario.js                 // e2e tests (protractor)
    second/
      second-controller.js
      second-controller.spec.js
      second-module.js
      second.html
      second.pageobject.js
      second.scenario.js
    third/                              // parent state 'third'
      third-controller.js
      third-controller.spec.js
      third-module.js
      third.html
      third.pageobject.js
      third.scenario.js
      one/                              // child state 'third.one'
        one-controller.js
        one-controller.spec.js
        one-module.js
        one.html
        one.pageobject.js
        one.scenario.js
      two/                              // child state 'third.two'
        two-controller.js
        two-controller.spec.js
        two-module.js
        two.html
        two.pageobject.js
        two.scenario.js
test/
  e2e/
    protractor.conf.js
    scenarios.js
  unit/
    karma.conf.js
    coverage/
      ...

Important!

compiler.jar (6,8 MB) is not in this repo. You have to download it manually and place it inside the closure/ directory.

'states' are independent modules

Yes, with square brackets []. They live inside the xyz-module.js files. They have their own config() function and they all define their own routes.

/**
 * Create namespace.
 */
goog.provide('my.first.module');

/**
 * Require controller.
 */
goog.require('my.first.Ctrl');

/**
 * First module.
 */
my.first.module = angular.module('first', [
  'ui.router'
]);

/**
 * Configuration function.
 */
my.first.module.configuration = function($stateProvider) {

  $stateProvider.state('first', {
    url: '/first',
    templateUrl: 'states/first/first.html',
    controller: 'FirstCtrl as first'
  });

};

/**
 * Init first module.
 */
my.first.module
  .config(my.first.module.configuration)
  .controller('FirstCtrl', my.first.Ctrl);

Each state has its own controller. They live in the xyz-controller.js files.

/**
 * Create namespace.
 */
goog.provide('my.first.Ctrl');

/**
 * First controller.
 */
my.first.Ctrl = function() {
  // ...
};

Each state creates an own namespace. Inside the main app.js we use goog.require() to pull in all submodules and inject them into our main app.

Child states don't bubble up to our main app.js. Load them in their parent state, i.e. third.one and third.two are required in third state which is itself required by our main app.js.

This structure makes it much easier to understand data coming from resolve.

Karma unit tests with Istanbul code coverage

Using code coverage with AngularJS is really easy. Though only few projects actually include Istanbul. This seed has it built-in.

preprocessors: {
  'app/states/**/!(*.pageobject|*.scenario|*.spec).js': 'coverage'
}

minimatch is magic. The pattern includes all *.js files in our states/ directory. It ignores all *.pageobject.js, *.scenario.js and *.spec.js files. They would pass and show up green in the coverage report but they create noise. So leave them out.

To increase readability inside your Karma tests use the $injector service instead injecting services inline.

Good

beforeEach(inject(function($rootScope, $controller) {
  // ...
}));

Better

beforeEach(inject(function($injector) {
  var $rootScope = $injector.get('$rootScope');
  var $controller = $injector.get('$controller');
  // ...
}));

That one line beforeEach(inject(function(...) { can become pretty long and hard to read.

Protractor tests using Page Objects

From Organizing Real Tests: Page Objects

When writing real tests scripts for your page, it's best to use the Page Objects pattern to make your tests more readable

xyz.pageobject.js

var XYZ = function() {

  this.navigate = function() {
    browser.get('index.html#/xyz');
  };

  // ng-repeat
  this.animals = element.all(by.repeater('animal in first.animals'));

};

module.exports = new XYZ();

xyz.scenario.js

describe('first', function() {

  var xyz = require('./xyz.pageobject.js');

  beforeEach(function() {
    xyz.navigate();
  });

  it('should render all animals', function() {
    expect(xyz.animals.count()).toBe(3);
  });

});

Grunt shell instead multiple plugins

  • Why do you use grunt (or gulp or any other build tool)? Why no plain npm?

    npm run karma would create annoying npm-debug.log files when tests fail.

  • Why don't you use plugins for karma and protractor?

    The less plugins the better. Starting karma and protractor is a one liner.

Test

Karma

grunt karma

Protractor

  • start the local server

    grunt connect
  • start protractor

    grunt protractor

Build

The default task

grunt

creates app.min.js in app/js/.

References

License

MIT

Plea

@google why you not making your nghellostyle public?

More Repositories

1

json2csv

Convert json to csv with column titles
JavaScript
2,713
star
2

swift-linechart

Line Chart library for iOS written in Swift
Swift
602
star
3

nodejs-pdf-docs

Node.js Manual & Documentation (.pdf, .mobi, .epub)
TeX
497
star
4

lockit

Authentication solution for Express
JavaScript
439
star
5

sf-city-lots-json

really big json file representing san francisco's subdivision parcels
145
star
6

flexbox-grid

Grid system using CSS flex properties
CSS
128
star
7

express-upload-progress

File uploading with Express.js and progress bar
JavaScript
119
star
8

swift-timeago

Relative time / time ago for Swift
Swift
56
star
9

json2csv-stream

Transform stream from json to csv
JavaScript
52
star
10

node-require-s--best-practices

Some best practices / conventions when using Node's `require()`
JavaScript
51
star
11

couchdb

CouchDB client in Go
Go
44
star
12

tu-darmstadt-latex-thesis

LaTeX template for any thesis at the TU Darmstadt
41
star
13

swift-couchbaselite-cheatsheet

Swift CouchbaseLite Cheat Sheet
Objective-C
39
star
14

keycloak

Go library for accessing the Keycloak API
Go
34
star
15

swift-piechart

Piechart library for iOS written in Swift
Swift
32
star
16

ng-form-shake

CSS3 form shake effect with AngularJS
CSS
27
star
17

couchdb-cookie-auth

CouchDB cookie authentication example with PouchDB
JavaScript
25
star
18

ng-signup-form

GitHub like signup form with AngularJS
JavaScript
24
star
19

email

Easily send HTML emails with Golang
Go
19
star
20

mozilla-persona-express-couchdb

Mozilla Persona example app with Express and CouchDB
JavaScript
18
star
21

csrf-express-angular

CSRF protection example app built with Express and Angular
JavaScript
18
star
22

gartner-wcm-d3

Interactive summary of Gartner's Magic Quadrant for Web Content Management with d3.js
JavaScript
17
star
23

swift-couchdb

CouchDB client for Swift
Swift
15
star
24

couch-pwd

CouchDB like salt and password generation with PBKDF2
JavaScript
12
star
25

follow

Go client for CouchDB _changes API
Go
12
star
26

primus-express-session

Share a user session between Express and Primus
JavaScript
11
star
27

d3-hill-chart

d3 hill chart
JavaScript
10
star
28

uid

URL safe string generator for Go / Golang
Go
10
star
29

pouchdb-vs-couchbase

cordova app to compare performance of pouchdb and couchbase lite phonegap
Objective-C
9
star
30

simple-recaptcha

simple implementation of google's reCAPTCHA
JavaScript
9
star
31

codedeploy-golang

AWS CodeDeploy Golang app
Shell
9
star
32

lockit-signup

sign up routes for lockit middleware
JavaScript
9
star
33

lockit-login

login and logout routes for lockit
JavaScript
9
star
34

lockit-couchdb-adapter

CouchDB adapter for lockit
JavaScript
8
star
35

lockit-sendmail

Email utilities for lockit
JavaScript
7
star
36

lockit-mongodb-adapter

MongoDB adapter for lockit
JavaScript
7
star
37

bannerify

Add banner to your browserify bundle
JavaScript
7
star
38

memorystore

memory session store for gorilla/sessions
Go
6
star
39

dok

JavaScript documentation generator based on JSDoc
JavaScript
6
star
40

papertrail

Golang client for papertrail
Go
6
star
41

lockit-utilities

Utilities module for lockit
JavaScript
6
star
42

old-zemirco.github.io

My private blog powered by Octopress
JavaScript
5
star
43

zemirco.github.io

http://mircozeiss.com/ powered by Jekyll
CSS
4
star
44

lockit-delete-account

delete account middleware for lockit
JavaScript
4
star
45

dcp

Discovery and Basic Configuration Protocol (DCP)
Go
4
star
46

enterjs

react d3 showcase
JavaScript
3
star
47

grunt-jade-preprocess

Grunt deployment process with Jade
JavaScript
3
star
48

seriesci-demo

Series CI demo
Shell
3
star
49

lockit-forgot-password

forgot password middleware for lockit
JavaScript
3
star
50

panel.jsx

React Panel component
JavaScript
2
star
51

swift-http

Swift HTTP
Swift
2
star
52

swift-scale

d3.js like scale in Swift
Swift
2
star
53

ng-scoreboard-clock

An angular.js directive to display the current time as seen on sports scoreboards.
JavaScript
2
star
54

couchdb-browser-auth

CouchDB user authentication from the browser
JavaScript
2
star
55

lockit-template-blank

blank email template for lockit-sendmail
JavaScript
2
star
56

codedeploy-couchdb

CodeDeploy CouchDB
Shell
2
star
57

docker

Dockerfiles
Go
1
star
58

branch

branch demo
1
star
59

enterjs2020

enterJS 2020
TypeScript
1
star
60

couchdb-ssl

CouchDB with nginx
Nginx
1
star
61

jira

JIRA REST API client in Go
Go
1
star
62

protoc-gen-twirp-es

JavaScript code generator for Twirp RPC
Go
1
star
63

sankey

D3 sankey
JavaScript
1
star
64

zemirq

nng test
Go
1
star
65

dotfiles

dotfiles
Vim Script
1
star
66

lockit-profile

sample '/profile' route for Lockit
JavaScript
1
star
67

swift-querystring

Swift library for encoding classes into URL query parameters
Swift
1
star
68

table.jsx

React Table component
JavaScript
1
star
69

lockit-koa-login

Lockit login module for Koa
JavaScript
1
star
70

string2stream

Converts a string into a stream by emitting one character after the other
JavaScript
1
star
71

enterjs2019

enterJS 2019 demo app
JavaScript
1
star
72

lockit-sql-adapter

SQL adapter for lockit
JavaScript
1
star