AJS-02

Embed Size (px)

Citation preview

  • 8/13/2019 AJS-02

    1/12

    Conceptual Overview

    1. 2. 3. 4. 5. 6. Invoice:7. Qty: 8. Costs: 9. Total:{{qty * cost | currency}}10. 11. 12.

    This is an HTML file with some new markup. In

    AJS, a file like this is called a template. When

    AJS starts your application, it parses and

    processes the new markup from the template

    using a compiler. The loaded, transformed

    and rendered DOM is called view. The first

    kind of new markup are called directives.

    They apply special behavior to attributes or

    elements in the HTML. The ng-appattribute is

    linked to a directive that automatically initializes our application. In case of the inputelement,

    AJS is able to automatically validate that the entered text is non empty by evaluating the

    requiredattribute. The ng-modeldirective stores/updates the value of the input field into/from

    a variable and shows the validation state of the input field by adding css classes. In AJS,directives are the only place where an application touches the DOM. To access the DOM

    directly, you need to write a custom directive.

    The second kind of new markup are the double curly braces {{ expression | filter }}. When

    the compiler encounters this markup, it will replace it with the evaluated value of the markup.

    An expressionin a template is a JavaScript-like code snippet that allows to read and write

    variables. AJS provides a scopefor the variables accessible to expressions. The values that are

    stored in variables on the scope are referred to as the model. A filterformats the value of the

    expression for display to the user. AJS provides live bindings: Whenever the input values

    change, the value of the expressions are automatically recalculated and the DOM is updatedwith their values. The concept behind this is two-way data binding.

    1. 2. 3. 4. 5. angular.module('invoice1', [])6. .controller('InvoiceController', function() {7. this.qty= 1;

  • 8/13/2019 AJS-02

    2/12

    8. this.cost= 2;9. this.inCurr= 'EUR';10. this.currencies= ['USD', 'EUR', 'CNY'];11. this.exRate= { USD: 1, EUR: 0.74, CNY: 6.09 };12. this.total= functiontotal(outCurr) {13. return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);14. };15. this.convertCurrency= functionconvertCurrency(amount, inCurr, outCurr) {16. return amount * this.exRate[outCurr] * 1 / this.exRate[inCurr];17. };18. this.pay= functionpay() { window.alert("Thanks!"); };19. });20. 21. 22. 23. 24. Invoice:25. Quantity: 26. 27. Costs: 28. 29. {{c}}30. 31. 32. 33. Total:34. 35. {{invoice.total(c) | currency:c}}36. 37. Pay38. 39. 40. 41.

    A constructor function creates a

    controllerwhose purpose is to expose

    variables and functionality to expressions

    and directives. The ng-controllerdirective

    tells AJS that the InvoiceController is

    responsible for the element with the

    directive and all of the elements children.

    The syntax InvoiceController as invoice

    tells AJS to instantiate the controller and

    save it in the variable invoicein the

    current scope. All expressions in the page to read and write variables within that controllerinstance are prefixed with invoice. The possible currencies are defined in the controller and

    added to the template using ng-repeat. The result of the totalfunction in the controller is

    bound to the DOM using {{ invoice.total() }}. This binding is live and the DOM will be

    automatically updated whenever the result of the function changes. The directive ngClickused

    with the button will evaluate the corresponding expression whenever the button is clicked. We

    are also creating a moduleat which we register the controller.

  • 8/13/2019 AJS-02

    3/12

    All the logic in the example is contained in the InvoiceController . It is a good practice to move

    view independent logic from the controller into a serviceso it can reused by other parts of the

    application as well. Later on, the service can be changed to load the exchange rate from the

    web without changing the controller.

    1. angular.module('finance2', [])2. .factory('currencyConverter', function() {3. var currencies= ['USD', 'EUR', 'CNY'],4. exRates= { USD: 1, EUR: 0.74, CNY: 6.09 };5. return { currencies: currencies, convert: convert };6. function convert(amount, inCurr, outCurr) {7. return amount * exRates[outCurr] * 1 / exRates[inCurr];8. }9. });10.11.angular.module('invoice2', ['finance2'])12. .controller('InvoiceController', ['currencyConverter', function(currencyConverter) {13. this.qty= 1;14.

    this.cost= 2;15. this.inCurr= 'EUR';

    16. this.currencies= currencyConverter.currencies;17.18. this.total= functiontotal(outCurr) {19. return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);20. };21. this.pay= functionpay() {22. window.alert("Thanks!");23. };24. }]);

    The convertCurrency function and the definition

    of the existing currencies are moved to a

    different module. The controller gets hold of the

    now separated function through dependency

    injection. Everything within AJS (directives,

    filters, controllers, service, ) is created and

    wired using dependency injection. Within AJS,

    the DI container is called injector. To use DI,

    there needs to be a place where all the things

    that should work together are registered. In AJS,

    this is the purpose of modules. When AJS starts,

    it will use the configuration of the module withthe name defined by the ng-appdirective,

    including the configuration of all modules that

    this module depends on.

    In this example, the template contains the directive which tells AJS

    to use the invoice2module as the main module for the application. [11] shows that this

    depends on the finance2module. By this, AJS uses the InvoiceController and the

  • 8/13/2019 AJS-02

    4/12

    currencyConverterservice. After AJS knows of all the parts of the application, it has to create

    them. Controllers are created using a factory function. There are multiple ways to define the

    factory for services. Here, we are using a function that returns the currencyConverter function

    as the factory for the service.

    How does the InvoiceController get a reference to the currencyConverter function? Witharguments defined on the constructor function, the injector creates objects in the right order

    and passes the previously created objects into the factories of the objects that depend on

    them. By the argument currencyConverter of the InvoiceController, AJS knows about the

    dependency between the controller and the service and calls the controller with the service

    instance as argument.

    We now pass an array to the module.controller function instead of a plain function. The array

    first contains the name of the service dependencies that the controller needs. The last entry in

    the array is the controller constructor function. AJS uses this array syntax to define the

    dependencies so that the DI also works after minifying the code, which will most probablyrename the argument name of the controller constructor function to something shorter like a.

    In the following, we fetch the exchange rates from the Yahoo Finance API.

    1. angular.module('finance3', [])2. .factory('currencyConverter', ['$http', function($http) {3. var YAHOO_FINANCE_URL_PATTERN=4. 'http://query.yahooapis.com/v1/public/yql?q=select * from '+5. 'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+6. 'env=store://datatables.org/alltableswithkeys& callback=JSON_CALLBACK',7. currencies= ['USD', 'EUR', 'CNY'],8. exRates= {};9. refresh();10. return { currencies: currencies, convert: convert, refresh: refresh };11.12. function convert(amount, inCurr, outCurr) {13. return amount * exRates[outCurr] * 1 / exRates[inCurr];14. }15.16. function refresh() {17. var url= YAHOO_FINANCE_URL_PATTERN.18. replace('PAIRS', 'USD' + currencies.join('","USD'));19. return $http.jsonp(url).success(function(data) {20. var newUsdToForeignRates= {};21. angular.forEach(data.query.results.rate, function(rate) {22. var currency= rate.id.substring(3,6);23. newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);24. });25. usdToForeignRates= newUsdToForeignRates;26. });27. }28. }]);

    Our currencyConverter service of the finance module now uses the $httpservice, a built-in

    service provided by AJS for accessing the backend. It is a wrapper around XMLHttpRequest and

    JSONPtransports.

  • 8/13/2019 AJS-02

    5/12

    Tutorial

    Bootstrapping AJS apps

    1. 2. ...3. 4. ...5.

    Nothing here {{'yet' + '!'}}

    The ng-appattribute represents the ngAppAJS directive. It is used to flag the html element that

    AJS should consider to be the root element of the application. The entire html page or only a

    portion of it can be treated as an AJS application. [3] downloads the angular.jsscript and

    registers a callback that will be executed by the browser when the containing HTML page is fully

    downloaded. When the callback is executed, AJS will bootstrap the application with the root of

    the application DOM being the element on which the ngAppdirective was defined. [5] includes a

    double-curly binding with an expression. The binding tells AJS to evaluate the expression andinsert the result into the DOM in place of the binding. AJS expression is a JavaScript-like code

    snippet that is evaluated by AJS in the context of the current model scope, rather than within

    the scope of the global context (window). The binding is static and the model is empty.

    Generally AJS apps are bootstrapped using ngApp

    directive but you can also use imperative or manual

    way. During app bootstrap: [1] The injectoris created

    [2] The injectorcreates the root scope that will

    become the context for the model of the app [3] AJS

    compiles the DOM starting at the ngAppelement,processing any directives and bindings found along the

    way. Once the app is bootstrapped, AJS waits for

    browser events that might change the model. If the

    model changes, the affected bindings will be updated.

    Static Template

    The above is a purely static HTML page. The HTML code will be turned into a template that AJS

    will use to dynamically display the same result with any set of data.

    1. 2. 3. Nexus S4.

    Fast just got faster with Nexus S.

    5. 6. 7. Motorola XOOMwith Wi-Fi8.

    The Next, Next Generation tablet.

  • 8/13/2019 AJS-02

    6/12

    9. 10.

    Angular Templates

    For AJS apps, the use if MVC design pattern is encouraged. In AJS, the view is a projection of the

    model through the HTML template. This means that whenever the model changes, AJSrefreshes the appropriate binding points, which updates the view.

    1. 2. 3. ...4. 5. 6. 7. 8. 9. 10. {{phone.name}}11.

    {{phone.snippet}}

    12. 13. 14.15.

    The ng-controllerdirective attaches a

    PhoneListCtrlcontroller to the DOM at this

    point. The expressions in the bindings refer to

    the application model set up in the controller.

    The data model (a simple array of phones in

    object literal notation) is now instantiatedwithin the PhoneListCtrlcontroller. It is

    simply a constructor function that takes a

    $scopeparameter.

    1. varphonecatApp = angular.module('phonecatApp', []);2. phonecatApp.controller('PhoneListCtrl', function($scope) {3. $scope.phones = [4. {'name': 'Nexus S', 'snippet': 'Fast just got faster with Nexus S.'},5. {'name': 'Motorola XOOM with Wi-Fi', 'snippet': 'The NNG tablet.'},6. {'name': 'MOTOROLA XOOM', 'snippet': 'The NNG tablet.'}7. ];8.

    });

    The PhoneListCtrlcontroller is registered in the AJS module phonecatApp, specified in the ng-

    appdirective as the module to load when bootstrapping the AJS. By providing context for the

    data model, the controller allows us to establish data-binding between the model and the view.

    The PhoneListCtrlcontroller attaches the phone data to the $scopethat was injected into the

    controller function. This scope is a prototypical descendant of the root scope that was created

  • 8/13/2019 AJS-02

    7/12

    when the application was defined. This controller scope is available to all bindings located

    within the tag. A scope can be seen as the glue which

    allows the template, model and controller to work together.

    Filtering Repeaters

    1. 2. 3. 4. 5. Search: 6. 7. 8. 9. 10. 11. {{phone.name}}12.

    {{phone.snippet}}

    13. 14. 15. 16. 17.

    We added a standard HTML

    tag and used AJS

    filterfunction to process the

    input for the ngRepeat

    directive. The data that a user

    types into the input box

    (named query) is immediatelyavailable as a filter input in the

    list repeater (phone in phones

    | filter:query). The filter

    function uses the query value

    to create a new array that

    contains only those records

    that match query. ngRepeat

    automatically updates the view in response to the changing number of phones returned by the

    filterfilter.

    Two-way Data Binding

    1. Search: 2. Sort by:3. 4. Alphabetical5. Newest

  • 8/13/2019 AJS-02

    8/12

    6. 7. 8. 9. {{phone.name}}10.

    {{phone.snippet}}

    11. 12.

    We added a html

    element named orderProp

    so that users can pick from

    the two provided sorting

    options. We then chained

    the filterfilter with

    orderByfilter to further

    process the input into the

    repeater. orderByis a filter

    that takes an input array,

    copies it and reorders the

    copy which is then returned.

    AJS creates two-way binding

    between the selectelement

    and the orderPropmodel. orderPropis then used as the input for the orderByfilter.

    1. varphonecatApp = angular.module('phonecatApp', []);2. phonecatApp.controller('PhoneListCtrl', function($scope) {3. $scope.phones = [4. {'name': 'Nexus S', 'snippet': 'Fast Nexus S.', 'age': 1},5. {'name': 'Wi-Fi', 'snippet': 'The NNG tablet.', 'age': 2},6. {'name': 'MOTOROLA, 'snippet': 'The NNG tablet.', 'age': 3}7. ];8. $scope.orderProp = 'age';9. });

    We modified the phonesmodel and added an ageproperty to each phone record. We added a

    line to the controller that sets the default value of orderPropto ageelse the orderByfilter

    would remain uninitialized until a user picked an option from the drop down menu. There is a

    two-way data-binding. When the app is loaded, Newest is selected in the drop down menu

    because orderPropis set to agein the controller. The binding works in the direction from our

    model to the UI. If you select Alphabetically the model will be updated and the phones will bereordered. The binding works in the direction from the UI to the model.

    XHRs & Dependency Injection

    The app/phones/phones.json file is a dataset containing a list of phones in JSON format.

    1. [ { "age": 13, "id": "moto", "name": "Motorola", "snippet": "Hello"... }, ...]

  • 8/13/2019 AJS-02

    9/12

    AJS $httpis a built-in service injected by AJS DI subsystem where needed.

    1. varphonecatApp = angular.module('phonecatApp', []);2. phonecatApp.controller('PhoneListCtrl', function($scope, $http) {3. $http.get('phones/phones.json').success(function(data) {4. $scope.phones = data;5.

    });6. $scope.orderProp = 'age';

    7. });$httpmakes an HTTP GET

    request to the web server,

    asking for phone/phones.json

    (the url is relative to the

    index.htmlfile) and returns a

    promise objectwith a

    success method. We call this

    method to handle theasynchronous response and

    assign the phone data to the

    scope controlled by this

    controller, as a model called

    phones. To use a service in AJS, declare the name of the required dependencies as arguments to

    the controllers constructor function. The dependency injector takes care of creating any

    transitive dependencies the service may have. AJS built-in services, scope methods and some

    APIs have a $ prefix to namespace them. To prevent collisions, avoid naming your services and

    models starting with $. When JavaScript is minified, the function arguments of the controller is

    minified and DI would not be able to identify services correctly. To overcome the issue, use

    $injectproperty on the controller function or the inline bracket notation which wraps the

    function to be injected into an array of strings (representing the dependency names) followed

    by the function to be injected. In the second method, the constructor function is provided inline

    as an anonymous function when registering the controller.

    1. functionPhoneListCtrl($scope, $http) {...}2. PhoneListCtrl.$inject = ['$scope', '$http'];3. phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);4.5. functionPhoneListCtrl($scope, $http) {...}6. phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);7.8. phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {..

    .}]);

    Templating Links & Images

    The app/phones/phones.json file is a dataset containing a list of phones in JSON format.

  • 8/13/2019 AJS-02

    10/12

    1. [ { ... "id": "moto", "imageUrl": "img/phone/a.jpg", "name": "Motorola", ... }, ...]Template:

    1. ...2. 3. 4. 5. {{phone.name}}6.

    {{phone.snippet}}

    7. 8. 9. ...

    We add phone images to each record using an image tag with the ngSrcdirective which

    prevents the browser from treating the AJS {{ expression }}markup literally and initiating a

    request to invalid urlhttp://.../app/{{phone.imageUrl}} which it would have done if the

    regular srcattribute had been used.

    Routing and Multiple Views

    A layout template is a template that is common for all views in our application. Other partial

    templates are included into this layout template depending on the current routethe view

    that is currently displayed to the user. Application routes in AJS are declared via the

    $routeProvider, which is the provider of the $route service. This service makes it easy to wire

    together controllers, view templates, and the current URL location in the browser. Using this

    feature, we can implement deep linking, which lets us utilize the browsers history (back and

    forward navigation) and bookmarks.

    When the application bootstraps, AJS creates an injector that will be used for all DI stuff in thisapp. The injector itself doesnt know anything about what $httpor $routeservices do, in fact it

    doesnt even know about the existence of these services unless it is configured with proper

    module definitions. The sole responsibilities of the injector are to load specified module

    definition(s), register all service providers defined in these modules, and when asked, inject a

    specified function with dependencies (services) that it lazily instantiates via their providers.

    Providers are objects that provide (create) instances of services and expose configuration APIs

    that can be used to control the creation and runtime behavior of a service. In case of the $route

    service, the $routeProvider exposes APIs that allow you to define routes for your application.

    NOTE: Providers can only be injected into configfunctions. Thus, you could not inject

    $routeProviderinto PhoneListCtrl.

    AJS modules solve the problem of removing global state from the application and provide a way

    of configuring the injector. As opposed to AMD or require.js modules, AJS modules dont try to

    solve the problem of script load ordering or lazy script fetching. These goals are totally

    independent and both module systems can live side by side and fulfil their goals.

    http://.../app/%7b%7bphone.imageUrl%7d%7dhttp://.../app/%7b%7bphone.imageUrl%7d%7dhttp://.../app/%7b%7bphone.imageUrl%7d%7dhttp://.../app/%7b%7bphone.imageUrl%7d%7d
  • 8/13/2019 AJS-02

    11/12

    app/js/app.js

    1. varphonecatApp = angular.module('phonecatApp', [2. 'ngRoute', 'phonecatControllers']);3.4. phonecatApp.config(['$routeProvider', function($routeProvider) {5. $routeProvider.6. when('/phones', {7. templateUrl: 'partials/phone-list.html' ,8. controller: 'PhoneListCtrl'9. }).10. when('/phones/:phoneId', {11. templateUrl: 'partials/phone-detail.html',12. controller: 'PhoneDetailCtrl'13. }).14. otherwise({15. redirectTo: '/phones'16. });17. }]);

    In order to configure the app with routes, create a module phonecatApp. The second argument

    passed to angular.module lists the modules that phonecatAppdepends on. By listing ngRoute

    and phonecatControllers modules as dependencies, we can use the directives and services

    they provide. Using configAPI we request the $routeProvider to be injected into our config

    function and use the $routeProvider.when API to define our routes. /phones/:phoneId is an

    example of URL template matching. The variable is extracted into the $routeParamsobject.

    app/js/controllers.js

    1. varphonecatControllers = angular.module('phonecatControllers', []);2.3. phonecatControllers.controller( 'PhoneListCtrl', ['$scope', '$http',4. function($scope, $http) {5. $http.get('phones/phones.json').success(function(data) {6. $scope.phones = data;7. });8. $scope.orderProp = 'age';9. }]);10.11.phonecatControllers.controller( 'PhoneDetailCtrl', ['$scope', '$routeParams',12. function($scope, $routeParams) {13. $scope.phoneId = $routeParams.phoneId;14. }]);

    The $route service is usually used in conjunction with the ngViewdirective whose role is to

    include the view template for the current route into the layout template. Starting with AJS 1.2,ngRoute is in its own module and must be loaded by loading the angular-route.js file distributed

    with AJS using the tag.

  • 8/13/2019 AJS-02

    12/12