Introducción a AngularJS

[Fuente: http://angularjs.org/]

¿Porqué utilizar AngularJS?

El lenguaje HTML es bueno para declarar documentos estáticos, pero falla cuando intenta utuilizar vistas dinámicas en aplicaciones web. AngujarJS te permite extender el vocabulario HTML para tu aplicación. El entorno resultante es muy expresivo , legible y rápido de programar.

Alternativas

Existen otros frameworks que resuelven las carencias del HTML abstrayendo el HTML ,el CSS y/o el Javascript o proporcionando una forma imperativa de manipular el DOM.Ninguna de estas soluciones trata la raíz del problema: HTML no fue diseñado para vistas dinámicas.

Extensibilidad

AngularJS es un conjunto de herramientas para construirte el framework que más se ajuste a tu aplicación. Es completamente extensible y funciona bien con otras librerías. Cada aspecto puede ser modificado o ajustado a tus necesidades. Sigue leyendo para saber más.

Hola Mundo

Ejemplo 1

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}!</h1>
    </div>
  </body>
</html>

Añadiendo algo de control

Data Binding

El Data-binding es la forma automática de actualizar la vista según el modelo cambia, así como actualizar el modelo cuando la vista cambia. Esto ahora mucha DOM manipulation.

Controller

Los controllers gestionan el comportamiento que hay detrás de los elementos DOM. AngularJS te permite expresar ese comportamiento de una forma legible sin que tenga que haber el código feo de manipular el DOM , registrar callbacks o listeners de cambios en el modelo.

Plain JavaScript

A diferencia de otros frameworks, no hay necesidad de heredar de tipos propietarios o envoilver el modelo en metodos getter. Tan solo se utiliza Javascript plano. Esto hace el código más fácil de testear, mantener, reutilizar y libre de código oscuro.

Ejemplo 2

index.html

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script src="todo.js"></script>
    <link rel="stylesheet" href="todo.css">
  </head>
  <body>
    <h2>Todo</h2>
    <div ng-controller="TodoCtrl">
      <span>{{remaining()}} of {{todos.length}} remaining</span>
      [ <a href="" ng-click="archive()">archive</a> ]
      <ul class="unstyled">
        <li ng-repeat="todo in todos">
          <input type="checkbox" ng-model="todo.done">
          <span class="done-{{todo.done}}">{{todo.text}}</span>
        </li>
      </ul>
      <form ng-submit="addTodo()">
        <input type="text" ng-model="todoText"  size="30"
               placeholder="add new todo here">
        <input class="btn-primary" type="submit" value="add">
      </form>
    </div>
  </body>
</html>

todo.js

function TodoCtrl($scope) {
  $scope.todos = [
    {text:'learn angular', done:true},
    {text:'build an angular app', done:false}];

  $scope.addTodo = function() {
    $scope.todos.push({text:$scope.todoText, done:false});
    $scope.todoText = '';
  };

  $scope.remaining = function() {
    var count = 0;
    angular.forEach($scope.todos, function(todo) {
      count += todo.done ? 0 : 1;
    });
    return count;
  };

  $scope.archive = function() {
    var oldTodos = $scope.todos;
    $scope.todos = [];
    angular.forEach(oldTodos, function(todo) {
      if (!todo.done) $scope.todos.push(todo);
    });
  };
}

todo.css

.done-true {
  text-decoration: line-through;
  color: grey;
}

Enganchando con servicios de Back

Deep Linking

Un deep link refleja donde está el usuario en tu app en cada momento, es útil para que los usuarios puedan tener en sus marcadores (o favoritos) accesos directos a links de tu app. Las aplicaciones con html estáticos hacen esto automáticamente , pero las aplicaciones AJAX por su naturaleza no lo hacen. AngularJS combina los beneficios de poder hacer deep link con la comodidad del comportamiento de una aplicación de escritorio.

Validación de formularios

La validación de formularios del lado del cliente es una parte importante para conseguir una  buena user experiencce. AngularJS te permite declarar las reglas de validación del formulario sin tener que escribir código Javascript.Escribes menos código , vete a tomarte la cervecita antes.

Comunicación con el servidor

AngularJS te proporciona una capa de gestionar servicios encima de XHR, lo cual simplifica dramáticamente el código. Se hace un wrap de XHR para darte manejo de excepciones y “promisas”. Las “promisas” simplifican bastante tu código gestionan el retorno asíncrono de los datos. Esto te permite asignar propiedades de forma síncrona aunque el retorno sea asíncrono.

Ejemplo 3
index.html

<!doctype html>
<html ng-app="project">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular-resource.min.js">
    </script>
    <script src="https://cdn.firebase.com/v0/firebase.js"></script>
    <script src="http://firebase.github.io/angularFire/angularFire.js"></script>
    <script src="project.js"></script>
  </head>
  <body>
    <h2>JavaScript Projects</h2>
    <div ng-view></div>
  </body>
</html>

project.js

angular.module('project', ['firebase']).
  value('fbURL', 'https://angularjs-projects.firebaseio.com/').
  factory('Projects', function(angularFireCollection, fbURL) {
    return angularFireCollection(fbURL);
  }).
  config(function($routeProvider) {
    $routeProvider.
      when('/', {controller:ListCtrl, templateUrl:'list.html'}).
      when('/edit/:projectId', {controller:EditCtrl, templateUrl:'detail.html'}).
      when('/new', {controller:CreateCtrl, templateUrl:'detail.html'}).
      otherwise({redirectTo:'/'});
  });

function ListCtrl($scope, Projects) {
  $scope.projects = Projects;
}

function CreateCtrl($scope, $location, $timeout, Projects) {
  $scope.save = function() {
    Projects.add($scope.project, function() {
      $timeout(function() { $location.path('/'); });
    });
  }
}

function EditCtrl($scope, $location, $routeParams, angularFire, fbURL) {
  angularFire(fbURL + $routeParams.projectId, $scope, 'remote', {}).
  then(function() {
    $scope.project = angular.copy($scope.remote);
    $scope.project.$id = $routeParams.projectId;
    $scope.isClean = function() {
      return angular.equals($scope.remote, $scope.project);
    }
    $scope.destroy = function() {
      $scope.remote = null;
      $location.path('/');
    };
    $scope.save = function() {
      $scope.remote = angular.copy($scope.project);
      $location.path('/');
    };
  });
}

list.html

<input type="text" ng-model="search" class="search-query" placeholder="Search">
<table>
  <thead>
  <tr>
    <th>Project</th>
    <th>Description</th>
    <th><a href="#/new"><i class="icon-plus-sign"></i></a></th>
  </tr>
  </thead>
  <tbody>
  <tr ng-repeat="project in projects | filter:search | orderBy:'name'">
    <td><a href="{{project.site}}" target="_blank">{{project.name}}</a></td>
    <td>{{project.description}}</td>
    <td>
      <a href="#/edit/{{project.$id}}"><i class="icon-pencil"></i></a>
    </td>
  </tr>
  </tbody>
</table>

detail.html

<form name="myForm">
  <div class="control-group" ng-class="{error: myForm.name.$invalid}">
    <label>Name</label>
    <input type="text" name="name" ng-model="project.name" required>
    <span ng-show="myForm.name.$error.required" class="help-inline">
        Required</span>
  </div>

  <div class="control-group" ng-class="{error: myForm.site.$invalid}">
    <label>Website</label>
    <input type="url" name="site" ng-model="project.site" required>
    <span ng-show="myForm.site.$error.required" class="help-inline">
        Required</span>
    <span ng-show="myForm.site.$error.url" class="help-inline">
        Not a URL</span>
  </div>

  <label>Description</label>
  <textarea name="description" ng-model="project.description"></textarea>

  <br>
  <a href="#/" class="btn">Cancel</a>
  <button ng-click="save()" ng-disabled="isClean() || myForm.$invalid"
          class="btn btn-primary">Save</button>
  <button ng-click="destroy()"
          ng-show="project.$id" class="btn btn-danger">Delete</button>
</form>

Creando componentes

Directivas

Las directivas es una característica única y muy poderosa de Angular. Con las directivas podemos inventarnos nuevas tags de HTML que sean específicas a nuestra aplicación.

Componentes reutilizables

Utilizamos directivas también con el objetivo de hacer componentes reutilizables. Un componente te permite envolver y esconder un estructura DOM y CSS complejas y su comportamiento. Esto te permite enfocarte en lo que tu aplicación hace o qué aspecto tendrá de forma separada.

Localización

Una parte importante de las aplicaciones serias es la localización. En Angular con directivas y filtros puedes construir bloques que estén en todos los idiomas que quieras.

Ejemplo 4

index.html

<!doctype html>
<html ng-app="components">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script src="components.js"></script>
    <script src="beers.js"></script>
  </head>
  <body>
    <tabs>
      <pane title="Localization">
        Date: {{ '2012-04-01' | date:'fullDate' }} <br>
        Currency: {{ 123456 | currency }} <br>
        Number: {{ 98765.4321 | number }} <br>
      </pane>
      <pane title="Pluralization">
        <div ng-controller="BeerCounter">
          <div ng-repeat="beerCount in beers">
            <ng-pluralize count="beerCount" when="beerForms"></ng-pluralize>
          </div>
        </div>
      </pane>
    </tabs>
  </body>
</html>

components.js

angular.module('components', []).
  directive('tabs', function() {
    return {
      restrict: 'E',
      transclude: true,
      scope: {},
      controller: function($scope, $element) {
        var panes = $scope.panes = [];
 
        $scope.select = function(pane) {
          angular.forEach(panes, function(pane) {
            pane.selected = false;
          });
          pane.selected = true;
        }
 
        this.addPane = function(pane) {
          if (panes.length == 0) $scope.select(pane);
          panes.push(pane);
        }
      },
      template:
        '<div class="tabbable">' +
          '<ul class="nav nav-tabs">' +
            '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+
              '<a href="" ng-click="select(pane)">{{pane.title}}</a>' +
            '</li>' +
          '</ul>' +
          '<div class="tab-content" ng-transclude></div>' +
        '</div>',
      replace: true
    };
  }).
  directive('pane', function() {
    return {
      require: '^tabs',
      restrict: 'E',
      transclude: true,
      scope: { title: '@' },
      link: function(scope, element, attrs, tabsCtrl) {
        tabsCtrl.addPane(scope);
      },
      template:
        '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +
        '</div>',
      replace: true
    };
  })

beers.js

function BeerCounter($scope, $locale) {
  $scope.beers = [0, 1, 2, 3, 4, 5, 6];
  if ($locale.id == 'en-us') {
    $scope.beerForms = {
      0: 'no beers',
      one: '{} beer',
      other: '{} beers'
    };
  } else {
    $scope.beerForms = {
      0: 'žiadne pivo',
      one: '{} pivo',
      few: '{} pivá',
      other: '{} pív'
    };
  }
}

Embed an inject

Embeddable (Anidable)

Angular JS se acopla bien con las otras tecnologías. Muchos otros frameworks requieren que te lo bajes completo. En la página fuente podemos ver viendo el código fuente que tiene varias aplicaciones Angular JS embebidas en ella.

Injectable

La inyección de dependencia en AngularJS te permite describir declarativamente cóomo tu aplicación estará ensamblada. Esto significa que tu aplicación no necesita ningún método main().

La inyección de dependencia es también el núcleo de AngularJS. Esto significa que cualquier componente que no se ajusta a tus necesidades puede ser fácilmente reemplazado.

Testable

AngularJS fue diseñado desde el principio para ser testeable. Fomenta la separación de la vista y del comportamiento, viene de serie con mocks, y se aprovecha completamente de la inyección de dependencia. Viene ya con un escenario de ejecución end-to-end incluido