On Github JSainsburyPLC / angular-training
Created by Chris Atkin
Angular is an MVC framework for creating web apps.
Create interactive single page applications, using JavaScript, HTML and more
Extend HTML with data binding and new features
Build well structured, easily testable, and maintainable front-end applications
<script src="https://code.angularjs.org/1.5.0/angular.js"></script> <div ng-app=""> <p>Name: <input type="text" ng-model="name"> </p> <p ng-bind="name"></p> </div>
ng-app: bootstraps your application to a HTML element
ng-model: binds an input element to an Angular model
ng-bind: binds the contents of an element to a model
<p>{{ name }}</p> <p>{{ 5 + 5 }}</p> <p>{{ myFunction() }}</p>
Expressions can be written within double curly braces (or mustaches). They'll print any variables, expressions or returns of functions placed within.
Angular modules define AngularJS applications.
Angular controllers control AngularJS applications.
<div ng-app="myApp" ng-controller="myCtrl"> First Name: <input type="text" ng-model="firstName"><br> Last Name: <input type="text" ng-model="lastName"><br> <br> Full Name: {{firstName + " " + lastName}} </div> <script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope) { $scope.firstName = 'Chris'; $scope.lastName = 'Atkin'; }); </script>
var app = angular.module('myApp', []);
Modules are constructed by passing two arguments to the module method (the module name, and an array of dependencies). To use a module, either:
Modules can be passed as dependencies to other modules, using their name or returned object.
While a single module is simple, it will not scale to large applications. Instead it is recommended to break applications into multiple modules like this:
A module is a collection of configuration and run blocks.
config - executed during provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
run - executed after injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.
var app = angular.module('myApp'); app.controller('myCtrl', function($scope) { $scope.firstName = 'Chris'; $scope.lastName = 'Atkin'; });
Controllers are best used to connect Services and providers to views and markup.
They should do as little business logic as possible (which should occur in Services), and avoid directly manipulating the view (which should occur in Directives);
$scope is the glue between markup and controllers, and can be used for variables and functions.
app.controller('myCtrl', function($scope) { $scope.name = 'Chris'; $scope.myFunction = function() { return 'Hello world'; } });
<p>{{ name }}</p> <!--<p>Chris</p>--> <p>{{ myFunction() }}</p> <!--<p>Hello World</p>-->
Starting in Angular 1.2, controllers can be used with controllerAs syntax.
app.controller('myCtrl', function() { var vm = this; vm.name = 'Chris'; });
<div ng-controller="myCtrl as vm"> <p>{{ vm.name }}</p> </div>
This makes scoping much clearer, and allows us to nest controllers. This becomes very useful in routing.
Angular provides many ways to extend HTML elements and attributes using Directives.
These can be used to bind data, dynamically hide or show elements, repeat elements over collections or almost anything else that requires manipulation of the DOM.
They can be very powerful, and take variables, expressions or functions as inputs.
Directives can be bound to an element as an attribute, create as a standalone element, or declared via classes and comments. However, it's recommended to only define them as attributes or elements, to make it easier to identify directives.
<my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span>
These directives allow you to bind a variable to a HTML element. As we saw previously, ng-bind can also be used as an expression, with curly braces.
<input type="text" ng-model="vm.name"> <p ng-bind="vm.name"></p> <!-- is the same as --> <p>{{ vm.name }}</p>
ng-bind can be much faster than an expression, but may need additional markup.
These directives allow elements to be hidden or revealed based on scope properties. The element is hidden by CSS.
<p ng-show="true">This should be visible</p> <p ng-hide="true">This should be hidden</p>
This directive is similar to ng-show/ng-hide, but removes the element from the DOM.
<p ng-if="true">This should be included in the DOM</p> <p ng-if="false">This should be removed from the DOM</p>
This directive allows you to repeat an element for a collection.
app.controller('myCtrl', function() { var vm = this; vm.items = ['item1', 'item2', 'item3']; });
<p ng-repeat="item in vm.items">{{ item }}</p> <!-- <p>item1</p> <p>item2</p> <p>item3</p> -->
ng-repeat can access properties on an array of objects, and order the array while repeating.
app.controller('myCtrl', function() { var vm = this; vm.people = [ { name: 'Chris', id: 2}, { name: 'Mark', id: 1} ]; });
<p ng-repeat="person in vm.people | orderBy:'id'">{{ person.name }}</p> <!-- <p>Mark</p> <p>Chris</p> -->
This directive allows CSS classes to be added to an element depending on variables. It can use the variable explicitly:
vm.class = 'myClass'; <p ng-class="vm.class">I'll get 'myClass' as a CSS class</p>
Or as part of an expression:
vm.error = true; <p ng-class="{ 'error-class': vm.error }">I'm an error!</p>
ng-class can accept a range of expressions, including arrays, ternary operators and more.
Directives can be defined with the directive method on a module.
angular.module('myApp').directive('myDirective', function() { return { template: '<p>{{ vm.myVariable }}</p>', controller: 'myCtrl', restrict: 'EA', controllerAs: 'vm', bindToController: { myVariable: '=', }, scope: {} } });
template or templateUrl: markup for your directive, either a string or path to html template
controller: controller for your directive, specified either by a controller name or a function
restrict: how your directive can be used: A as an attribute, E as an element, C as a class, M as a comment
controllerAs: the controller-as alias for your template
bindToController: variables, specified as attributes when the directive is called, that will be bound to the controller above
scope: the scope that will be created for the directive. Can be inherited from it's parent element or created in isolation
Directives should be named in JavaScript as camelCase. Each capital letter will represent a dash when the directive is used.
app.directive('myAwesomeDirective', function() { return { restrict: 'E', template: '<h1>This is awesome!</h1>' }; });
<my-awesome-directive></my-awesome-directive>
Using a unique prefix can avoid colliding with inbuilt directives or elements, such as dialog or tabs. Core Angular directives are prefixed with ng-.
Angular 1.5 introduces components. These are syntactic sugar around directives, encouraging good behaviour and reducing boilerplate.
app.component('list', { bindings: { items: '=' }, templateUrl: 'list.html', controller: function ListCtrl() {} });
These include options to require parent components, use one-way bindings and an $onInit function.
Filters in Angular are used to manipulate bound data. They can be used in HTML expressions, or in controllers via the $filter service.
var formattedDate = $filter('date')(vm.dateOfBirth, 'dd/MM/yyyy');
<p>{{ vm.dateOfBirth | date:'dd/MM/yyyy' }}</p>
Angular has built in filters for formatting dates, numbers and currency, ordering arrays and more. You can also define your own filters.
<p>{{ vm.dateOfBirth | date:'dd/MM/yyyy' }}</p> <!--<p></p>-->
The date filter formats dates. The string in single quotes defines the date format. Angular supports a range of constructed and default date formats, such as 'dd/MM/yyyy' and 'fullDate'.
<p>{{ vm.myDecimal | number:4 }}</p>
The number filter formats numbers, changing the decimal places (via the passed parameter) or displaying the infinity sign for infinite values. If the input is null or undefined, it will just be returned.
<p ng-repeat="vm.people | orderBy:'name'">{{ person.name }}</p>
The orderBy filter orders a specified array by an expression. It is ordered alphabetically for strings and numerically for numbers.
Angular services are substitutable objects that are wired together using dependency injection. You can use services to organize and share code across your app.
Lazily instantiated – Angular only instantiates a service when an application component depends on it
Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory
app.factory('notify', function($window) { var msgs = []; return function(msg) { msgs.push(msg); if (msgs.length == 3) { $window.alert(msgs.join("\n")); msgs = []; } }; }); app.controller('MyController', function ($scope, notify) { $scope.callNotify = function(msg) { notify(msg); }; });
Angular provides two methods to define singleton services: service and factory.
Both form closures, can run code on creation and serve the same purpose.
service defines a class that will be instantiated on first call, and the instance will be returned on future calls.
app.service('MyService', function () { this.sayHello = function () { console.log('hello'); }; });
factory defines a function that returns an object; this will be run on first call, and the resulting object will be returned for future calls.
app.factory('MyService', function () { return { sayHello: function () { console.log('hello'); }; } });
DI is pervasive throughout Angular. You can use it when defining components or when providing run and config blocks for a module.
Components such as services, directives, filters, and controllers are defined by an injectable factory method or constructor function. These components can be injected with "service" and "value" components as dependencies.
app.controller('myCtrl', ['dep1', 'dep2', function(dep1, dep2) { dep1.aMethod = function() { // ... } }]); app.service('myService', ['depService', function(depService) { // ... }]); app.directive('myDirective', ['depService', function(depService) { // ... }]);
Angular supports three types of DI annotation; implicit, inline and $inject.
With implicit annotation, Angular will match the function parameter names to providers. This is very simple, but is not minification safe.
With inline annotation, the component function is provided as an Array, whose elements consist of a list of strings (the names of the dependencies) followed by the function itself.
The third way to annotate dependency injection in Angular is using $inject. Using this notation dependencies are very clear, and it avoids lengthy string arrays, as used in inline.
MyController.$inject = ['$scope', 'greeter']; var MyController = function($scope, greeter) { // ... } app.controller('MyController', MyController);
With both inline & $inject annotations, the order of declared dependencies must match the order of function parameters.
How you organise your applications structure is up to you; Angular does not impose a folder structure. There are many right ways to structure an application; the key is consistency across a project.
The structure should follow these 4 basic guidelines.
Locating code is easy Identify code at a glance Flat structure as much as possible Try to stay DRY (Don't Repeat Yourself) or T-DRYOne way to structure a small Angular application that's outgrown a single folder, is to group objects by their type.
app/ app.module.js app.config.js app.routes.js controllers/ attendees.controller.js sessions.controller.js speakers.controller.js services/ data.service.js logger.service.js spinner.service.js templates/ attendees.html sessions.html speakers.html
A better way to structure a medium to large app, is to group components by the feature and module they are part of.
app/ app.module.js app.routes.js layout/ shell.html shell.controller.js people/ attendees.html attendees.controller.js sessions/ sessions.html sessions.controller.js services/ data.service.js logger.service.js spinner.service.js
Naming conventions, like structure, have no enforced rules; again consistency across a project is key. The naming conventions should simply help the findability and communication of code. Here's an example:
admin.module.js //A module config.route.js //Route configuration registration.js //Controller registration.controller.js //Controller alternative logger.service.js //Service spinner.directive.js //Directive
In the earlier examples, ng-controller was used to join a controller to markup. For any practical application we need a better way to link controllers and views, and support moving through our application using URLs.
For this we having routing in Angular, allowing an app to have multiple views and move between them, based on state and URL.
Angular has it's own routing module, ng-route.
It supports loading views into ng-view elements, loading templates and routing based on URLs.
While this can solve most basic routing problems, alternative routing libraries exist...
ui-router is a routing solution for Angular, supporting all the functions of ng-route and adding many more. It has become the de-facto solution to flexible routing with nested views.
ui-router uses the ui-view directive to specify elements to load views into.
<!-- index.html --> <body> <div ui-view></div> <a ui-sref="state1">State 1</a> <a ui-sref="state2">State 2</a> </body>
It also uses the ui-sref directive. This generates href attributes for the states they link to.
These will be loaded into the ui-view within index.html. Each has their own ui-view, to display nested views.
<!-- partials/state1.html --> <h1>State 1</h1> <hr/> <a ui-sref="state1.list">Show List</a> <div ui-view></div>
<!-- partials/state2.html --> <h1>State 2</h1> <hr/> <a ui-sref="state2.list">Show List</a> <div ui-view></div>
These will be loaded into the ui-view of their parent templates.
<!-- partials/state1.list.html --> <h3>List of State 1 Items</h3> <ul> <li ng-repeat="item in items">{{ item }}</li> </ul>
<!-- partials/state2.list.html --> <h3>List of State 2 Things</h3> <ul> <li ng-repeat="thing in things">{{ thing }}</li> </ul>
We connect templates to controllers, and define state hierarchies, using $stateProvider. Set these in your module's config block.
myApp.config(function($stateProvider, $urlRouterProvider) { // // For any unmatched url, redirect to /state1 $urlRouterProvider.otherwise("/state1"); // // Now set up the states $stateProvider .state('state1', { url: "/state1", templateUrl: "partials/state1.html" }) .state('state1.list', { url: "/list", templateUrl: "partials/state1.list.html", controller: function($scope) { $scope.items = ["A", "List", "Of", "Items"]; } }) .state('state2', { url: "/state2", templateUrl: "partials/state2.html" }) .state('state2.list', { url: "/list", templateUrl: "partials/state2.list.html", controller: function($scope) { $scope.things = ["A", "Set", "Of", "Things"]; } }); });
One of ui-routers other features, it being able to have multiple ui-view elements per template. You can provide a name for a ui-view and load specific templates into it.
<!-- index.html --> <body> <div ui-view="viewA"></div> <div ui-view="viewB"></div> <!-- Also a way to navigate --> <a ui-sref="route1">Route 1</a> <a ui-sref="route2">Route 2</a> </body>
myApp.config(function($stateProvider) { $stateProvider .state('index', { url: "", views: { "viewA": { template: "index.viewA" }, "viewB": { template: "index.viewB" } } }) .state('route1', { url: "/route1", views: { "viewA": { template: "route1.viewA" }, "viewB": { template: "route1.viewB" } } }) .state('route2', { url: "/route2", views: { "viewA": { template: "route2.viewA" }, "viewB": { template: "route2.viewB" } } }) });
ui-router supports both template strings and templateUrl for specifying templates.
$stateProvider .state('state1', { template: "<h2>My Template</h2>" }) .state('state2', { templateUrl: "state2.html", });
States in ui-router can be configured with a range of options, aside from their controller, template and URL. These include:
ui-router supports a range of URL parameters.
url: "/contacts/" // Matches '/contacts/' in the URL url: "/contacts/:id" // Matches '/contacts/10' or '/contacts/chris' url: "/contacts/{id}" // The same as above, but with curly braces url: "/contacts/{id:int}" // Interprets the id parameters as an integer
It also support Regex parameters.
url: "/contacts/{id:[0-9]{1,8}}" // Will only match 1-8 numeric characters
ui-router supports nesting states. Child states inherit the following from their parents:
This is useful for sibling states that require the same data, to have it resolved and inherited from their shared parent.
No other state data is inherited.
The $stateParams service allows you to access parameters passed to the current state. Using this, controllers and services can access parts of the navigated URL and state.
// If you had a url on your state of: url: '/users/:id' // Then you navigated your browser to: '/users/123' // Your $stateParams object would be { id:'123' }
The $stateParams object will only contain the params that were registered with that state. It will not contain params registered on other states, including ancestors.
$http is a core Angular service for communicating with remote HTTP servers via the browser's XMLHttpRequest object.
The service takes a single configuration object, generates a HTTP request and returns a Promise for the request.
$http({ method: 'GET', url: '/api/users' })
The response object resolved from the Promise returned by $http has the following properties:
$http also provides a number of shortcut methods for common HTTP methods. These include:
$http.get('/url', config) $http.post('/url', data, config) $http.put('/url', data, config)
Angular also includes the ngResource module, which contains the $resource factory. This creates a resource object that simplifies interaction with RESTful data sources.
// Define a RESTful resource var Users = $resource('/users/:id'); // Get a user from the resource var user = Users.get({ id: 123 }); // Get all users var allUsers = Users.query();
$resource returns a special Promise object from it's methods. You can bind this to an element, and the promise will magically unwrap its value when it resolves from the server.
var Users = $resource('/users/:id'); vm.user = Users.get({ id: 123 });
<h1>Hello {{ vm.user }}</h1>
The Promise objects returned by $resource are not real promises, due to their auto-unwrapping functionality. To use them with $q and similar, use the $promise property of the returned object.
var user = Users.get({ id: 123 }); var userPromise = user.$promise;
To share server side data between controllers, it is useful to wrap a $resource in a service. It can then be injected into other parts of your application, and protect it's internal resource methods.
app.service('UserService', function ($resource) { var Users = $resource('/users/:id') this.getUser = function (id) { return Users.get({ id: id }).$promise; }; });
ES6, or ECMAScript 6, are new features added to JavaScript in 2015. These include changes to variable scope, function syntax, string manipulation and more.
Currently ES6 features have poor support in even modern browsers. To use them in our projects, we need to transpile them to their ES5 nearest equivalents. To do this, we use a tool called Babel.
Variables declared with var are scoped to their surrounding functions. ES6 provides two new ways to declare variables, let and const, which both have block scoping.
if(true) { let x = 1; } console.log(x); // undefined
const also declares a read-only reference to a value.
const MY_CONSTANT = 1; MY_CONSTANT = 2 // Error const SOME_CONST; // Error
Arrow functions makes for short, concise code.
let books = [{title: 'X', price: 10}, {title: 'Y', price: 15}]; let titles = books.map( item => item.title ); // ES5 equivalent: var titles = books.map(function(item) { return item.title; });
Arrow functions also inherit this from their surrounding context, helping to avoid issues with binding functions to the correct context.
ES6 includes a number of new String methods. These eliminate a number of indexOf implementations used currently.
'my string'.startsWith('my'); //true 'my string'.endsWith('my'); // false 'my string'.includes('str'); // true 'my '.repeat(3); // 'my my my '
ES6 also includes String templating, to make string concatenation and formatting easier. Note; those are back-ticks not single-quotes.
const name = 'John'; const apples = 5; const pears = () => 3; console.log(`This is ${name}.`); console.log(`He carries ${apples} apples and ${pears()} pears.`); // ES5 equivalent: console.log('He carries ' + apples + ' apples and ' + pears() +' pears.');