Un tutorial paso a paso para su primera aplicación AngularJS

Publicado: 2022-03-11
AngularJS ha evolucionado y se ha vuelto aún mejor. Ahora llamado Angular, fue completamente reescrito hacia un nuevo flujo de trabajo de desarrollo. Consulte nuestro nuevo tutorial de Angular 5 y el tutorial completo de Angular 6 aún más nuevo, que incluye Material y Firebase.

¿Qué es AngularJS?

AngularJS es un marco JavaScript MVC desarrollado por Google que le permite crear aplicaciones front-end bien estructuradas, fácilmente comprobables y mantenibles.

¿Y por qué debería usarlo?

Si aún no ha probado AngularJS, se lo está perdiendo. El marco consta de un conjunto de herramientas estrechamente integrado que lo ayudará a crear aplicaciones ricas y bien estructuradas del lado del cliente de manera modular, con menos código y más flexibilidad.

AngularJS amplía HTML proporcionando directivas que agregan funcionalidad a su marcado y le permiten crear potentes plantillas dinámicas. También puede crear sus propias directivas, creando componentes reutilizables que satisfagan sus necesidades y abstrayendo toda la lógica de manipulación del DOM.

También implementa el enlace de datos bidireccional, conectando su HTML (vistas) a sus objetos JavaScript (modelos) sin problemas. En términos simples, esto significa que cualquier actualización en su modelo se reflejará inmediatamente en su vista sin necesidad de manipular DOM o manejar eventos (por ejemplo, con jQuery).

Angular proporciona servicios además de XHR que simplifican drásticamente su código y le permiten abstraer las llamadas API en servicios reutilizables. Con eso, puede mover su modelo y lógica comercial al front-end y crear aplicaciones web independientes del back-end.

Finalmente, me encanta Angular por su flexibilidad con respecto a la comunicación con el servidor. Como la mayoría de los marcos MVC de JavaScript, le permite trabajar con cualquier tecnología del lado del servidor siempre que pueda servir su aplicación a través de una API web RESTful. Pero Angular también proporciona servicios además de XHR que simplifican drásticamente su código y le permiten abstraer llamadas API en servicios reutilizables. Como resultado, puede mover su modelo y lógica empresarial al front-end y crear aplicaciones web independientes del back-end. En esta publicación, haremos precisamente eso, un paso a la vez.

Entonces, ¿por dónde empiezo?

Primero, decidamos la naturaleza de la aplicación que queremos construir. En esta guía, preferimos no dedicar demasiado tiempo al back-end, por lo que escribiremos algo basado en datos que se pueden obtener fácilmente en Internet, ¡como una aplicación de transmisión de deportes!

Dado que soy un gran fanático de las carreras de autos y la Fórmula 1, usaré un servicio API de autosport para que actúe como nuestro back-end. Afortunadamente, los muchachos de Ergast tienen la amabilidad de proporcionar una API de automovilismo gratuita que será perfecta para nosotros.

Para obtener un adelanto de lo que vamos a construir, eche un vistazo a la demostración en vivo. Para embellecer la demostración y mostrar algunas plantillas angulares, apliqué un tema de Bootstrap de WrapBootstrap, pero dado que este artículo no trata sobre CSS, simplemente lo abstraeré de los ejemplos y lo dejaré fuera.

Tutorial de introducción

Comencemos nuestra aplicación de ejemplo con algunos repetitivos. Recomiendo el proyecto de semillas angulares, ya que no solo le brinda un gran esqueleto para el arranque, sino que también sienta las bases para las pruebas unitarias con Karma y Jasmine (no haremos ninguna prueba en esta demostración, así que simplemente deje esas cosas a un lado por ahora; consulte la Parte 2 de este tutorial para obtener más información sobre cómo configurar su proyecto para pruebas unitarias y de extremo a extremo).

EDITAR (mayo de 2014): desde que escribí este tutorial, el proyecto de semilla angular ha sufrido algunos cambios importantes (incluida la adición de Bower como administrador de paquetes). Si tiene alguna duda sobre cómo implementar el proyecto, eche un vistazo rápido a la primera sección de su guía de referencia. En la Parte 2 de este tutorial, Bower, entre otras herramientas, se trata con mayor detalle.

Bien, ahora que hemos clonado el repositorio e instalado las dependencias, el esqueleto de nuestra aplicación se verá así:

tutorial de angularjs - comienza con el esqueleto

Ahora podemos empezar a codificar. Mientras intentamos crear una fuente de deportes para un campeonato de carreras, comencemos con la vista más relevante: la tabla del campeonato .

la tabla del campeonato

Dado que ya tenemos una lista de controladores definida dentro de nuestro alcance (cuelgue conmigo, llegaremos allí) e ignorando cualquier CSS (para facilitar la lectura), nuestro HTML podría verse así:

 <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>

Lo primero que notará en esta plantilla es el uso de expresiones ("{{" y "}}") para devolver valores de variables. En el desarrollo de AngularJS, las expresiones le permiten ejecutar algunos cálculos para devolver un valor deseado. Algunas expresiones válidas serían:

  • {{ 1 + 1 }}
  • {{ 946757880 | date }}
  • {{ user.name }}

Efectivamente, las expresiones son fragmentos similares a JavaScript. Pero a pesar de ser muy poderoso, no debe usar expresiones para implementar una lógica de nivel superior. Para eso, usamos directivas.

Comprender las directivas básicas

Lo segundo que notará es la presencia de ng-attributes , que no vería en el marcado típico. Esas son directivas.

En un nivel alto, las directivas son marcadores (como atributos, etiquetas y nombres de clase) que le indican a AngularJS que adjunte un comportamiento dado a un elemento DOM (o lo transforme, lo reemplace, etc.). Echemos un vistazo a los que ya hemos visto:

  • La directiva ng-app es responsable de iniciar su aplicación definiendo su alcance. En AngularJS, puede tener varias aplicaciones dentro de la misma página, por lo que esta directiva define dónde comienza y termina cada aplicación distinta.

  • La directiva ng-controller define qué controlador estará a cargo de su vista. En este caso, denotamos driversController , que proporcionará nuestra lista de controladores ( driversList ).

  • La directiva ng-repeat es una de las más utilizadas y sirve para definir el alcance de su plantilla al recorrer las colecciones. En el ejemplo anterior, replica una línea en la tabla para cada controlador en driversList .

Adición de controladores

Por supuesto, no sirve de nada nuestra vista sin un controlador. driversController a nuestro controllers.js:

 angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });

Es posible que haya notado la variable $scope que estamos pasando como parámetro al controlador. Se supone que la variable $scope vincula su controlador y sus vistas. En particular, contiene todos los datos que se utilizarán dentro de su plantilla. Todo lo que agregue (como la lista de driversList en el ejemplo anterior) será directamente accesible en sus vistas. Por ahora, solo trabajemos con una matriz de datos ficticia (estática), que reemplazaremos más adelante con nuestro servicio API.

Ahora, agregue esto a app.js:

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

Con esta línea de código, inicializamos nuestra aplicación y registramos los módulos de los que depende. Volveremos a ese archivo ( app.js ) más adelante.

Ahora, pongamos todo junto en index.html :

 <!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <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>

Errores menores de Modulo, ahora puede iniciar su aplicación y verificar su lista (estática) de controladores.

Nota: si necesita ayuda para depurar su aplicación y visualizar sus modelos y alcance dentro del navegador, le recomiendo que eche un vistazo al increíble complemento Batarang para Chrome.

Carga de datos desde el servidor

Dado que ya sabemos cómo mostrar los datos de nuestro controlador en nuestra vista, es hora de obtener datos en vivo de un servidor RESTful.

Para facilitar la comunicación con los servidores HTTP, AngularJS proporciona los servicios $http y $resource . El primero no es más que una capa sobre XMLHttpRequest o JSONP, mientras que el último proporciona un mayor nivel de abstracción. Usaremos $http .

Para abstraer las llamadas a la API de nuestro servidor desde el controlador, creemos nuestro propio servicio personalizado que obtendrá nuestros datos y actuará como un envoltorio alrededor $http agregando esto a nuestro services.js :

 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; });

Con las dos primeras líneas, creamos un nuevo módulo ( F1FeederApp.services ) y registramos un servicio dentro de ese módulo ( ergastAPIservice ). Observe que pasamos $http como parámetro a ese servicio. Esto le dice al motor de inyección de dependencias de Angular que nuestro nuevo servicio requiere (o depende de ) el servicio $http .

De manera similar, debemos decirle a Angular que incluya nuestro nuevo módulo en nuestra aplicación. Registrémoslo con app.js , reemplazando nuestro código existente con:

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

Ahora, todo lo que tenemos que hacer es modificar un poco nuestro controller.js , incluir ergastAPIservice como una dependencia, y estaremos listos para comenzar:

 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; }); });

Ahora recarga la aplicación y mira el resultado. Tenga en cuenta que no hicimos ningún cambio en nuestra plantilla, pero agregamos una variable nameFilter a nuestro alcance. Pongamos esa variable en uso.

filtros

¡Genial! Tenemos un controlador funcional. Pero solo muestra una lista de controladores. Agreguemos algunas funciones implementando una entrada de búsqueda de texto simple que filtrará nuestra lista. Agreguemos la siguiente línea a nuestro index.html , justo debajo de la etiqueta <body> :

 <input type="text" ng-model="nameFilter" placeholder="Search..."/>

Ahora estamos haciendo uso de la directiva ng-model . Esta directiva vincula nuestro campo de texto a la variable $scope.nameFilter y se asegura de que su valor esté siempre actualizado con el valor de entrada. Ahora, visitemos index.html una vez más y hagamos un pequeño ajuste en la línea que contiene la directiva ng-repeat :

 <tr ng-repeat="driver in driversList | filter: nameFilter">

Esta línea le dice ng-repeat que, antes de generar los datos, la matriz driversList debe filtrarse por el valor almacenado en nameFilter .

En este punto, se activa el enlace de datos bidireccional: cada vez que se ingresa un valor en el campo de búsqueda, Angular se asegura de inmediato de que el $scope.nameFilter que asociamos con él se actualice con el nuevo valor. Dado que el enlace funciona en ambos sentidos, en el momento en que se actualiza el valor de nameFilter , la segunda directiva asociada a él (es decir, ng-repeat ) también obtiene el nuevo valor y la vista se actualiza inmediatamente.

Vuelva a cargar la aplicación y consulte la barra de búsqueda.

barra de búsqueda de aplicaciones

Tenga en cuenta que este filtro buscará la palabra clave en todos los atributos del modelo, incluidos los que no se estaban utilizando. Digamos que solo queremos filtrar por Driver.givenName y Driver.familyName : Primero, agregamos driversController , justo debajo de $scope.driversList = []; línea:

 $scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };

Ahora, de vuelta a index.html , actualizamos la línea que contiene la directiva ng-repeat :

 <tr ng-repeat="driver in driversList | filter: searchFilter">

Vuelva a cargar la aplicación una vez más y ahora tenemos una búsqueda por nombre.

Rutas

Nuestro próximo objetivo es crear una página de detalles del conductor que nos permita hacer clic en cada conductor y ver los detalles de su carrera.

Primero, incluyamos el servicio $routeProvider (en app.js ) que nos ayudará a lidiar con estas variadas rutas de aplicaciones . Luego, agregaremos dos rutas de este tipo: una para la clasificación del campeonato y otra para los detalles del piloto. Aquí está nuestra nueva app.js :

 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'}); }]);

Con ese cambio, al navegar a http://domain/#/drivers se cargará driversController y se buscará la vista parcial para representar en partials/drivers.html . ¡Pero espera! Todavía no tenemos vistas parciales, ¿verdad? Tendremos que crear esos también.

Vistas parciales

AngularJS le permitirá vincular sus rutas a controladores y vistas específicos.

Pero primero, debemos decirle a Angular dónde renderizar estas vistas parciales. Para eso, usaremos la directiva ng-view , modificando nuestro index.html para reflejar lo siguiente:

 <!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>

Ahora, siempre que naveguemos por las rutas de nuestra aplicación, Angular cargará la vista asociada y la representará en lugar de la etiqueta <ng-view> . Todo lo que tenemos que hacer es crear un archivo llamado partials/drivers.html y poner nuestra tabla HTML de campeonato allí. También aprovecharemos esta oportunidad para vincular el nombre del conductor a nuestra ruta de detalles del conductor:

 <input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>

Finalmente, decidamos qué queremos mostrar en la página de detalles. ¿Qué tal un resumen de todos los datos relevantes sobre el conductor (por ejemplo, nacimiento, nacionalidad) junto con una tabla que contiene sus resultados recientes? Para hacer eso, agregamos a services.js :

 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' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });

Esta vez, proporcionamos la identificación del conductor al servicio para recuperar la información relevante únicamente para un conductor específico. Ahora, modificamos controllers.js :

 angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });

Lo importante a tener en cuenta aquí es que acabamos de inyectar el servicio $routeParams en el controlador del controlador. Este servicio nos permitirá acceder a nuestros parámetros de URL (para el :id , en este caso) usando $routeParams.id .

Ahora que tenemos nuestros datos en el alcance, solo necesitamos la vista parcial restante. Creemos un archivo llamado partials/driver.html y agreguemos:

 <section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>

Tenga en cuenta que ahora le estamos dando un buen uso a la directiva ng-show . Esta directiva solo mostrará el elemento HTML si la expresión proporcionada es true (es decir, ni false ni null ). En este caso, el avatar solo aparecerá una vez que el controlador haya cargado el objeto controlador en el osciloscopio.

Últimos retoques

Agregue un montón de CSS y renderice su página. Deberías terminar con algo como esto:

página renderizada con CSS

Ahora está listo para iniciar su aplicación y asegurarse de que ambas rutas funcionen como se desea. También puede agregar un menú estático a index.html para mejorar las capacidades de navegación del usuario. Las posibilidades son infinitas.

EDITAR (mayo de 2014): he recibido muchas solicitudes de una versión descargable del código que construimos en este tutorial. Por lo tanto, he decidido publicarlo aquí (despojado de cualquier CSS). Sin embargo, realmente no recomiendo descargarlo, ya que esta guía contiene todos los pasos que necesita para construir la misma aplicación con sus propias manos, lo que será un ejercicio de aprendizaje mucho más útil y efectivo.

Conclusión

En este punto del tutorial, hemos cubierto todo lo que necesita para escribir una aplicación simple (como un alimentador de Fórmula 1). Cada una de las páginas restantes en la demostración en vivo (p. ej., tabla de campeonato de constructores, detalles del equipo, calendario) comparten la misma estructura y conceptos básicos que hemos revisado aquí.

Finalmente, tenga en cuenta que Angular es un marco muy poderoso y apenas hemos arañado la superficie en términos de todo lo que tiene para ofrecer. En la Parte 2 de este tutorial, daremos ejemplos de por qué Angular se destaca entre sus marcos MVC front-end similares: capacidad de prueba. Revisaremos el proceso de escribir y ejecutar pruebas unitarias con Karma, logrando una integración continua con Yeomen, Grunt y Bower, y otras fortalezas de este fantástico marco de front-end.