Skip to content

Instantly share code, notes, and snippets.

@fakhrizaki
Last active December 20, 2015 01:10
Show Gist options
  • Save fakhrizaki/011e9f5672146b444162 to your computer and use it in GitHub Desktop.
Save fakhrizaki/011e9f5672146b444162 to your computer and use it in GitHub Desktop.
Understanding Angular JS 1

Dependency injection:

In simple words, it is passing objects to functions

Angular annotates (converts them to a string) all the arguments passed to the controller function and check if it contains any of the default services (e.g. $scope, $http). It then parses those services returns them as an object in place of that argument to the calling function. The order of the parameters doesn't matter in this case as Angular inserts the service object in its right place.

$scope:

It is the service that is being passed to the controller function. It is a bond between the DOM and the JS.

Angular gives a new instance of the $scope object to each controller. So variables to attached to $scope inside one controller doesn't effect the variables or functions attached to it in the other controller. Each controller has its own version of $scope instance.

How to include external modules:

angular.module('myApp', [<modules>]);

<modules> - is the list (array) of external modules that are not part of angular itself. E.g. ngMessages

This tells Angular that my app is dependent on this dependency list. In this way all the features of that modules are available to the app to be used.

Example: by including ngResource in our app dependency list, it exposes $resource services to our app, which can be passed to the controller function for fetching data from external source.

Dependency Injection and Minification:

Minification can break Angular's dependency injection, because it renames Angular services to reduce the size of the file. Example: $scope turns to a after minification.

// this won't work after minification
app.controller("mainController", function($scope, $log) {
  // code goes here
});

So the trick is to use the ARRAY form of dependency injection - pass an array of dependencies as string and the function as a last argument with the list of same dependencies in the SAME order:

// this will work after minification
app.controller("mainController", ['$scope', '$log', function(a, b) {
  // code goes here
}]);

The above works, because the minifier will not ever change the string (anything between the two quotes)

Scope and Interpolation:

Interpolation:

Creating a string by combining strings and variables

Example: 'My name is ' + name

$scope

Whatever is sitting in the $scope, becomes available inside the view.

// in controller
$scope.name = 'Tony';

// in HTML
My name is {{ name }}

Angular will look in to the scope for that variable and interpolate it.

Two Way Data-binding and Directives:

Directives:

A directive is an instruction to Angular to manipulate a piece of DOM. For example, hide this, create this, add a class etc.

ng-model

<input type="text" ng-model="name" />

ng-model="name" says, look for name in the $scope object and attach the element to that model/property.

Two way data binding

View updates the model, and the model updates the view

MV* - Model-View-Whatever

// model

$scope.handle = '';
$scope.lowerCaseHandle = function () {
  // returns the lower case handle using $filters service
};

// view

<input type="text" ng-model="handle" />
<h1>twitter.com/{{ lowerCaseHandle() }}</h1>

// * - whatever ???

Watchers and Digest Loop is the * (Whatever)

JS has event loop - that is constantly listening to events happening on the page like click, mouseover, keypress, change etc. In jQuery or normal JS, we manually listen to these events.

AngularJS is adding these event listeners for you, and is extending the event loop by automatic controlling the binding between the model and the view.

Watcher

Everytime you put the $scope variable or a function on the page, AngularJS automatically adds a watcher to the watch list, which keeps track of the original value and the new value every time something happened, which might have changed the value of that variable (e.g. typing inside the textbox).

Digest Cycle

That part of watching and checking the changes is inside Angular and it is happening in the Digest Loop or the Digest Cycle. It goes through the watch list and compares the old value to the new value of each variable. If some variable has change then it updates the DOM accordingly. Then it runs one more cycle, where it goes to through the watch list to see if it has changed anything else, until the old and the new value match. When the entire digest cycle completes it updates the DOM.

// DOM will not be updated in this case

setTimeout(function () {
  $scope.handle = 'newtwitterhandle';
}, 5000);

The above will not update the DOM, because setTimeout() is out of Angular's context, so it never started the digest loop or checked the watch list. The solution is to wrap this in $scope.$apply() to inform Angular to notice this change which is happening outside of your context.

// DOM will be updated in this case

setTimeout(function () {
  
  // Ensuring Angular to run its normal process of 
  // digest loop and listening to the watch list
  
  $scope.$apply(function() {
    $scope.handle = 'newtwitterhandle';
  });
  
}, 5000);

When to call $apply()?

When you are out of Angular context, for example when using setTimeout or inside jQuery.

However Angular provides some useful replacements like $timeout service, which is a replacement of setTimeout but in an Angular way.

Single Page Application - SPA

<a href="#bookmark">Take me to bookmark</a>

<div id="bookmark">I am a bookmark somewhere down in the HTML document</div>

Clicking on a tag will take the user to the #bookmark element. This is there in HTML for a long time.

JS has a native method to listen this change called hashchange

window.addEventListener('hashchange', function() {
  console.log('hash changed!', window.location.hash); //outputs #bookmark
});

Important point here is that the #bookmark element deosn't need to exist in order for the hash event to work. The above code will still output the new hash change. Which means #/bookmark/2 should work fine without any error.

So the below code should be clear now;

<a href="#/page/1">Page 1</a>
<a href="#/page/2">Page 2</a>
<a href="#/page/3">Page 3</a>
window.addEventListener('hashchange', function() {
  
  if (window.location.hash === '#/page/1') {
    console.log('I am page 1');
  }
  
  if (window.location.hash === '#/page/2') {
    console.log('Let\'s go to page 2');
  }
  
  if (window.location.hash === '#/page/3') {
    console.log('I am at page 3 now');
  }
  
});

So in SPA, we fetch content asynchronously identified by the hash fragment.

Routing, Controllers & Templates - Keys to SPA in AngularJS

$location service provides methods to read the hash

//outputs the hash value without the hash - Example: /bookmarks/2
console.log($location.path());

angular-route is an external Angular module that handles routing in AngularJS.

In HTML

<html ng-app="myApp">
  
  <head>
    <script src="angular.min.js"></script>
    <script src="angular-route.min.js"></script>
  </head>
  
  <body>
    
    <!-- The routes template will go in here, based on the route -->
    <div ng-view></div>
  
  </body>
</html>
// app.js

var app = angular.module('myApp', ['ngRoute']);

// configure the app using the $routeProvider service given by angular-route module
app.config(function($routeProvider) {
  
  $routeProvider
  
  .when('/', {
    templateUrl: 'pages/first.html',
    controller: 'firstController'
  })
  
  .when('/second', {
    templateUrl: 'pages/second.html',
    controller: 'secondController'
  })
  
  .when('/third/:id', {
    templateUrl: 'pages/third.html',
    controller: 'thirdController'
  })
  
});

// first controller
app.controller('firstController', ['$scope', function($scope) {

}]);

// second controller
app.controller('firstController', ['$scope', function($scope) {
  
}]);


// third controller with a parameter
app.controller('firstController', ['$scope', '$routeParams', function($scope, $routeParams) {
  
  console.log($routeParams.id); //outputs the 3 if the hash is /third/3

}]);

Singleton

Services in Angular are Singleton. Singleton means one and only copy of an object. So same object of the service is being shared among the controllers, which means services can be used to share data between multiple pages of SPA.

$scope is an exception. Angular creates a separate object for each controller. While other services are singleton, including custom services.

Custom Directives:

Normalization

Directives are a piece of UI that can be used at many places. They are there to avoid repetition. It makes HTML cleaner and manageable.

Angular normalizes the directive name to match the standard. For e.g. it will convert this <search-result></search-result> custom directive to searchResult in JS.

Creating a custom directive

// the below custom directive can be used as <search-result>

app.directive('searchResult', function() {
  return {
    template: '<div>I am a custom directive</div>'
  }
});
// output

<search-result>
  <div>I am a custom directive</div>
</search-result>

To avoid <search-result> in the output, add property replace: true to the directive object.

Directives can be passed as attributes as well. The below markup will give the same output.

<div search-result></div>

The above two methods of using the directive are by default. However the usage can be restricted via the restrict property in the directive definition. It can be accept more than values.

restrict: 'AE' // A = attribute, E = element, C = class name, M = comments

A and E are recommended. However class name and comments may be useful in certain circumstances.

Templates for custom directives:

Directive templates can be saved in a separate files for code cleanup, say in folder directives/search-result.html, then it can be used in the directive definition by templateUrl property.

Isolated scope:

By default the directive has the scope of the controller where it is being called, which is not a good approach and could be dangerous. It is better to create an isolated scope for directives which can be done by passing a scope object as an attribute to the directive.

scope: {

  // if the propertyName in the directive definition is a text value
  propertyName: '@' 

  // for objects <search-result person="person"></search-result>
  propertyName: '=' 

  // for functions <search-result get-name="functionName(person)"></search-result>
  // then in the template it should be called as {{ functionName({ person: personObject }) }}
  propertyName: '&' 
  
  // link is useful to play with the directive markup just before it is being given to the DOM
  // the scope used in this is not $scope. elem is the directive element
  link: function (scope, elem, params) {
  }

}

Transclusion:

Adding one document into another. By default Angular replaces the content defined within the directive with its template. When we want to add extra content before/after the directive template then this is called Transclusion. It is being added to the template using ng-transclude directive. Consider it as a placeholder for the content within your directive tags.

// HTML

<search-result>
  <small>I have been injected into the search-result directive</small>
</search-result>

// directive template

<div>
  <h1>Directive template goes here</h1>
  <ng-transclude></ng-transclude>
</div>

// in directive definition

{
  transclude: true
}

Nested controllers:

Controllers can be nested in parent/child form. There are two approaches to access parent scope from its child controller.

Approach #1

Create a parent variable within the scope, and variables to that parent.

app.controller('parentController', function($scope) {
  $scope.parentScope = {};
  $scope.parentScope.name = 'Tony';
});
<div ng-controller="parentController">
  <h1>{{ parentScope.name }}</h1>
  
  <div ng-controller="childController">
    <h1>Parent: {{ parentScope.name }}</h1>
  </div>
</div>

Approach #2 (controller as - recommended):

Use of controller as and this instead of the $scope in controller function. This results in a much cleaner code. However this approach has a limitation that you cannot add watchers in your controller, because that requires $scope.

app.controller('parentController', function() {
  this.name = 'Tony';
});
<div ng-controller="parentController as parent">
  <h1>{{ parent.name }}</h1>
  
  <div ng-controller="childController as child">
    <h1>Parent: {{ parent.name }}</h1>
  </div>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment