angular-training



angular-training

0 0


angular-training

Presentation for Angular Training

On Github JSainsburyPLC / angular-training

ngTraining

An Introduction to Angular

Created by Chris Atkin

Introduction

What is Angular?

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

Getting Started

<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

Expressions

<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.

Modules & Controllers

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>

Modules

Constructing Modules

var app = angular.module('myApp', []);
  • Defined with the module method
  • First argument is the module name (used in ng-app)
  • Second argument is an array of dependencies
  • Dependency array must be present, even if empty, to declare a module
  • Returns a module object that can be used to define controllers, etc

Using Modules

Modules are constructed by passing two arguments to the module method (the module name, and an array of dependencies). To use a module, either:

  • Store the returned object from the module constructor call, and call methods on it
  • Call the module method again, passing only the module name

Modules can be passed as dependencies to other modules, using their name or returned object.

Recommended Setup

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 for each feature
  • A module for reusable components (especially directives and filters)
  • And an application level module which depends on the above modules and contains any initialization code.

Run & Config

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.

Controllers

Constructing Controllers

var app = angular.module('myApp');
app.controller('myCtrl', function($scope) {
    $scope.firstName = 'Chris';
    $scope.lastName = 'Atkin';
});
  • Defined with the controller method
  • First argument is the controller name (used in ng-controller)
  • Second argument is the controller function
  • The module is retrieved by only passing it's name; no dependency array

Recommended Usage

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 & controller members

$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>-->

Controller-As Syntax

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.

Directives

Directives

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.

Using Directives

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>

ng-model & ng-bind

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.

ng-show & ng-hide

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>

ng-if

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>

ng-repeat

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 continued

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>
-->

ng-class

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.

Custom Directives

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: {}
    }
});

Defining Directives

  • 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

Defining Directives, part 2

  • 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

Naming Directives

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-.

Components in Angular 1.5

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

Angular Filters

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.

Date Filter

<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'.

Number Filter

<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.

Order-By Filter

<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.

Services

What are Angular Services?

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

Using Services

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);
    };
});

Services & Factories

Angular provides two methods to define singleton services: service and factory.

Both form closures, can run code on creation and serve the same purpose.

#TeamService

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');
    };
});

#TeamFactory

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');
        };
    }
});

Dependency Injection (DI)

Using Dependency Injection

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.

Using DI in components

app.controller('myCtrl', ['dep1', 'dep2', function(dep1, dep2) {
    dep1.aMethod = function() {
        // ...
    }
}]);

app.service('myService', ['depService', function(depService) {
  // ...
}]);

app.directive('myDirective', ['depService', function(depService) {
  // ...
}]);

DI Annotation

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.

$inject

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.

Application Structure

Structuring Angular

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 LIFT Guidelines

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-DRY

John Papa, Angular App Structuring Guidelines

The Sock Drawer

One 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

Structure by Feature

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

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

Routing

Routing Introduction

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.

Honourable Mention: ng-route

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

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, GitHub

Getting Started

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.

Adding Templates

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>

Adding Child Templates

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>

Wiring it all up

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"];
      }
    });
});

Named Views

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>

Named Views, part 2

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" }
      }
    })
});

Providing Templates

ui-router supports both template strings and templateUrl for specifying templates.

$stateProvider

    .state('state1', {
      template: "<h2>My Template</h2>"
    })

    .state('state2', {
      templateUrl: "state2.html",
    });

State Configuration

States in ui-router can be configured with a range of options, aside from their controller, template and URL. These include:

  • resolve: a map of dependencies which should be injected into the controller
  • abstract: abstract states cannot be directly activated, but can be inherited
  • onEnter, onExit: callback functions when the state is entered and exited
  • data: arbitrary data object, useful for custom properties and configuration

URL Parameters

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

State Inheritance

ui-router supports nesting states. Child states inherit the following from their parents:

  • Resolved dependencies via resolve
  • Custom data properties

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.

$stateParams

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.

Communication

$http

$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'
})

$http responses

The response object resolved from the Promise returned by $http has the following properties:

  • data: {string|Object - The response body
  • status: {number} - HTTP status code of the response
  • config: {Object} - The request configuration object
  • statusText: {string} - HTTP response status text
  • headers: {function[headerName]} - Function to get headers

$http shortcuts

$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)

ng-Resource & $resource

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

$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>

Promise Caveats

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;

Resource Services

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

ES6/ES2015

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.

Babel

ES6 - Variable Scope

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

ES6 - Arrow Functions

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 - Strings Methods

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 - String Templating

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.');
ngTraining An Introduction to Angular Created by Chris Atkin