Introducción a AngularJS

con ejemplos

 

5 de Marzo de 2015

UJI-Castellón

Lorenzo González

Profesor de Formación Profesional

en el CIPFP Mislata (Valencia)

¿Qué es AngularJS?

Un framework JavaScript

Alternativas


 
  • Backbone: Bajo nivel. Pasó su tiempo

  • Knockout: Se queda corto

  • Ember: Hace unos años parecía la alternativa a AngularJS

  • React: Es lo que ahora es más "cool" pero no es tan completo

¿Por qué no aparece JQuery?

JQuery no es un framework es una librería para manejar el DOM

y vamos a seguir usándola pero desde AngularJS

¿Para qué usar AngularJS?

Para aplicaciones CRUD, es decir, de entrada y procesamiento de datos.

En AngujarJs lo importante es el modelo

Cuando queremos que el diseñador pueda modificar el HTML

Por muy fácil que parezca AngularJS,

como cualquier otro framework

es necesario

conocerlo en profundidad

AngularJS 2.0 va a cambiarlo todo radicalmente rompiendo la compatibilidad

Mi opinión es que es un cambio para

mejor y necesario

Empecemos

Estructura de Angular

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

function MainCtrl($scope) {
  $scope.nombre="Lorenzo";
}

app.controller('MainCtrl', MainCtrl); 

Mi primera aplicación

app.js

index.html

<html ng-app="app">
  <head>
    <script src="angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <div>
       Mi nombre es:{{nombre}}
    </div>
  </body>

</html>

El $scope

  • Es donde guardamos nuestros datos a presentar: ViewModel
  • Nunca se guardan valores directamente en el $scope sino que guardamos objetos en propiedades del $scope
$scope.miModelo={
   prop1:'valor1',
   prop2:'valor2'
}

Siempre debería haber un "." en el HTML al acceder al $scope

AngularJS sincroniza

el $scope y el HTML

Al actualizar el $scope se actualiza el HTML

Añadir un <input>

index.html

<html ng-app="app">
  <head>
    <script src="angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <div>
      <input ng-model="nombre" >
      Mi nombre es:{{nombre}}
    </div>
  </body>

</html>

Al modificar el HTML se actualiza el $scope lo que a su vez actualiza el HTML

"Two-way data binding"

¿Cómo realiza AngularJS el  Two-way data binding?

dirty-checking

¿Lo mejor del

"Two-way data binding" mediante

"dirty-checking"?

Mi modelo JavaScript es un

POJSO

(Plain-Old JavaScript Object)

var modelo={
  nombre:"Lorenzo",
  apellidos:"Diaz Ortega",
  direccion: {
    localidad:"Valencia",
    calle:"Las barcas"
    numero:4
  }

}

Se pueden lanzar eventos ante cambios del modelo


function MainCtrl($scope) {
  $scope.nombre="Lorenzo";

  $scope.$watch("nombre",function(newNombre,oldNombre) {

      alert("El nuevo nombre es:" + newNombre);

  });


  $scope.cambiarNombre=function() {
     $scope.nombre="Juan";
  } 


}

El método $watch avisa de cambios en el modelo

La función "cambiarNombre" no tiene que avisar de lo que cambia.

¿Y cuál es el precio?

No se puede modificar nada fuera de los eventos que ofrece AngularJS

$("#menu").on('click', function() {
    $scope.nombre="Juan"
});

Este código no funciona a menos que avises a AngularJS 

El rendimiento

Es un problema con páginas con muchísimos datos a mostrar: Scrolls Infinitos

<html ng-app="app">
  <head>
    <script src="angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <div>
      <input ng-model="nombre" >
      Mi nombre es {{::nombre}}
    </div>
  </body>

</html>

Solución con Angular 1.3: One-way data binding

El campo "nombre" ya no se actualiza si hay nuevos cambios

Directivas


Directivas


 
  • Son tag o atributos HTML específicos de AngularJS

  • Modifican el DOM

  • Lo mejor: te puedes crear tus propias directivas


  •  

Algunas

Directivas estándar

 
  • ng-click: Llamar a una función del $scope

  • ng-show: Mostrar un elemento

  • ng-hide: Ocultar un elemento

  • ng-disabled: Deshabilitar un elemento

 
function MainCtrl($scope) {
  $scope.nombre="Lorenzo";

  $scope.cambiarNombre=function() {
     $scope.nombre="Juan";
  } 

}
  <body ng-controller="MainCtrl">
    <div>
      <input ng-model="nombre" >
      Mi nombre es {{nombre}}

      <button ng-click="cambiarNombre()">Cambiar Nombre</button>

    </div>
  </body>

Directiva ng-click

function MainCtrl($scope) {
  $scope.nombre="Lorenzo";

  $scope.cambiarNombre=function() {
     $scope.nombre="Juan";
  } 

}
  <body ng-controller="MainCtrl">
    <div>
      <input ng-model="nombre" >
      <div ng-show="nombre=='Juan'">Mi nombre es {{nombre}}</div>

      <button ng-click="cambiarNombre()">Cambiar Nombre</button>

    </div>
  </body>

Directiva ng-show

function MainCtrl($scope) {
  $scope.nombre="Lorenzo";

  $scope.cambiarNombre=function() {
     $scope.nombre="Juan";
  } 

}
  <body ng-controller="MainCtrl">
    <div>
      <input ng-model="nombre" >
      <div ng-hide="nombre=='Juan'">Mi nombre es {{nombre}}</div>

      <button ng-click="cambiarNombre()">Cambiar Nombre</button>

    </div>
  </body>

Directiva ng-hide

function MainCtrl($scope) {
  $scope.nombre="Lorenzo";

  $scope.cambiarNombre=function() {
     $scope.nombre="Juan";
  } 

}
  <body ng-controller="MainCtrl">
    <div>
      <input ng-model="nombre"  ng-disabled="nombre=='Juan'" >
      Mi nombre es {{nombre}}

      <button ng-click="cambiarNombre()">Cambiar Nombre</button>

    </div>
  </body>

Directiva ng-disabled

Directivas sobre Arrays

 
 
 
 
  • ng-repeat: Repetir tags

  • ng-options: Rellenar un <select>
function MainCtrl($scope) {

  $scope.usuarios = [
    {
      idUsuario:34,
      nombre:"Juan",
      ape1:"Gonzalez"
    },
    {
      idUsuario:46,
      nombre:"Ana",
      ape1:"Morales"
    },
    {
      idUsuario:62,
      nombre:"Sara",
      ape1:"Diaz"
    },
    {
      idUsuario:18,
      nombre:"Marcos",
      ape1:"Ortega"
    } 
  ]

}

Directiva ng-repeat

  <body ng-controller="MainCtrl">

    <table>
      <tr>
        <th>Nombre</th>
        <th>Apellido</th>
      </tr>

      <tr ng-repeat="usuario in usuarios">
        <td>{{usuario.nombre}}</td>
        <td>{{usuario.ape1}}</td>
      </tr>
    </table>
    
  </body>

Directiva ng-repeat

function MainCtrl($scope) {

  $scope.usuarios = [
    {
      idUsuario:34,
      nombre:"Juan",
      ape1:"Gonzalez"
    },
    {
      idUsuario:46,
      nombre:"Ana",
      ape1:"Morales"
    },
    {
      idUsuario:62,
      nombre:"Sara",
      ape1:"Diaz"
    },
    {
      idUsuario:18,
      nombre:"Marcos",
      ape1:"Ortega"
    } 
  ]

  $scope.usuarioSeleccionado=null;

}

Directiva ng-select

  <body ng-controller="MainCtrl">
    <p>
      Selecciona un usuario:
      <select 
          ng-model="usuarioSeleccionado" 
          ng-options="usuario as usuario.nombre for usuario in usuarios" 
       >
        <option value="">-- Selecciona un usuario --</option>
      </select>
    </p>
    <p>
      El usuario seleccionado es: 
      {{usuarioSeleccionado.nombre}} {{usuarioSeleccionado.ape1}}
    </p>
  </body>

Directiva ng-select

Resumen Directivas

  • Las directivas modifican el DOM; los controladores NO lo modifican

  • Las directivas son declarativas 

  • Debes crearte nuevas directivas

  • Es complejo crear nuevas directivas

JQuery lo puedes seguir usando dentro de tus directivas

Servicios

 

Servicios

  • Son funcionalidades de la aplicación no relacionadas con el interfaz gráfico*

  • Nunca acceden a la página (DOM)*

  • Siempre son Singletons

  • Se obtienen mediante inyección de dependencias

  •  

*: A veces pueden modificar el DOM para ventanas modales, notificaciones, etc.    

Tipos de servicios

  • constant

  • value


  • factory


  • service

  • provider

Ejemplo "service"

function Login() {

   var logged=false;

   this.login=function(user,password) {
      if ((user==="foo") && (password==="bar")) {
         logged=true;
      }

     return logged;
   }
   
   this.isLogged=function() {
     return logged;
   }


}


app.service("login",Login);

"service" vs "value" vs "factory"



app.service("login",Login);         //Es la clase del Singleton



app.value("login",new Login());    //Es el objeto Singleton


app.factory("login",function() {   //Es una función que crea el Singleton
    return new Login()
});

¿Cómo inyectamos dependencias?

function Login() {

   var logged=false;

   this.login=function(user,password) {
      if ((user==="foo") && (password==="bar")) {
         logged=true;
      }

     return logged;
   }
   
   this.isLogged=function() {
     return logged;
   }


}


app.service("login",Login);
app.value("defaultUserPassword",{
  user:"foo",
  password:"bar"
});

function Login(defaultUserPassword) {

   var logged=false;

   this.login=function(user,password) {
      if ((defaultUserPassword.user===user) && 
         (defaultUserPassword.password===password)) {
         logged=true;
      }

     return logged;
   }
   
   this.isLogged=function() {
     return logged;
   }
}

Las dependencias son parámetros del constructor

¿Y qué pasa si minimizamos el código?

¡La inyección dejará de funcionar!

Solución

Login.$inject=['defaultUserPassword'];
function Login(defaultUserPassword) {


}

app.service("login",Login);

$inject es un array con los nombres de los servicios a inyectar

Providers

  • Un provider es una factoría de un servicio

  • Se ejecuta antes de "iniciarse" la aplicación por lo que NO existe aun ningún servicio creado, solo otros Providers

  • Permite configurase y consecuentemente configurar al servicio que creará

  • Permite configurarse mediante otros Providers o Constantes

  • Obligatoriamente tiene un método "$get" que es el que crea el servicio

¿Cuándo usar un Provider?

Cuando para inicializar el servicio se debe ejecutar código debido a la complejidad del servicio

AngularJS tiene unos bloques llamados "config" que permiten ejecutar código que define los Providers

app.config(function() {

//Este código se ejecuta para inicializar los providers

});
function Login(defaultUserPassword) {
   var logged=false;
   this.login=function(user,password) {
      if ((defaultUserPassword.user===user) && 
         (defaultUserPassword.password===password)) {
         logged=true;
      }

     return logged;
   }
   this.isLogged=function() {
     return logged;
   }
}

function LoginProvider() {
  var defaultUserPassword={
    user:undefined,
    password:undefined
  }
  
  this.setUser=function(user) {
    defaultUserPassword.user=user;
  }
  this.setPassword=function(password) {
    defaultUserPassword.password=password;
  }  
  this.$get=function() {
    return new Login(defaultUserPassword);
  }
}

app.provider("login",LoginProvider);
app.config(function(loginProvider) {

  loginProvider.setUser("foo");
  loginProvider.setPassword("bar");

});

Configurando el provider

Configuramos el provider "programaticamente", lo que nos da mucha versatilidad

IMPORTANTE: No injectamos el servicio sino el provider. Por éso se llama "loginProvider"

¿Y si queremos configurar el propio Provider con valores iniciales?

No podemos usar un "value" ya que es un servicio y por lo tanto no está inicializado.

AngularJS se inventa un nuevo servicio llamado "constant" que puede inyectarse en un Provider y en un  bloque "config"

app.constant("initialUserPassword",{
  user:"foo",
  password:"bar"
});
app.constant("initialUserPassword",{
  user:"foo",
  password:"bar"
});



$inject=['initialUserPassword']
function LoginProvider(initialUserPassword) {
  var defaultUserPassword={
  }
  angular.extend(defaultUserPassword,initialUserPassword);
  
  this.setUser=function(user) {
    defaultUserPassword.user=user;
  }
  this.setPassword=function(password) {
    defaultUserPassword.password=password;
  }  
  this.$get=function() {
    return new Login(defaultUserPassword);
  }
}

app.provider("login",LoginProvider);

app.config(function(loginProvider) {
  //loginProvider.setUser("foo2");
  //loginProvider.setPassword("bar2");
});

Usando "constant" en el Provider

Secuencia de inicialización de los servicios

No usar "new" para crear un objeto

Hacer que AngularJS cree el objeto para permitir inyecciones de la clase

Se usa el servicio "$inject" con el método "instantiate".

    var locals = {
        defaultUserPassword: defaultUserPassword
    };
    var login = $injector.instantiate(Login,locals);    
    
Login.$inject=['defaultUserPassword'];
function Login(defaultUserPassword) {

   //Aqui vael código pero no lo hemos puesto para ahorrar espacio

}


$inject=['initialUserPassword']
function LoginProvider(initialUserPassword) {
  var defaultUserPassword={
  }
  angular.extend(defaultUserPassword,initialUserPassword);
  
  this.setUser=function(user) {
    defaultUserPassword.user=user;
  }
  this.setPassword=function(password) {
    defaultUserPassword.password=password;
  }  
  this.$get=function() {
    var locals = {
        defaultUserPassword: defaultUserPassword
    };
    var login = $injector.instantiate(Login,locals);    
    
    return login;
  }
}

app.provider("login",LoginProvider);

Servicios predefinidos

  • $http


  • $timeout


  • $q


  • $window


  • $document

Ejemplo del servicio $http

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

MainCtrl.$inject=['$scope','$http'];
function MainCtrl($scope,$http) {

  var config={
    method:"GET",
    url:"https://api.github.com/repos/angular/angular.js/issues?state=open"
  }
   
  var promise=$http(config);

  promise.then(function(response){
      $scope.issues=response.data;
  },function(response) {
      alert("Fallo la petición:" + response.status);
  })

}

app.controller('MainCtrl',MainCtrl);

Ejemplo del servicio $http

    <table>

      <tr>
        <th>Numero</th>
        <th>Titulo</th>
        <th>Usuario</th>
      </tr>

      <tr ng-repeat="issue in issues">
        <td>{{issue.number}}</td>
        <td>{{issue.title}}</td>
        <td>{{issue.user.login}}</td>
      </tr>

    </table> 

Filtros


Filtros

 

  • Permiten modificar cómo se presentan los datos


  • Se pueden aplicar tanto al HTML como al JavaScript


  • Cada uno se puede crear sus propios filtros

Tipos de Filtros

 

 

  • Escalares

    • lowercase / uppercase

    • number

    • date

    • currency

  • Arrays

    • orderBy

    • limitTo

    • filter

 
MainCtrl.$inject=['$scope'];
function MainCtrl($scope) {
  $scope.texto = "HOLA mundo";
  $scope.numero = 1.14159;
  $scope.precio = 99.9;
  $scope.fecha = new Date();
});
  <body ng-controller="MainCtrl">

    <p>Texto: {{texto  | uppercase }}</p>
    <p>Texto: {{texto  | lowercase }}</p>
    <p>Numero:{{numero | number:4  }}</p>
    <p>Precio:{{precio | currency  }}</p>
    <br />
    <p>Fecha:            {{fecha | date:'dd/MM/yyyy' }}</p>
    <p>Fecha (short):    {{fecha | date:'short' }}</p>
    <p>Fecha (shortDate):{{fecha | date:'shortDate' }}</p>

  </body>

NOTA: Cuidado al usar formatos de fecha predefinidos.  Pueden cambiar entre versiones

Ejemplo de filtros de escalares

MainCtrl.$inject=['$scope'];
function MainCtrl($scope) {

  $scope.usuarios = [
    {
      idUsuario:34,
      nombre:"Juan",
      ape1:"Gonzalez"
    },
    {
      idUsuario:46,
      nombre:"Ana",
      ape1:"Morales"
    },
    {
      idUsuario:62,
      nombre:"Sara",
      ape1:"Diaz"
    },
    {
      idUsuario:18,
      nombre:"Marcos",
      ape1:"Ortega"
    } 
  ]

}

Ejemplo de filtro "orderBy"

Ejemplo de filtro "orderBy"

  <body ng-controller="MainCtrl">

    <table>
      <tr>
        <th>Nombre</th>
        <th>Apellido</th>
      </tr>

      <tr ng-repeat="usuario in usuarios | orderBy:['nombre','ape1']">
        <td>{{usuario.nombre}}</td>
        <td>{{usuario.ape1}}</td>
      </tr>
    </table>
    
  </body>

¿Necesitas más filtros?

Módulos


Módulos

 

 

Agrupan funcionalidades 

para posteriormente 

añadirlas a la aplicación

 

  • Definen nuevos:

    • Controladores

    • Directivas

    • Filtros

    • Servicios

 
 
 
 
 
  • Ejecutan código al cargar el módulo:

    • config: Código de configuración

    • run: Código "Main"

Contenido de un módulo

Directiva ng-app

 
 

 

  • Indica el nombre del módulo inicial a cargar

  • El resto de módulos se cargan como dependencias de este primer módulo

 
<html ng-app="app">

  <head>
    <script src="angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">   
  
  </body>

</html>

Ejemplo de módulos

angular.module("nombre",['ui.router']);
var modulo=angular.module("nombre");
modulo.controller("MainCrtl",MainCrtl);

Creando un módulo con dependencia de 'ui.router':

Obteniendo un módulo:

Usando el módulo para definir un controlador:

Para acabar

Broccoli

Protractor

Bower

Gulp

Grunt

Herramientas

Consejos

  • Controladores

    • Quita todo lo que puedas de los controladores, usa servicios reutilizables.

    • No uses $scope, hay una nueva forma de declarar un controlador que usa "this".

    • No uses controladores, crea directivas que hacen la función del controlador.

  • Módulos

    • Los módulos son útiles en librerías de terceros, en tu aplicación usa un único módulo llamado "app"

Más consejos

  • Usa promesas para casi todo, nunca sabes cuando algo será asíncrono.

  • No abuses del $watch, no suele ser necesario.

  • Arquitectura

    • Un fichero cada clase

    • Ordena los ficheros por funcionalidades y no por tipos

    • Arquitectura en 3 capas

  • Vuelve a simplificar tus controladores otra vez

Nos hemos dejado más cosas.......

especialmente las rutas, pero hoy no era un buen día para contarlas  ;-)

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

 

Gracias a todos

por vuestra atención

 

¿Preguntas?

@logongas

 

 

Mas en: http://cursoangular.es

Ejemplo en:  https://github.com/logongas/decharlas2015

Made with Slides.com