AngularJS Training

JavaScript tips and best pratices

Variable declaration

What is the diffrence between:

function() {
    var name = "Toto"
    console.log("Hello, " + name);
}

And:

function() {
    name = "Toto"
    console.log("Hello, " + name);
}

Variable declaration

If you do not use "var" to declare a variable, the variable would be declare directly on the window object

function() {
    name = "Toto"

    // this
    console.log("Hello, " + name);

    // same as this
    console.log("Hello, " + window.name);
}

// and also available from every where 
// outside the scope of the function
console.log("Hello, " + window.name);
  • can be source of conflict (jQuery declare "$" as a global variable, for exemple)
  • use this notation to create a namespace for your application

Variable declaration

Exemple of namespace:

// create the namespace
ManagersUtils = {};

// declare a variable in the namespace
ManagersUtils.cxsContext = '/cxs/';
ManagersUtils.formMappingActionStrategies = ['alwaysSet', 'setIfMissing', 'merge'];

// declare a function in the namespace
ManagersUtils.getQueryOrDefault = function (query) {
    // do something
};

ManagerUtils wrap all the function, constants, variables needed for the application, this limit the conflict with other API in the page, because "ManagerUtils" is a reserved name for us.

Stop global declarations

  • Always use namespace to wrap your functions
  • Stop global declarations
  • Using namespace is the first step of JavaScript Object programming
  • A namespace is in fact an object with properties containing variables and functions.
  • APIs use namespace, jquery use "$", angularjs use "angular", underscore use "_"
  • Don't use existing API namespace for your namespaces

JavaScript is turn based

The JavaScript code we write doesn’t all run in one go, instead it executes in turns. Each of these turns runs uninterupted from start to finish, and when a turn is running, nothing else happens in our browser.

var button = document.getElementById('clickMe');

function buttonClicked () {
  alert('the button was clicked');
}

button.addEventListener('click', buttonClicked);

function timerComplete () {
  alert('timer complete');
}

setTimeout(timerComplete, 2000);

Callbacks system

a callback is a piece of executable code that is passed as an argument to other code

function mySandwich(param1, param2, callback) {
    alert('Started eating my sandwich.\n\nIt has: ' + param1 + ', ' + param2);
    callback();
}

mySandwich('ham', 'cheese', function() {
    alert('Finished eating my sandwich.');
});

Really powerfull system, allow to create generics behavior. Also usefull for Asynchronous operations

AngularJs concepts

Angularjs, HTML extends ?

Angular is used directly in the HTML to attach logical operations.

Tags, attributs or class are the commons usage of angularjs html elements.

This approach allow to separate the JavaScript code from the view.

Angular provide builtin elements, but allow us to create new components by many ways that we will see later.


<div ng-repeat="(tagId, tag) in tags">
    <div ng-if="tag.enabled">
        <h2>{{tag.name}}</h2>
    </div>
</div>

Cross data binding

Remember JavaScript is turn based, angular perform changes on model and view at the end of each turn of executions.

Cross data binding

<div ng-app>
    <input type="text" ng-model="message" />
    <input type="range"  min="1" max="100" ng-model="size" />
    <hr>
    <div style="font-size:{{size}}em;">{{message}}</div>
</div>

Cross data binding: apply

Sometime you would need to apply the modification on the model manually, why and when do you have to do that ?

function Ctrl($scope) {
  $scope.message = "Waiting 2000ms for update";
    
    setTimeout(function () {
        $scope.$apply(function () {
            $scope.message = "Timeout called!";
        });
    }, 2000);
}

Remember javascript is turn based. Angular can't know that the timeout turn need to update the model. That's why we have to manualy wrap the model update in the apply fct

Angular provide a $timeout service to avoid that, it was fot the exemple :)

Cross data binding: watch

Sometime you want to listen on model modification, to update the state of the model, or execute action. For that there is the "watch" function.

var scope = $rootScope;
scope.name = 'misko';
scope.counter = 0;

expect(scope.counter).toEqual(0);
scope.$watch('name', function(newValue, oldValue) {
  scope.counter = scope.counter + 1;
});
expect(scope.counter).toEqual(0);

scope.$digest();
// the listener is always called during the first $digest loop after it was registered
expect(scope.counter).toEqual(1);

scope.$digest();
// but now it will not be called unless the value changes
expect(scope.counter).toEqual(1);

scope.name = 'adam';
scope.$digest();
expect(scope.counter).toEqual(2);

Cross data binding: watch

// Using a function as a watchExpression
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
  // This function returns the value being watched. It is called for each turn of the $digest loop
  function() { return food; },
  // This is the change listener, called when the value returned from the above function changes
  function(newValue, oldValue) {
    if ( newValue !== oldValue ) {
      // Only increment the counter if the value changed
      scope.foodCounter = scope.foodCounter + 1;
    }
  }
);
// No digest has been run so the counter will be zero
expect(scope.foodCounter).toEqual(0);

// Run the digest but since food has not changed count will still be zero
scope.$digest();
expect(scope.foodCounter).toEqual(0);

// Update food and run digest.  Now the counter will increment
food = 'cheeseburger';
scope.$digest();
expect(scope.foodCounter).toEqual(1);

Angularjs bootstraping app

Bootstraping of angular app is done automatically on DOMContentLoaded event or when angular.js script is evaluated. Automatic bootstraping only bootstrap the first ng-app="module" in the page.

 

But sometime we need to have more control over the initialization process. A manual bootstraping is possible, using manual bootstraping you can have multiple ng-app in the same page.

Angularjs manual bootstraping

<!doctype html>
<html>
<body>
  <div ng-controller="MyController">
    Hello {{greetMe}}!
  </div>
  <script src="http://code.angularjs.org/snapshot/angular.js"></script>

  <script>
    angular.module('myApp', [])
      .controller('MyController', ['$scope', function ($scope) {
        $scope.greetMe = 'World';
      }]);

    angular.element(document).ready(function() {
      angular.bootstrap(document, ['myApp']);
    });
  </script>
</body>
</html>

AngularJS App details

Angularjs components:

<div ng-app="ngAppDemo" ng-strict-di>
    <div ng-controller="GoodController1">
        I can add: {{a}} + {{b}} =  {{ a+b }}
    </div>

    <div ng-controller="GoodController2">
        Name: <input ng-model="name"><br />
        Hello, {{name}}!
    </div>
</div>
angular.module('ngAppDemo', [])

.controller('GoodController1', ['$scope', function($scope) {
  $scope.a = 1;
  $scope.b = 2;
}])

.controller('GoodController2', GoodController2);
function GoodController2($scope) {
  $scope.name = "World";
}
GoodController2.$inject = ['$scope'];

identify:

application, controller, scope, injection, template, module.

Dependency injection

angular.module('myModule', [])


.factory('serviceId', ['depService', function(depService) {
  // ...
}])
.directive('directiveName', ['depService', function(depService) {
  // ...
}])
.filter('filterName', ['depService', function(depService) {
  // ...
}]);

The way you define a directive, service, or filter is with a factory function. The factory methods are registered with modules. The recommended way of declaring factories is:

Dependency injection

someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
  ...
  $scope.aMethod = function() {
    ...
  }
  ...
}]);

Controllers are "classes" or "constructor functions" that are responsible for providing the application behavior that supports the declarative markup in the template. The recommended way of declaring Controllers is using the array notation:

Module

Angular apps don't have a main method. Instead modules declaratively specify how an application should be bootstrapped. There are several advantages to this approach:

  • The declarative process is easier to understand.
  • You can package code as reusable modules.
  • The modules can be loaded in any order (or even in parallel) because modules delay execution.
  • Unit tests only have to load relevant modules, which keeps them fast.
  • End-to-end tests can use modules to override configuration.

Module

<div ng-app="myApp">
  <div>
    {{ 'World' | greet }}
  </div>
</div>
// declare a module
var myAppModule = angular.module('myApp', []);

// configure the module.
// in this example we will create a greeting filter
myAppModule.filter('greet', function() {
 return function(name) {
    return 'Hello, ' + name + '!';
  };
});

introducing filters !

  • notice the empty array
  • notice the ng-app

Module config

angular.module('myModule', []).
config(function(injectables) { // provider-injector
  // You can have as many of these as you want.
  // You can only inject Providers (not instances)
  // into config blocks.
}).
run(function(injectables) { // instance-injector
  // You can have as many of these as you want.
  // You can only inject instances (not Providers)
  // into run blocks
});

Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application.

Module config

angular.module('myModule', []).
  value('a', 123).
  factory('a', function() { return 123; }).
  directive('directiveName', ...).
  filter('filterName', ...);

// is same as

angular.module('myModule', []).
  config(function($provide, $compileProvider, $filterProvider) {
    $provide.value('a', 123);
    $provide.factory('a', function() { return 123; });
    $compileProvider.directive('directiveName', ...);
    $filterProvider.register('filterName', ...);
  });

When bootstrapping, first Angular applies all constant definitions. Then Angular applies configuration blocks in the same order they were registered.

There are some convenience methods on the module which are equivalent to the config block. For example:

Providers:

Each web application you build is composed of objects that collaborate to get stuff done. These objects need to be instantiated and wired together for the app to work. In Angular apps most of these objects are instantiated and wired together automatically by the injector service.

 

Provider, value, factory, service and constant are differente entry point to create your own API (services) or angular specialized objects (directives, filters, controllers, ...)

Providers: Value recipe

Let's say that we want to have a very simple service called "clientId" that provides a string representing an authentication id used for some remote API. You would define it like this:

// declaration of a service "clientId" using value recipe
var myApp = angular.module('myApp', []);
myApp.value('clientId', 'a12345654321x');

// declaration of a controller, where we inject the "clientId" service
myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
  this.clientId = clientId;
}]);
<html ng-app="myApp">
  <body ng-controller="DemoController as demo">
    Client ID: {{demo.clientId}}
  </body>
</html>

Notice this new notation, no scope

Providers: Factory recipe

The Value recipe is very simple to write, but lacks some important features we often need when creating services. 

  • ability to use other services (have dependencies)
  • service initialization
  • delayed/lazy initialization
// we could have parameters here for dependencies on other services
myApp.factory('clientId', function clientIdFactory() {
  return 'a12345654321x';
});

Providers: Service recipe

JavaScript developers often use custom types to write object-oriented code.
function UnicornLauncher(apiToken) {

  this.launchedCount = 0;
  this.launch = function() {
    // Make a request to the remote 
    // API and include the apiToken
    this.launchedCount++;
  }
}
// using factory
myApp.factory('unicornLauncher', ["apiToken", function(apiToken) {
  return new UnicornLauncher(apiToken);
}]);
// using service is faster for this need
myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);

Providers: Service recipe

Angular services are substitutable objects that are wired together using injection. You can use services to organize and share code across your app.

 

Angular only instantiates a service when an application component depends on it.

 

Each component dependent on a service gets a reference to the single instance generated by the service factory.

 

Angular offers several useful services (like $http), but for most applications you'll also want to create your own.

Providers: Provider recipe

 core recipe type and all the other recipe types are just syntactic sugar on top of it.
myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
  var useTinfoilShielding = false;

  this.useTinfoilShielding = function(value) {
    useTinfoilShielding = !!value;
  };

  this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {

    // let's assume that the UnicornLauncher constructor was also changed to
    // accept and use the useTinfoilShielding argument
    return new UnicornLauncher(apiToken, useTinfoilShielding);
  }];
});
myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
  unicornLauncherProvider.useTinfoilShielding(true);
}]);

Providers: Constant recipe

 Constants are available during configuration and runtime phase
myApp.constant('planetName', 'Greasy Giant');

// config phase, let's configure the unicornLauncher with a default planet
myApp.config(['unicornLauncherProvider', 'planetName', 
    function(unicornLauncherProvider, planetName) {
      unicornLauncherProvider.useTinfoilShielding(true);
      unicornLauncherProvider.stampText(planetName);
    }]);

// runtime, let's use the default planet of other purpose
myApp.controller('DemoController', ["clientId", "planetName", 
    function DemoController(clientId, planetName) {
      this.clientId = clientId;
      this.planetName = planetName;
    }]);

Providers: Conclusion

  • The injector uses recipes to create two types of objects: services and special purpose objects
  • There are five recipe types that define how to create objects: Value, Factory, Service, Provider and Constant.
  • Factory and Service are the most commonly used recipes.
  • Provider is the most complex recipe type. You don't need it unless you are building a reusable piece of code that needs global configuration.
  • All special purpose objects except for the Controller are defined via Factory recipes.

Service example

<body ng-app="myServiceModule">
    <div id="simple" ng-controller="MyController">
        <input ng-init="message='test'" ng-model="message" />
        <button ng-click="callLog(message);">Log</button> 
        <button ng-click="callNotify();">Notify</button>
    </div>
</body>
angular.module('myServiceModule', [])
    .service('notifyService', function(){
      // list of msg logged
      var msgs = [];

      // log a new message
      this.log = function(msg) {
        msgs.push(msg);
      }
      
      // display all the message
      this.notify = function() {
        window.alert(msgs.join("\n"));
        msgs = [];
      }
  })
angular.module('myServiceModule').
  controller('MyController', 
    ['$scope','notifyService', 
        function ($scope, notifyService) {

            // init message
            $scope.message = "test";

            $scope.callLog = function(msg) {
              notifyService.log(msg);
              $scope.message = "";
            };

            $scope.callNotify = function() {
              notifyService.notify();
            };
          }])

Directive

At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS'sHTML compiler to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children.

 

Angular normalizes an element's tag and attribute name to determine which elements match which directives.

 

Directive declare as ngBind, sould be use in HTML with ng-bind, there is other syntaxes for usage, but this is the prefered one.

Directive

<body ng-app="HelloApp">
  <div ng-controller="HelloCtrl">
    <input type="text" ng-model="name"/>
    <hello-world name="name"></hello-world>
  </div>
</body>
angular.module('components', [])
    .directive('helloWorld', function () {
        return {
            restrict: 'E',
            scope:{
                name:'='
            },
            template: '<span>Hello {{name}}</span>'

        }
    })

angular.module('HelloApp', ['components'])
.controller("HelloCtrl", ["$scope", function($scope){
  $scope.name = "Toto";
}])

Directive: Types

<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>

Best Practice: Prefer using directives via tag name and attributes over comment and class names.

You can restrict the type of the directive, using "restrict" option:

  • 'A' : only matches attributes name
  • 'E' : only matches element name
  • 'C' : only matches class name
  • 'AEC' : matches attribute, element or class name

Directive: Template

Not mandatory but a directive can have associated template:

angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() { // using templateUrl
  return {
    restrict: 'E',
    templateUrl: 'my-customer.html'
  };
})
.directive('myCustomer', function() { // using plain template
  return {
    restrict: 'E',
    template: 'Name: {{customer.name}} Address: {{customer.address}}'
  };
});
<div ng-controller="Controller">
  <div my-customer></div>
</div>

notice the inheritance of the scope and the restrict 'E'

Directive: Isolated scope

By default directive inherit of the parent scope, but directive can have there own scope.

angular.module('docsIsolateScopeDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
  $scope.igor = { name: 'Igor', address: '123 Somewhere' };
}])
.directive('myCustomer', function() {
  return {
    restrict: 'E',
    scope: {
      customerInfo: '=info',
      info: '=' // in case attr is same name
    },
    templateUrl: 'my-customer-iso.html'
  };
});
<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
  <hr>
  <my-customer info="igor"></my-customer>
</div>

notice the "=info"

and the "="

Directive: isolated scope

There is multiple way to pass data to the isolated scope. We have seen thr "=info" or "=" when the attribut is the same name as the scoped model.

But there is also "@info" or "&info"

  • @ or @attr - bind a local scope property to the value of DOM attribute. The result is always a string since DOM attributes are strings.
  • = or =attr - set up bi-directional binding between a local scope property and the parent scope property of name defined via the value of the attr attribute. 
  • & or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. 

Directive: DOM modification

angular.module('docsTimeDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.format = 'M/d/yy h:mm:ss a';
}])
.directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) {
  return {
    link: function (scope, element, attrs) {
        var format, timeoutId;

        scope.$watch(attrs.myCurrentTime, function(value) {
          format = value;
          element.text(dateFilter(new Date(), format));
        });
    
        // start the UI update process; save the timeoutId for canceling
        timeoutId = $interval(function() {
          element.text(dateFilter(new Date(), format)); // update DOM
        }, 1000);
    }
  };
}]);
<div ng-controller="Controller">
  Date format: <input ng-model="format"> <hr/>
  Current time is: <span my-current-time="format"></span>
</div>

Directive: Transclude

sometime you want to use the body of the directive inside the directive template it self, for that have "transclude" property

<div ng-controller="Controller">
  <my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.name = 'Tobias';
}])
.directive('myDialog', function() {
  return {
    restrict: 'E',
    transclude: true,
    template: '<div class="alert" ng-transclude></div>'
  };
});

Directive: definition

Finally directive can have there own controller, exactly the same way a controller work on a piece of the template.

Directive can be really complexe, it's always better to keep them simple. But sometime it's not possible. Here is a link with all the parameter explained for directives:

https://docs.angularjs.org/api/ng/service/$compile

And also an interesting article on different DOM modification hooks, compile, link, pre-link and post-link. A must read for directive developer:

http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/

Routing

Sometime you want to provide pages in your app, sort of flow of views. This is possible using routes.

<div ng-controller="MainController">
  Choose:
  <a href="Book/Moby">Moby</a> |
  <a href="Book/Moby/ch/1">Moby: Ch1</a> |
  <a href="Book/Gatsby">Gatsby</a> |
  <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
  <a href="Book/Scarlet">Scarlet Letter</a><br/>

  <div ng-view></div>

  <hr />

  <pre>{{$location.path()}}</pre>
  <pre>{{$route.current.templateUrl}}</pre>
  <pre>{{$route.current.params}}</pre>
  <pre>{{$route.current.scope.name}}</pre>
  <pre>{{$routeParams}}</pre>
</div>
// routing using config block to configure the routeProvider
.config(function($routeProvider) {
  $routeProvider
   .when('/Book/:bookId', {
    templateUrl: 'book.html',
    controller: 'BookController'
  })
  .when('/Book/:bookId/ch/:chapterId', {
    templateUrl: 'chapter.html',
    controller: 'ChapterController'
  });
});

Base template:

Routing:

Routing

.controller('MainController', function($scope, $route, $routeParams, $location) {
     $scope.$route = $route;
     $scope.$location = $location;
     $scope.$routeParams = $routeParams;
 })

 .controller('BookController', function($scope, $routeParams) {
     $scope.name = "BookController";
     $scope.params = $routeParams;
 })

 .controller('ChapterController', function($scope, $routeParams) {
     $scope.name = "ChapterController";
     $scope.params = $routeParams;
 })
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
Chapter Id: {{params.chapterId}}
controller: {{name}}<br />
Book Id: {{params.bookId}}

book.html

chapter.html

Routing: resolve

The exemple above was simple, sometime you want to resolve some object before display the view. Because you are loading the book using an external service for exemple. Let see how to do that:

// routing using config block to configure the routeProvider
.config(function($routeProvider) {
  $routeProvider
   .when('/Book/:bookId', {
    templateUrl: 'book.html',
    controller: 'BookController',
    resolve: {
         // resolve book here to avoid async issue using deferred API
         "book": function ($q, $route, bookService) {
             var book = $q.defer();
             bookService.getBook($route.current.params.bookId).success(function (bookData) {
                 book.resolve(bookData);
             });
             return book.promise;
          }
    }
  })
...
});

Routing: resolve

The "book" is now injectable directly in the BookContoller, let take a look at the bookService also:

.controller('BookController', function($scope, $routeParams, book) {
     $scope.name = "BookController";
     $scope.params = $routeParams;
     $scope.book = book;
 })
.service('bookService', ['$http', function ($http) {
    ...

    this.getBook = function (id) {
        return $http.get("/externalAPI/book/" + id);
    };
    ...
});

Advantages: view display only when book is back from the request, error can be handle to cancel the display of the view. and catch error message

Routing: resolve + service

I really like this approach to design routes, centralize all the services that call external API or not in "services" so they are the only place where $http is used.

By doing that I can use a .config block to react to configure $http only one time for all the calls in my app.

This allow me to catch all the errors using a generic function, use credentials, etc ...

angular.module('wemServices', ['underscore', 'ui-notification', 'i18n'])
    .config(['$httpProvider', 'NotificationProvider', function ($httpProvider, NotificationProvider) {
        $httpProvider.defaults.headers.common['Accept-Language'] = ManagersContext.jahiaUILocale;

        $httpProvider.interceptors.push(function ($q) {
            return {
                'responseError': function (rejection) {
                    if (ManagersUtils.notificationProvider && rejection.status != 404) {
                        ManagersUtils.notificationProvider.error("Error : " + rejection.statusText);
                    }
                    return $q.reject(rejection);
                }
            };
        });
...

Tutorial

We are now more than ready to start devellop an Angularjs APP let's do the angularjs APP tutorial: https://docs.angularjs.org/tutorial

AngularJS 2

(small talk)

Angular2

  • Written inTypeScript, typed language. 
    • Type error at compile time, easy to debug
    • better support auto-completion by IDEs
    • JavaScript VM is able to make better code optimizations
  • No controllers, Angular2 bet on component-based UI.
  • No more cross data binding, More explicit data-flow, no circular dependencies, better perf.
  • Real modules, version 1 doesn't allow to load module asynchronously without hacky solutions
  • No more $scope, properties are directly bind to properties of the "component"

Component exemple

@Component({
  selector: 'sample-app',
  componentServices: [
    NameList
  ]
})
@Template({
  url: './templates/sample-app.html',
  directives: [Foreach]
})
class SampleApp {
  constructor() {
    this.names = NameList.get();
    this.newName = '';
  }
  addName(newname) {
    this.names.push(newname.value);
    newname.value = '';
  }
}
...
<ul>
  <li *foreach="#name in names"></li>
</ul>
...

No scope at all :)

Day-02 Angular 1.0

By Tarun Sharma

Day-02 Angular 1.0

  • 893