Taller AngularJS – Paso 0 – Paso 1



Taller AngularJS – Paso 0 – Paso 1

0 0


taller-angular


On Github juanfran / taller-angular

Taller AngularJS

Instalación

git clone --depth=14 https://github.com/angular/angular-phonecat.git
cd angular-phonecat
npm install

Comandos

npm start //inicia un servidor local
npm test //tests unitarios con karma
npm run protractor //tests E2E con Protractor
npm run update-webdriver //instala los drivers necesarios para Protractor

http://localhost:8000/app/index.html

Paso 0

git checkout -f step-0

app/index.html

<!doctype html>
<html lang="en" ng-app>
<head>
  <meta charset="utf-8">
  <title>My HTML File</title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
  <link rel="stylesheet" href="css/app.css">
  <script src="bower_components/angular/angular.js"></script>
</head>
<body>

  <p>Nothing here {{'yet' + '!'}}</p>

</body>
</html>

Iniciamos aplicación la aplicación con ng-app que es una directiva. Esta directiva se encarga de decirla a Angular cual es elemento padre de nuestra aplicación

Templates de Angular, expresiones parecidas a javascript

Paso 1

git checkout -f step-1

Módulos

var myApp = angular.module('myApp', []);
<html ng-app="myApp">

MVC

Podemos agrupar todo en un módulo (Modelo, vista, controlador)

Controller

var myApp = angular.module('myApp', []);

myApp.controller('MyFirstCtrl', function ($scope) {
  $scope.saludo = "Hello world";
});
<div ng-controller="MyFirstCtrl">
     <h1>{{saludo}}</h1>
</div>

Injección de dependencias

$scope, es la comunicación que hay entre el controller y la vista

Todo lo que metamos en $scope es visible en el html

Directivas

<ul>
    <li ng-repeat="element in list">
      <p>{{element}}</p>
    </li>
  </ul>

Una directiva añade un coportamiento a un elemento del DOM

Las directivas de Angular empiezan con ng

Ejercicio

<p>Total number of phones: 2</p>
  <ul>
    <li>
      <span>Nexus S</span>
      <p>
        Fast just got faster with Nexus S.
      </p>
    </li>
    <li>
      <span>Motorola XOOM™ with Wi-Fi</span>
      <p>
        The Next, Next Generation tablet.
      </p>
    </li>
  </ul>

Test controller

describe('PhoneListCtrl', function(){

  beforeEach(module('phonecatApp'));

  it('should create "phones" model with 2 phones', inject(function($controller) {
    var scope = {},
        ctrl = $controller('PhoneListCtrl', {$scope:scope});

    expect(scope.phones.length).toBe(2);
  }));

});;

En Angular los tests son muy importantes, nos da muchas herramientas para que Angular sea muy sencillo de testas

Teneis una carpeta con archivos preparados para los tests

npm test

Paso 2

git checkout -f step-2

Data-binding

phonecatApp.controller('PhoneListCtrl', function ($scope) {
  $scope.prueba = 'Hola mundo';
});
<input type="text" ng-model="prueba" />
<p>{{prueba}}</p>

Filtros

<li ng-repeat="phone in phones | filter:query">

filter es un servicio

Ejercicio

test/e2e/scenarios.js

describe('PhoneCat App', function() {
  describe('Phone list view', function() {
    beforeEach(function() {
      browser.get('app/index.html');
    });
    it('should filter the phone list as user types into the search box', function() {
      var phoneList = element.all(by.repeater('phone in phones'));
      var query = element(by.model('query'));

      expect(phoneList.count()).toBe(3);

      query.sendKeys('nexus');
      expect(phoneList.count()).toBe(1);

      query.clear();
      query.sendKeys('motorola');
      expect(phoneList.count()).toBe(2);
    });
  });
});

+ añadir la query en el title de la página

npm run protractor

ng-bind-template

Paso 3

git checkout -f step-3

Ejercicio

Nuevo filtro `| orderBy:orderProp`

describe('PhoneCat controllers', function() {
  describe('PhoneListCtrl', function(){
    var scope, ctrl;

    beforeEach(module('phonecatApp'));

    beforeEach(inject(function($controller) {
      scope = {};
      ctrl = $controller('PhoneListCtrl', {$scope:scope});
    }));

    it('should create "phones" model with 3 phones', function() {
      expect(scope.phones.length).toBe(3);
    });

    it('should set the default value of orderProp model', function() {
      expect(scope.orderProp).toBe('age');
    });
  });
});

Hemos añadido la propiedad age en el scope

Crear un combo con propiedad age

it('should be possible to control phone order via the drop down select box', function() {
      var phoneNameColumn = element.all(by.repeater('phone in phones').column('{{phone.name}}'));
      var query = element(by.model('query'));

      function getNames() {
        return phoneNameColumn.map(function(elm) {
          return elm.getText();
        });
      }

      query.sendKeys('tablet');

      expect(getNames()).toEqual([
        "Motorola XOOM\u2122 with Wi-Fi",
        "MOTOROLA XOOM\u2122"
      ]);

      element(by.model('orderProp')).findElement(by.css('option[value="name"]')).click();

      expect(getNames()).toEqual([
        "MOTOROLA XOOM\u2122",
        "Motorola XOOM\u2122 with Wi-Fi"
      ]);
    });

Paso 4

git checkout -f step-4
myApp.controller('MyFirstCtrl', function ($scope, $http) {
    $http.get('urljson.json').success(function (data) {
        //callback action
    });
});

Servicio $http

describe('PhoneListCtrl', function(){
    var scope, ctrl, $httpBackend;

    // Load our app module definition before each test.
    beforeEach(module('phonecatApp'));

    // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
    // This allows us to inject a service but then attach it to a variable
    // with the same name as the service in order to avoid a name conflict.
    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
      $httpBackend = _$httpBackend_;
      $httpBackend.expectGET('phones/phones.json').
          respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);

      scope = $rootScope.$new();
      ctrl = $controller('PhoneListCtrl', {$scope: scope});
    }));

    it('should create "phones" model with 2 phones fetched from xhr', function() {
      expect(scope.phones).toBeUndefined();
      $httpBackend.flush();

      expect(scope.phones).toEqual([{name: 'Nexus S'},
                                   {name: 'Motorola DROID'}]);
    });

    it('should set the default value of orderProp model', function() {
      expect(scope.orderProp).toBe('age');
    });

el $httpBackend.flush() hace que se ejecute la llamada

Paso 5

git checkout -f step-5
<img ng-src="{{url}}">

Añadir link a los teléfonos usando su campo id

it('should render phone specific links', function() {
      var query = element(by.model('query'));
      query.sendKeys('nexus');
      element(by.css('.phones li a')).click();
      browser.getLocationAbsUrl().then(function(url) {
        expect(url.split('#')[1]).toBe('/phones/nexus-s');
      });
    });

Paso 6

git checkout -f step-6

Factory

myApp.factory('Amazon', function($http) {
     var apiKey = 1234;

      return {
           getAll: function () {
                return $http.get('urljson.json', key: apiKey);
           }
      }
});

myApp.controller('MyFirstCtrl', function (Amazon) {
    Amazon.getAll();
});

Service vs Factory vs Provider

//cuando llamos a un Provider en el config nos devulve una instancia de esta función
myApp.provider('Amazon', function () {
    var apiKey = '';

    //lo puedo llamar desde el .config
    this.setApiKey = function(key) {
      apiKey = key;
    };

    // Factory, el servicio es una instancia del lo que retorna $get
    this.$get = function($http) {
      return {
           getAll: function () {
                return $http.get('urljson.json', key: apiKey);
           }
      }
   }
});

myApp.config(function(AmazonProvider) {
   AmazonProvider.setApiKey(12345);
});

Providers

  • Exponen APIs de configuración para poder controlar la creación y el comportamiento de un servicio
  • Los provider sólo se pueden usar en la función config de Angular
<script src="bower_components/angular-route/angular-route.js"></script>

routeProvider

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider
      .when('/phones', {
        templateUrl: 'xx.html',
        controller: 'controller'
      })
<div ng-view></div>

Nos olvidamos del ng-controller

Ejercicio

Mover el listado fuera del index Mover nuestro controller a un nuevo módulo Injectar el nuevo módulos y el módulo de ngRoute en el módulo phonecatApp Crear la página de detalle de un teléfono pero que sólo muestre el id, usar el servicio $routeParams para recuperarlo
it('should redirect index.html to index.html#/phones', function() {
    browser.get('app/index.html');
    browser.getLocationAbsUrl().then(function(url) {
        expect(url.split('#')[1]).toBe('/phones');
      });
  });

  describe('Phone list view', function() {
    beforeEach(function() {
      browser.get('app/index.html#/phones');
    });
...

  describe('Phone detail view', function() {

    beforeEach(function() {
      browser.get('app/index.html#/phones/nexus-s');
    });


    it('should display placeholder page with phoneId', function() {
      expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');
    });
  });

Paso 7

git checkout -f step-7

Ejercicio

Traer por ajax el detalle del teléfono y pintar la información en su vista Test unitarios del controller de detalle

Paso 8

git checkout -f step-8

Ejercicio

Crear un filtro (`.filter`) en un nuevo módulo e injectarselo a nuestra aplicación El filtro tiene que hacaer un return de una funcion La función tiene que recibir un parámetro, si es true devolver \u2713 y si es false \u2718 {{phone.connectivity.infrared | checkmark}} Test!! --> inject(function(checkmarkFilter) {

Paso 9

git checkout -f step-9

Ejercicio

En el detalle del teléfono añadir una imagen principal con la primera foto Al hacer click cambiar la imagen principal (directiva ng-click) Test de protractor

Paso 10

git checkout -f step-10
<script src="bower_components/angular-resource/angular-resource.js"></script>

var myRest = $resource('url', {}, {
    query: {method:'GET', params:{}, isArray:true}
});

myRest.query();
myRest.get({params1: 111}, function() {});

Módulo que para lidiar con REST es mucho más cómodo que $http

Explicar las acciones, query

Ejercicio

Crear nuestro propio servicio de factoria que devuelva $resource que lea del json de listado y de detalle Actualizar listado y detalle para que usen este servicio en vez de $http Comprobar los test