En el anterior post sobre AngularJS vimos por encima el concepto de templates, como un mecanismo para separar fragmentos de código HTML.
En este post vamos a profundizar en el concepto de template, y vamos a explorar los mecanismos que proporciona Angular para inyectar código en nuestro layout, gracias a sus capacidades de routing. El tutorial que proponemos está basado en este fantástico tutorial de scotch.io.
Contenido de la demo
Vamos a crear una simple web con páginas home, about y contact (todo ello en modo single-page-view gracias a Angular).
Las ventajas:
- Aplicación single-page
- No hay que refrescar la página al cambiar de vista
- Cada página contiene diferente información
Estructura de archivos
- script.js: Aquí meteremos todo nuestro código Angular
- index.html: Layout principal
- pages/
- home.html
- about.html
- contact.html
Layout
Empezando por la parte más simple, vamos a crear en nuestro index.html un layout simple con una barra de navegación, e incluiremos Bootstrap como front-end framework y Font Awesome para disponer de iconos SVG escalables. Además, incluiré la etiqueta ng-app=”demoApp” en nuestro html, ya que así es como llamaré al módulo Angular para esta aplicación, y ng-controller=”mainCtrl” en nuestro body, dado que así llamaré al controlador.
<!-- index.html -->
<!DOCTYPE html>
<html ng-app="demoApp">
<head>
<!-- SCROLLS -->
<!-- load bootstrap and fontawesome via CDN -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.css" />
<!-- SPELLS -->
<!-- load angular via CDN -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="mainCtrl">
<!-- HEADER AND NAVBAR -->
<header>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Angular Routing Example</a>
</div>
<ul class="nav navbar-nav navbar-right">
<li><a href="#"><i class="fa fa-home"></i> Home</a></li>
<li><a href="#about"><i class="fa fa-shield"></i> About</a></li>
<li><a href="#contact"><i class="fa fa-comment"></i> Contact</a></li>
</ul>
</div>
</nav>
</header>
<!-- MAIN CONTENT AND INJECTED VIEWS -->
<div id="main">
<!-- angular templating -->
<!-- this is where content will be injected -->
</div>
<!-- FOOTER -->
<footer class="text-center">
<hr>
<strong>- KaiKreations -</strong>
</footer>
</body>
</html>
DETALLE: Observar que cuando, en nuestra barra de navegación, ponemos enlaces a las otras páginas, son enlaces internos (#, #about, #contact). Esto es por que no queremos que el navegador intente acceder a otra página, recordemos que nuestra aplicación es single-page-view.
Como vemos en el código, estamos incluyendo Bootstrap, Font Awesome y Angular a partir de sus CDNs, así como nuestro archivo script.js.
Acto seguido, definimos un header, con enlaces a las otras vistas, el cuadro de contenido central (main) y un footer.
Cabe destacar el uso que hacemos de font awesome, añadiendo los iconos de clases “fa fa-home”, “fa fa-shield” y “fa fa-comment” a nuestros enlaces.
Aplicación y controlador Angular
Abrimos nuestro script.js y:
- Creamos un módulo Angular que llamaremos demoApp
- Añadimos un controlador a nuestro módulo, llamado mainCtrl
- Creamos el mensaje “Hello world!” a través del controlador.
// script.js
// create the module and name it demoApp
var demoApp = angular.module('demoApp', []);
// create the controller and inject Angular's $scope
demoApp.controller('mainCtrl', function($scope) {
// create a message to display in our view
$scope.message = 'Hello world!';
});
Probando que la estructura funciona
Añadimos el mensaje en el main div de index.html, del siguiente modo:
<div id="main">
{{ message }}
<!-- angular templating -->
<!-- this is where content will be injected -->
</div>
Y ejecutamos un servidor para ver el resultado. Desde Ubuntu, lo más sencillo es movernos al directorio donde tenemos los archivos, ejecutar por consola:
python -m SimpleHTTPServer
Y acceder a la url y puerto que nos indíque, que típicamente será: http://localhost:8000/
Utilizando rutas
Veamos como utilizar las herramientas de routing de Angular.
En primer lugar, necesitaremos indicarle a Angular donde queremos que ponga el contenido que va a ir cambiando en función de la página. Lo indicaremos con ng-view.
ng-view: Inyección de páginas
Angular nos proporciona la directiva ng-view, que permite inyectar contenido desde templates situados en una ruta determinada (en este caso “/home”, “/about” y “/contact”) en nuestro layout.
¿Como funciona? Debemos añadir la etiqueta
allí donde queremos que se rendericen nuestras páginas, es decir, abrimos index.html y en el main, añadimos:
<div id="main">
<!-- angular templating -->
<!-- this is where content will be injected -->
<div ng-view></div>
</div>
Providers: configurando las vistas a través de rutas
Utilizaremos el provider $routeProvider de Angular para gestionar nuestras rutas. De este modo, Angular se encargará de obtener los templates e inyectarlos en nuestro layout.
DETALLE: Los providers, son objetos que crean (proporcionan) instancias de servicios y exponen APIs de configuración que pueden usarse para controlar la creación y el comportamiento de un servicio en tiempo de ejecución.
Por este motivo, los providers solo pueden inyectarse en funciones config, es decir, no podemos inyectar un provider en un controlador.
$routeProvider
Es un provider del servicio $route que expone una API que nos permite definir rutas para nuestra aplicación.
Cabe destacar que desde Angular 1.2, ya no está incluido en el módulo core, y para usarlo hay que incluir en la aplicación, el módulo ngRoute. Por este motivo, abriremos nuestro index.html y añadiremos la siguiente linea en el head:
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular-route.js"></script>
Dicho esto, vamos a incluir $routeProvider en nuestro código, actualizando nuestro script.js del siguiente modo:
// script.js
// include ngRoute for all our routing needs
var demoApp = angular.module('demoApp', ['ngRoute']);
// configure our routes
demoApp.config(function($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl : 'pages/home.html',
controller : 'mainCtrl'
})
// route for the about page
.when('/about', {
templateUrl : 'pages/about.html',
controller : 'aboutCtrl'
})
// route for the contact page
.when('/contact', {
templateUrl : 'pages/contact.html',
controller : 'contactCtrl'
});
});
// create the controller and inject Angular's $scope
demoApp.controller('mainCtrl', function($scope) {
// create a message to display in our view
$scope.message = 'Hello world, this is the home page!';
});
demoApp.controller('aboutCtrl', function($scope) {
$scope.message = 'Hi! This is the about page.';
});
demoApp.controller('contactCtrl', function($scope) {
$scope.message = 'Would you like to contact us?';
});
Explicando el código:
- demoApp.config(function($routeProvider) {…}: Como ya se ha comentado, los providers deben inyectarse como funciones config de nuestro módulo.
- $routeProvider.when(‘url’, {…}): El método when de $routeProvider nos permite decirle a angular que template cargar (templateUrl) y que controlador utilizar (controller) al seleccionar un enlace a una cierta url.
Creando nuestros templates:
Creamos el directorio pages, y los archivos home.html, about.html y contact.html. Les damos el siguiente contenido:
home.html:
<!-- home.html -->
<div class="jumbotron text-center">
<h1>Home Page</h1>
<p>{{ message }}</p>
</div>
about.html:
<!-- about.html -->
<div class="jumbotron text-center">
<h1>About Page</h1>
<p>{{ message }}</p>
</div>
contact.html:
<!-- contact.html -->
<div class="jumbotron text-center">
<h1>Contact Page</h1>
<p>{{ message }}</p>
</div>
Probando los resultados
Volvemos a ejecutar nuestro servidor, y podemos ver como ahora se carga el jumbotron con Hello world!, y además, los enlaces para cambiar de vista funcionan como era de esperar:
DETALLE: Podemos observar que nuestra URL tiene un aspecto ligeramente desagradable (url/#/pagina). El hashtag lo incluye por defecto Angular, pero podemos deshacernos de el configurando el provider $locationProvider, y definiendo un path base para nuestras rutas relativas.
Mejorando el aspecto de nuestras URLs
Utilizando $locationProvider
Este provider sirve para configurar el servicio $location, que parsea la URL en la barra de direcciones, para efectuar cambios sobre la aplicación, y viceversa.
A través de $locationProvider, estableceremos el modo Html5 a true.
¿Qué es la API de Historial HTML5?
Es una forma estandarizada de manipular el historial del navegador utilizando un script. Esto le permite a Angular cambiar las rutas y URLs de nuestras páginas sin refrescar la página.
Veamos como afectan los cambios a nuestro script.js:
var demoApp = angular.module('demoApp', ['ngRoute']);
// configure our routes
demoApp.config(function($routeProvider, $locationProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl : 'pages/home.html',
controller : 'mainCtrl'
})
// route for the about page
.when('/about', {
templateUrl : 'pages/about.html',
controller : 'aboutCtrl'
})
// route for the contact page
.when('/contact', {
templateUrl : 'pages/contact.html',
controller : 'contactCtrl'
});
$locationProvider.html5Mode(true);
});
Estableciendo el directorio base para nuestros links relativos
Lo único que debemos hacer es incluir la etiqueta en nuestro de index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<base href="/">
</head>
Comprobamos de nuevo los resultados. Si nuestro navegador soporta HTML5, deberíamos ver como han cambiado las URLs:
Eso es todo. Espero que el tutorial haya sido de ayuda.
¡Saludos!
Buenas tardes me gustaría saber si hay una forma de hacer gets dinámicos en las templateUrl con el fin de generar templetes construidos por medio de php mil gracias muy buen articulo
Hola Edilson,
No se si acabo de entender lo que preguntas.
Podrías construir templateUrls a partir de una función, por ejemplo, inyectando a la función de configuración un Service que genere esas urls, es decir, algo del estilo:
demoApp.config(function($routeProvider, $locationProvider, MiServicio) {
$routeProvider
// route for the home page
.when('/', {
templateUrl : MiServicio.homePage(),
controller : 'mainCtrl'
})
});
Espero que esto responda a tus dudas, un saludo.
Hola, soy un poco nueva en angularjs pero mi duda es si tengo varios js que estan trabajando con ngroute, de que forma puedo hacer para que los controladores que trengo en ese js puedan ser llamados por un js que sea el principal para el ruteo de las vistas o pueda acceder a los controladores de estos modulos para que se puedan ver estas vistas. Espero hacerme enteneder. gracias
Muy buen tutorial, muchas gracias
Hola que cambio cambios hay que realizar para funciones con las ultimas versiones de angular y angular-router
Muchas gracias (Y) feliz navidad
Buenas como estas? Muy buen post gracias por compartirlo, tengo una duda y no se si alguien podria contestarmela. cuando inicio mi servidor mi url aparece como localhost:8000/#!/… y seguidamente localhost:8000/#!/#about etc… por lo tanto no me estan cargando las vistas segun tengo entendido es porque utilizo nodejs con express (?) cuando realizo los link amigables simplemente no carga el angular. Seria de gran ayuda porque realmente lo he intentado con varios ejercicios y no me aparecen nada en ng-view