Empezamos en este artículo una nueva sección del curso de Angular 5. Vamos a aprender a crear y usar formularios en nuestras aplicaciones Angular. Después de todo, ¿qué aplicación web no incluye formularios? Aunque sólo sea un mero formulario de contacto, para que nuestro visitantes se pongan en contacto con nosotros, al menos tendremos ese. Si la aplicación es un poco compleja o aspiracional, seguro que necesitaremos más.
La implementación de formularios en vistas de una aplicación Angular puede parecer un poco más compleja que la inserción de un formulario en bruto en HTML (y lo es). A cambio, nos permite unas prestaciones muy interesantes, a nivel de validación de datos, comunicaciones en tiempo real entre la vista y la lógica del componente, etc.
En esta sección empezaremos viendo los formularios desde lo más simple, formularios minimalistas hasta lo patético, hasta las prestaciones más interesantes que nos pueda ofrecer Angular al respecto. Además, cuando se tercie, según el desarrollo de los artículos, aprovecharemos para repasar conocimientos que ya hemos adquirido anteriormente en el curso, así como para ir introduciendo ampliaciones de los mismos o conceptos nuevos que, si bien, no sean exclusiva o específicamente de formularios, sí encajen bien y nos permitan aprender y progresar como desarrolladores de frontend con Angular.
Y basta de cháchara introductoria. Vamos a ello.
CREANDO LA APLICACIÓN
Vamos a empezar creando una aplicación nueva, para no mezclar lo que aprendermos a partir de ahora con lo que ya teníamos, a fin de no recargar innecesariamente el código. Después de todo, estas aplicaciones tienen fines meramente didácticos.
ng new angular-myforms -p amf --routing true
ATENCIÓN. Hemos llamado a la aplicación angular-myforms , en lugar de, simplemente, forms , porque dentro del propio Angular ya existe un módulo con ese nombre y, como ya he comentado en alguna ocasión, tratamos de evitar colisiones de nombres. |
Como ya sabes, tras un breve lapso de tiempo, se habrá creado el directorio con toda la estructura base de la aplicación. Abrimos el VSC, abrimos la carpeta de la aplicación, y en el package.json
editamos la línea del ng serve
para agregarle las opciones --aot
y -o
. Esto es como lo hemos hecho siempre, y no tiene nada nuevo.
LA ESTRUCTURA BASE DE LA APLICACIÓN
Nos crearemos un módulo para almacenar los formularios que vayamos creando a lo largo de este y los siguientes artículos. Lo llamaremos MyForms
:
ng g m MyForms --routing
Como ves, lo creamos con su propio sistema de enrutamiento, lo que nos permitirá cargarlo de forma perezosa.
También vamos a crear un módulo para elementos comunes, o generales, que no tengan, a priori, cabida «lógica» en otra parte de la aplicación. Lo llamaremos commons
(como ya hicimos en el ejecicio de la sección anterior). Esta vez lo crearemos sin enrutador propio. Los componentes de este módulo deberán cargarse de forma directa.
ng g m commons
Dentro de commons
crearemos un componente para la barra de navegación (navbar
), otro para la cabecera (header
) y un componente para la vista principal de la aplicación (home
). También crearemos un componente para la vista derivada de los errores de tipo 404, al que llamaremos notFoundComponent
. Los cuatro los haremos exportables.
ng g c commons/navbar --export
ng g c commons/header --export
ng g c commons/home --export
ng g c commons/notFound --export
Además (y esto recuérdalo porque es de una importancia extrema), en la lógica del módulo debemos importar el RouterModule
, que «vive» en @angular/router
. No importa que hayamos creado nuestro módulo CommonsModule
sin enrutamiento interno. Debemos importar RouterModule
por una razón. Entre los componentes de este módulo va la barra de navegación (NavbarComponent
). Como ya sabemos, los enlaces no se construyen con la propiedad href
de HTML, sino con la propiedad routerLink
de Angular. Pues bien. Si no importamos el RouterModule
en la lógica del módulo, la propiedad routerLink
no funcionará. En concreto, el archivo commons.module.ts
debe quedarnos así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NavbarComponent } from './navbar/navbar.component'; import { HeaderComponent } from './header/header.component'; import { HomeComponent } from './home/home.component'; import { RouterModule } from '@angular/router'; @NgModule({ imports: [ CommonModule, RouterModule ], declarations: [NavbarComponent, HeaderComponent, HomeComponent], exports: [NavbarComponent, HeaderComponent, HomeComponent] }) export class CommonsModule { } |
MUCHA ATENCIÓN. Aún a riesgo de hacerme cansino, insisto en la importancia de esta fase. Si no, no te funcionarán los enlaces. |
IMPORTANDO CommonsModule EN AppModule
Para dejar preparadas todas las importaciones, y dado que CommonsModule
tiene componentes que deberán ser visibles desde AppModule
, es necesario importar el primero en el segundo. Además, ya que vamos a tocar app.module.ts
, importaremos, como ya aprendimos a hacer en este artículo, la notación en español para números, fechas, etc. Nos queda así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { registerLocaleData } from '@angular/common'; import localeEs from '@angular/common/locales/es'; registerLocaleData(localeEs); import { AppComponent } from './app.component'; import { CommonsModule } from './commons/commons.module'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, CommonsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Observa que lo importamos en las líneas de la 5
a la7
, y en la 10
, y añadimos el CommonsModule
a la propiedad declarations
del decorador, en la línea 19
. Si estás usando VSC correctamente configurado, con que lo añadas a declarations
ya te hace la importación automáticamente.
EL ENRUTADOR RAÍZ
Este es otro elemento que tenemos que modificar. Tenemos el componente HomeComponent
, que tendrá la vista de inicio y se cargará en directo, y el módulo MyFormsModule
, que hemos creado con su propio enrutador, y se cargará en diferido (por carga perezosa). Para terminar, el NotfoundComponent
se cargará cuando se teclee una ruta que no se encuentre en la matriz Routes
. En concreto, app-routing-module.ts
nos queda así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './commons/home/home.component'; import { NotFoundComponent } from './commons/not-found/not-found.component'; const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'formularios', loadChildren: './my-forms/my-forms.module#MyFormsModule' }, { path: '**', component: NotFoundComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
EL ENRUTAMIENTO DE MyFormsModule
Para poder crear una estructura mínima, vamos a generar un componente, llamado Form01Component
, en el módulo MyFormsModule
, así:
ng g c MyForms/form01
Será el que empleemos en el primer formulario de ejemplo que crearemos en esta aplicación aunque, por ahora, lo vamos a crear «en bruto», es decir, sin contenido específico alguno.
Y, como es lógico, vamos a iniciar el enrutamiento interno del módulo (my-forms-routing.module.ts
), sólo para «irlo dejando preparado»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { Form01Component } from './form01/form01.component'; const routes: Routes = [ { path: 'f01', component: Form01Component } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class MyFormsRoutingModule { } |
INSTALANDO LIBRERÍAS EXTERNAS
Instalaremos jQuery y Bootstrap, como ya sabemos:
npm install --save jquery
npm install --save bootstrap@3
Ahora vamos a instalar la librería de JavaScript moment.js
, que usaremos en algunos formularios:
npm install --save moment
En package.json
se deben haber creado las siguientes líneas:
"jquery": "^3.3.1",
"bootstrap": "3.3.7",
"moment": "^2.22.1",
ATENCIÓN. Te preguntarás acerca de por qué hemos instalado bootstrap 3, en lugar de bootstrap 4, que lleva ya bastante tiempo en el mercado, y es un producto maduro y estable. La razón es muy sencilla. Existen algunas incompatibilidades entre Angular 5 y Bootstrap 4. A la hora de incorporar determinados elementos o de poner la aplicación en producción, surgen incompatibilidades que dan gran cantidad de problemas, obligándonos, incluso, a modificar elementos del CLI a mano, cosa que no es, en modo alguno, aceptable. En la parte del curso dedicada a actualizar a Angular 6, donde detallaremos las diferencias y mejoras en la nueva versión, aprenderemos a integrar bootstrap 4. |
En .angular-cli.json
modificaremos las claves styles
y scripts
para que queden así:
1 2 3 4 5 6 7 8 9 |
"styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ], "scripts": [ "../node_modules/jquery/dist/jquery.js", "../node_modules/bootstrap/dist/js/bootstrap.min.js", "../node_modules/moment/min/moment.min.js" ], |
Observa que los CSS de bootstrap los incluímos antes de los estilos de la aplicación. De este modo, cuando lo deseemos, podremos sobrescribir las clases originales de bootstrap, para modificar el aspecto de nuestra web.
LOS HTML
Llegados a este punto, vamos a modificar los HTML de los componentes para poner un rótulo relevante de lo que hacen, o la mecánica básica necesaria.
HomeComponent Y NotFoundComponent
Estos son los más simples. Ya los adornaremos si llega el caso pero, por ahora, con lo mínimo ya basta:
1 2 3 4 |
<!-- home.component.html --> <div class='container'> <h1>Vista de inicio</h1> </div> |
1 2 3 4 5 |
<!-- not-found.component.html --> <div class='container'> <h1>Recurso no encontrado.</h1> <h3>Revisa la URL</h3> </div> |
HeaderComponent
Como ya tenemos bootstrap, a esta vista vamos a darle un toque un «pelín» más visual:
1 2 3 4 |
<!-- Cabecera global de la página --> <div class='container-fluid jumbotron'> <h1>Cabecera global del sitio</h1> </div> |
NavbarComponent
Esta vista ya tiene un poco más de materia, ya que vamos a crear una barra de navegación de bootstrap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<!-- navbar.component.html --> <nav class="navbar navbar-default navbar-fixed-top navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Alternar navegación</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" routerLink='' style='cursor:pointer;'>MyForms</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li> <a routerLink='' style='cursor:pointer;'>Inicio</a> </li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Formularios <span class="caret"></span> </a> <ul class="dropdown-menu"> <li> <a routerLink='formularios/f01'>Formulario 01</a> </li> <!-- <li> <a routerLink=''>2</a> </li> --> </ul> </li> </ul> </div> <!-- /.navbar-collapse --> </div> <!-- /.container-fluid --> </nav> |
AppComponent
Aquí tenemos que eliminar el código que aparece por defecto al crear la aplicación, e insertar los elementos comunes a todo el sitio, así:
1 2 3 4 5 |
<!-- app.component.html --> <amf-navbar></amf-navbar> <amf-header></amf-header> <router-outlet></router-outlet> |
LOS ESTILOS GENERALES DEL SITIO
Aprovechando que los estilos generales del sitio (styles.css
) los hemos incluido después de los de los de bootstrap en .angular-cli.json
, vamos a retocarlos un poco, para que nos quede esto algo más aparente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
body{ width: 100%; position: absolute; z-index: 2; } .jumbotron { position: fixed; width: 100%; top: 51px; z-index: 2 } .container { position: relative; top: 246px; z-index: 1; } |
ATENCIÓN: Observa que, como hemos dicho, estamos sobrescribiendo, parcialmente, algunas clases de bootstrap. Esto es muy habitual en desarrollos web. Sea cual sea el framework CSS que emplees (bootstrap, materialize, milligram, o el que sea), será muy fácil que, por tu diseño, tengas que sobrescribir algunas de las clases tipificadas por el framework. Por esta razón, si te fijas en el fichero .angular-cli.json verás que cargamos es CSS de bootstrap antes de los estilos globales de la aplicación. Si lo hiciéramos al revés, no podríamos sobrescribir las clases. En la aplicación anterior no lo hicimos así, pero aquí ya vamos a empezar a cuidar más esos detalles. |
CONCLUYENDO
Cerramos este artículo aquí, para no hacerlo demasiado largo y recargado. Hemos aprovechado para repasar conceptos previso, matizando y ampliando algunos detalles:
- Creamos la aplicación «en bruto».
- Creamos una estructura mínima de módulos y componentes.
- Nos aseguramos de importar
RouterModule
en la lógica de los módulos donde vamos a tener enlaces. De momento es uno solo (CommonsModule
), pero podrían ser más. EnMyFormsModule
no es necesario preocuparnos de esto porque, en primer lugar, no está previsto usar enlaces ahí y, sobre todo, porque si nos hace falta, como es un módulo con enrutamiento interno, ya nos hace las importaciones necesarias en el archivo de dicho enrutamiento (my-forms-routing.module.ts
). - Creamos el enrutador raíz (el de
AppModule
) y el deMyFormsModule
. - Instalamos las librerías externas que vamos a usar en la aplicación (si más adelante necesitamos alguna otra, ya la instalaremos).
- Preparamos básicamente las vistas y los estilos generales del sitio.
En el próximo artículo construiremos nuestro primer formulario en Angular. El estado inicial de la aplicación, tal como la tenemos en este momento, te lo dejo en este enlace.