Brown Bag Presentation

Meet AngularJS!

Data Binding and Responding to Scope Changes

This example demonstrates how to react on a model change to trigger some further actions. The value greeting will be changed whenever there’s a change to the name model and the value is not blank.

<html>
  <head>
    <script src="js/angular.js"></script>
    <script src="js/app.js"></script>
    <link rel="stylesheet" href="css/bootstrap.css">
  </head>
  <body ng-app>
    <div ng-controller="MyCtrl">
      <input type="text" ng-model="name" placeholder="Enter your name">
      <p>{{greeting}}</p>
    </div>
  </body>
</html>
function MyCtrl($scope) {
  $scope.name = "";

  $scope.$watch("name", function(newValue, oldValue) {
    if (newValue.length > 0) {
      $scope.greeting = "Greetings " + newValue;
    }
  });
}

About Angular

How does it stand out from the crowd?

Core Features

Core concepts

Modules

Modules exist as containers that can provide configuration information, runtime dependencies, and other infrastructure support.

An Angular (main) "application" is just a module except...

With Angular you can place all your controllers into a one module and all your services into a second module, or put the controllers and services all inside a single module

Scopes

Scope Inheritance

In this example, we can then reference data on the ParentController’s containing $scope on the child scope.

<div ng-controller="ParentController">
  <div ng-controller="ChildController">
    <a href="#" ng-click="sayHello()">Say hello</a>
  </div>
  {{ person }}
</div>
  var app = angular.module('myApp', []);

  app.controller('ParentController', function($scope) {
    $scope.person = { greeted: false };
  })

  app.controller('ChildController', function($scope) {
    $scope.sayHello = function() {
      $scope.person.name = "Chuck Norris";
      $scope.person.greeted = true;
    }
  })

Controllers

var app = angular.module('myApp', []);
app.controller('MyController', ['$scope', function($scope) {
  $scope.counter = 0;
  $scope.add = function(amount) {
    $scope.counter += amount;
  };

  $scope.subtract = function(amount) {
    $scope.counter -= amount;
  };
}]);

One major difference between other JavaScript frameworks and AngularJS is that the controller is not the appropriate place to do any DOM manipulation or formatting, data manipulation, or state maintenance beyond holding the model data. It is simply the glue between the view and the $scope model.

Services

The most common and flexible way to create a service uses the angular.module API factory:

// Example service that holds on to the current_user for the lifetime of the app
angular.module('myApp.services', [])
  .factory('UserService', ['$http', function($http) {

    var current_user;

    return {
      getCurrentUser: function() {
        return current_user;
      },
      setUsername: function(user) {
        current_user; = user;
      }
    }

  }]);

Views

Expressions

Filters

Angular Filters are typically used to format expressions in bindings in your template. They transform the input data to a new formatted data type. Filters are invoked in the HTML with the | (pipe) character in the template binding.

Built-in filter examples:

<div>{{ name | uppercase }}</div>
<div>{{ 123.456789 | number:2 }}</div>
<div>{{ 123 | currency }}</div>

<div>{{ today | date:'medium' }}</div>
<div>{{ today | date:'short' }}</div>
<div>{{ today | date:'fullDate' }}</div>
<div>{{ today | date:'longDate' }}</div>
<div>{{ today | date:'mediumDate' }}</div>
<div>{{ today | date:'shortDate' }}</div>
<div>{{ today | date:'mediumTime' }}</div>
<div>{{ today | date:'shortTime' }}</div>
<div>Month in year: {{ today | date:'MMMM' }}</div>
<div>Short month in year: {{ today | date:'MMM' }}</div>
<div>Padded month in year: {{ today | date:'MM' }}</div>
<div>Month in year: {{ today | date:'M' }}</div>

<div>{{ ['Ari', 'Lerner', 'Likes', 'To', 'Eat', 'Pizza'] | filter:'e' }}</div>

<div>
  {{ [{
      'name': 'Ari',
      'City': 'San Francisco',
      'favorite food': 'Pizza'
      }, {
      'name': 'Nate',
      'City': 'San Francisco',
      'favorite food': 'indian food'
      }] | filter:{'favorite food': 'Pizza'} }}
</div>

Implementing a Custom Filter to Reverse an Input String:

<body ng-app="MyApp">
  <input type="text" ng-model="text" placeholder="Enter text"/>
  <p>Input: {{ text }}</p>
  <p>Filtered input: {{ text | reverse }}</p>
</body>
var app = angular.module("MyApp", []);

// Create custom filter
app.filter("reverse", function() {
  return function(input) {
    var result = "";
    input = input || "";
    for (var i=0; i<input.length; i++) {
      result = input.charAt(i) + result;
    }
    return result;
  };
});

Advanced Concepts

Directives

Directives are Angular’s method of creating new HTML elements with their own custom functionality. For instance, we can create our own custom element "my-directive"

angular.module('BrownBagDemoApp')
  .directive('myDirective', function () {
    return {
      restrict: 'E',
        template: '<a href="http://pentaho.com">Click me to go to Pentaho</a>'
    };
  });

Digest Cycle

How does Angular know when something has changed and it's time to update the bindings??

Dependency Injection

// Implicit
angular.module('myApp')
  .controller('MyCtrl', function($scope, myService) {
  // Do something awesome
});


// Explicit
var MyCtrl = function(some$scope, someService) {
  // Do something awesome
}
MyCtrl.$inject = ['$scope', 'myService'];


// Inline
angular.module('myApp')
  .controller('MyCtrl', ['$scope', 'myService',
  	function($scope, myService) {
      // Do something awesome
}]);

Routing Services

angular.module("MyApp", []).
  config(function($routeProvider, $locationProvider) {
    $routeProvider
      .when("/persons", {
      	templateUrl: "partials/index.html"
      })
      .when("/persons/:id", { // In ShowCtrl, id is available via $routeParams.id
      	templateUrl: "partials/show.html",
      	controller: "ShowCtrl"
      })
      .when("/login", {
      	templateUrl: "partials/login.html",
      	controller: "LoginCtrl"
      })
      .when("/help", {
      	templateUrl: "partials/help.html"
      })
      .otherwise( {
       	redirectTo: "/persons"
      });
  });

Testing

Unit Test with Jasmine

Anatomy of a Jasmine Test

describe('hello World test', function () {

  var greeter;
  beforeEach(function () {
    greeter = new Greeter();
  });

  it('should say hello to the World', function () {
    expect(greeter.say('World')).toEqual('Hello, World!');
  });

});

Testing AngularJS Objects with Jasmine

We need to ensure that a proper AngularJS module is initialized and the whole DI machinery is configured

describe('UserService Service', function() {

  // indicate that services from a given module should be prepared for the test
  // $injector should be created for a given module (and all the dependent modules).
  beforeEach(module('services'));

  var userService;

  beforeEach(inject(function(UserService) { // inject UserService and assign to local variable
    userService = UserService;
  }));

  // execute test
  it('should have user', function() {
    expect(userService.getUser()).toEqual('testUser');
  });
});

Best Practices

There are some great resources available on this topic already. Instead of duplicating all of them, here are some links to review.

However, it does makes sense reiterate some of the most relevant ideas as well as a few that apply to Pentaho-specific development.

In a large scale web application, it is not uncommon to have dozens and dozens of different routes. While the $routeProvider service offers a very nice, fluent style API, route definitions can get quite verbose (especially when the resolve property is used). If we combine a large number of routes with the relative verboseness of each definition, we quickly end up maintaining a huge JavaScript file with several hundreds of lines of code! Worse yet, having all routes defined in one huge file means that this file must be modified quite frequently by all developers working on a project – a recipe for creating a bottlenecks and disastrous merge issues.

With AngularJS, we are not forced to define all the routes in one central file! If we take the approach of modularizing each functional area (has its own dedicated module), we can move routes linked to a certain part of an application to the corresponding module.

In the AngularJS module system, each and every module can have its associated config function, where we can inject $routeProvider service and define routes. For example: each of these submodules defines its own routes as follows:

angular.module('admin-users', [])
.config(function ($routeProvider) {
  $routeProvider.when('/admin/users', {...});
  $routeProvider.when('/admin/users/new', {...});
  $routeProvider.when('/admin/users/:userId', {...});
});

angular.module('admin-projects', [])
.config(function ($routeProvider) {
  $routeProvider.when('/admin/users', {...});
  $routeProvider.when('/admin/users/new', {...});
  $routeProvider.when('/admin/users/:userId', {...});
});

angular.module('admin', ['admin-projects', 'admin-users']);

Pentaho Platform Integration

Plugging into Pentaho User Console

A seed project has been created to demonstrate the usage of the PUC Plugin API. The REAMDME on the Github page describes how to use it. https://github.com/pentaho/pentaho-angular-seed