AngularJS: Conceptos Básicos

Actualmente es cada vez más frecuente utilizar HTML5 como plataforma para desarrollar aplicaciones complejas. El aumento de rendimiento de los motores de javascript y las posibilidades que ofrecen HTML5 y CSS3 hacen que se pueda conseguir una experiencia cercana a las aplicaciones de escritorio, con la ventaja de poder ser casi multiplataforma (siempre hay pequeñas diferencias con las que lidiar cuando saltamos de un navegador a otro).

Logo de AngularJS

Para facilitar el desarrollo de este tipo de aplicaciones han ido surgiendo multitud de frameworks e incluso en este blog ya he escrito sobre algunos de ellos como Knockout y jQuery Mobile.

Últimamente he estado trabajando con angularjs, un framework desarrollado por Google y, como creo que es importante conocer los frameworks que se utilizan, en estos post voy a intentar explicar los conceptos básicos sobre los que se asienta la filosofía de angular.

OJO: en estos post no vas a encontrar información detallada sobre cómo trabajar con angular ni mucho código fuente, si estás buscando ese tipo de detalles, te recomiendo que eches un vistazo a la documentación oficial o sigas el tutorial. Si quieres profundizar en el tema, AngularJS: Up and Running me pareció un buen libro y tengo buenas referencias del curso de AngularJS que ofrecen Pedro Hurtado y Xavier Cerdá.

Arquitectura de la Capa de Presentación

Angular ha sido definido como un framework Model-View-Whatever, es decir, Modelo-Vista-Lo-que-sea. Pretende de esta forma huir de la discusión entre MVVM vs MVC vs MVP, pero lo cierto es que desde mi punto de vista tiene un marcado estilo MVVM.

Pese a este estilo MVVM, la aplicación se organiza entorno a Controllers responsables de construir los ViewModels que se enlazarán a las vistas. El papel que juegan los Controllers es un tanto limitado, por lo que no creo que se puedan comparar a los Controllers de la típica arquitectura web MVC, donde construyen un modelo «tonto» y luego se encargan ellos de gestionar las operaciones. En angular el controller puede construir un ViewModel que luego toma el control completo de las operaciones realizadas en la vista, por lo que básicamente actúan como factorías de ViewModels.

El enlace entre vistas y ViewModels se realiza mediante un sistema de databinding declarativo bidireccional. Para ello se «decoran» los elementos html con directivas (de las que hablaremos más adelante) y se usa un sistema de plantillas parecido a handlebars:

<div ng-controller="PhoneCtrl">
  <ul>
    <li ng-repeat="phone in phones">{{phone.name}}</li>
  </ul>
</div>

Una cosa que resulta extraña al principio es que no existe una definición del ViewModel como tal, sino que cada Controller tiene asociado un objecto $scope sobre el que construirlo. En el ejemplo anterior, el controller PhoneCtrl añadiría a su objeto $scope una lista de teléfonos, cada uno de ellos con una propiedad name, y eso sería lo que se enlaza a la vista.

Otro aspecto curioso es que los $scopes, es decir, ViewModels, están jerarquizados. Es decir, en una misma página podemos tener $scopes anidados unos con otros, y los $scope hijos pueden acceder a las propiedades y funciones de los $scope padre.

La raíz del árbol de $scopes es el $rootScope y podríamos considerarlo como el ViewModel global en el que colocar datos como el usuario loggeado actualmente o funciones de utilidad que queramos tener accesibles en cualquier otro $scope.

La jerarquía de Controllers/$scopes se corresponde con la jerarquía de elementos html en la página. Puesto que los Controllers están asociados a elementos html, si el elemento asociado a un Controller contiene un elemento asociado a otro Controller, el $scope del segundo podrá acceder a las propiedades del primero.

Para navegar entre las páginas de la aplicación se utilizan rutas, que podemos asociar a Controllers y fragmentos de html. Cuando se accede a una ruta, se carga ese fragmento de html (que puede residir en un archivo externo) mediante ajax y se inserta en la página actual en el elemento que esté marcado con la directiva ng-view:

<body ng-app="MyApp">
  <div ng-view>
	<!-- 
	  Aquí se irán cargando las vistas asociadas a las rutas
	  de la aplicación MyApp definida en el elemento body
	  con la directiva ng-app
	-->
  </div>
</body>

Angular se encarga de mantener el historial de navegacion y todas esas cosas básicas, por lo que el manejo es bastante sencillo.

Resumen

Este post presenta una visión general de la forma en que se organiza la capa superior de angularjs, donde encontramos elementos conocidos (Views, Controllers, ViewModels, Routes), pero también algunas particularidades, como la jerarquización de los ViewModels o el reducido papel que juegan los Controllers en esta historia.

En próximos posts veremos más cosas interesantes de angular, como su organización en módulos, los servicios, el sistema de inyección de dependencias o el uso de directivas para encapsular comportamiento del interfaz de usario.

11 comentarios en “AngularJS: Conceptos Básicos

  1. Las librerías de este estilo como AngularJS y Knockout tiene buena pinta y veo que mucha gente las está siguiendo pero según lo que he visto y no me convence es que se basan en «instrumentalizar el html», es decir, los databinding se hacen añadiendo ciertos atributos a los elementos html tal y como has puesto en los trozos de código. Con Backbone.js de eso se encarga el javascript al definir la vista, esto hace que el html y javasctipt estén más separados y eso me parece que está muy bien.

  2. Es un punto interesante. Yo al principio era muy reticente a «ensuciar» el documento HTML con cosas «no estándar», pero mi visión actual ha cambiado.

    Para mi hay diferencia entre desarrollar una página web y una aplicación web. En el caso de la página web, prima el contenido y es fundamental cuidar la estructura y la semántica del documento HTML. En el caso de una aplicación… bueno, yo creo que realmente se trata de aprovechar HTML/CSS/JS como plataforma, aunque sea a costa de «pervertirlo» un poco.

    Sobre Backbone, he visto dos formas de trabajar con él.

    Una está basada en templates (handlebars o similar), que en el fondo es lo mismo que Knockout o Angular. Es cierto que defines la vista en javascript, pero al final lo que haces es referenciar una plantilla que contiene «html instrumentalizado».

    La otra opción es generarse «a pelo» el DOM desde javascript. Podríamos argumentar que es más puro porque el HTML que se acaba generando es más limpio, pero teniendo un lenguaje declarativo para construir interfaces de usuario (HTML), me parece poco práctico hacerlo de forma imperativa desde código javascript.

    Antes de arrancar mi proyecto actual (en el que estoy usando angular), Backbone fue una de las opciones que consideré, pero me pareció que requería demasiado código de relleno para empezar a hacer cosas prácticas.

    Cuestión de gustos, supongo.

  3. Pedro Hurtado dijo:

    Buenas,

    Una puntualización a este parrafo

    «Otro aspecto curioso es que los $scopes, es decir, ViewModels, están jerarquizados. Es decir, en una misma página podemos tener $scopes anidados unos con otros, y los $scope hijos pueden acceder a las propiedades y funciones de los $scope padre.»

    Efectivamente puedes tener varios scopes anidados pero el scope hijo no accede a las propiedades del padre si que este tiene las mismas propiedades del padre y no los metodos, donde si podemos crear diferentes para cada hijo.

    Solamente heredamos del padre aquello que no es una function(), para acceder a una function del parent tendrías que acceder por medio de la propiedad $parent, pero cuidado con esto puesto que ciertas directivas crean sus propios scopes y no heredan normalmente del padre sino del $rootScope. Con lo cual una buena herramienta si la sabemos utilizar:)

  4. Pedro, ¿estás seguro de eso?

    En este fiddle se ve cómo ChildCtrl referencia no sólo propiedades sino también función del $scope padre: http://jsfiddle.net/HwDJS/4/

    Lo que sí es cierto es que hay que tener cuidado con cómo se tratan las propiedades del $scope padre, porque si sobreescribes la referencia al objeto original en el $scope hijo, sólo se enterarían sus descendientes (pero no padres o hermanos).

  5. Pedro Hurtado dijo:

    Cambialo por esto

    Html

    Click me!
    {{message}}

    Javascript

    var myApp = angular.module(«MyApp», []);

    myApp.controller(‘ParentCtrl’, function($scope) {
    $scope.sayHello = function() {
    $scope.message = «hola caracola»;
    };
    $scope.Propiedad = «Hola»;
    });

    myApp.controller(‘ChildCtrl’, function($scope) {
    // No tengo nada en mi scope, todo lo que pase
    // es culpa de ParentCtrl
    $scope.Pepe=function(){
    console.log($scope.Propiedad);
    $scope.SayHello();
    }
    });

  6. Pedro Hurtado dijo:

    Perdona pero en el código como puedes observar estoy llamanda a «SayHello» y el método del padre es «sayHello», con lo cual usted lleva razón:). Si te fijas en el método $new del objeto Scope de angular

    $new: function(isolate) {
    var Child,
    child;

    if (isFunction(isolate)) {
    // TODO: remove at some point
    throw Error(‘API-CHANGE: Use $controller to instantiate controllers.’);
    }
    if (isolate) {
    child = new Scope();
    child.$root = this.$root;
    } else {
    Child = function() {}; // should be anonymous; This is so that when the minifier munges
    // the name it does not become random set of chars. These will then show up as class
    // name in the debugger.
    Child.prototype = this;
    child = new Child();
    child.$id = nextUid();
    }
    child[‘this’] = child;
    child.$$listeners = {};
    child.$parent = this;
    child.$$asyncQueue = [];
    child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
    child.$$prevSibling = this.$$childTail;
    if (this.$$childHead) {
    this.$$childTail.$$nextSibling = child;
    this.$$childTail = child;
    } else {
    this.$$childHead = this.$$childTail = child;
    }
    return child;
    },
    puedes ver que si no es isolated a prototype se le asigna this con lo cual eso lo que tu comentas, lo que ocurre es que en un escenario donde yo cree una directiva el scope se creaba a a partir del $rootScope y por eso no veía las funciones y tampoco los datos. Lo que ocurre es que necesitaba compartirlos e hice un apaño para obtener estos.

    Con lo cual usted tiene toda la razón del mundo y por tanto mil perdones a mi puntualización:)

    Saludos

  7. Una aclaración respecto del comentario de «pico.dev».

    El hecho de «invadir o ensuciar» el HTML con atributos no estándares en realidad no es un defecto, al menos de KnockoutJS.
    Se puede mantener todo el HTML limpio de data-bind e injectar en el load del documento los enlaces que correspondan sobre los controles, ya que la tarea de enlace no es responsabilidad de la Vista sino de los controlares asociados a la vista (ej. el load de la vista)
    De esta forma no hay invación de responsabilidades entre los documentos HTML, CSS y JS

    Saludos!.

  8. Buenas,

    Ante todo estoy empezando con MEAN, vengo del mundo c# y MVC y la verdad estoy un poco perdida con un detalle: ¿qué entorno de programación dispone de las herramientas «y demás» adecuadas para un proyecto MEAN de cierta magnitud?

    Puede usarse VisualStudio? O WebStorm… alguno. Algo que te permita trabajar con unas condiciones mínimas.

    Gracias :)

  9. Hola Carla,

    Tienes muchas alternativas. Puedes utilizar Visual Studio, aunque yo para trabajar con Javascript prefiero alternativas más ligeras. WebStorm está bien, pero personalmente suelo usar un editor de texto (Sublime, emacs, el que sea…) y aprovecha las herramientas que hay sobre node para la parte de minificar, tests, etc.

    Puedes ver más información en estos posts:

    https://blog.koalite.com/2013/10/desarrolla-en-javascript-mas-comodamente-usando-grunt/
    https://blog.koalite.com/2013/09/node-js-de-servidor-a-navaja-suiza/

    Un saludo,

    Juanma.

Comentarios cerrados.