Workshop 14: AngularJS Parte III
-
Upload
visual-engineering -
Category
Software
-
view
274 -
download
2
Transcript of Workshop 14: AngularJS Parte III
Front End Workshops
XII. AngularJS - Part 3
Juan Luís Marí[email protected]
Pablo [email protected]
Héctor [email protected]
Overview
“AngularJS is turn-based”
In previous chapters...
● AngularJS intro● Modules and dependency injection● Filters, Controllers and Services● Routes● Digest cycle ● Data binding● Directives ...
Overview
● Scope functions
● Patterns
● Testing
● Directives functions (continuation of custom directives)
○ compile, controller and link functions
● Build-in directives
Scope functions
— Watch, apply & Digest—
Scope functions
Watch, apply and digest are $scope functions used to monitor and process
binded data in terms of the digest cycle.
There are used very scarcely but it is good to know in order to understand bad
updates, performance issues and digest loop errors.
http://tutorials.jenkov.com/angularjs/watch-digest-apply.html
Scope functions - watch
Data binding creates ‘watchers’ internally. Every digest cycle will check
watched variables for changes and react accordingly.
Variables are watched implicitly when we use double curly brackets:
{{myVar}}
and explicitly:
$scope.watch(‘myVar’, function(){...});
As 1st parameter we can call a function, also.
The 2nd function is called a ‘listener’.
http://tutorials.jenkov.com/angularjs/watch-digest-apply.html
Scope functions - apply
Through the digest cycle, watched variables will be monitored.
But sometimes we don’t have a variable to watch and we are waiting for an
async event.
Some directives and services already use apply for us internally.
● ng-click
● $timeout
● $http
and explicitly:
$scope.$apply(function() {
// your code here
});
The 2nd function is a ‘listener’. The ‘value argument’ can be a function too.
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Scope functions - apply
function Ctrl($scope) { $scope.message = "Waiting 2000ms for update"; setTimeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000);}Note: Instead, you could use $timeout.
Scope functions - apply
When to use apply:● Very rarely ( or better NEVER ). Two well-know cases:
○ dealing with sockets○ wrapping non-Angular code○ when you know a variable is initialized outside the digest cycle
How to use apply:● with a function preferably:
$scope.apply( function() {});● Tip: Use $timeout without delay (but it will be defered).
http://stackoverflow.com/questions/23070822/angular-scope-apply-vs-timeout-as-a-safe-apply
Scope functions - digest
$scope.digest triggers a digest cycle, but we should never call it directly.
● digest is triggered internally
● after the execution of $scope.apply, at the end of its execution
We use apply (and therefore digest) when we don’t have a variable to watch
and we are waiting for an async event.
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Common Patterns
— non trivial only—
Known patterns
Angular is already build as a collection of patterns
● Services are singletons.
● Factories are factories.
● Data-binding and $scope.watch implements a watcher pattern.
● Controller and Template.
● Dependency injections: everywhere.
http://blog.mgechev.com/2014/05/08/angularjs-in-patterns-part-1-overview-of-angularjs/
Observer (publish-subscribe)
Observers are similar to watchers, but instead of permanently watch a fuction they subscribe to events. To subscribe:
$scope.on(‘event-name’, function handler() {
//your code here
}
And to launch the event:function ExampleCtrl($scope) {
$scope.$emit('event-name', { foo: 'bar' }); //upwards on the scope hierarchy
$scope.$broadcast('event-name', { foo: 'bar' }); //downwards to all child
$rootScope.$broadcast('event-name', { foo: 'bar' }); //to everybody
}
http://blog.mgechev.com/2014/07/05/angularjs-in-patterns-part-3/
Event listener (View observer)
Multiple build-in directives are event listeners:
● ng-click, ng-dbl-click
● ng-mouse…
● ng-keydown, ng-keyup
● ng-change
You can add your own event listeners. Let see an example:
http://tutorials.jenkov.com/angularjs/events.html
Event listener exampleangular.module('myApp', [])
.directive('myDirective', function myDirective () {
return {
restrict: 'AE',
link: myDirectiveLink
} });
function myDirectiveLink (scope, element, attrs) {
var domElement = element[0]; // Get the native JS element
domElement.addEventListener("dragstart", myEventListener, false); // You can use ngDraggable but it uses jQuery
}
function myEventListener () {
// Handle event here
}
Other patterns
Proxy
● a service using $http or $resource (Restful http) internally.
Data Mapper
● a service returning Model instances calling $http or $resource.
Testing
— AngularJS embraces testing—
AngularJS takes well care of testing:
● Well supported by Karma (test runner)
● Can use Mocha, Sinon, Chai ...
● Protractor and ngMock specially build for testing Angular.
● KEY: inject dependencies and mock key elements
Testing in AngularJS
https://www.smashingmagazine.com/2014/10/introduction-to-unit-testing-in-angularjs/https://quickleft.com/blog/angularjs-unit-testing-for-real-though/
E2E framework based on Jasmine and Selenium’s WebDriver
Ability to simulate user interaction
Simulation in selenium and most browsers
Usually, we will run tests in Selenium (without UI) and all target browsers.
We can automate this with Gulp
Protractor
Provides several services to mock Angular behaviour:
● $timeout, ● $controller : inject controllers● $httpBackend: patch or mock $http calls● module() and inject():
○ resolve dependencies on tests○ patch methods○ mock scope
ngMock
http://www.bradoncode.com/blog/2015/05/27/ngmock-fundamentals-angularjs-testing-inject/
Unit Testing
● Testing individual, small units of code → Need of isolated code○ Note: Bad isolated code may force the app to create related pieces as server’s
requests or new DOM elements → Affect other tests and produce errors
● Solutions: Dependency Injection and Mocking Services○ DOM elements abstracted → never directly modified
○ XHR requests and petitions → simulated (by dependency injection of $httpBackend)
■ All we really need is to verify whether a certain request has been sent or not, or
alternatively just let the application make requests, respond with pre-trained
responses and assert that the end result is what we expect it to be.
Dependency Injection ● AngularJS built-in. Allows to pass in a
component's dependencies and stub or mock
them as you wish.
● Does not modify any global variable, so it does
not affect other tests
angular.module('app', []).controller(ExampleCtrl, function ExampleCtrl($scope) {
...});
describe('ExampleCtrl tests', function() { beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){ // The injector unwraps the underscores (_) from around the parameter names when matching $controller = _$controller_; }));
describe('block to be tested', function() { it('test1', function() { // create a scope object for us to use. var $scope = {}; // now run that scope through the controller function, injecting any services or other injectables we need. // **NOTE**: this is the only time the controller function will be run, so anything that occurs inside of that will already be done before the first spec. var controller = $controller('ExampleController', { $scope: $scope }); }; }); });
$httpBackend● angular-mocks module allows to inject and mock the AngularJS services, so they can be extended
and used synchronously → $httpBackend is one of them
● Service in module ngMock
● Fake HTTP backend implementation suitable for unit testing applications that use the $http service.
● Allows not using external dependencies, so the requests to URLs are mocked.
● With dependency injection, it is easy to inject $httpBackend mock and use it to verify the requests
and respond with some testing data without sending a request to a real server.
● $httpBackend.flush() allows to flush pending requests, so the test will run synchronous but
preserving the async of the backend API
Custom directives - Continuation
— DOM Manipulation—
There are 3 types of functions, by order of execution:○ compile, controller and link
● Compile happens once, before the template is compiled.● The rest of functions is run once for each time the directive is used
■ For example in a ng-repeat of 4 elements, 4 loops○ Controller initialize the scope.○ Link happens when the linking is being made, by default after.○ We can divide link into two, pre-link and post-link
■ Pre-link happens before the element is linked to the scope■ Post-link happens just after, when the element affected is on the DOM.
● This is the most usual and potentially safest
Custom directives: functions
http://www.undefinednull.com/2014/07/07/practical-guide-to-prelink-postlink-and-controller-methods-of-angular-directives/
compile: function CompilingFunction($templateElement, $templateAttributes) {
…}
link: function LinkingFunction($scope, $element, $attributes) { ... }
…}
link: {
pre: function PreLinkingFunction($scope, $element, $attributes) { ... },
post: function PostLinkingFunction($scope, $element, $attributes) { ... },
}
Custom directives: functions
http://plnkr.co/edit/qrDMJBlnwdNlfBqEEXL2?p=previewhttps://github.com/angular/angular.js/wiki/Understanding-Directives
Custom directives: link, prelink, postlink
● There are 4 arguments available for these functions (in this order)
○ scope, elements, attributes and controllers
● You can access the DOM, you have the element.
● By default use link directly, which is equivalent to post-link alone.
● Remember, if possible provide values as soon as you can.
○ Don’t wait to post-link, do it in the controller or in compile
● [post-]link is the View part where you have everything in place and you do
the last adjustments and decisions regarding the DOM.
Custom directives: link, prelink, postlinkvar app = angular.module('app', []);app.directive('dad', function () { return { restrict: 'EA', template: '<div class="dad">{{greeting}}{{name}}'+ '<son></son>'+ '</div>', link: { pre: function(scope,elem,attr){ scope.name = 'Paul'; scope.greeting = 'Hey, I am '; } } };})
app.directive('son', function () { return { restrict: 'EA', template: '<div class="son">{{sonSays}}</div>', link: function(scope,elem,attr){ scope.sonSays = 'Hey, I am David, and my dad is '+ scope.name; } };});
<div ng-app="app"> <dad></dad></div>
Hey, I am PaulHey, I am David, and my dad is Paul
Custom directives: post-link,
● It is safe to manipulate the DOM in post-link as the element is already in
the DOM.
● It is possible to access the scope
● All child directives are linked so it’s safe to access them
○ their scope and the elements they affect.
● It is safe to attach events handlers to elements.
Custom directives: pre-link,
● Use of pre-link is scarce,
○ A child needs data from its parent
● Safe to attach an event to the DOM element
○ Not safe to access DOM elements from child directives
● The scope is not linked yet.
Custom directives: compile
● In this phase AngularJS manipulates the DOM of the HTML template
● Each directive has a chance to do some processing for all and each DOM
nodes it appears in.
● The scope is not attached yet.
● The template is still bare, without binding nor substitutions.
Built-in directives
Built-in directives
● ng-model● ng-src● ng-class● ng-style● ng-click● ng-if● ng-show● ng-include● ng-repeat
ng-model● Binds inputs to scope properties● If property does not exist, it is created and added to the scope● Watches model by reference Important!● Allows animation and validation
<script>
angular.module("module", [])
.controller("controller", ['$scope', function($scope) {
$scope.value = "Luis";
}]);
</script>
<div ng-controller="controller">
Name: <input ng-model="value"/>
</div>
ng-src● src leads to problems when evaluating expressions● Prevents browser from loading resources before an expression is
evaluated
Buggy way
Correct way
<img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
<img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
ng-class
● Allows to dynamically set CSS classes on an HTML● Ways of operating (syntax):
○ String○ Key-value pair object○ Array of strings (type 1) or objects (type 2)○ Ternary operator
● Usage options: class and attribute
ng-classString syntax
Array syntax
Key-value pair syntax
<input type="text" ng-model="style">
<div ng-class="style">String syntax</div>
<input type="text" ng-model="styleOne">
<input type="text" ng-model="styleTwo">
<div ng-class="[styleOne, styleTwo]">Array syntax</div>
<input type="checkbox" ng-model="great"> Barça
<input type="checkbox" ng-model="poor"> Madrid
<div ng-class="{ styleOne: great, styleTwo: poor }">
ng-classTernary operator
Specific numbers
ngClass as a class
ngClass as an attribute (every example shown except the last one)
ng-class="variableToEvaluate ? 'class-if-true' : 'class-if-false'">
<ul><li ng-class="{ 'text-success': $first }" ng-repeat="item in items">{{ item.name }}</li></ul>
<div class="item ng-class:type;">Stuff</div>
<div class="item ng-class:[styleOne, styleTwo];">Stuff</div>
<div class="item ng-class:{ 'text-error': wrong };">Stuff</div>
ng-style
● Allows setting style on an HTML conditionally● Interpolates javascript object into style attribute, not css class
Following directive will be translated to style=”color:red”
Following directive will be translated to class=”color”
For interpolation, instead of doing this:
do this:
ng-style="{color: 'red'}"
ng-class="{color: colorClass}"
style="width: {{progress}}"
ng-style="{width: progress}"
ng-click
● Specify custom behavior when an element is clicked
<button ng-click="count = 0">
Reset
</button>
<button ng-click="reset()">
Reset
</button>
ng-show
● Shows / hides HTML element based on an expression● Adds / removes ng-hide class● Animations
Element visible when $scope.value is truthy
Element hidden when $scope.value is falsy
<div ng-show="value"></div>
<div ng-show="value" class="ng-hide"></div>
ng-if
● Removes / recreates part of the DOM● scope is removed and recreated● ng-model with ng-if issues● Animations
Similar usage to ngShow / ngHide<div ng-if="value"></div>
ngAnimate
● Include angular-animate.js and load module● Directives support
○ ngRepeat○ ngShow○ ngHide○ ngIf○ ...
● CSS / JS based animations
ngAnimate
CSS based animations
● No need of JavaScript code at all● CSS class referenced between HTML and CSS● 2 css classes added depending on the animation event
<div ng-if="bool" class="fade">
Fade me in out
</div>
<button ng-click="bool=true">Fade In!</button>
<button ng-click="bool=false">Fade Out!</button>
.fade.ng-enter {
transition:0.5s linear all;
opacity:0;
}
.fade.ng-enter.ng-enter-active {
opacity:1;
}
ngAnimate
CSS based animations
● Staggering animations● ng-Animate css class● Custom keyframe animations
.zipper.ng-animate {
transition:0.5s linear all;
}
.zipper.ng-enter {
opacity:0;
}
.zipper.ng-enter.ng-enter-active {
opacity:1;
}
.zipper.ng-leave {
opacity:1;
}
.zipper.ng-leave.ng-leave-active {
opacity:0;
}
ngAnimate
JS based animations
Register JavaScript animation on the module
<div ng-repeat="item in items" class="slide">
{{ item }}
</div>
myModule.animation('.slide', [function() {
return {
enter: function(element, doneFn) {
// Do some cool animation and call doneFn
},
move: function(element, doneFn) {},
leave: function(element, doneFn) {}
}
}]);
ngAnimate
ngAnimate documentation: https://docs.angularjs.org/api/ngAnimate
ng-include● Add HTML code from external file to the current one
○ Fetches, compiles and includes an external HTML fragment● Create new scope for the included template● The included HTML files can also contain AngularJS code
<div ng-include="'myFile.html'"></div>
ng-include
● Cross-domain:○ By default, the ng-include directive does not allow you to include
files from other domains.○ To include files from another domain, you can add a whitelist of
legal files and/or domains in the config function of your application:
var app = angular.module('myApp', [])
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'http://www.refsnesdata.no/**'
]);
});
ng-repeat
● Instantiates a template once per item from a collection<div ng-repeat="item in collection"> ... </div>
● Each template instance gets its own scope
● It is also possible to get ngRepeat to iterate over the properties of an object○ Note: The built-in filters orderBy and filter do not work with objects
<div ng-repeat="(key, value) in myObj"> ... </div>
ng-repeat
ng-repeat
● Continuously watching if changes in the collection happens○ Automatically changing DOM (when adding, removing or reordering items)
○ “Keep track” items with their DOM elements → Avoids re-render them if not needed
● Default tracking function (changeable with track-by)○ Tracks by identity of item
○ Does not allows duplicated items
● ng-repeat in more than one parent element of the HTML○ ng-repeat-start
○ ng-repeat-endrange of the repeater