git clone --depth=14 https://github.com/angular/angular-phonecat.git cd angular-phonecat npm install
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
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
git checkout -f step-1
var myApp = angular.module('myApp', []);
<html ng-app="myApp">
MVC
Podemos agrupar todo en un módulo (Modelo, vista, controlador)
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
<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
<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>
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
git checkout -f step-2
phonecatApp.controller('PhoneListCtrl', function ($scope) { $scope.prueba = 'Hola mundo'; });
<input type="text" ng-model="prueba" /> <p>{{prueba}}</p>
<li ng-repeat="phone in phones | filter:query">
filter es un servicio
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
git checkout -f step-3
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" ]); });
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
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'); }); });
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); });
<script src="bower_components/angular-route/angular-route.js"></script>
phonecatApp.config(['$routeProvider', function($routeProvider) { $routeProvider .when('/phones', { templateUrl: 'xx.html', controller: 'controller' })
<div ng-view></div>
Nos olvidamos del ng-controller
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'); }); });
git checkout -f step-7
git checkout -f step-8
git checkout -f step-9
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