AngularJS

Enlaces

Por ejemplo:

Introducción

AngularJS es un framework para el desarrollo de SPA (Single Page Applications). El objetivo es poder desarrollar aplicaciones modulares y testeables. No es un toolkit gráfico.

Se puede usar para crear una página web, pero también para hacer una aplicación móvil (usando por ejemplo PhoneGap).

Como toolkit para generar HMI para móviles, puede usarse Unity Framework.

AngularJS es un framework para el lado cliente de las aplicaciones.

Ahora la tendencia es MEAN (MongoDB/Mongoose + ExpressJS + AngularJS + NodeJS). Todo en JavaScript.

contexto.

AngularJS es un framework MVC. Alternativa a Dojo (que es un toolkit, que incluye un framework MVC), backbone, ember, ... Tiene la ventaja de tener a Google detrás. Y por lo que dicen, parece bien pensado.

Implementa MVW: Model-View-Whatever. En otras palabras, permite implementar diferentes patrones de diseño: Model-View-Controller, Model-View-Presenter, Model-View-View-Controller, ... En donde:

  • Model: contiene los datos por ejemplo en la base de datos.
  • View: muestra la interfaz con el usuario. Puede haber una o varias para un mismo set de datos.
  • Controller: es quien está en medio controlando la interacción entre ambos. Típicamente, esta capa se ejecuta en el cliente.

Ejemplo sencillo

Por ejemplo:

<!DOCTYPE html>
<html>

<body>

<div ng-app="">
  <p>Name: <input type="text" ng-model="name"></p>
  <p ng-bind="name"></p>
</div>

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>

</body>
</html>

Nota

interesa poner el script al final, para que cargue la página rápidamente y no se quede esperando a que cargue el script.

Directivas

Permiten extender el HTML. Son atributos en los tags HTML (como se puede ver en la página anterior). Por ejemplo:

  • ng-app: indica que es una aplicación de Angular.
  • ng-model: envía valores de alguna entrada de la vista HTML a la variable $scope del controlador.
  • ng-bind: hace lo opuesto a ng-model, es decir, trae una variable de $scope a la página HTML.
  • ng-init: sirve para inicializar datos de la aplicación. (Sería el valor por defecto). Se usa poco, porque es parte de la responsabilidad de los controladores que veremos después.
  • ng-repeat: sirve para iterar en una lista.

También tenemos:

  • ng-disabled: como ng-bind, afectando el atribut HTML “disable”.
  • ng-show: como ng-bind, afectando el atribut HTML “show”.

También tenemos eventos:

  • ng-click: define que es lo que hace cuando se hace click. Puede aparecer una expresión de AngularJS que veremos a continuación.

Nota

aunque las directivas comienzan con ng-, HTML5 puede extenderse. Para hacerlo compatible con HTML5, hay que hacer data-ng-.

Las últimas directiva que veremos son:

  • ng-controller: ésta la dejamos para más tarde donde hablaremos de controladores y de $scope.
  • ng-view: es sustituido en función del routing que indiquemos. Lo veremos posteriormente.

Expresiones y Data Binding

Dentro de la página HTML, podemos añadir expresiones, que son como pequeños trozos de código JavaScript. El resultado de su ejecución aparecerá en el HTML. Aparecen como:

{{ <codigo> }}

Hablamos de Data Binding a la referencia a variables dentro de $scope. Por ejemplo:

{{ empleado.nombre }}

Las expresiones pueden tener la pinta:

{{ 5 + edad + 8 }}
{{ firstName + " " + lastName[0] + "" + lastName[1] }}

Filtros

Dentro de las expresiones, podemos añadir filtros. Por ejemplo:

{{ person.lastName | uppercase }}
{{ person.name | lowercase }}
{{ (cantidad * precio) | currency }

También hay directivas que aceptan filtros, como a ng-repeat:

<li ng-repeat="x in names | filter:surname  | orderBy:'country'" >

Routing

Especificamos las rutas:

angular.module('F1FeederApp', [
  'F1FeederApp.services',
  'F1FeederApp.controllers',
  'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider.
    when("/drivers", {
       templateUrl: "partials/drivers.html",
       controller: "driversController"}).
    when("/drivers/:id", {
       templateUrl: "partials/driver.html",
       controller: "driverController"}).
    otherwise({
      redirectTo: '/drivers'});
}]);

En la generación del módulo, usamos el método config.

Y la página principal podría tener la pinta:

<!DOCTYPE html>
<html>
<head>
  <title>F-1 Feeder</title>
</head>

<body ng-app="F1FeederApp">
  <ng-view></ng-view>
  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/angular-route/angular-route.js"></script>
  <script src="js/app.js"></script>
  <script src="js/services.js"></script>
  <script src="js/controllers.js"></script>
</body>
</html>

Views

Las vistas funcionan como si fueran templates (me recuerda un poco a jinja2). Es básicamente un fichero HTML que será compilado (¿en el cliente?):

<!DOCTYPE html> <html ng-app> <head>

<title>Hola Mundo</title> <script type=”text/javascript”

</head> <body>

Escribe un nombre: <input type=”text” ng-model=”sometext” />

<h1>Hola {{ sometext }}</h1>

</body> </html>

Este fichero se puede ver perfectamente en un navegador. Hay que notar que:

  • ng-app: indica cuál es el scope de la aplicación.
  • script: hace referencia a angular.min.js.
  • ng-model: esto es una directiva de AngularJS. Básicamente define una variable (en este caso “sometext”), de forma que puede ser referenciado con {{ sometext }}. Conforme se edita el texto en “input”, se modifica allí donde se referencia.

Controllers

El controller viene a ser un “intermediario” que adapta la información entre su origen y la vista.

En la práctica, un controlador es simplemente una función que tiene como parámetro el objeto $scope:

function ($scope) {...}

El objeto $scope contiene las variables y métodos con los que interactúan las directivas con las que hablábamos con anterioridad.

Un ejemplo mínimo sería:

function personController($scope) {
    $scope.person = {
        firstName: "José",
        lastName: "García"
        fullName: function () {
           return $scope.person.firstName + " " + $scope.person.lastName;
           }
    };
}

Sin embargo, la funcion anterior se crea en el espacio de nombres global (y eso NO mola). Lo que haremos es incorporarlo a un módulo de AngularJS:

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

app.controller("myCtrl", function($scope) {
    $scope.firstName = "José";
    $scope.lastName = "García";
});

Ello crea un módulo llamado “myApp” y añade el controlador “myCtrl” y define la función asociada.

Servicios

Lo normal no será que los datos estén hardcoded en el controlador. Lo normal será que se lean los datos de un servidor o de una base de datos. Para ello existen los servicios.

Tenemos el servicio $http:

var promesa = $http.get( <url> )

que devuelve una “promesa”. Dada la naturaleza asíncrona de $http, se devuelve un objeto que contiene las funciones success y failure:

promesa.success( function (response) {
  <hacemos algo con la información recibida>
  }
)

Angular admite cualquier arquitectura. Podemos hacer que el servicio forme parte del controlador. Algo así:

function customersController($scope,$http) {
    $http.get( 'http://server.com/service.php');
        .success(function(response) {$scope.names = response;});
}

que como vemos, lee información de un servidor y la incorpora a $scope.

Sin embargo, es deseable la separación de “concerns”.

En ese sentido podemos crear nuestro propio servicio que implementará la API con la que se comunica con un determinado servidor/servicio:

angular.module('F1FeederApp.services', []).
  factory('ergastAPIservice', function($http) {

    var ergastAPI = {};

    ergastAPI.getDrivers = function() {
      return $http({
        method: 'JSONP',
        url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    return ergastAPI;
  });

¿qué hemos hecho?:

  • Hemos creado un modulo para los servicios “F1FeederApp.services”..
  • Hemos creado el sevicio “ergastAPIservice” y lo hemos añadido al módulo. Pasamos como parámetro $http para declarar esa dependencia.

¿Cómo se usaría esta API desde un controlador?:

angular.module('F1FeederApp.controllers', []).
  controller('driversController', function($scope, ergastAPIservice) {
    $scope.nameFilter = null;
    $scope.driversList = [];

    ergastAPIservice.getDrivers().success(function (response) {
        //Dig into the responde to get the relevant data
        $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
    });
  });

Para la siguiente vista:

<!DOCTYPE html>
<html>
<head>
    <title>Ejemplo2</title>
    <script type="text/javascript"
        src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" />
    <script type="text/javascript" src="controller.js" ></script>
    <script src="app.js" ></script>

</head>
<body ng-app="EjemploApp" ng-controller="mivistaController">
    <div ng-repeat="f in files">
       <p>{{ f.name }}&nbsp;{{f.extension}}</p><br/>
    </div>
</body>
</html>

notar que:

  • ng-app: ahora asignamos un nombre de aplicación (puede haber múltiples aplicaciones y así acotamos su scope).
  • ng-controller: identifica el controller que aplica. El contenido del controller es accesible dentro del scope.

Creamos el fichero controller.js de la siguiente forma:

angular.module('EjemploApp.controllers', []).
controller('mivistaController', function($scope) {
  $scope.files = [
    {
      name: 'foto_bonita',
      extension: 'jpg'
    },
    {
      name: 'imagen',
      extension: 'png'
    }
  ];
});

Para terminar, ya tenemos que definir la aplicación app.js:

angular.module('EjemploApp', [
  'EjemploApp.controllers'
]);

Services

AngularJS proporciona los siguientes servicios para leer datos de un servidor:

  • $http: es una capa sobre XMLHttpRequest y JSONP. Devuelve una promesa.
  • $resource: aporta mayor nivel de abstracción.

Creamos el fichero services.js. Encapsula las comunicaciones con el servidor:

angular.module('EjemploApp.services', []).
  factory('lyricsAPIservice', function($http) {

    var lyricsAPI = {};

    lyricsAPI.get = function() {
      return $http({
        method: 'JSONP',
        //url: 'http://api.chartlyrics.com/apiv1.asmx/SearchLyric?artist=Bjork&song=Hunter'
        url: 'http://api.lyricsnmusic.com/songs?api_key=d39c276574d9bcffa3804b0f97bde1&q=Bjork%20Hunter&callback=JSON_CALLBACK'
      });
    }

    return lyricsAPI;
  });

El controller es el que adapta dichos datos a cada vista:

angular.module('EjemploApp.controllers', []).
  controller('mivistaController', function($scope, lyricsAPIservice) {
  $scope.files = [];


  lyricsAPIservice.get().success(function (response) {
        //Dig into the responde to get the relevant data
        //$scope.files =[{'name':'hola','ext':'png'}];
        //var xml = response;
        //$scope.files = [{'name':'hola','ext':'png'}]; //.MRData.StandingsTable.StandingsLists[0].DriverStandings;
        $scope.files = response.data;//[{'name':'hola','ext':'png'}];
    });
});

Tenemos que añadir el servicio a app.js:

angular.module('EjemploApp', [
  'EjemploApp.controllers',
  'EjemploApp.services'
]);

Y por último, la vista será:

<!DOCTYPE html>
<html>
   <head>
       <title>Ejemplo2</title>
       <script type="text/javascript"
           src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
       <script type="text/javascript"  src="app.js" ></script>
       <script type="text/javascript" src="services.js"></script>
       <script type="text/javascript" src="controller.js"></script>
   </head>
   <body  ng-app="EjemploApp" ng-controller="mivistaController">
       <div ng-repeat="f in files">
          <p> {{ f.artist.name }}&nbsp;{{ f.title }} {{ f.url }}</p><br/>
       </div>
   </body>
</html>

Ejemplo mínimo

Un ejemplo de Model sería:

//Model: Objetos JavaScript
$scope.files = ['foo','bar', 'baz'];

que guarda un array de nombres. Son los datos que vamos a presentar o malipular.

Un ejemplo de View sería:

<!-- View: HTML -->
<div ng-repeat="f in files"></div>

que es un trozo de HTML. Aparece ng-repeat que es una directiva.

Por último, un ejemplo de Controller:

//Controller: Código Javascript
function addFile(fileName){
  $scope.files.push(fileName);
}

Qué hay que saber

  1. Implement the basic nuts-and-bolts of Angular: views, models, scope, two-way databinding, and controllers. Our hands-on how-to guide.
  2. Routing – how to use Angular’s page routing system to implement single-page-applications that are the new popular approach to modern Javascript/AJAX style development
  3. Directives – how Angular allows us to create special active extensions to HTML
  4. CSS integration and creating polished interfaces