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.
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.
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.
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)
Creating a string by combining strings and variables
Example: 'My name is ' + name
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.
A directive is an instruction to Angular to manipulate a piece of DOM. For example, hide this, create this, add a class etc.
<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.
View updates the model, and the model updates the view
// 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 ???
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.
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).
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 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.
<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.
$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
}]);
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.
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.
// 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.
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.
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) {
}
}
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
}
Controllers can be nested in parent/child form. There are two approaches to access parent scope from its child controller.
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>
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>