Supongamos que estoy familiarizado con el desarrollo de aplicaciones del lado del cliente en jQuery, pero ahora me gustaría empezar a utilizar AngularJS. ¿Puedes describir el cambio de paradigma que es necesario? Aquí hay algunas preguntas que podrían ayudarte a enmarcar una respuesta:
No estoy buscando una comparación detallada entre jQuery
y AngularJS
.
En jQuery, diseñas una página, y luego la haces dinámica. Esto se debe a que jQuery fue diseñado para aumentar y ha crecido increíblemente desde esa simple premisa. Pero en AngularJS, debes empezar desde cero con tu arquitectura en mente. En lugar de empezar pensando "tengo este trozo del DOM y quiero hacer que haga X", tienes que empezar con lo que quieres conseguir, luego ir diseñando tu aplicación, y finalmente ir diseñando tu vista.
Del mismo modo, no empieces con la idea de que jQuery hace X, Y, y Z, así que sólo voy a añadir AngularJS encima de eso para los modelos y controladores. Esto es realmente tentador cuando estás empezando, por lo que siempre recomiendo que los nuevos desarrolladores de AngularJS no usen jQuery en absoluto, al menos hasta que se acostumbren a hacer las cosas a la "manera de Angular".
He visto a muchos desarrolladores aquí y en la lista de correo crear estas elaboradas soluciones con plugins de jQuery de 150 o 200 líneas de código que luego pegan en AngularJS con una colección de callbacks y $apply
s que son confusos y enrevesados; ¡pero al final consiguen que funcione! El problema es que en la mayoría de los casos ese plugin de jQuery podría reescribirse en AngularJS en una fracción del código, donde de repente todo se vuelve comprensible y sencillo.
La línea de fondo es esta: cuando solucionas, primero "piensa en AngularJS"; si no puedes pensar en una solución, pregunta a la comunidad; si después de todo eso no hay una solución fácil, entonces siéntete libre de recurrir a jQuery. Pero no dejes que jQuery se convierta en una muleta o nunca dominarás AngularJS.
Primero debes saber que las aplicaciones de una sola página son aplicaciones. No son páginas web. Así que tenemos que pensar como un desarrollador del lado del servidor además de pensar como un desarrollador del lado del cliente. Tenemos que pensar en cómo dividir nuestra aplicación en componentes individuales, extensibles y comprobables. Entonces, ¿cómo se hace eso? ¿Cómo "pensar en AngularJS"? Aquí hay algunos principios generales, contrastados con jQuery.
En jQuery, cambiamos programáticamente la vista. Podríamos tener un menú desplegable definido como un ul
así:
<ul class="main-menu">
<li class="active">
<a href="#/home">Home</a>
</li>
<li>
<a href="#/menu1">Menu 1</a>
<ul>
<li><a href="#/sm1">Submenu 1</a></li>
<li><a href="#/sm2">Submenu 2</a></li>
<li><a href="#/sm3">Submenu 3</a></li>
</ul>
</li>
<li>
<a href="#/home">Menu 2</a>
</li>
</ul>
En jQuery, en nuestra lógica de aplicación, lo activaríamos con algo como:
$('.main-menu').dropdownMenu();
Cuando sólo miramos la vista, no es inmediatamente obvio que haya alguna funcionalidad aquí. Para aplicaciones pequeñas, eso está bien. Pero para aplicaciones no triviales, las cosas se vuelven rápidamente confusas y difíciles de mantener.
En AngularJS, sin embargo, la vista es el registro oficial de la funcionalidad basada en la vista. Nuestra declaración ul
se vería así en su lugar:
<ul class="main-menu" dropdown-menu>
...
</ul>
Estas dos hacen lo mismo, pero en la versión de AngularJS cualquiera que mire la plantilla sabe lo que se supone que debe ocurrir. Cada vez que un nuevo miembro del equipo de desarrollo se incorpora, puede mirar esto y entonces saber que hay una directiva llamada dropdownMenu
operando en ella; no necesita intuir la respuesta correcta ni rebuscar en ningún código. La vista nos dice lo que debe ocurrir. Mucho más limpio.
Los desarrolladores que son nuevos en AngularJS suelen hacer una pregunta del tipo: cómo encuentro todos los enlaces de un tipo concreto y les añado una directiva. El desarrollador siempre se queda boquiabierto cuando le respondemos: no lo haces. Pero la razón por la que no lo haces es que esto es como medio-jQuery, medio-AngularJS, y no es bueno. El problema aquí es que el desarrollador está tratando de "hacer jQuery" en el contexto de AngularJS. Eso nunca va a funcionar bien. La vista es el registro oficial. Fuera de una directiva (más sobre esto más adelante), nunca, nunca, nunca cambias el DOM. Y las directivas se aplican en la vista, así que la intención es clara.
Recuerda: no diseñes y luego marques. Debes diseñar, y luego diseñar.
Esta es, de lejos, una de las características más impresionantes de AngularJS y elimina mucha de la necesidad de hacer el tipo de manipulaciones del DOM que mencioné en la sección anterior. AngularJS actualizará automáticamente tu vista para que tú no tengas que hacerlo. En jQuery, respondemos a eventos y luego actualizamos el contenido. Algo así como
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
Para una vista que se ve así:
<ul class="messages" id="log">
</ul>
Aparte de mezclar las preocupaciones, también tenemos los mismos problemas de significar la intención que he mencionado antes. Pero lo más importante es que tenemos que referenciar y actualizar manualmente un nodo DOM. Y si queremos borrar una entrada de registro, tenemos que codificar contra el DOM para eso también. ¿Cómo probamos la lógica aparte del DOM? ¿Y qué pasa si queremos cambiar la presentación? Esto es un poco desordenado y un poco frágil. Pero en AngularJS, podemos hacer esto:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
Y nuestra vista puede tener este aspecto:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
Pero para eso, nuestra vista puede tener este aspecto:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
Y ahora en lugar de usar una lista desordenada, estamos usando cajas de alerta de Bootstrap. ¡Y no hemos tenido que cambiar el código del controlador! Pero lo más importante es que no importa dónde o cómo se actualice el registro, la vista también cambiará. Automáticamente. ¡Genial!
Aunque no lo mostré aquí, el enlace de datos es bidireccional. Así que esos mensajes de registro también podrían ser editables en la vista simplemente haciendo esto: <input ng-model="entry.msg" />
. Y hubo mucho regocijo.
En jQuery, el DOM es algo así como el modelo. Pero en AngularJS, tenemos una capa de modelo separada que podemos manejar como queramos, de forma completamente independiente a la vista. Esto ayuda a la vinculación de datos mencionada anteriormente, mantiene la separación de preocupaciones, e introduce una testabilidad mucho mayor. Otras respuestas mencionaron este punto, así que lo dejaré así.
Y todo lo anterior se relaciona con este tema general: mantén tus preocupaciones separadas. La vista actúa como el registro oficial de lo que se supone que debe ocurrir (en su mayor parte); el modelo representa los datos; tienes una capa de servicio para realizar tareas reutilizables; manipulas el DOM y aumentas la vista con directivas; y lo unes todo con los controladores. Esto también se ha mencionado en otras respuestas, y lo único que yo añadiría tiene que ver con la comprobabilidad, de la que hablo en otra sección más adelante.
Para ayudarnos con la separación de preocupaciones está la inyección de dependencia (DI). Si vienes de un lenguaje del lado del servidor (desde Java hasta PHP) probablemente ya estés familiarizado con este concepto, pero si eres un tipo del lado del cliente que viene de jQuery, este concepto puede parecer cualquier cosa, desde tonto hasta superfluo o hipster. Pero no lo es. :-) Desde una perspectiva amplia, DI significa que puedes declarar componentes con mucha libertad y luego desde cualquier otro componente, sólo tienes que pedir una instancia del mismo y se te concederá. No tienes que saber sobre el orden de carga, ni la ubicación de los archivos, ni nada de eso. El poder puede no ser inmediatamente visible, pero voy a proporcionar sólo un ejemplo (común): las pruebas. Digamos que en nuestra aplicación, necesitamos un servicio que implemente el almacenamiento del lado del servidor a través de una API REST y, dependiendo del estado de la aplicación, también el almacenamiento local. Cuando ejecutamos pruebas en nuestros controladores, no queremos tener que comunicarnos con el servidor - estamos probando el controlador, después de todo. Podemos simplemente añadir un servicio falso con el mismo nombre que nuestro componente original, y el inyector se asegurará de que nuestro controlador obtenga el falso automáticamente - nuestro controlador no sabe ni necesita saber la diferencia. Hablando de pruebas...
Esto es realmente parte de la sección 3 sobre la arquitectura, pero es tan importante que lo estoy poniendo como su propia sección de nivel superior. De todos los plugins de jQuery que has visto, usado o escrito, ¿cuántos de ellos tienen un conjunto de pruebas? No muchos, porque jQuery no es muy propenso a ello. Pero AngularJS sí. En jQuery, la única manera de probar es a menudo crear el componente de forma independiente con una página de muestra/demo contra la que nuestras pruebas pueden realizar la manipulación del DOM. Así que tenemos que desarrollar un componente por separado y entonces integrarlo en nuestra aplicación. ¡Qué inconveniente! Así que la mayoría de las veces, cuando desarrollamos con jQuery, optamos por el desarrollo iterativo en lugar del desarrollo dirigido por pruebas. ¿Y quién podría culparnos? Pero como tenemos separación de preocupaciones, ¡podemos hacer desarrollo dirigido por pruebas de forma iterativa en AngularJS! Por ejemplo, digamos que queremos una directiva súper simple para indicar en nuestro menú cuál es nuestra ruta actual. Podemos declarar lo que queremos en la vista de nuestra aplicación:
<a href="/hello" when-active>Hello</a>
Bien, ahora podemos escribir un test para la inexistente directiva when-active
:
it( 'should add "active" when the route changes', inject(function() {
var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );
$location.path('/not-matching');
expect( elm.hasClass('active') ).toBeFalsey();
$location.path( '/hello' );
expect( elm.hasClass('active') ).toBeTruthy();
}));
Y cuando ejecutamos nuestro test, podemos confirmar que falla. Sólo ahora debemos crear nuestra directiva:
.directive( 'whenActive', function ( $location ) {
return {
scope: true,
link: function ( scope, element, attrs ) {
scope.$on( '$routeChangeSuccess', function () {
if ( $location.path() == element.attr( 'href' ) ) {
element.addClass( 'active' );
}
else {
element.removeClass( 'active' );
}
});
}
};
});
Nuestra prueba pasa ahora y nuestro menú funciona como se pide. Nuestro desarrollo es a la vez iterativo y dirigido por pruebas. Genial.
A menudo oirás "sólo hacer la manipulación del DOM en una directiva". Esto es una necesidad. ¡Trátalo con la debida deferencia!
Pero profundicemos un poco más...
Algunas directivas sólo decoran lo que ya está en la vista (piensa en ngClass
) y, por lo tanto, a veces hacen la manipulación del DOM directamente y entonces están básicamente hechas. Pero si una directiva es como un "widget" y tiene una plantilla, debería también respetar la separación de preocupaciones. Es decir, la plantilla también debería permanecer en gran medida independiente de su implementación en las funciones de enlace y controlador.
AngularJS viene con todo un conjunto de herramientas para hacer esto muy fácil; con ngClass
podemos actualizar dinámicamente la clase; ngModel
permite la vinculación de datos de dos vías; ngShow
y ngHide
muestran u ocultan programáticamente un elemento; y muchos más - incluyendo los que escribimos nosotros mismos. En otras palabras, podemos hacer todo tipo de maravillas sin manipular el DOM. Cuanta menos manipulación del DOM, más fácil es probar las directivas, más fácil es darles estilo, más fácil es cambiarlas en el futuro, y más reutilizables y distribuibles son.
Veo que muchos desarrolladores nuevos en AngularJS utilizan las directivas como el lugar para lanzar un montón de jQuery. En otras palabras, piensan "ya que no puedo hacer la manipulación del DOM en el controlador, tomaré ese código y lo pondré en una directiva". Aunque ciertamente eso es mucho mejor, a menudo es todavía incorrecto.
Piensa en el registrador que programamos en la sección 3. Incluso si lo ponemos en una directiva, todavía queremos hacerlo a la "manera de Angular". Todavía no se necesita ninguna manipulación del DOM. Hay muchas veces en las que la manipulación del DOM es necesaria, pero es mucho más rara de lo que crees. Antes de manipular el DOM en cualquier parte de tu aplicación, pregúntate si realmente lo necesitas. Podría haber una forma mejor.
Aquí hay un ejemplo rápido que muestra el patrón que veo con más frecuencia. Queremos un botón conmutable. (Nota: este ejemplo es un poco artificioso y un poco verboso para representar casos más complicados que se resuelven exactamente de la misma manera).
.directive( 'myDirective', function () {
return {
template: '<a class="btn">Toggle me!</a>',
link: function ( scope, element, attrs ) {
var on = false;
$(element).click( function () {
on = !on;
$(element).toggleClass('active', on);
});
}
};
});
Hay algunas cosas mal con esto:
angular.element
y nuestro componente seguirá funcionando cuando se deje caer en un proyecto que no tenga jQuery.angular.element
) siempre utilizará jQuery si está cargado. Así que no necesitamos usar el $
- podemos simplemente usar angular.element
.$
- el elemento
que se pasa a la función link
ya sería un elemento jQuery!.directive( 'myDirective', function () {
return {
scope: true,
template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
link: function ( scope, element, attrs ) {
scope.on = false;
scope.toggle = function () {
scope.on = !scope.on;
};
}
};
});
Una vez más, el material de la plantilla está en la plantilla, por lo que usted (o sus usuarios) puede fácilmente cambiarla por una que cumpla con cualquier estilo necesario, y la lógica nunca tuvo que ser tocada. Reutilización - ¡boom!
Y aún quedan todas esas otras ventajas, como las pruebas: ¡es fácil! No importa lo que haya en la plantilla, la API interna de la directiva nunca se toca, así que refactorizar es fácil. Puedes cambiar la plantilla todo lo que quieras sin tocar la directiva. Y no importa lo que cambies, tus pruebas siguen pasando.
¡w00t!
Entonces, si las directivas no son sólo colecciones de funciones similares a jQuery, ¿qué son? Las directivas son en realidad extensiones de HTML. Si el HTML no hace algo que necesitas que haga, escribes una directiva que lo haga por ti, y luego la usas como si fuera parte del HTML.
Dicho de otro modo, si AngularJS no hace algo fuera de la caja, piensa en cómo el equipo lo lograría para que encaje bien con ngClick
, ngClass
, etc.
Ni siquiera uses jQuery. Ni siquiera lo incluyas. Te retrasará. Y cuando llegues a un problema que creas que ya sabes cómo resolver en jQuery, antes de coger el $
, intenta pensar en cómo hacerlo dentro de los confines de AngularJS. Si no lo sabes, ¡pregúntalo! 19 de cada 20 veces, la mejor manera de hacerlo no necesita jQuery y tratar de resolverlo con jQuery resulta en más trabajo para ti.
En jQuery, los selectores se usan para encontrar elementos DOM y luego enlazar/registrar manejadores de eventos a ellos. Cuando se dispara un evento, ese código (imperativo) se ejecuta para actualizar/cambiar el DOM.
En AngularJS, debes pensar en vistas en lugar de elementos del DOM. Las vistas son HTML (declarativo) que contienen directivas de AngularJS. Las directivas configuran los manejadores de eventos entre bastidores por nosotros y nos dan un databinding dinámico. Los selectores son raramente usados, por lo que la necesidad de IDs (y algunos tipos de clases) es muy reducida. Las vistas están ligadas a los modelos (a través de los ámbitos). Las vistas son una proyección del modelo. Los eventos cambian los modelos (es decir, los datos, las propiedades del ámbito), y las vistas que proyectan esos modelos se actualizan "automáticamente".
En AngularJS, piensa en los modelos, más que en los elementos del DOM seleccionados por jQuery que contienen tus datos. Piensa en las vistas como proyecciones de esos modelos, en lugar de registrar callbacks para manipular lo que el usuario ve.
jQuery emplea JavaScript no intrusivo - el comportamiento (JavaScript) está separado de la estructura (HTML).
AngularJS utiliza controladores y directivas (cada una de las cuales puede tener su propio controlador, y/o funciones de compilación y enlace) para separar el comportamiento de la vista/estructura (HTML). Angular también tiene servicios y filtros para ayudar a separar/organizar tu aplicación.
Ver también https://stackoverflow.com/a/14346528/215945
Un enfoque para diseñar una aplicación AngularJS:
Puedes hacer mucho con jQuery sin saber cómo funciona la herencia prototípica de JavaScript. Cuando desarrolles aplicaciones AngularJS, evitarás algunas trampas comunes si tienes una buena comprensión de la herencia de JavaScript. Lectura recomendada: https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
¿Puede describir el cambio de paradigma que es necesario?
Imperativo vs Declarativo
Con jQuery le dices al DOM lo que tiene que pasar, paso a paso. Con AngularJS describes qué resultados quieres pero no cómo hacerlo. Más sobre esto aquí. También, mira la respuesta de Mark Rajcok.
¿Cómo puedo diseñar y diseñar aplicaciones web del lado del cliente de manera diferente?
AngularJS es un framework del lado del cliente que utiliza el patrón MVC (mira su representación gráfica). Se centra en gran medida en la separación de preocupaciones.
¿Cuál es la mayor diferencia? ¿Qué debería dejar de hacer/usar; qué debería empezar a hacer/usar en su lugar?
jQuery es una biblioteca
AngularJS es un hermoso framework del lado del cliente, altamente comprobable, que combina toneladas de cosas interesantes como MVC, inyección de dependencia, vinculación de datos y mucho más.
Se centra en la separación de preocupaciones y en las pruebas (pruebas unitarias y pruebas de extremo a extremo), lo que facilita el desarrollo dirigido por pruebas.
La mejor manera de comenzar es a través de su impresionante tutorial. Puedes seguir los pasos en un par de horas; sin embargo, en caso de que quieras dominar los conceptos entre bastidores, incluyen un sinfín de referencias para seguir leyendo.
¿Hay consideraciones/restricciones del lado del servidor?
Puede utilizarlo en aplicaciones existentes en las que ya esté utilizando jQuery puro. Sin embargo, si quieres aprovechar al máximo las características de AngularJS puedes considerar codificar el lado del servidor utilizando un enfoque RESTful.
Hacerlo te permitirá aprovechar su fábrica de recursos, que crea una abstracción de tu [API] RESTful del lado del servidor11 y hace que las llamadas del lado del servidor (obtener, guardar, eliminar, etc.) sean increíblemente fáciles.