AngularJS

Single Page Application

  • Single Web Page
  • JS Heavy Client Application
  • Server communication via AJAX (or WebSockets)

SPA

Why Angular?

  • Fast prototyping & development
  • Easy HTML extension
  • Huge community & OS projects
<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
    </script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}!</h1>
    </div>
  </body>
</html>

Hello Angular!

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
    </script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}!</h1>
    </div>
  </body>
</html>

Hello Anular!

ng-app

Declares application scope

ng-model

Declares model/variable binding (with input)

Directives

A declarative user interface

Angular build-in directives

ng-directive-name

{{ yourName }}

Databinding

+ watch & change

<input type="text" ng-model="yourName" placeholder="Enter a name here">




<h1>Hello {{yourName}}!</h1>

More ng-stuff!

<!doctype html>
<html ng-app="todoApp">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
    </script>
    <script src="todo.js"></script>
    <link rel="stylesheet" href="todo.css">
  </head>
  <body>
    <h2>Todo</h2>
    <div ng-controller="TodoListController">
      <span>{{remaining()}} of {{todos.length}} remaining</span>
      [ <a href="" ng-click="archive()">archive</a> ]
      <ul class="unstyled">
        <li ng-repeat="todo in todos">
          <input type="checkbox" ng-model="todo.done">
          <span class="done-{{todo.done}}">{{todo.text}}</span>
        </li>
      </ul>
      <form ng-submit="addTodo()">
        <input type="text" ng-model="todoText"  size="30"
               placeholder="add new todo here">
        <input class="btn-primary" type="submit" value="add">
      </form>
    </div>
  </body>
</html>

More ng-stuff!

angular.module('todoApp', [])
  .controller('TodoListController', function($scope) {
    $scope.todos = [
      {text:'learn angular', done:true},
      {text:'build an angular app', done:false}];
 
    $scope.addTodo = function() {
      $scope.todos.push({text:$scope.todoText, done:false});
      $scope.todoText = '';
    };
 
    $scope.remaining = function() {
      var count = 0;
      $scope.todos.forEach(function(todo) {
        count += todo.done ? 0 : 1;
      });
      return count;
    };
 
    $scope.archive = function() {
      var oldTodos = $scope.todos;
      $scope.todos = [];
      oldTodos.forEach(function(todo) {
        if (!todo.done) $scope.todos.push(todo);
      });
    };
  });

TODO App (1)

<!doctype html>
<html ng-app="todoApp">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
    </script>
    <script src="todo.js"></script>
    <link rel="stylesheet" href="todo.css">
  </head>
angular.module('todoApp', [])

angular.module defines module: application = one or more modules

module name

module dependencies

TODO App (2)

  <body>
    <h2>Todo</h2>
    <div ng-controller="TodoListController">
      ...
      <ul class="unstyled">
        <li ng-repeat="todo in todos">
          <input type="checkbox" ng-model="todo.done">
          <span class="done-{{todo.done}}">{{todo.text}}</span>
        </li>
      </ul>
angular.module('todoApp', [])
  .controller('TodoListController', function($scope) {
    $scope.todos = [
      {text:'learn angular', done:true},
      {text:'build an angular app', done:false}
    ];
    ...
  });
    

text binding span content

text binding class name

$scope

JS object

  • give access to properties
  • give access to functions
  • "digest cycle"
    • watch values
    • detects changes
    • applies dependent  changes

TODO App (3)

    <form ng-submit="addTodo()">
      <input type="text" ng-model="todoText"  size="30"
             placeholder="add new todo here">
      <input class="btn-primary" type="submit" value="add">
    </form>
angular.module('todoApp', [])
  .controller('TodoListController', function($scope) {
    ...
    $scope.addTodo = function() {
      $scope.todos.push({text:$scope.todoText, done:false});
      $scope.todoText = '';
    };
    ...
  });

TODO App (4)

  <div ng-controller="TodoListController">
    <span>{{remaining()}} of {{todos.length}} remaining</span>
    [ <a href="" ng-click="archive()">archive</a> ]
angular.module('todoApp', [])
  .controller('TodoListController', function($scope) {
    ...
    $scope.remaining = function() {
      var count = 0;
      $scope.todos.forEach(function(todo) {
        count += todo.done ? 0 : 1;
      });
      return count;
    };

    $scope.archive = function() {
      var oldTodos = $scope.todos;
      $scope.todos = [];
      oldTodos.forEach(function(todo) {
        if (!todo.done) $scope.todos.push(todo);
      });
    };
    ...
  });

Shopping Cart

Shopping Cart

<html ng-app='myApp'>
    <body ng-controller='CartController'>
        <h1>Your Order</h1>
        <div ng-repeat='item in items'>
            <span>{{item.title}}</span>
            <input ng-model='item.quantity'>
            <span>{{item.price | currency}}</span>
            <span>{{item.price * item.quantity | currency}}</span>
            <button ng-click="remove($index)">Remove</button>
        </div>
        <script src="angular.js"></script>
        <script src="myapp.js"></script>
    </body>
</html>
angular.module('myApp', []).controller('CartController', function($scope){
    $scope.items = [
        {title: 'Paint pots', quantity: 8, price: 3.95},
        {title: 'Polka dots', quantity: 17, price: 12.95},
        {title: 'Pebbles', quantity: 5, price: 6.95}
    ];
    $scope.remove = function(index) {
        $scope.items.splice(index, 1);
    }
});

More Theory

  • Commonly used features
  • Communication with Server
  • Directives

Templates & Data Binding

<p>{{greeting}}</p>

<p ng-bind="greeting"></p>

$scope.greeting = "Hello there!"

---

<p>Hello there!</p>
<p>Hello {{name}}!</p>

<p ng-bind-template="Hello {{name}}!"></p>

$scope.name = "Ned Stark"

---

<p>Hello Ned Stark!</p>

While loading application:

  • {{zxc}} => {{zxc}}
  • ng-bind =>
  • Hi {{zxc}} => Hi {{zxc}}
  • ng-bind-template =>
  • {{}} expression changes only interior
  • ng-bind changes content of tag 

Templates & Data Binding

<p ng-cloak>Hello {{name}}!</p>

<p ng-cloak>Hello {{name}} from {{city}}!</p>
  • ng-cloak - hides content while loading
  • CSS: 

 

 

  • after compiling, "ng-cloak" attribute is removed
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
  display: none !important;
}

Templates & Databinding

<p>{{count * price() | currency}}</p>

<p ng-bind="count * price() | currency"></p>
<p>Hello {{name}} from {{city}}!</p>

<p>
    Hello 
    <span ng-bind="name"></span> 
    from 
    <span ng-bind="city"></span>
    !
</p>

<p ng-bind-template="Hello {{name}} from {{city}}!">
</p>
  • {{}} - expression
  • ng-bind - expression
  • ng-bind - only one expression
  • ng-bind-template - multiple expressions

Templates & Databinding

<a href="{{user.profile.url}}">
    Link to user profile
</a>
<img src="{{user.avatar.url}}">

<a ng-href="user.profile.url">
    Link to user profile
</a>
<img ng-src="user.avatar.url">
  • src/href + databinding = broken image/link
  • instead use ng-src/ng-href
  • databinding works with class atribute 
  • complex dynamic classes - ng-class
  • ng-class evaluates values for truthy/falsy
<form class="form form-{{form.state}}">
    ...
</form>

<ul>
    <li ng-class="{'active': state == 'home'}">
        Home
    </li>
    <li ng-class="{'active': state == 'settings'}>
        Settings
    </li>
</ul>

Controllers & scopes

<div ng-controller="MyCtrl">
  <!-- Here we have access to MyCtrl scope -->
</div>
<div ng-controller="MyCtrl">
  <!-- Here we have access to MyCtrl scope -->
  <div ng-controller="InnerCtrl">
    <!-- Here we have access to InnerCtrl scope -->
  </div>
</div>
  • controller - creates new $scope and allows to control it
  • controllers can be nested
  • new $scope is child of parent scope
  • top level scope is $rootScope created for angular application
<body ng-app>
  <!-- Here we have access to app's rootScope -->
  <div ng-controller="MyCtrl">
    <!-- Here we have access to MyCtrl scope -->
  </div>
</body>

Controllers & scopes

<div ng-controller="MyCtrl">
  <p>Hello {{name}}!</p>
  <p>You live in: {{place}}</p>
  <p>Your job is: {{job}}</p>
  <div ng-controller="InnerCtrl">
    <p>Your name is: {{name}}</p>
    <p>You live in: {{place}}</p>
    <p>Your job is: {{job}}</p>
  </div>
</div>

app.controller('MyCtrl', function($scope){
    $scope.name = "Cercei";
    $scope.place = "King's Landing";
});
app.controller('InnerCtrl', function($scope){
    $scope.name = "Joffrey";
    $scope.job = "King";
    $scope.$parent.job = "Queen";
});
  • new $scope inherits parent $scope prototype
  • inner $scope overshadows parent $scope properties
  • when property not found in $scope, it's looked up in parent $scope
  • parent has no direct access to child $scope
  • child can access parent scope by $scope.$parent

Scopes & watches

<div ng-controller="MyCtrl">
  <p>Hello {{name}}!</p>
  <p>Name length: {{length}}</p>
  <p>It's a {{message}} name!</p>
  <input type="text" ng-model="name">
</div>

app.controller('MyCtrl', function($scope){
    $scope.name = "Hodor";
    $scope.length = $scope.name.length;
    $scope.message = "short";

    $scope.$watch('name', function(val){
        $scope.length = val.length;
    })

    $scope.$watch('length', function(val){
      $scope.message = 
        (val > 20) ? 'long' : 'short';
    });
});
  • each 'live' databinding use watch internally
  • controllers allow watch scope properties
  • all watches are checked and applied during
    Digest Cycle
  • beware watch loops - Digest Cycle stops after 10 iterations
  • outside of digest loop use $scope.$apply(function)

Conditional interface

<div>
  <ul ng-show="user.interests">Interests:
    <li ng-repeat="interest in user.interests">
        {{interest}}
    </li>
  </ul>
  <p ng-hide="user.interests">
    No interests selected!
  </p>
</div>

<div>
  <video ng-if="intro.active" src="tutorial.ogg">
  </video>
</div>
  • to show or hide part of UI use ng-show/ng-hide
  • it applies CSS display: none!important
  • ng-if actually removes and recreates DOM nodes

Conditional interface

<div>
  <ul ng-show="user.interests">Interests:
    <li ng-repeat="interest in user.interests">
        {{interest}}
    </li>
  </ul>
  <p ng-hide="user.interests">
    No interests selected!
  </p>
</div>

<div>
  <video ng-if="intro.active" src="tutorial.ogg">
  </video>
</div>

<div ng-switch on="user.category">
  <p>Your prize is:</p>
  <b ng-switch-when="child">bag of candies!</b>
  <b ng-switch-when="adult">bottle of beer!</b>
  <b ng-switch-default>10$</b>
</div>
  • to show or hide part of UI use ng-show/ng-hide
  • it applies CSS display: none!important
  • ng-if actually removes and recreates DOM nodes
  • ng-switch generates DOM content based by choice

Operations within interface

<div ng-include="sidebar.html"></div>
  • ng-include loads template via AJAX
  • ng-init creates or sets value within scope
  • ng-click executes commands
  • ng-click can create or set value (like ng-init)
  • ng-click can run function
<div ng-init="votes = 0">
  <button ng-click="active = true">
    Activate
  </button>
  <button ng-click="votes = votes + 1">
    Vote
  </button>
  <button ng-click="activate()">
    Activate
  </button>
</div>

Collections

  • ng-repeat iterate over collection
  • copy of template for each item
  • separate scope for each item
  • special properties for local scope:
    • $index (0 .. length -1)
    • $first / $last (bool)
    • $middle (bool)
    • $odd / $even (bool)
<table>
  <tr ng-repeat="person in people">
    <td ng-class="{'row-odd': $odd}">Entry</td>
    <td>{{person.name}}</td>
    <td>{{person.city}}</td>
    <td>{{person.age}}</td>
    <td>
      <button ng-click="notice($index)">
        Send notice
      </button>
    </td>
  </tr>
</table>

Collections

  • ng-repeat easily combines with filters:
    • filter - query matching
    • orderBy - choose property to order items
  <div ng-controller="MyCtrl">
    <form class="form-inline">
      <input ng-model="query" type="text"
        placeholder="Filter by" autofocus>
    </form>
    <ul ng-repeat="friend in friends | filter:query | orderBy: 'name' ">
      <li>{{friend.name}}</li>
    </ul>
  </div>

Forms

ng-submit cancel default browser submit action and launch provided function for handling form processing

<form action="/user/profile" method="POST">
  <input type="text" name="nick">
  <textarea name="comment"></textarea>
  <input type="submit value="Send">
</form> 

<form ng-submit="postComment()">
  <input type="text" ng-model="post.nick">
  <textarea ng-model="post.comment"></textarea>
  <button>Send</button>
</form>

app.controller('CommentCtrl', function($scope){
  $scope.postComment = function(){
    sendToServer($scope.post);
  }
});

Form inputs

ng-model works with most types of inputs:

text, email, password, radio, checkbox, textarea

<form>
  Name: 
  <input type="text" ng-model="user.name" required /><br />
  E-mail: 
  <input type="email" ng-model="user.email" /><br />
  Gender: 
  <input type="radio" ng-model="user.gender" value="male" />
  male
  <input type="radio" ng-model="user.gender" value="female" />
  female<br />
  <textarea ng-model="user.description"></textarea>
  <input type="submit" ng-click="update(user)" value="Save" />
</form>

Form inputs

ng-model works also with select

ng-repeat can be used for dynamic options list

<form>
  <select name="singleSelect" ng-model="data.singleSelect">
    <option value="option-1">Option 1</option>
    <option value="option-2">Option 2</option>
  </select>
  <br>
  <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
    <option ng-repeat="option in data.availableOptions" value="{{option.id}}">
      {{option.name}}
    </option>
  </select>
  <input type="submit" ng-click="update(user)" value="Save" />
</form>

Form validation

ng-model adds these classes:

  • ng-valid: the model is valid
  • ng-invalid: the model is invalid
  • ng-pristine: the control hasn't been interacted with yet
  • ng-dirty: the control has been interacted with
  • ng-touched: the control has been blurred
  • ng-untouched: the control hasn't been blurred
<form novalidate>
  Name: 
  <input type="text" ng-model="user.name" required>
  E-mail: 
  <input type="email" ng-model="user.email" required>
  <input type="submit" ng-click="save()" value="Save">
</form>

Form object

validation adds these properties:

  • formObject.$valid: form is valid
  • formObject.$invalid: form is invalid
  • formObject.$pristine: form hasn't been interacted with yet
  • formObject.$dirty: the control has been interacted with
  • formObject.$submitted: form has been submitted
<form name="userForm" ng-submit="saveUser()" novalidate>
  Name: 
  <input type="text" ng-model="user.name" required>
  E-mail: 
  <input type="email" ng-model="user.email" required>
  <input type="submit" ng-disabled="userForm.$invalid" value="Save">
</form>

Form field validation

validation adds these properties:

  • formObject.email.$valid: field is valid
  • formObject.email.$invalid: field is invalid
  • formObject.email.$pristine: field hasn't been interacted with yet
  • formObject.email.$dirty: field has been interacted with
  • formObject.email.$touched: field has been blurred
  • formObject.email.$untouched: field hasn't been blurred
<form name="userForm" ng-submit="saveUser()" novalidate>
  Name: 
  <input type="text" ng-model="user.name" name="name" required>
  E-mail: 
  <input type="email" ng-model="user.email" name="email" required>
  <input type="submit" ng-disabled="userForm.$invalid" value="Save">
</form>

Form validators

basic validators:

  • required / ng-required
  • ng-minlength - min length of string
  • ng-maxlength - max length of string
  • ng-pattern - RegExp validation

 

  • ng-trim allows to preserve leading/trailing whitespaces
  • ng-change - calls expression any time model changes
<input
  ng-model="string"
  [name="string"]
  [required="string"]
  [ng-required="boolean"]
  [ng-minlength="number"]
  [ng-maxlength="number"]
  [ng-pattern="string"]
  [ng-change="string"]
  [ng-trim="boolean"]>
...
</input>

Form errors

<div class="form-group">
  <label>Name</label>
  <input type="text" name="name" class="form-control" ng-model="name" required>
  <p ng-show="userForm.name.$invalid && userForm.name.$dirty" class="help-block">
    You name is required.</p>
</div>

<div class="form-group">
  <label>Username</label>
  <input type="text" name="username" class="form-control" 
    ng-model="user.username" ng-minlength="3" ng-maxlength="8">    
  <p ng-show="userForm.username.$error.minlength" class="help-block">Username is too short.</p>
  <p ng-show="userForm.username.$error.maxlength" class="help-block">Username is too long.</p>
</div>

objectForm.model.$error:

  • object containing all model errors

  • key - validator, value - error message

Routing

<html ng-app="admin">
  <head>
   <script src="src/angular.js">
   </script>
   <script src="src/app.js"></script>
  </head>
  <body>
    <h1>Admin panel</h1>
    <div ng-view></div>
  </body>
</html>

ng-view - directive loading and switching views based on route

$routeProvider - service user for defining routes

$routeParams - service for retrieving set of route parameters

var app = angular.module('admin', []);
app.config(function($routeProvider){
  $routeProvider
    .when('/', {
      controller: 'DashboardCtrl',
      templateUrl: 'users/dashboard.html'
    })
    .when('/user/:id', {
      controller: 'UserCtrl',
      templateUrl: 'users/profile.html',
    })
    .otherwise({redirectTo: '/'});
});
app.controller('DashboardCtrl', function($scope){
  $scope.users = gelAllUsers();
});
app.controller(
  'UserCtrl', function($scope, $routeParams){
    $scope.user = getUser($routeParams.id);
});

Services, DI & modules

Modules - since JS have no modules system (almost true), Angular implements it's own system

Dependency Injection - it's mechanism used when dependency is required for module - it prepares and provides dependency for module

Services - modules that contains logic unrelated to views (reusable logic outside of controllers and directives) 

 

app.config(function($routeProvider){
  ...
});
app.controller(
  'DashboardCtrl', function($scope){
  ...
});
app.controller(
  'UserCtrl', function($scope, $routeParams){
   ...
});

Built-in services

  • $rootScope - root scope access
  • $location - URL API
  • $http - HTTP server comunication (AJAX)
  • $q - promises (sane asynchronous workflows)
  • $sce - Strict Contextual Escaping 
  • $document - digest cycle aware document
  • $window - digest cycle aware window
  • $timeout - digest cycle aware timeouts
  • $routeProvider - routing definitions
  • ...

Custom services

  • factory() - function that returns one instance (singletion)
  • service() - creates new instance from constructor
  • constant() - holds value (can used in config)
  • value() - holds value (variable, object, function)
  • provider() - object with $get method, used internally to build factories and services, can be used in config

Custom service

app.factory('Users', function() {
  function create(user) {  ...  }
  function getAll() { ... }
  function find(id) { ... }
  function delete(id) { ... }

  return {
    create: create,
    getAll: getAll,
    find: find,
    delete: delete
  }
});

app.controller('MyCtrl', function($scope, Users){
    $scope.usersList = Users.getAll();
    $scope.currentUser = Users.find(1);
})
app.factory('github', function() {
    var serviceInstance = {};
    // define Github API calls    
    return serviceInstance;
  });

app.controller('MyCtrl', 
  function($scope, Users, github){
    $scope.usersList = Users.getAll();
    $scope.currentUser = Users.find(1);
    $scope.projects = 
      github.getProjects(currentUser.email);
})

Custom service

app.factory('CurrentUser', function() {
  function isAuthorised() {  ...  }
  function getCurrentUser() { ... }
  function authorize() { ... }
  function isAdmin() { ... }
  function logout() { ... }

  return {
    isAuthorised: isAuthorised,
    getCurrentUser: getCurrentUSer,
    authorize: authorize,
    isAdmin: isAdmin,
    logout: logout
  }
});
app.factory('audio', function($document) {
  var audio = $document[0].createElement('audio');
  return audio;
});

app.factory('player', function(audio) {
  var player = {
    playing: false,
    current: null,
    ready: false,

    play: function(program) {
      ...
    },
    stop: function() {
      ...
    }
  };
  return player;
});

AJAX

Asynchronous JavaScript and XML

Standard flow

  • URL input / link click
    1. browser makes HTTP GET request
    2. server sends back HTML + asets
    3. browser loads and render new page
  • Sending form
    1. browser makes HTTP POST request (form data included)
    2. server receives data, process and sends back new page
    3. browser loads and render new page

AJAX

Asynchronous JavaScript and XML

AJAX flow

  • user or client action
    1. create XMLHttpRequest
      1. set request endpoint (URL)
      2. set request type (GET, POST, PUT, DELETE)
      3. add data if needed
      4. define callback (function to process results)
    2. run request
    3. server receives request, process it and sends back respond
    4. browser detects respond and fire callback

REST(ful) API

 Representational State Transfer (REST)

GET
Read a specific resource (by an identifier) or a collection of resources.

PUT
Update a specific resource (by an identifier) or a collection of resources.

DELETE
Remove/delete a specific resource by an identifier.

POST
Create a new resource. Also a catch-all verb for operations that don't fit into the other categories.

/users/12345

- collection: users

- item id: 12345

 

/customers/33245

/user/12/comments

/orders/8769/lineitems/1

Asynchronous JS

 Callbacks

function doNumbers(x, y, callback) {
  var my_number = 
    Math.ceil(Math.random() * (x - y) + y);
    callback(my_number);
}

doNumbers(5, 15, function(num) {
  console.log("callback called! " + num);
});
//------------------------------------
function handleData(data) {
  alert(data);
}
$.ajax({
  url : 'example.com',
  type: 'GET',
  success : handleData
});

button.on('click' function(){
  console.log('button is clicked!');
})
  • useful for non-blocking operations
  • used for complex data flow and events handling
  • hard to handle in large applications
  • "callback hell"

Asynchronous JS

 Promises

var promise = getData()
    .then(function(string) {
      console.log(string)
    }, function(error) {
      console.error(error)
    })
    .finally(function() {
      console.log('Finished at:', new Date())
    });

var promise = getData()
    .then(function(num) {
      console.log(num)
      return num * 2
    })
    .then(function(num) {
      console.log(num) // = random number * 2
    })
  • useful for non-blocking operations
  • easy success/error handling
  • allows for async chaining
  • no more "callback hell"
  • magic inside!

Server communication

 $http


var promise = $http({
  method: 'GET',
  url: '/api/users.json'
});

promise.then(function(resp) {
  // do sth with users
});

promise.success(function(...));
promise.error(function(...));


$http.get('api/user', {params: {id: '5'}})
  .success(function(data, status, headers, config) {
    // Do something successful. 
  }).error(function(data, status, headers, config) {
    // Handle the error
  });

var postData = {text: 'long blob of text'};
$http.post('api/user', postData, {params: {id: '5'}})
  .success(function(data, status, headers, config) {
    // Do something successful
  }).error(function(data, status, headers, config) {
    // Handle the error
  });

app.controller('MyCtrl', 
  function($scope, $http){
    $scope.users = [];

    $http.get('api/user')
      .success(function(data) {
        $scope.users = data.users;
      }).error(function() {
        console.log('Cannot load users!');
      });
})

Server communication

 $http

  app.factory('githubService', function($http) {
    var githubUsername;
    var doRequest = function(path) {
      return $http({
        method: 'JSONP',
        url: 'https://api.github.com/users/' + githubUsername + '/' + path + '?callback=JSON_CALLBACK'
      });
    }
    return {
      events: function() { return doRequest('events'); },
      setUsername: function(newUsername) { githubUsername = newUsername; }
    };
  });

Server communication

 $http

app.factory('Findings', function($http) {
  var base;
  base = "/api/v1/findings";
  return {
    index: function(params) {
      return $http.get(base + ".json", {
        params: params
      });
    },
    "new": function(params) {
      return $http.post(base + ".json", params);
    },
    update: function(id) {
      return $http.put(base + "/" + id + ".json");
    },
    single: function(id) {
      return $http.get(base + "/" + id + ".json");
    },
    remove: function(id) {
      return $http["delete"](base + "/" + id + ".json");
    },
    title: function(link) {
      return $http.get(base + "/get_title.json", {
        params: {
          link: link
        }
      });
    }
  };
});
app.controller('findingCtrl', 
  function($scope, $routeParams, Findings, Comments) {
    $scope.id = $routeParams.finding_id;

    Findings.single($scope.id).success(function(data) {
      return $scope.finding = data;
    });

    $scope.saveComment = function(id, type, content) {
      if (content != null) {
        Comments["new"](id, type, content).then(function() {
          Findings.single($scope.id).success(function(data) {
            $scope.finding = data;
          });
          $scope.content = '';
        });
      }
    };
});

Directives

How to use directive?

  1. A new HTML element (<date-picker></date-picker>).
  2. An attribute on an element (<input type="text" date-picker/>).
  3. As a class (<input type="text" class="date-picker"/>).
  4. As comment (<!--directive:date-picker-->).

Best practices: element or attribute

Directives

app.directive('helloWorld', function() {
  return {
      restrict: 'AE',
      replace: 'true',
      template: '<h3>Hello World!!</h3>'
  };
});
<div>
 <hello-world/>
</div>

<div>
 <div hello-world></div>
</div>

-------------------------

<div>
  <h3>Hello World!</h3>
</div>
app.directive('helloWorld', function() {
  return {
      restrict: 'AE',
      template: '<h3>Hello World!!</h3>'
  };
});
<div>
 <hello-world/>
</div>

<div>
  <hello-world>
    <h3>Hello World!</h3>
  </hello-world>
</div>
---------------------
<div>
 <div hello-world></div>
</div>

<div>
  <div hello-world>
    <h3>Hello World!</h3>
  </div>
</div>

Directives

app.directive('helloWorld', function() {
  return {
    restrict: 'AE',
    template: '<h3>{{greeting}} {{name}}{{shout()}}</h3>',
    link: function(scope, element, attr) {
      scope.greeting = "Hi";
      scope.shout = function() {
        return "!".repeat(scope.name.length);
      }
    }    
  };
});

Link function gives access to:

  • child scope (inherited from parent scope)
  • DOM element where directive is applied
  • array of normalized attributes of element

Directives

Why using parent scope would be a bad idea?

  • code is dependent on parent scope
  • code can conflict with context
  • code is not reusable

How to solve this problem?

  • don't expect the exact context
  • communicate via interface
  • isolate inner scope

Directives

app.directive('helloWorld', function() {
  return {
    restrict: 'AE',
    scope: {
      greeting: '@greeting', // in this case '@'
      name: '=name' },
    template: '<h3>{{greeting}} {{name}}{{shout()}}</h3>',
    link: function(scope, element, attr) {
      //scope.greeting = "Hi";
      scope.shout = function() {
        return "!".repeat(scope.name.length);
      }
    }    
  };
});

<div hello-world greeting="Hi" 
    name="user.name"></div>

<div hello-world greeting="{{user.greeting}}" 
    name="user.name"></div>

Passing variables to scope:

  • use scope object definition
  • each property is declared in attributes
  • each property has type of binding
    • '@' one way text binding
    • '=' two way binding
    • '&' function/expression callback

Directives

app.directive('helloWorld', function() {
  return {
    restrict: 'AE',
    scope: {
      greeting: '@greeting', // in this case '@'
      name: '=name' },
    template: '<h3>{{greeting}} {{name}}{{shout()}}</h3>',
    link: function(scope, element, attr) {
      //scope.greeting = "Hi";
      scope.shout = function() {
        return "!".repeat(scope.name.length);
      }
    }    
  };
});

<div hello-world greeting="Hi" 
    name="user.name"></div>

<div hello-world greeting="{{user.greeting}}" 
    name="user.name"></div>

Passing variables to scope:

  • use scope object definition
  • each property is declared in attributes
  • each property has type of binding
    • '@' one way text binding
    • '=' two way binding
    • '&' function/expression callback

Isolated scope

  • directive has no access to parent scope
  • parent controller has no access to directive scope
  • can be hard at times, but leads to nice modularity

When to use directives

  • if you need to manipulate DOM
  • if you need to hack HTML
  • if you need to extend HTML
  • if you build interface components
  • if you integrate UI plugins with Angular

Good luck!

PS Workshops!

AngularJS Intro

By Krzysztof Jung

AngularJS Intro

  • 1,380