Angular y Nodejs
Angular y Nodejs
https://dogramcode.com/biblioteca
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
Angular y Node.js
Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
PRESENTACIÓN
Presentación
Este libro se dirige a cualquier informático que desee optimizar el desarrollo industrial de sus
Índice
aplicaciones web, con la implementación de una arquitectura MEAN (basada en MongoDB, el
framework Express, el framework Angular y un servidor Node.js). El autor le da las claves para cumplir
Autor
con los nuevos requisitos cada vez más exigentes, de este tipo de desarrollo, a saber, la necesidad de
rg
reutilizar componentes de software para aumentar la productividad del desarrollo y la optimización de la
Características
carga del servidor, que no para de aumentar.
o
n.
Descargas
io
El desarrollo de una aplicación Angular dentro de una arquitectura MEAN, se beneficia de la
ac
consistencia del uso de JavaScript y su extensión TypeScript, a nivel del cliente y del servidor. En los
primeros dos capítulos, el lector encontrará la información necesaria para comprender este lenguaje, que
m
permite la programación orientada a objetos con clases. A continuación se detallan, por un lado, la
ra
aplicación Angular (en la versión 8 en el momento de la escritura de este libro). Esto le permite crear
aplicaciones de una sola página (interactuando con el servidor solo para intercambiar datos) y
pr
demuestra una modularidad ejemplar, organizada en dos niveles: módulos y componentes. El libro
do
también presenta el sistema de gestión de base de datos NoSQL MongoDB que, combinado con los
otros dos pilares de la arquitectura, permitirá un acceso eficiente a un volumen muy alto de datos.
e to
visualización de gráficos y la anotación gráfica de Google Maps. Por último, también se presenta al final
w
w
w
ÍNDICE
Introducción
1. Introducción
2. La arquitectura MEAN para una aplicación web
2.1 El principio de las aplicaciones mono página (single page applications)
2.2 El paradigma de diseño modelo vista-controlador
3. Angular en el centro de la arquitectura MEAN
4. Presentación de El hilo rojo: una aplicación de e-commerce
El lenguaje JavaScript
1. Introducción a JavaScript
1.1 Breve repaso histórico
1.2 Panorama de la utilización de JavaScript
1.3 Las librerías y los frameworks aplicativos JavaScript
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
5. La programación funcional en JavaScript
o
5.1 Una función que se pasa como argumento (función de callback)
n.
5.1.1 Ejemplo con el método forEach()
io
5.1.2 Ejemplo con el método map() ac
5.2 Una función devuelve una función (factory)
m
6. La programación orientada a objetos con JavaScript
ra
6.7 La herencia
6.8 El encadenamiento de métodos
7. Las principales aportaciones de la norme ECMAScript 6
7.1 La norma ECMAScript
7.2 La palabra reservada let
7.3 La interpolación de variables en las cadenas
7.4 Los argumentos por defecto
7.5 Una manipulación más cómoda de las listas
7.5.1 La estructura for (... of ...) ...
7.5.2 El método includes()
7.6 El operador "fat arrow" (=>)
7.7 Las clases
8. La programación reactiva, observables y promises (promesas)
8.1 Primer ejemplo: un observable en un botón
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
2.3.2 Las interfaces
o
2.3.3 La programación genérica
n.
io
3. El lenguaje Dart
3.1 Instalación y prueba de Dart ac
3.2 Generación del código JavaScript con Dart
m
3.3 Las clases
ra
3.3.1 La herencia
og
La plataforma Node.js
do
1. Presentación de Node.js
to
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
3.1 Las colecciones y los documentos
o
3.2 Los índices
n.
io
4. Implementación de MongoDB
4.1 Instalación de MongoDB ac
4.1.1 Instalación de MongoDB en Linux
m
4.1.2 Instalación de MongoDB en Windows o en Mac OS
ra
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
5.5.1 Utilización de las funciones de callback
o
5.5.2 Utilización del módulo async
n.
io
5.5.3 El método async.series()
5.5.4 El método async.waterfall() ac
m
6.Interrogación de MongoDB con las rutas gestionadas por express
ra
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
6.3 Especificación de los componentes en el módulo
o
6.4 Activación del módulo
n.
6.5 La página web front
io
7.El ciclo de vida de un componente ac
7.1 El hook ngOnChanges()
m
7.2 El hook ngOnInit()
ra
1. Las plantillas
e
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
3.2 Envío de datos JSON al servidor
o
3.3 Envío de datos a través de Query string
n.
io
4. Implementación de los servicios en El hilo rojo
4.1 Declaración de las rutas del lado servidor ac
4.2 Gestión de los productos
m
4.2.1 Visualización de los selectores
ra
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
rg
5.2 El módulo de enrutado asociado al feature module research
o
5.3 El módulo de enrutado asociado al feature module cart
n.
io
6. Conocimientos adquiridos en este capítulo
Angular y la visualización de información
ac
m
1. Introducción
ra
https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
índice
o rg
n.
AUTOR
io
Pierre POMPIDOR ac Más información
m
ra
畑
og
pr
do
CARACTERÍSTICAS
e to
.d
w
w
w
DESCARGAS
https://dogramcode.com/programacion
Angular y Node.js
Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN
Este libro se dirige a cualquier informático que desee optimizar el desarrollo industrial de sus aplicaciones web, con la
implementación de una arquitectura MEAN (basada en MongoDB, el framework Express, el framework Angular y un servidor
Node.js). El autor le da las claves para cumplir con los nuevos requisitos cada vez más exigentes, de este tipo de desarrollo, a
rg
saber, la necesidad de reutilizar componentes de software para aumentar la productividad del desarrollo y la optimización de la
o
carga del servidor, que no para de aumentar.
n.
io
El desarrollo de una aplicación Angular dentro de una arquitectura MEAN, se beneficia de la consistencia del uso de JavaScript y
ac
su extensión TypeScript, a nivel del cliente y del servidor. En los primeros dos capítulos, el lector encontrará la información
m
necesaria para comprender este lenguaje, que permite la programación orientada a objetos con clases. A continuación se detallan,
ra
por un lado, la implementación de un servidor Node.js extremadamente reactivo y por otro lado, el framework de aplicación
og
Angular (en la versión 8 en el momento de la escritura de este libro). Esto le permite crear aplicaciones de una sola página
(interactuando con el servidor solo para intercambiar datos) y demuestra una modularidad ejemplar, organizada en dos niveles:
pr
módulos y componentes. El libro también presenta el sistema de gestión de base de datos NoSQL MongoDB que, combinado con
do
los otros dos pilares de la arquitectura, permitirá un acceso eficiente a un volumen muy alto de datos.
e to
La visualización de información también es un área emblemática de Angular, también se estudia la visualización de gráficos y la
.d
anotación gráfica de Google Maps. Finalmente, también se presenta al final del libro el uso de tres librerías de componentes
w
A lo largo del libro, un hilo común con numerosos ejemplos de código guía al lector hacia la creación de una aplicación de
comercio electrónico, un ejemplo ideal para ilustrar la implementación de una arquitectura MEAN.
Autor(es)
Pierre POMPIDOR
Profesor de informática en la Universidad de Montpellier, Pierre POMPIDOR enseña el diseño y la programación de
aplicaciones web, así como la visualización de información con estudiantes de Licenciatura y Máster. A través de este libro
y con toda su pedagogía, transmite al lector sus conocimientos sobre el desarrollo industrial de aplicaciones web con una
arquitectura MEAN.
https://dogramcode.com/programacion
Organización de este trabajo
Este libro se construye globalmente de arriba abajo, es decir de la explotación de los datos (con el sistema
de gestión de bases de datos MongoDB, los servicios web Node.js), hacia las operaciones aplicativas
(Angular, así como sus librerías gráficas y de componentes que la enriquecen).
Ya sea para Angular (versión 8 en el momento de la edición de este libro) o Node.js, todas las tecnologías
rg
presentes se basan en JavaScript. Después de un capítulo de introducción a estas tecnologías, el segundo
o
capítulo se dedicará a este lenguaje. Por supuesto, esta presentación será muy resumida, y no sustituirá a
n.
ninguno de los numerosos libros dedicados a este rico lenguaje, pero permitirá a un informático dominar un
io
lenguaje de programación moderno, familiarizarse con JavaScript. Por otro lado, se supone que el lector
ac
conoce las bases de los lenguajes HTML y CSS.
m
ra
Desde ECMAScript 6, JavaScript incorpora los atributos de la programación orientada a objetos con las
og
clases, pero por el momento de manera muy tímida. Para beneficiarse plenamente de este paradigma de
pr
programación, utilizar una extensión de JavaScript es todavía más inevitable, porque Angular favorece
do
TypeScript, desarrollado por Microsoft. Una presentación rápida de TypeScript y una muy breve
introducción a Dart (que no se implementa en este libro), serán el objeto del tercer capítulo.
e to
Las presentaciones de los lenguajes soportados (JavaScript y TypeScript), serán la materia del cuarto
.d
capítulo, dedicado a la plataforma Node.js, que permite crear servidores extremadamente reactivos.
w
w
Respecto a nuestra problemática global, nos interesamos por la creación de servidores HTTP que
w
El quinto capítulo presentará el sistema de gestión de bases de datos NoSQL MongoDB. Además de que la
consulta de las colecciones se puede escribir en JavaScript, la eficacia en la gestión de grandes volúmenes
de datos supone la gran fuerza de este SGBD. Estudiaremos con cuidado toda la expresividad ofrecida por
las «consultas» MongoDB.
Desde el sexto hasta el séptimo capítulo, se estudiará el framework aplicativo Angular. Para empezar,
describiremos el entorno técnico necesario para la creación de un proyecto Angular, poniendo el acento en
la herramienta Angular CLI. Seguirá una explicación ilustrada de la estructuración de una aplicación,
gracias a los módulos y componentes. A continuación, se presentará la gestión de las plantillas, así como las
facetas múltiples de la extensión del lenguaje HTML. La conexión a un servidor Node.js a través de los
servicios y la implementación del enrutado, que permite al router de Angular activar los componentes como
consecuencia de la selección de rutas, completarán análisis del framework. El octavo capítulo sobre los
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:06 p. m.]
servicios, presentará el núcleo de nuestra aplicación de e-commerce de ejemplo.
Antes de terminar, abordaremos la visualización de información, que es un uso primordial del framework
Angular, así como la implementación de diferentes librerías de componentes gráficos. La creación de
cuadros de mando (dashboards) se nutre del flujo de datos - que gestiona perfectamente Angular - y
difundido por numerosas librerías. En este último capítulo, este punto se ilustrará con la utilización de las
librerías D3.js y dc.js, para la visualización de gráficas y de la librería de componentes PrimeNG, para
monitorizar esta visualización a través de un Google Map. También descubriremos las librerías Material y
ngx-bootstrap para integrar numerosos componentes de interfaz (GUI componentes).
Antes de entrar en este tema, le avisamos de dos normas o criterios de escritura que pueden aparecer en los
esquemas de programación de este libro:
Los ángulos < y >, que generalizan el nombre de una entidad de programación (variable, método,
clase, etc...).
Los puntos suspensivos (...), que representan el código (generalmente el contenido de un bloque de
rg
instrucciones).
o
n.
Por ejemplo, en el siguiente esquema de programación:
io
import {Injectable} from '@angular/core';
ac
m
@Injectable()
ra
...
pr
}
do
<nombre del servicio> concierne a diferentes nombres posibles de servicios y los puntos
e to
Ir arriba
https://dogramcode.com/programacion
Introducción
El hecho de que el desarrollo de las aplicaciones web esté desde siempre en constante evolución, habla
sobre las dos necesidades que cada vez se vuelven más significativas:
La primera es industrializar y por lo tanto, reutilizar, las piezas de software cuyo funcionamiento ha
sido puesto a prueba, para aumentar la productividad (y la seguridad) del desarrollo.
rg
La segundo es optimizar la carga de los servidores que no para de aumentar (principalmente en el
o
caso de la gestión de las redes sociales o del comercio en línea).
n.
io
La industrialización del desarrollo de aplicaciones web, exige crear aplicaciones modulares. De esta
ac
manera, los módulos implementan grandes funcionalidades y se pueden identificar las piezas de software
m
(componentes) a partir de los cuales se construyen los módulos.
ra
La optimización de la carga de los servidores, implica reenfocar éstos sobre la gestión de los flujos de datos
og
y descargarlos de la tarea de generación de páginas web. Con este enfoque, el cliente (en la mayor parte de
pr
los casos, un navegador), debe recibir una aplicación completa (o casi completa, algunos componentes se
do
podrían cargar más adelante), lo suficientemente ligera como para ser cargada en la memoria central y
to
beneficiarse del entorno de ejecución del navegador. Esto también implica que el usuario debe poder
e
.d
navegar entre las vistas internas a la aplicación que se llama «mono página».
w
Para satisfacer estas exigencias, la solución más avanzada en nuestra opinión, es utilizar el framework
Angular de Google. Este framework es ideal para crear aplicaciones mono página, integradas en una
arquitectura cliente-servidor que implementan los servicios web, con la plataforma JavaScript Node.js. En
efecto, la nueva versión de Angular (la primera versión se llamaba AngularJS y quedó obsoleta), permite
realizar aplicaciones web modulares y se asocia de manera natural a un servidor Node.js. En el transcurs
de la redacción de este libro, la versión estable de Angular es la 8.0. Las versiones posteriores deberían
encadenarse con una frecuencia muy rápida, una compatibilidad ascendente de manera global y
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:44 p. m.]
continuamente garantizada (ya lo es desde la versiónde 2.0).
Es importante observar que el término Angular en este libro, nunca hará referencia a
AngularJS.
Angular es la solución más exitosa para la creación de aplicaciones mono página, por cuatro razones
principales (la primera es realmente fundamental):
Crear aplicaciones perfectamente modulares (y además, los módulos de Angular se basan en las
reglas para administrar los módulos Node.js).
Extender el lenguaje HTML gracias a directivas muy potentes, principalmente designando los
componentes por medio de etiquetas.
Asociar operaciones a los flujos de datos asíncronos (por medio de la programación reactiva).
rg
Activar las diferentes vistas del interfaz, gracias a un router extremadamente perfeccionado, que
o
n.
vincula la selección de rutas a la activación de componentes.
io
Angular solo puede demostrar toda su potencia si no está asociado a un servidor Node.js (también
ac
desarrollado con JavaScript). Se optimiza el intercambio de datos (por defecto asíncrono), entre el servidor
m
y el cliente. Esta asociación establece la arquitectura de software llamada MEAN.
ra
og
Por lo tanto, en esta introducción vamos a presentar la arquitectura MEAN y sus componentes, la aplicación
pr
de e-commerce (un marketplace) de ejemplo que nos va a acompañar a lo largo de todo el libro y para
do
terminar, vamos a introducir los contenidos de los diferentes capítulos del libro.
e to
.d
w
w
w
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:44 p. m.]
La arquitectura
MEAN para una aplicación
web
La arquitectura MEAN pone en juego a cuatro actores: el SGBD MongoDB, el framework Express, el
framework Angular y un servidor Node.js.
rg
Esta arquitectura permite:
o
n.
Desacoplar completamente la parte cliente (el «client-side») de la parte servidor (el «server-side»).
io
El cliente invoca al servidor, seleccionando las rutas para las que la sintaxis RESTful orientada a
datos, no exhibe las operaciones subyacentes.
ac
m
Acceder a los datos de manera extremadamente reactiva, gracias a la eficacia del servidor Node.js y
ra
Crear páginas web de manera modular y reutilizable (con componentes y módulos), gracias al
pr
permite una programación orientada a objetos con clases (también es posible utilizar el lenguaje
e
Dart).
.d
w
w
applications)
Las aplicaciones mono página inicialmente se cargan en el cliente, los accesos en el servidor solo se
destinan a los intercambios de datos.
El fuerte desacoplamiento entre la aplicación cliente y el servidor, permite una evolución sencilla de
estas dos entidades.
El servidor es mucho menos solicitado por cada cliente, ya que la tarea de generar las páginas no le
corresponde.
La navegación del internauta en la aplicación, se hace localmente (la vista global de la aplicación es
una composición de diferentes vistas interna
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:59 p. m.]
Esta arquitectura se adapta particularmente a las aplicaciones:
Típicamente, las aplicaciones de gestión de correo, mensajería instantánea, redes sociales o e-commerce, se
inscriben totalmente en el marco de esta solución.
org
n.
io
ac
m
ra
og
pr
do
to
El modelo agrupa los datos (tanto si se gestionan por el servidor o el cliente) y las operaciones
w
w
La vista se corresponde con la interfaz de la aplicación (la vista puede estar compuesta y construida
sobre diferentes sub-vistas).
El controlador hace referencia a un mecanismo que permite, a partir de una acción sobre la vista,
iniciar una operación que (en la mayor parte de los casos) actualiza el modelo. Por su parte, el
controlador a su vez puede hacer que la vista se actualice.
Algunos autores introducen una ambigüedad al integrar como parte del controlador, la
facultad de realizar algunas operaciones. No hay que fomentar esta visión.
https://dogramcode.com/programacion
Este paradigma relativamente antiguo es muy popular, ya que instaura un doble desacoplamiento: por una
parte, entre la vista y el modelo (lo que facilita, por ejemplo, poder portar una aplicación sobre otro recurso
multimedia diferente al navegador) y por otra parte, entre las acciones realizadas sobre la vista y las
De esta manera:
El modelo agrupa los datos (ya sean gestionados por el servidor o por el cliente) Y las operaciones
de negocio realizadas sobre ellas.
La vista se corresponde con la interfaz de la aplicación (la vista se compone y construye sobre una
agregación de plantillas).
El controlador hace referencia a un mecanismo que permite, a partir de una acción sobre la vista,
iniciar una operación: esta operación actualiza el modelo (en la mayor parte de los casos); el
controlador puede a su vez, modificar la vista.
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
Angular ofrece la posibilidad de relacionar de manera bidireccional los elementos del modelo y su
cobertura mediática en la vista (la actualización de uno de estos elementos, ya sea a través del modelo o de
la vista, implica respectivamente la actualización de la visualización del elemento o de su valor en el
modelo).
Esta funcionalidad (ver la sección Data bindings entre el componente y la plantilla del capítulo Angular: las
plantillas, bindings y directivas) se aproxima en Angular al sub-paradigma de diseño modelo-vista-vista
modelo (MVVM), pero sin hacer que entre en este paradigma.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:59 p. m.]
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:59 p. m.]
Angular en el centro de la arquitectura
MEAN
La aplicación Angular generalmente está formada por diferentes módulos (un módulo principal y varios
(sub)módulos, que implementan las grandes funcionalidades de la aplicación). Los módulos agrupan ellos
mismos los componentes implementados por las clases. Cada componente para el que la vista aparece en la
rg
interfaz global (que se corresponde con la página única de la aplicación), tiene una plantilla. Esta plantilla
o
n.
se puede integrar en la plantilla de otro componente, a través de una etiqueta que representa el
io
componente (esta etiqueta está en lenguaje HTML), o se puede insertar en un momento dado en una
ac
ubicación particular (un outlet) por el router que activa temporalmente el componente.
m
La implementación del router permite crear vistas temporales e instaura un desacoplamiento fuerte entre
ra
una acción (expresada por la selección de una ruta) y uno o varios componentes activados, como
og
consecuencia de esta acción.
pr
Los servicios Angular que son componentes de «utilidades» (se podrán utilizar por varios componentes y
do
no tienen plantilla), administran principalmente el flujo de datos desde o hacia el servidor Node.js. En
to
cuanto a él, el servidor Node.js dialoga con los servicios Angular a través de servicios web gestionados por
e
.d
el framework Express, y gestiona el flujo de datos desde o hacia las bases de datos del sistema de gestión de
w
La navegación dentro de la aplicación orquestada por el router y el intercambio de datos entre la aplicación
Angular y el servidor, se expresa gracias a las rutas.
Más adelante, hay dos esquemas simplificados (ya que solo presentan un único módulo, un único servicio
Angular y un único servicio web), que muestran los principales actores de una arquitectura MEAN y
precisan las dos gestiones diferentes de las rutas (que se explicarán en detalle más adelante):
Dentro de la aplicación, el router gestiona las rutas internas, para activar temporalmente los
componentes.
Los servicios Angular que invocan los servicios web del servidor Node.js a través de las consultas
HTTP que especifican las rutas RESTful.
A continuación se muestra el esquema que representa los principales actores de una arquitectura MEAN:
https://dogramcode.com/programacion
Material para descargar
rg
Angular y entre esta y el servidor:
o
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
https://dogramcode.com/programacion
Presentación
de El hilo rojo: una aplicación
de e-commerce
Para ilustrar nuestro propósito, le proponemos crear paso a paso una maqueta de aplicación de e-commerce
(un marketplace). El hilo rojo solo presentará el marco mínimo de una aplicación cómo esta, tanto desde el
lado de la aplicación cliente Angular, como del servidor (implementaremos una base de datos
rg
extremadamente simplificada).
o
n.
Por lo tanto, esta maqueta de una aplicación de e-commerce se construirá a través de la arquitectura MEAN,
io
movilizando: ac
m
Una base de datos MongoDB que tiene tres colecciones:
ra
pr
Un servidor Node.js que gestiona los servicios web que intercambian datos entre la base de datos
.d
Una aplicación mono página Angular modular que gestiona, a diferencia del módulo principal, dos
w
Un módulo que permite seleccionar los productos aplicando cualquier combinación de criterios
de búsqueda sobre:
sus tipos;
sus marcas;
su popularidad.
La página de bienvenida presenta el bloque de búsqueda que permite hacer las selecciones tanto de los
criterios de búsqueda, como de las palabras clave:
org
n.
io
El internauta solicita tener los productos (sin realizar una selección): ac
m
ra
og
pr
do
e to
.d
w
w
w
Seleccione ahora los teléfonos; se abre la lista de tipos de productos (construida automáticamente a partir de
la colección Products de la base de datos) y se selecciona el ítem phone:
org
n.
io
ac
m
ra
og
haberse identificado):
do
e to
.d
w
w
w
rg
1. Breve repaso histórico
o
n.
io
JavaScript es un lenguaje de programación creado en 1995 por Brendan Eich, que trabajaba para la
ac
sociedad Netscape. El objetivo original del lenguaje era crear scripts, es decir, programas interpretados que
m
se ejecutan por un navegador, principalmente para manipular los datos del DOM (Document Object Model),
ra
es decir, los objetos (en sentido informático del término), que representan los elementos del documento
og
etiquetado (por ejemplo, una página HTML) y asignado en memoria del navegador.
pr
asíncrono a los datos proporcionados por el servidor) e incluso ha invertido recientemente en el «lado
to
A pesar de su nombre, que puede dar lugar a confusión, JavaScript tiene muy poca relación con el lenguaje
w
Java (solo algunas estructuras sintácticas que provienen del lenguaje C).
w
w
Del lado cliente, los scripts JavaScript implementados por el navegador, se utilizan para:
Gestionar los eventos relacionados con una página HTML, por ejemplo, para controlar el contenido
de un formulario antes de su envío o hacer aparecer pop-ups.
El código JavaScript implementados en el lado servidor, se usan no solo para gestionar los web sockets,
sino sobre todo para crear servidores HTTP muy reactivos, ya que se basan en la arquitectura de eventos
rg
JavaScript. El principal entorno JavaScript que permite crear servidores como estos, es Node.js (ver el
o
n.
capítulo La plataforma Node.js).
io
ac
m
Este libro no abordará las arquitecturas basadas en la utilización de los sockets, ya que su
ra
utilización no forma parte de nuestro propósito (la arquitectura MEAN que se basa en la
og
utilización de HTTP para una comunicación asíncrona de datos). El lector interesado por este
dominio, podrá encontrar la felicidad descubriendo el mundo de socket.io.
pr
do
to
Varios cientos de librerías «utilitarias» y docenas de frameworks de aplicaciones JavaScript, han visto la
w
w
luz desde la creación de este lenguaje. Sería inútil intentar hacer un tour en este libro. Aquí solo citaremos
algunas referencias muy populares.
A continuación se muestran cinco librerías de «utilidades», que sin lugar a dudas, están entre las más
utilizadas:
jQuery permite acceder y modificar el DOM de manera muy sencilla, importar datos desde el
servidor de manera asíncrona (a través de la arquitectura AJAX) y propone un buen conjunto de
widgets gráficos (por ejemplo, para crear pestañas, desplegar acordeones, gestionar el copiar-pegar o
el drag and drop.
D3.js y dc.js que permiten crear interfaces gráficas en 2D, encapsulando el lenguaje SVG (ver el
capítulo Angular y la visualización de información).
Three.js permite crear escenas 3D.
Bootstrap permite gestionar el web responsivo (adaptación de las interfaces a las diferentes
Aunque no tenga ninguna sección completamente dedicada, en este libro aparecerán ejemplos
que usan código jQuery.
En lo que respecta a los frameworks aplicativos, debemos citar (por orden alfabético), los tres siguientes
que son actualmente los más completos con Angular: Ember, Knockout y Vue.js. Por otro lado, tres
librerías: Polymer, React y Riot que permiten crear interfaces complejas y son más que simples librerías de
componentes gráficos. Todos estos frameworks y librerías tienen una comunidad importante de
desarrolladores y son la base de numerosas aplicaciones web.
Sea como fuere, el framework Angular, que está en el núcleo de nuestro libro, es el que ofrece
indiscutiblemente la mayor modularidad y permite un uso industrializado más potente. Pero también es el
más complejo.
console.log("Hola");
<html>
<head>
<script src="hola.js"
type="text/javascript" language="javascript">
</script>
</head>
<body> JavaScript le dice hola en la consola </body>
</html>
Los mensajes generados por el método log() del objeto console, se muestran en las consolas que
forman parte de las herramientas del navegador (pulse la tecla [F12] para que aparezcan).
Si utilizará una versión antigua de un navegador, es muy importante no utilizar una etiqueta
<script> auto-cerrada.
Si crea código JavaScript con un framework (por ejemplo, Angular), será el propio
framework quién le de las reglas de creación de este código.
Del lado servidor, el código JavaScript se guarda en las carpetas asociadas a sus aplicaciones.
rg
Gracias a las consolas, visualizar los mensajes emitidos:
o
n.
Por el intérprete JavaScript, principalmente los errores de programación (los enlaces pulsables,
io
le permiten posicionarse en las líneas de código con error).
ac
Por su código (como en el ejemplo anterior).
m
ra
Gracias a las pestañas llamadas Elementos, HTML..., mostrar la jerarquía de las etiquetas de la
og
página web.
pr
Gracias a las pestañas llamadas Inspector DOM..., mostrar la jerarquía de los objetos asignados en
do
memoria del navegador y que se corresponden con todos los documentos cargados en todas las
to
La consola web que muestra los mensajes (emitidos por los scripts JavaScript o relativos a las cargas
w
w
rg
1. Las variables
o
n.
io
Las variables son tipadas dinámicamente (y no durante sus declaraciones). Se declaran por:
ac
La palabra reservada var: el ámbito de la variable es el de la función en la que parece (pero no las
m
funciones incluidas): desde la creación de la palabra reservada let, este tipado es muy
ra
desaconsejable.
og
La palabra reservada let: el ámbito de la variable está sujeta al bloque de instrucciones.
pr
do
JavaScript tipa (de manera interna) las variables, según seis tipos primitivos:
w
w
b. El transtipado
Diferentes funciones y operadores que permiten transtipar un valor. Los más útiles en JavaScript son
aquellos que transforman una cadena de caracteres (string) en un valor numérico (number):
o rg
parseInt(<string>) extrae (si existe) el valor entero utilizado como prefijo de la cadena:
n.
io
parseInt("123.45") devuelve 123. ac
parseInt("123.45bla") devuelve 123.
m
ra
cadena:
pr
Por otro lado, el operador de concatenación de cadenas de caracteres, es el signo aritmético más. A
continuación se muestra un ejemplo de implementación:
let tres = 3;
console.log("1 2 "+tres+" ya");
Los objetos.
Los objetos se van a describir en la sección La programación orientada a objetos con JavaScript, pero ahora
vamos a decir algunas palabras sobre las listas.
Las listas se crean utilizando la función constructora Array(), o directamente usando los corchetes (que
solo son azúcar sintáctico).
Por ejemplo, a continuación se muestran dos maneras de crear una lista llamada miLista que contienen
las cifras 1, 2 y 3 y la cadena de caracteres «ya»:
rg
console.log(miLista.length);
o
n.
io
Los métodos siguientes (entre muchos otros, pero estos son realmente muy útiles) se pueden aplicar a las
ac
listas:
m
splice(...), para añadir o eliminar elementos a una posición dada en una lista:
e to
<lista>.splice(<posición>,
.d
[elementos a añadir])
w
w
Por ejemplo:
miLista.splice(3, 0, 4)
miLista.splice(3, 1)
Para aplicar una operación sobre cada elemento de una lista, hay dos métodos principales:
Estos dos métodos utilizan funciones de callback (consulte la sección Una función que se pasa como
argumento (función de callback)).
org
n.
Una sintaxis ligera permite enmarcar la expresión regular por slashs (como en el mítico lenguaje Perl) y
io
ac
aplicar a esta expresión un método en el que la cadena de caracteres se pasa como argumento:
m
El método test(), para probar si la cadena de caracteres incluye el motivo de la expresión
ra
regular.
og
A continuación se muestra un ejemplo que verifica si la cadena tiene el formato de una dirección de e-mail
(ciertamente un poco imperfecto):
En el caso en el que deba interpolar el valor de una variable en la expresión regular, no puede utilizar más
la sintaxis «ligera» con los slashs. Es necesario crear la expresión regular con la función constructora
RegExp().
A continuación se muestra un ejemplo que muestra la cadena de caracteres incluida entre los valores de las
dos variables:
org
n.
io
4. Los bloques de instrucciones ac
m
En JavaScript, los bloques de instrucciones normalmente se rodean por llaves. En el caso en el que el
ra
bloque solo contenga una única instrucción, las llaves se pueden omitir. Pero incluso en este caso, es
og
preferible conservar las llaves, lo que da una seguridad durante la adición de una nueva instrucción en el
pr
bloque.
do
to
if (stock == 0) {
console.log("El producto ya no está disponible");
}
if (stock == 0) {
console.log("El producto ya no está disponible");
}
else {
if (stock == 0) {
console.log("El producto ya no está disponible");
}
else if ( stock < 5 ) {
console.log("Atención, solo hay algunos productos en stock");
}
else {
console.log("Date un capricho, compra todo");
}
rg
b. La estructura switch de conmutador múltiple
.o
on
El conmutador clásico en programación también existe en JavaScript.
i
A continuación se muestra un ejemplo (incompleto):
ac
am
switch (codeError) {
gr
case 401:
ro
console.log("usuario no autenticado");
op
break;
case 403:
od
break;
...
default:
console.log("Código no identificado");
w
var i=0;
while (i < 10) {
console.log(i); i++;
}
var i = 0;
do {
console.log(i);
i++;
rg
} while (i < 10);
o
n.
io
Con la estructura for (...; ...; ...) ...:
ac
Esta es la estructura clásica que permite manipular el índice del bucle en el encabezado del bucle:
m
ra
console.log(i);
pr
i++;
}
do
e to
Se pueden implementar dos estructuras iterativas «automáticas», es decir, que no usan índices de bucle:
w
w
La estructura for (... in ...) ... permite tanto iterar sobre todos los elementos de una lista (a
través de sus índices), como sobre las propiedades de un objeto (la notación «literal» de los objetos se
explicará más adelante).
A continuación se muestra un primer ejemplo del uso de esta estructura iterativa, que permite aquí acceder
a los índices de los elementos de una lista (que contiene los nombres de marcas misteriosas):
La estructura for (... of ...) (introducida a través de la norma ECMAScript 6), permite acceder
directamente a los elementos de la lista.
En JavaScript:
Una función se puede pasar como argumento a una función host. La función que se pasa como
rg
argumento, se llama función de callback (o función de rellamada).
o
n.
Una función (normalmente llamada factory), también puede devolver una función.
io
ac
1. Una función que se pasa como argumento (función de callback)
m
ra
og
Pasar una función como argumento de una función host, permite generalmente definir el código:
do
que se debe ejecutar al final de la ejecución de la función (pero posiblemente también durante la
to
ejecución de la función);
e
.d
A través de los dos métodos forEach() y map() aplicados a las listas, damos a continuación dos
ejemplos del uso de los métodos de callback.
También hay que observar que ECMA6 introdujo una nueva sintaxis más ligera, para especificar una
función de callback (esta sintaxis también tiene un efecto inicial sobre el contexto de ejecución). Esta
sintaxis que utiliza la fat arrow se explicará un poco más adelante.
En la sección La programación orientada a objetos con JavaScript, verá cómo se implementa la función
forEach().
Supongamos una colección que lista los productos (representados por objetos), puestos en la cesta de la
compra con su precio y cantidad.
Desea modificar esta colección para añadir a cada producto una nueva propiedad llamada subTotal,
que tiene como valor el número de ocurrencias del producto multiplicado por su precio.
function subTotal(e) {
e.subtotal = e.price * e.quantity;
return e;
En la sección La programación orientada a objetos con JavaScript, verá cómo se implementa la función
map().
(Para entender este ejemplo, diríjase si necesita a la sección La programación orientada a objetos con
JavaScript.)
this.productDisplay = function () {
console.log(type+":");
for (let product of this.lista) console.log(product);
}
}
var manufacture = function (ProductType) {
return function set(product) {
ProductType.lista.push(product);
};
};
org
n.
Casi todo es objeto en JavaScript. Sin embargo, este lenguaje no gestiona las clases (al contrario de lo que
io
sucede, por ejemplo, con los lenguajes Java, C++ o Python) antes de su normalización por la norma
ac
ECMAScript 6 (en 2015, es decir, veinte años después de su creación). Vamos a basarnos en esta evolución
m
principal para presentar la programación orientada a objetos con JavaScript, en tres momentos diferentes:
ra
La introducción de las clases y de una herencia simplificada, con la norma ECMAScript 6.
pr
do
La utilización de extensiones del lenguaje JavaScript, para gestionar las clases, principalmente con
TypeScript (este último punto será el objeto del capítulo siguiente).
e to
En su gestión tradicional (sin clases), los objetos se pueden crear de dos maneras:
.d
w
Utilizando una función constructora y llamando a esta función a través de la instrucción new.
La herencia está garantizada por los objetos particulares llamados prototipos: es la herencia por delegación.
Usando las llaves, el objeto creado hereda el «meta-objeto» Object (este punto se detallará más
adelante).
Un valor booleano.
Un valor escalar (un entero, un real, una cadena de caracteres, etc...).
Una lista (tabla dinámica), por ejemplo ["Konia", "Peach", "Threestars"].
Un objeto.
El código que implementa una función.
org
...
n.
io
Observe también que:
ac
Incluso si en teoría no es necesario rodear los nombres de las propiedades por comillas simples o
m
dobles, normalmente es preferible hacerlo ya que algunos parseadores/analizadores lo exigen.
ra
og
Imagine que ha creado un objeto llamado PeachPhone, que contiene la propiedad brand. Puede crear
un nuevo objeto llamado topPhone, que contiene la propiedad name y hereda las propiedades
contenidas en el objeto PeachPhone, gracias a la propiedad __proto__:
let topPhone = {
name: "topPhone",
__proto__: PeachPhone
};
console.dir(topPhone); // Muestra:
// name: "PeachPhone"
// __proto__: Object PeachPhone
Por definición, __proto__ es un puntero al objeto heredado (una referencia), si las propiedades del
objeto heredado (o los valores de estos) se modifican después de la creación del objeto del que heredan, éste
hereda automáticamente las nuevas propiedades/valores:
let topPhone = {
name: "topPhone",
__proto__: PeachPhone
};
PeachPhone.brand = "Pear";
b. La propiedad prototype
Aunque un objeto pueda heredar globalmente de otro objeto, es habitual hacer que solo herede la «parte
heredable» de este objeto.
let PeachPhone = {
"comment": "esta marca es realmente misteriosa",
"prototype": { "brand": "Peach" }
};
let topPhone = {
name: "topPhone",
__proto__: PeachPhone.prototype
};
console.dir(topPhone); // Muestra:
// name: "topPhone"
// __proto__: Object ...
La función constructora sirve de modelo para definir las propiedades creadas en el objeto, la palabra clave
this hace referencia en ella al objeto que se está creando.
Suponga una función llamada phone(), que recibe como argumentos dos cadenas de caracteres
correspondientes al nombre de modelo y la marca de un teléfono. Esta función crea un nuevo objeto que
tiene dos propiedades llamadas name y brand, que tienen como valores los valores que se pasan como
argumentos.
Por otro lado, la función phone() siendo ella misma un objeto, está afiliado a su prototipo un método
llamado whoAmI(), heredado por todos los objetos que crea.
En el siguiente ejemplo, se crean dos objetos gracias a la función phone; estos dos objetos tienen como
propiedades name y brand y utilizan el método whoAmI() asociado al prototipo del objeto-función
phone:
La función phone, como todas las funciones en JavaScript, es un objeto que tiene una propiedad
prototype que se referencia por los objetos topPhone y bigPhone, a través de sus propiedades
proto :
A continuación se muestra un ejemplo que vuelve a implementar el método forEach(), que accede a
cada elemento de una lista:
Array.prototype.myForEach = function(callback) {
for (let i in this) callback(this[i]);
}
A continuación se muestra un ejemplo de reimplementación del método map(), que aplica una función a
cada elemento de una lista y devuelve la lista modificada de esa manera:
Array.prototype.map = function(treatment) {
var results = [];
for (let i in this) results.push(treatment(this[i]));
return results;
}
<html>
<head>
<script>
console.log(this); // Muestra: Window
</script>
</head>
</html>
En una función constructora o en un método de su prototipo, this hace referencia al objeto actual:
function funcion(name) {
this.name = name;
console.log(this);
}
function.prototype.method = function() {
console.log(this);
}
Pero en una función de callback, this no hace referencia al objeto sobre el que se aplica el método que
llama esta función de callback:
function.prototype.method = function(callback) {
console.log(this); // Muestra: Object { name: "objeto1" }
callback();
};
Los métodos call(), apply() y bind() que permiten llamar a una función asociando su contexto
a un objeto particular, que se pasa como primer argumento de estos tres métodos.
Los dos primeros métodos solo se diferencian por la sintaxis de paso de argumentos:
nuevaFuncion = function.bind(objeto [,
argumento1, argumento2, ...])
A continuación se muestra cómo implementar estos tres métodos, para que la función de callback anterior
conozca al objeto actual de la función, que llama a esta función de callback. Observe que se pasa un
argumento a la función de callback.
function.prototype.methode = function(callback) {
console.log(this); // Muestra: Object { name: "objeto1" }
callback.call(this, "un argumento");
};
function.prototype.method = function(callback) {
7. La herencia
En JavaScript «puro», la herencia se implementa por el encadenamiento de prototipos.
A continuación se muestra un ejemplo donde la función constructora PeachPhone() crea objetos que
se corresponden con los teléfonos de marca Peach. Esta función hereda el prototipo de la función
phone(). Observe la utilización del método call().
phone.prototype.whoAmI = function() {
console.log("Soy el "+this.name+" de marca "+this.brand);
}
8. El encadenamiento de métodos
Como en todos los lenguajes orientados a objetos, es común en JavaScript encadenar los métodos como
sigue:
Para implementar esto, es suficiente con que los métodos devuelvan el objeto sobre el que se aplican.
rg
A continuación se muestra un ejemplo:
o
n.
io
function append(string) { this.value += string; return this; }
ac
function create(string) { this.value = string; }
m
create.prototype.append = append;
ra
og
console.log(inicialValue.append('Fast').append('4U').value); //
do
2fast4U
e to
.d
w
w
w
rg
ECMAScript es un conjunto de normas de programación recomendadas para los lenguajes de script. Estas
o
normas se aplican principalmente por los intérpretes JavaScript (los de los navegadores y por Node.js).
n.
io
Estas recomendaciones son muy numerosas, por lo que solo detallaremos algunas de ellas, de las definidas
ac
en 2015 (norma ECMAScript 6), que han hecho evolucionar considerablemente el lenguaje y que son
m
ampliamente difundidas, desde:
ra
og
la utilización de las fat arrows para simplificar la escritura de las funciones de callback;
las clases.
Por ejemplo, se utiliza de manera sistemática para las variables locales a las estructuras iterativas:
for (let <variable> in ...) { ... }
for (let <variable> of ...) { ... }
www.detodoprogramacion.org
if (brands.includes("Peach")) {
console.log("Vendemos también marcas fruta...");
}
Por ejemplo, la escritura sintáctica de la función de callback que se pasa como argumento del método
forEach(), se puede simplificar.
De esta manera:
se convierte en:
Las llaves que rodean el bloque de instrucciones se pueden omitir, si este solo tiene una instrucción pero
entonces se realiza un «return» (un retorno de valor por la función), automáticamente sobre la evaluación de
esta instrucción.
Para terminar, además de estas simplificaciones de escritura, un punto importante es que el contexto del
objeto actual (this) se conserva en una función de callback utilizada en un método del objeto (atención,
la implementación de esta funcionalidad depende de la versión de su intérprete JavaScript).
7. Las clases
La clase tiene un constructor que se define por la función constructor() y se invoca por la
instrucción new.
class Phone {
constructor(name, brand) {
this.name = name;
this.brand = brand;
}
whoAmI() {
console.log("Soy el "+this.name
+" de marca "+this.brand);
}
}
Observables es cualquier entidad de un programa, que puede producir un flujo de eventos capturables a lo
rg
largo del tiempo: esto puede ser un control de la interfaz que se activa (por ejemplo, un botón que se pulsa)
o
o una variable que va a cambiar de valor (en este caso, los eventos se asocian a valores significativos). El
n.
objetivo de la programación reactiva, es poder asociar de manera asíncrona una operación a los valores
io
emitidos por el observable (la operación se asocia al observable a través de una suscripción). Más
ac
particularmente, una función se podrá ejecutar para cada valor recibido, otra en caso de error y para
m
terminar, una última en caso de fin explícito del flujo (las dos últimas funciones se podrían ocultar). Estas
ra
funciones son los observadores (observers), bien conocidos por el patrón de diseño «observador».
og
Este paradigma se implementa en JavaScript, a través de la librería RxJS (y en el momento de la redacción
pr
La librería RxJS se puede utilizar como módulo de Node.js o como un archivo a relacionar con un código
e
HTML.
.d
w
Para utilizarla en el ecosistema de Node.js (ver el capítulo La plataforma Node.js), conservando una
w
compatibilidad con el código JavaScript anteriores, es necesario instalar con el administrador de módulos
w
npm, los dos módulos rxjs y rxjs-compat (la opción save que permite especificar estás instalaciones en el
archivo package.json cuya la utilidad se verá más adelante en este libro):
Para utilizarla directamente en un código HTML, también es necesario utilizar npm con el comando npm
install -g rx-lite, y después volver a copiar el archivo rx.lite.min.js en el directorio donde se
localiza el código HTML.
var Rx = require('rxjs-compat');
var source = Rx.Observable;
range(): alimenta el observable con una secuencia de enteros comprendidos entre dos valores.
source.range(1, 10): alimenta al observable con los enteros de 1 a 10.
interval(): alimenta al observable con una señal generada en un intervalo de tiempo dado.
source.interval(1000): el observable recibe una señal cada segundo.
un observable en un botón;
un observable en el cambio de valor de un entero;
un observable en un intervalo de tiempo.
El primer ejemplo es un código HTML que hay que cambiar en su navegador. Los dos ejemplos siguientes
se ejecutan a través de Node.js, con el comando node <nombre del programa>.
<html>
<head>
<meta charset="utf-8" />
<script src="rx.lite.min.js"></script>
<script src="jquery.min.js"></script>
<script>
$().ready(function(){
org
Este ejemplo crea un observable sobre un entero cuyo valor varía de 1 a 5.
n.
io
Este observable está suscrito a tres observadores (observers):
ac
Un observador que muestra cada valor recibido.
m
Un observador que muestra un mensaje de error si este se emite.
ra
og
var Rx = require('rxjs-compat');
do
1
2
3
4
5
And that's all folks!
Como recordatorio, la sintaxis anterior utiliza las fat arrows. A continuación se muestra una versión más
conservadora, que no utiliza este operador:
var Rx = require('rxjs-compat');
El primer «Top» se muestra más de 1 segundo después el inicio de la ejecución del script, el tiempo de la
inicialización del observable.
4. Las promises
Las promises (promesas), pueden en algunos casos sustituir a los observables, aunque estos se utilizan más
para mejorar la legibilidad sintáctica que aportan, que para la gestión asíncrona de datos. En lo que respecta
a esto, la diferencia principal entre los observables y las promises, es que las promises solo esperan un
único evento (y no un flujo de eventos), lo que es muy limitado.
Para volver a una mejor legibilidad sintáctica adoptada por las promises, imaginemos que quiere, a partir
del nombre de un producto, conocer su marca y después, a partir de una marca, conocer el último producto
comprado de esta marca. Sabiendo que las funciones getBrand() y
getLastPurchaseByBrand() sustituyen respectivamente a estos roles.
La sintaxis clásica utilizando los callbacks es esta:
getBrand(productName, function(brand) {
getLastPurchaseByBrand(brand, function(purchase) {
...
});
});
getBrand(productName,
brand => { getLastPurchaseByBrand(brand,
purchase => { ... }) });
getBrand(productName)
.then(function(brand) {
return getLastPurchaseByBrand(brand);
})
.then(function(purchase) {
...
});
org
«heredan» (por supuesto, los objetos heredados pueden heredar de otros objetos y de esta manera,
n.
esta herencia de objeto en objeto, se llama encadenamiento de prototipos).
io
ac
En este tipo de lenguaje (en el que JavaScript es de lejos el más popular de los representantes), hay
m
dos aspectos que rápidamente se pueden mostrar como desconcertantes y por lo tanto, ser fuente de
ra
problemas:
og
Es más difícil leer el «perímetro» de un objeto (sus atributos, sus métodos), que utilizar la
pr
Las propiedades propias del objeto o de manera más específica, las heredadas, se pueden
e to
eliminar, modificar, incluso añadir en cualquier momento: por lo tanto, es posible utilizando
.d
Por otro lado, el tipado de las variables se hace dinámicamente en JavaScript y no aporta la seguridad
w
Por estas razones (y algunas otras, principalmente el dominio de las funciones de callback o la
pérdida de contexto), JavaScript normalmente se ha descrito bajo la óptica de una creación
industrializada de código. Para esto, se crearon extensiones como TypeScript (o lenguajes alternativos
en el caso de Dart).
Angular se puede implementar tanto con JavaScript «puro», como con TypeScript o Dart, pero
TypeScript es de lejos el más utilizado (y por ejemplo, elegido para la herramienta Angular CLI que
vamos a introducir más adelante y que pre-formatea las piezas básicas que utilizaremos). Por este
motivo, nuestro código se construirá con esta extensión de lenguaje, aunque Dart se presentará
brevemente.
rg
Crear clases e instanciarlas.
o
n.
Gestionar una herencia simplificada.
io
ac
Implementar las interfaces (dentro de la programación orientada a objetos).
m
Gestionar el acceso a los atributos por tres palabras reservadas: public, protected,
ra
private.
og
pr
do
1. El transpiler tsc
e to
tsc code.ts
2. Tipado estático de las variables
A continuación se muestra la especificación formal del tipado de una variable:
Dos observaciones:
Desde la introducción de let, la utilización de var no tiene mucho interés. Sin embargo,
siempre se puede utilizar.
Se puede añadir un espacio opcionalmente antes o después de los dos puntos que separan el
nombre de la variable, de su tipo.
El tipado de las listas puede utilizar tanto el objeto Array, como los corchetes utilizados como
accesos directos sintácticos:
c. El tipo enum
Un nuevo tipo enum lo introdujo TypeScript. Permite enumerar los valores (considerados como
símbolos), se podrían tomar por una variable.
Una función que no devuelve ningún valor (de hecho, un procedimiento), puede utilizar el tipo
void:
3. Las clases
Las clases se definen con la palabra reservada class y la instanciación de un objeto se realiza con
el operador new:
// Métodos de la clase
...
}
class Phone {
rg
private name: string; private brand: string;
o
n.
constructor(name, brand) {
io
this.name = name; this.brand = brand; ac
}
m
ra
whoAmI() {
og
console.log("Soy el "+this.name
pr
}
}
e to
.d
topPhone.whoAmI();
w
w
A continuación se muestra la producción del código JavaScript, realizada por el transpiler tsc:
tsc ejemplo_clase.ts que genera el código ejemplo_clase.js.
a. La herencia
Una clase derivada hereda de una clase madre, a través de la palabra reservada extends.
En el siguiente código, el atributo brand de la clase madre se declara protected para ser
accesible en la clase derivada.
class Phone {
private name: string;
protected brand: string;
whoAmI() {
console.log("Soy el "+this.name
+" de marca "+this.brand);
}
}
constructor(name) {
super(name);
this.brand = "Peach";
}
}
b. Las interfaces
Es posible crear interfaces para obligar a una clase a volver a implementar los atributos de clases
(propiedades y/o métodos).
interfaz forThePublic {
whoAmI() {
rg
console.log("Soy major conocido bajo el nombre de "
o
+this.nickname);
n.
io
}
} ac
m
let topPhone = new Phone("topPhone", "Peach", "high-classPhone");
ra
topPhone.whoAmI();
og
pr
do
c. La programación genérica
e to
La programación genérica permite configurar una clase con un tipo genérico que solo se definirá
.d
«contenedor» que puede almacenar una variable de cualquier tipo y define un descriptor de acceso):
class contenedor<T> {
content: T;
constructor (content: T) { this.content = content; }
getContent(): T { return this.content; }
}
El lenguaje Dart de Google inicialmente tendrá por objetivo ofrecer una alternativa a JavaScript, ya
sea del lado cliente (implementándolo con prioridad dentro del navegador Chrome) o del lado
rg
servidor. Pero actualmente, su desarrollo tiende a presentarlo como una extensión de JavaScript, a
o
n.
instancias de TypeScript. Dicho esto, en el momento en el que se escribe este libro, Dart es menos
io
popular que este último lenguaje (por varias razones, pero principalmente porque TypeScript se
utiliza por diferentes herramientas como Angular CLI).
ac
m
A diferencia de JavaScript, TypeScript es un lenguaje totalmente orientado a objetos. La
ra
og
implementación de Dart es muy diferente de la de JavaScript, cuyo objetivo es ser mejor en términos
de y seguridad. Su sintaxis, normalizada por ECMAScript, es relativamente cercana a la de
eficacia
pr
TypeScript.
do
to
Se sigue utilizando mucho la palabra reservada var en el tipado de las variables, pero puede ser
e
opcionalmente estática. Al contrario de lo que sucede con JavaScript, las variables son objetos (por
.d
w
ejemplo, una variable numérica se puede inicializar a null). Los tipos int y float permiten
w
distinguir los enteros de los flotantes. Las palabras reservadas final y const se han introducido
w
A continuación se muestra el enlace sobre sus diferentes declinaciones, según lo despliegue en Linux,
Mac Os o Windows: https://www.dartlang.org/tools/sdk#install
Una vez que se ha instalado el intérprete Dart, pruébelo con «este hello world», ejecutando el
comando dart hello.dart en su terminal o su línea de comandos:
void main() {
print('Hello, World!');
}
o rg
/usr/lib/dart/bin/dart2js --out=hello.js hello.dart
n.
io
ac
3. Las clases
m
ra
og
Dart es nativamente un lenguaje orientado a objetos con clases (cada objeto es una instancia de una
pr
Las clases se definen con la palabra reservada class y la instanciación de un objeto se realiza por
e to
el operador new.
.d
w
class <nombreDeLaClase> {
// Declaración de las propiedades
...
// Métodos de la clase
...
}
class Phone {
String name;
String brand;
Phone(name, brand) {
this.name = name;
this.brand = brand;
}
whoAmI() {
print("Soy el "+this.name+" de marca "+this.brand);
}
}
main() {
var topPhone = new Phone("topPhone", "Peach");
topPhone.whoAmI();
}
a. La herencia
Una clase derivada hereda de una clase madre, a través de la palabra reservada extends.
La llamada del constructor de la clase madre aparece justo antes del bloque de instrucciones del
constructor de la clase derivada.
class Phone {
String name;
String brand;
b. Las interfaces
Propiamente dicho, no hay interfaces en Dart, pero dando por hecho que una interfaz es una clase
abstracta, es necesario utilizar una e implementarla (y no derivarla), con la palabra reservada
implements.
A continuación se muestra un ejemplo:
rg
String nickname;
o
void whoAmI();
n.
}
io
class Phone implements forThePublic {
ac
m
String name;
ra
String brand;
og
String nickname;
pr
do
this.brand = brand;
e
.d
this.nickname = nickname;
w
}
w
w
whoAmI() {
print("Soy mejor conocido bajo el nombre de "+nickname);
}
}
main() {
var topPhone = new Phone("topPhone", "Peach", "high-classPhone");
topPhone.whoAmI();
}
rg
numerosos módulos de red (de los que se muestra a continuación los principales en orden alfabético:
o
n.
DNS, HTTP, TCP, TLS/SSL, UDP), para los principales sistemas operativos (Unix/Linux, Windows,
io
Mac OS). De esta manera, sustituye ventajosamente, en el marco que nos interesa aquí (es decir, la
creación y la gestión de aplicaciones web), a un servidor web como Apache.
ac
m
Creado por Ryan Lienhart Dahl en 2009, este entorno se ha hecho muy popular rápidamente, por sus
ra
og
Por lo tanto, integrar Node.js en el desarrollo de aplicaciones web forma parte de la lógica actual, que
w
w
consiste en hacer las operaciones de acceso a los datos lo menos bloqueantes posible (para solventar
w
la problemática llamada «bound I/O» según la que, antes que cualquier otra causa, la latencia global
de una aplicación se debe al tiempo de latencia de los accesos a los datos).
Por lo tanto, Node.js permite a las aplicaciones web crear servidores extremadamente reactivos.
En lo sucesivo, va a:
org
Por lo tanto, cree el archivo pruebaDeNode.js, que solo comprende una línea de código:
n.
io
console.log("Prueba de Node"); ac
m
ra
pr
do
Para instalar Node.js en Ubuntu, lo más sencillo es utilizar el comando curl y el administrador
to
Observe que en el momento en que se escribe este libro, la versión actual de Node.js es la 11. Para
una versión posterior, es suficiente con cambiar su número de versión en el comando de instalación.
Se puede crear un enlace simbólico llamado node, para lanzar de manera más natural sus
servidores:
node pruebaDeNode.js
Ejecute el instalador (el archivo .msi anteriormente descargado), aceptando las condiciones de
utilización y la configuración por defecto.
node pruebaDeNode.js
Descargue el paquete de instalación para Mac Os (Macintosh Installer), yendo al sitio oficial de
Node.js: https://nodejs.org/en/download/
node pruebaDeNode.js
rg
la mayor parte se debe instalar bajo demanda.
o
n.
Durante la creación de una aplicación que exige la instalación de módulos, dos métodos son posibles
io
para realizarla aquí: ac
m
Directamente con el administrador de módulos npm (y su opción install).
ra
og
Su aplicación Node.js se puede reutilizar a su vez como módulo en determinadas condiciones, que se
w
Los módulos son las piezas conceptuales de una aplicación Node.js. Un módulo se puede organizar
en diferentes códigos JavaScript y depender de otros módulos. De esta manera, todos los recursos
necesarios para un módulo (código, archivo package.json que especifica sus dependencias, etc.) se
agrupan en un paquete que, de hecho, es una carpeta.
Por lo tanto, si los dos términos son casi intercambiables, el término «módulo» hace pensar más en la
funcionalidad global, y «paquete» en la carpeta y la organización de los archivos de código que se
encuentran en ella.
Los módulos se instalan globalmente en la carpeta node_modules, situada a nivel de los directorios de
sistema, si se utiliza la opción -g:
o en caso contrario (sin la opción g) en el directorio actual (pero también en una carpeta llamada
node_modules).
{
"name": "<nombre de la aplicación>",
"version": "<versión de la aplicación>",
"description": "<descripción de la aplicación>",
"author": "<nombre del autor de la aplicación>",
"main": "<código a ejecutar como punto de entrada>",
"scripts": {
"start": "node <código a ejecutar>"
},
"dependencies": {
"<nombre del paquete>": "<versión mínima del paquete a instalar>",
...
}
}
npm install
npm start
En este caso, el valor de la propiedad main se inicializa a index.js. Explicaremos un poco el interés
de esta propiedad:
server.listen(8888);
Dos observaciones:
La función de callback se ejecuta una vez que el servidor HTTP se ha creado y devuelve al
cliente (es decir, al navegador) una cadena de caracteres bruta («Hello world de Node.js»).
El servidor HTTP funciona aquí en el puerto 8888 (elegido arbitralmente).
Para ejecutar este servidor, escriba el siguiente comando en un terminal (en Linux o macOS) o en una
línea de comandos (en Windows):
node helloConNode.js
Para probar este servidor, abra su navegador y en la barra de URL, invóquelo con la siguiente
URL: http://localhost:8888 o simplemente con: localhost:8888.
Y Node.js le saluda:
Para realizar pruebas enviando consultas HTTP a un servidor Node.js sin utilizar un navegador, puede
recurrir a curl que es una herramienta que se puede utilizar en línea de comandos (con un terminal
Linux o Mac Os o línea de comandos Windows), que permite realizar (entre otras) transferencias de
datos hacia un servidor.
curl localhost:8888
La opción X de curl permite especificar el método de envío de la consulta HTTP (por defecto
GET). De esta manera, lo anterior es equivalente a:
Para enviar datos a través del método POST con curl, utilice las opciones -X y --data:
(Vista la simplicidad de lo que quiere hacer, solo puede crear un único código.)
Para crear un módulo, es necesario formar un paquete y por lo tanto, una carpeta que contiene
esquemáticamente:
index.js: el código (que puede, dicho esto, tener un nombre diferente), que es el punto de
entrada al módulo.
La subcarpeta lib: que contiene los diferentes códigos JavaScript.
package.json: el archivo que lista las dependencias del módulo.
readme.md: el archivo que da la información de la utilización del módulo.
history.md: el histórico de las evoluciones del módulo y de sus actualizaciones.
/*!
El módulo de saludo
*/
'use strict';
module.exports = require('./lib/saludo');
El archivo package.json:
{
"contributors": [
{
"name": "Pierre Pompidor",
"email": "[email protected]"
}
],
"description": "No hace nada, pero es realmente muy amable",
"engines": {
"node": ">= 0.10.0"
},
"files": [
"index.js",
"lib/"
module.exports.saludo = function() {
return "Hola de Node a través de un módulo";
}
rg
response.end(saludo.saludo());
o
n.
});
io
server.listen(8888);
ac
m
ra
Lance el servidor:
og
pr
node holaDeNodeatravesdeUnModulo.js
do
to
Se trata de un entorno JavaScript, estos datos se formatearán de manera natural en JSON: este
formato es el de la serialización de los objetos JavaScript (también se utiliza en el archivo
package.json).
El módulo express para acceder a Node.js a través de las URL en formato REST (también
permite gestionar las plantillas).
El módulo fs para acceder a los archivos (en este ejemplo en formato JSON) almacenados en
el servidor.
También puede utilizar el módulo mongoose para optimizar la conexión a una base de datos NoSQL
MongoDB.
rg
Para instalar los módulos que no se instalan directamente con Node.js, va a utilizar el administrador
o
n.
de módulos npm.
io
ac
5. El módulo express
m
ra
og
De hecho, se puede utilizar como un pequeño framework que permite crear aplicaciones web (pero
to
usted no la usará de esa manera, ya que hacer gestionar la parte cliente por Angular es
e
.d
Por el contrario, si el método HTTP utilizado es DELETE, esto implica la eliminación (siempre a
través de una acción desencadenada por el controlador), de todos los productos implicados.
Si una aplicación sigue las principales reglas REST (aquellas que acabamos de ver y algunas otras
que no se detallarán aquí: conexiones sin memoria, etc..), se llama RESTful (también es un juego de
palabras, «restful» significa «descansar» en inglés).
Dicho esto, en la práctica pocas aplicaciones web lo son realmente, ya que muy habitualmente solo se
utiliza el método GET, sea cual sea la acción realizada. En este caso, se introduce un argumento en la
ruta para indicar la acción (lo que evidentemente altera la «pureza» de la ruta).
rg
De esta manera, la distinción entre la visualización o la eliminación de los productos de marca Peach,
o
n.
se puede distinguir de la siguiente manera:
io
www.mitiendaenlinea.net/products/visualizacion/telefono/Peach ac
m
www.mitiendaenlinea.net/products/eliminacion/telefono/Peach
ra
require(’express’);
w
w
crear su aplicación servidora con la función express() ofrecida, por este módulo: var
w
app = express();
Por ejemplo, quiere que su servidor le salude: en una ruta vacía y a través del método GET.
node holaDeNodeConExpress.js
rg
o
En su navegador, invoque a su servidor: localhost:8888.
n.
io
Y Node.js y express le saludan: ac
m
ra
og
pr
do
e to
.d
w
w
w
Nombre
Método HTTP Acción a realizar Método del framework Express
corto
Observe que el método POST se puede utilizar para actualizar un dato y PUT para añadir un dato.
La utilización de los métodos HTTP para hacer referencia a la acción a realizar, se llama CRUD
(Create, Read, Update, Delete).
rg
o
insertarán dinámicamente los datos).
n.
io
Hay cuatro motores de plantillas disponibles:
ac
EJS
m
ra
Hogan
og
Jade
pr
JsHTML
do
Aunque esto no forme parte del propósito global de nuestro libro (ya que Angular mostrará
to
Cree una carpeta de prueba TemplatingConNodeYExpress y cree en ella los módulos express y
EJS con npm:
Cree una carpeta views y en ella, una página HTML llamada testNodeExpress.ejs:
<!DOCTYPE html>
<html>
org
var express = require('express');
n.
var app = express();
io
app.set('view engine', 'ejs') ac
m
app.get('/', (req, res) => {
ra
res.setHeader('Content-Type', 'text/html');
og
res.render('testNodeExpress.ejs',
pr
});
e to
app.listen(8888);
.d
w
w
En este código, el método app.set() selecciona el motor de plantillas seleccionado (aquí EJS).
w
Lance su servidor:
node templatingConNodeYExpress.js
{
"name": "templatingConEjs",
"scripts": {
"start": "node templatingConNodeYExpress.js"
},
"dependencies": {
"ejs": "^2.6.1",
"express": "^4.16.4"
}
}
npm install
npm start
npm install
6. El módulo fs (FileSystem)
El módulo fs permite en el servidor Node.js acceder al sistema de archivos del ordenador sobre el que
se ejecute.
En nuestros primeros ejemplos (que no utilizan base de datos NoSQL), permitía explotar los datos
formateados en JSON y directamente almacenados en los archivos.
fs.readFileSync(file[, opciones])
fs.createReadStream(path[, opciones])
Una vez que se abre el flujo, se puede redirigir hacia otro flujo con el método pipe(). El
argumento response (se corresponde con el contenido del frame HTTP devuelto por el servidor)
que también es un flujo, va a utilizar este método para proporcionarle contenido.
Sin ir más allá sobre la gestión de los flujos con Node.js, es importante recordar los tres puntos
siguientes:
por un pipe abundado por un flujo entrante (el método pipe() se aplica al flujo en
entrada),
A continuación se muestra el código que permite abrir un archivo como un flujo, leer (consumir)
progresivamente sus datos y escribirlos en el HTTP que se devuelve al cliente.
npm install -g fs
Si formalmente no es necesario rodear los nombres de las propiedades de un objeto JavaScript con
comillas, es preferible hacerlo durante la serialización de objetos JavaScript.
[
{ "type": "phone", "brand": "Peach", "name": "topPhone"},
rg
{ "type": "phone", "brand": "Threestars", "name": "bigPhone"},
o
{ "type": "tablet", "brand": "Threestars", "name": "bigTablet"},
n.
{ "type": "phone", "brand": "Konia", "name": "4000"}
io
] ac
m
Considere para los siguientes ejemplos, que esta colección se serializa en un archivo llamado
ra
og
Products.json.
pr
do
Para validar los datos formateados en JSON (ya que es muy común olvidar una comilla
to
jsonformatter.org.
w
w
w
Un recurso genera una cross-origin HTTP request, cuando pide un recurso de un servidor que no es el
que lo sirvió.
Por defecto, el navegador que recibe el frame HTML desde el servidor que no es el que originalmente
proporcionó el recurso, lo bloquea y provoca un error. Para paliar este problema, el servidor debe
añadir en el encabezado del frame http, una autorización de acceso.
Aunque este problema no tiene impacto en los primeros ejemplos, agregue la siguiente línea de
código para autorizar al navegador a aceptar el nuevo recurso (y realizar una cross-origin request
sharing):
Para que el navegador deserialice automáticamente los datos, añada la línea siguiente en el
encabezado del frame HTTP:
res.setHeader('Content-Type', 'application/json');
A continuación se muestra el código del servidor devuelveLosDatos.js, que devuelve el contenido del
org
archivo Products.json en la ruta /Products:
n.
io
var express = require('express');
var fs = require('fs');
ac
m
var app = express();
ra
og
res.setHeader('Content-Type',
do
'application/json; charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
e to
.d
readable.on('open', () => {
w
readable.pipe(res);
w
app.listen(8888);
Y su archivo package.json:
{
"name": "devuelveLosDatos",
"description": "devuelve datos JSON leídos en un archivo",
"scripts": {
"start": "node devuelveLosDatos.js"
Lance el servidor:
npm start
org
n.
io
ac
m
ra
og
pr
do
e to
.d
En una ruta, se introduce un argumento por medio del carácter dos puntos (:).
Deserializar los datos proporcionados por el archivo Products.json, con el método parse()
del objeto JavaScript JSON.
Formar una colección que solo contendrá los objetos que satisfacen la restricción de la marca.
Serializar esta nueva colección con el método stringify() del objeto JavaScript JSON y
devolverla al cliente.
org
Si, por ejemplo, quiere filtrar los objetos de su colección de productos usando el nombre del producto
n.
que se pasa como argumento, a continuación se muestra el código más sencillo de escribir:
io
ac
var ProductsArray = JSON.parse(fs.readFileSync('Products.json',
m
'UTF-8'));
ra
if (ProductsArray[i].brand == searchedBrand)
do
Products.push( ProductsArray[i] );
to
}
e
.d
Aquí, puede leer el archivo Products.json de manera global con la función readFileSync(), ya
w
w
que deserializa una sola vez todos los datos contenidos en este.
w
Discutiremos más adelante una solución que consiste en deserializar progresivamente los datos de un
archivo muy voluminoso para la memoria central.
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type',
'application/json;charset=utf-8');
rg
});
o
n.
io
app.listen(8888);
ac
m
Y su archivo package.json:
ra
og
{
"name": "devuelveLosDatosFiltrados",
pr
"scripts": {
to
},
w
"dependencies": {
w
"express": "^4.16.4"
w
}
}
Lance el servidor:
npm start
n.
io
progresivamente a través de un flujo (un readable stream).
ac
Si el archivo JSON se formatea de manera que cada objeto de primer nivel de la colección está en una
m
única línea, una solución simple es utilizar el pipe en el flujo del módulo split que va a dividir los
ra
estrategia:
e to
var fs = require('fs');
w
objectStream.on('end', () => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type',
'application/json; charset=utf-8');
res.end(JSON.stringify(ProductsDeLaMarca));
});
});
rg
app.listen(8888);
o
n.
io
Esta solución no es demasiado habilidosa, ya que el archivo JSON se debe formatear de manera
específica y tiene que eliminar la comilla que separa los objetos.
ac
m
ra
El módulo JSONStream (que no se revisará en este libro), puede permitir consumir progresivamente
og
Instale el módulo split con npm e incluya esta dependencia en el archivo package.json, utilizando
do
su opción --save:
e to
Suponga el método app.get() que gestiona la ruta /Products, con la variable request que
representa la consulta:
org
type: phone
n.
brand: Peach,Threestars
io
ac
El módulo querystring proporciona el método parse() para analizar la cadena de consulta y
m
ra
console.log(query);
});
app.listen(8888);
El módulo body-parser permite acceder a los argumentos enviados a través del método POST.
app.listen(8888);
org
curl -X POST --data "type=phone&brand=Peach&brand=Threestars"
n.
io
en la URL localhost:8888/Products, obtiene esta visualización: ac
m
type: phone
ra
brand: Peach,Threestars
og
pr
do
e to
.d
w
w
w
rg
Antes de todo, instale openssl y pwgen, si todavía no lo ha hecho:
o
n.
apt-get install openssl
io
sudo apt-get install pwgen ac
m
Debe crear:
ra
og
1.
la
autoridad de certificación (certificate authority);
pr
En este ejemplo, usted será su propia autoridad de certificación (lo que por supuesto, es criticable).
org
openssl req -new -key server.key -out server.csr
n.
io
Elimine la passphrase de la clave (debe rellenar la passphrase):
ac
m
ra
cp server.key server.key.passphrase
og
to
openssl x509 -req -days 365 -in server.csr -signkey server.key -out
e
server.crt
.d
w
w
w
https.createServer(opciones, app).listen(8443);
Crear un servidor Node.js que responda a las consultas HTTP en diferentes métodos, así como
un servidor securizado HTTPS.
Probar este servidor a través de su navegador o gracias a la herramienta curl que se utiliza
rg
en línea de comandos.
o
n.
Crear un módulo para poder reutilizar código JavaScript.
io
ac
Invocarlo en rutas REST, utilizando el módulo express que es un pequeño framework que
permite crear aplicaciones web.
m
ra
Utilizar el módulo express para gestionar las plantillas, con el motor de plantillas EJS.
og
Acceder al sistema de archivos del ordenador sobre el que se ejecuta el servidor Node.js para
pr
flujo (streams).
w
w
Configurar las rutas REST y utilizar estos argumentos para filtrar las colecciones JSON.
Aunque todo esto es interesante, por supuesto, es inconcebible manipular los datos almacenados en
los archivos del sistema de archivos. Dicho esto, no es cuestión de renunciar a dos puntos:
El formateado de los datos en forma de colecciones de objetos JavaScript (esto forma parte
integrante del ecosistema).
Y la gestión de datos voluminosos.
Por estas dos razones, va a poner en servicio un sistema de gestión básico de datos NoSQL que
gestionará, a través de un formato optimizado, las colecciones de objetos JavaScript; por otra parte,
este SGBD deberá poder gestionar un gran volumen de datos.
rg
numerosas restricciones que impone, restricciones que pueden ser muy molestas cuando se prioriza el
o
accesos muy rápido a grandes volúmenes de datos. En parte, esta eficacia se debe a que las
n.
restricciones de integridad y las transacciones ACID no están garantizadas en MongoDB (también se
io
debe a una creación con buen rendimiento y dinámica de los índices). ac
m
MongoDB almacena los datos en un formato binario llamado BSON (Binary JSon), que permite (con
ra
algunas excepciones) serializar los objetos JavaScript en binario (siguiendo un formato llamado
og
inspira directamente en JSON, que es el formato textual de la serialización de los objetos JavaScript.
do
Entre la familia de los SGBD NoSQL, MongoDB se clasifica en la subfamilia de los SGBD
e to
org
eficaces, pero a costa de una cierta vulnerabilidad). Dicho esto, este beneficio no es forzosamente el
n.
que nos interesa, principalmente en el caso de El hilo rojo (la creación de una aplicación de comercio
io
en línea), incluso si una empresa de e-commerce puede al final gestionar muchos datos, entre decenas
ac
de miles de referencias de los productos en venta y la base de datos de clientes.
m
ra
Por otro lado, MongoDB es una buena opción para la gestión de datos heterogéneos y cuando es
og
predeterminar los atributos de las entidades en el origen de las tablas o las colecciones (por
difícil
pr
ejemplo, si quiere modelizar el contenido de los artículos de una enciclopedia en línea, sería muy
do
difícil hacer una lista previa de todos sus atributos y si esta lista se pudiera hacer, es cierto que la
to
Para volver al estudio de casos, la principal motivación es quizás la homogeneidad de esta opción en
w
el marco de un desarrollo guiado por JavaScript, al mismo tiempo tanto en el cliente como en el
w
w
servidor, y la flexibilidad en la representación de los datos que ofrece este lenguaje. Por otro lado, una
aplicación de comercio en línea, moviliza relativamente pocas entidades y es sencillo tener en cuenta
las restricciones de integridad al nivel de las operaciones.
rg
(mal), es necesario ser consciente de lo siguiente:
o
n.
En MongoDB, una base de datos no se supone que se construye siguiendo un esquema
io
ac
predefinido (incluso si es posible hacerlo por ejemplo con ODM Mongoose) y por lo tanto, las
m
tuplas no se supone que tengan una lista idéntica de atributos.
ra
Por otro lado, MongoDB no garantiza la verificación de las restricciones de integridad, esto
og
Un punto importante que hay que entender bien, es que MongoDB solo permite una gestión parcial de
do
las claves extranjeras. Si quiere identificar un documento (que llamaremos el documento destino) de
e to
una colección, en un documento (que llamaremos el documento origen) de otra colección, tiene dos
.d
soluciones:
w
w
2. Los índices
Una de las grandes fortalezas de MongoDB, es la gestión de índices.
Cada colección se indexa por defecto en el atributo _id, pero en cualquier momento puede crear
Para ver los índices que tiene una colección, aplique el método getIndexes() a la colección:
db.<nombreDeLaColeccion>.getIndexes()
db.<nombreDeLaColeccion>.createIndex(<atributos>, <opciones>)
Vista la complejidad de creación de los índices y las estrategias que implica, este punto no se
detallará en este libro, ya que se merece un capítulo entero.
rg
Toda la información útil está en el sitio web: https://docs.mongodb.com/manual/installation/
o
n.
Y en particular para Ubuntu, consulte: https://docs.mongodb.com/manual/tutorial/install-mongodb-
io
on-ubuntu/ ac
m
El lanzamiento del servicio se realiza con el siguiente comando (atención, Systemd es el
ra
sudo
pr
Para verificar cuál es el puerto ocupado por MongoDB (funciona por defecto en el puerto 27017):
e
.d
mongo
El mongo shell puede evaluar (casi) cualquier código JavaScript y mostrar los resultados a través de
su método print():
var me="Pierre"
print("Hola", me)
En los ejemplos que siguen, las palabras rodeadas por < y > se sustituyen por sus
propios datos:
show dbs
Pero atención, esta creación solo será efectiva si se crea una colección en la base de datos.
show colecciones
show tables
org
Para implementar las etapas de creación de una colección, cree una base de datos llamada
n.
io
OnlineSales, seleccionándola (incluso si no existe todavía):
ac
use OnlineSales
m
ra
og
La colección se creará cuando se inserten los primeros documentos. La colección se puede considerar
e
.d
db.<nombreDeLaColeccion>.insert(<Document>)
db.<nombreDeLaColeccion>.insert(<Collection>)
Ejemplos:
db.Products.insert([
{"type": "phone", "brand": "Peach", "name": "topPhone 8 64G"},
{"type": "phone", "brand": "Peach", "name": "topPhone 8 256G"},
La inserción de una fecha se hace a través del método Date() de mongo shell.
Atención, debe haber un objeto por línea (y por lo tanto, los objetos no están separados por comillas).
Para insertar una colección (es decir los objetos listados en una lista y por lo tanto, separados por
comillas), añada a la instrucción anterior la opción --jsonArray:
Con la opción --drop, si la colección ya existe, se sustituye (en caso contrario, los documentos se
añaden a la colección existente).
Para ejecutar los ejemplos de código asociados al libro, se debe crear una colección Products en
la base OnlineSales, con el siguiente comando:
db.<nombreDeLaColeccion>.find()
Por ejemplo:
db.Products.find()
El método find(), que devuelve una lista de objetos (los documentos seleccionados), puede
acceder al documento deseado a través de su índice.
Para contar todos los documentos de la colección, utilice el método count() en el resultado
devuelto por find():
db.<nombreDeLaColeccion>.find().count()
Por ejemplo:
db.Products.find().count()
db.<nombreDeLaColeccion>
.find()
.forEach((doc) => { <codeJavaScript> })
Por ejemplo:
El método find() puede tener como argumento un objeto que especifica los criterios de selección
de los objetos de una colección. Llamamos a este objeto «el objeto filtro»:
db.<nombreDeLaColeccion>.find(<ObjetoFiltro>)
Por ejemplo, si quiere seleccionar todos los productos de marca Peach de la colección Products,
la búsqueda se escribe de la siguiente manera:
db.Products.find({"brand": "Peach"})
Normalmente es útil usar el método toArray() que aplica al método find() que devuelve una
colección.
Por ejemplo:
db.Products
.find({"brand": "Peach"})
.toArray((err, documents) => { ... })
Para seleccionar solo el primer documento que cumpla los criterios de búsqueda, puede utilizar el
método findOne():
db.<nombreDeLaColeccion>.findOne(<ObjetoFiltro>)
Por ejemplo, a continuación se muestra una búsqueda de los productos cuyo precio sea superior o
igual a 300 euros e inferior o igual a 400 euros:
rg
db.Products.find({"price": {$gte:300, $lte:400}})
o
n.
io
c. El operador $exists ac
m
El operador $exists permite filtrar los documentos que contienen o no, la propiedad especificada
ra
Por ejemplo, a continuación se muestra una búsqueda de los productos de la colección Products,
pr
d. El operador $in
w
El operador $in permite enumerar los valores posibles para una propiedad.
Por ejemplo, a continuación se muestra una búsqueda de los productos de la colección Products,
cuyo tipo sea computer o tablet:
e. El operador $nin
El operador $nin permite excluir una serie de valores para una propiedad dada.
Por ejemplo, a continuación se muestra una búsqueda de los teléfonos que no sean ni de la marca
Konia, ni de marca Kowi:
f. El operador $or
El operador $or permite especificar varias condiciones, donde al menos uno de ellos debe ser
verdadero en la misma propiedad.
Por ejemplo, a continuación se muestra una búsqueda de teléfonos, cuya popularidad sea máxima o
no exista:
db.Products
.find({"type":"phone",
$or: [{"popularity":5},
{"popularity": {$exists: false}}]
})
g. El operador $not
El operador $not permite filtrar los documentos que no satisfagan la condición especificada.
Por ejemplo, a continuación se muestra una búsqueda de productos que no tengan un índice de
popularidad inferior a 4 (esto también incluye los productos que no tienen esta propiedad).
h. El operador $nor
El operador $nor permite filtrar los documentos que no satisfagan ninguna de las condiciones
especificadas.
Por ejemplo, a continuación se muestra una búsqueda de productos que no son teléfonos y cuestan
menos de 300 euros.
db.Products
.find({"$nor": [{"type":"phone"},
{"price":{$gte:500}}
]}
)
Directamente, indicando la expresión regular entre dos barras oblicuas (según la legendaria
sintaxis del lenguaje Perl).
O creando un objeto con la función constructora RegExp(), principalmente si la expresión
regular se debe construir a través de una variable.
rg
de los documentos de la colección Products.
o
n.
db.Products.find ({"name": /phone/i})
io
var keyword = "phone" ac
db.Products.find({"name": new RegExp(keyword, "i")})
m
ra
og
a. Las proyecciones
e
.d
Durante la utilización del método find(), un objeto se puede dar como segundo argumento para
w
conservar o excluir del resultado las propiedades de los documentos que se devolverán.
w
w
Este objeto consiste en uno o varias parejas «nombre de la propiedad»: valor, separadas por una
comilla, o valor = 1, para conservar la propiedad y valor = 0 para excluirla.
b. El método distinct()
El método distinct() devuelve los valores diferentes de una propiedad de los documentos
filtrados.
Los dos primeros argumentos del método distinct() que permite especificar:
Por ejemplo, a continuación se muestra la búsqueda de todas las marcas diferentes de los productos
vendidos:
org
db.Products.distinct("brand")
n.
io
incluso, la búsqueda de todas las marcas diferentes de teléfonos: ac
m
db.Products.distinct("brand", {"type": "phone"})
ra
og
pr
(Se esperan homónimos de sus clientes, sus direcciones de correo electrónico le permitirán
distinguirlos una vez que se haya completado su identificación en su sitio).
{ "_id": ObjectId("58d3cd3cda8751c171a6606f"),
"type":"phone",
"brand":"Threestars", "name":"bigPhone", "price":200, "stock":25 }
{ "_id": ObjectId("58d3cd3cda8751c171a66071"),
Estos dos productos se deben añadir a la cesta de la compra de Pierre Pompidor en un documento de
la colección Carts, y en este documento en valores de la propiedad order. A continuación se
muestra el documento relativo a la cesta de la compra de Pierre Pompidor, antes de la adición de los
dos productos:
{ "_id": ObjectId("58d517d42576fcbc8bb9aa81"),
"lastname": "Pompidor", "firstname": "Pierre",
"email":"[email protected]”,
"order": []
}
rg
¿Cuáles son las posibilidades que se ofrecen? Hay dos:
o
n.
Anidar los objetos que corresponden con los productos en el objeto cesta de la compra.
io
ac
Referenciar por sus identificadores a los objetos productos del objeto cesta de la compra.
m
ra
La primera solución es volver a copiar (totalmente o en parte) las propiedades de los productos
pr
{ "_id": ObjectId("58d517d42576fcbc8bb9aa81"),
w
"lastname":"Pompidor",
w
"firstname":"Pierre",
w
"email":"[email protected]",
"order": [
{"type":"phone",
"brand":"Threestars",
"name":"bigPhone",
"price":200},
{"type":"headset",
"brand":"Earlid",
"name":"Earlid Pro",
"price":200}
]
}
db.Carts.find({"email":"[email protected]"})[0]
.order
.forEach((doc) => {
print(doc.name);
})
org
n.
{ "_id": ObjectId("58d517d42576fcbc8bb9aa81"),
io
"lastname":"Pompidor", "firstname":"Pierre",
"email":"[email protected]",
ac
m
"order": [
ra
ObjectId("58d3cd3cda8751c171a6606f"),
og
ObjectId("58d3cd3cda8751c171a66071")
]
pr
}
do
e to
Hacer que se muestren los nombres de los productos de la cesta de la compra, se antoja un poco más
.d
complicado:
w
w
O bien usa una variable intermedia que contiene la lista de los identificadores de los productos.
w
O implementa un join.
En conclusión, el referenciado de los productos por sus identificadores es más elegante, pero el
acceso a la información es más laboriosa.
c. Los joins
A partir de la versión 3.2 de MongoDB, es posible realizar una unión.
db.<nombreDeLaColeccion>.aggregate([
{$<acción>: },
{$< acción >: },
...
])
org
$lookup: aplica una unión.
n.
io
Para empezar, seleccione en la colección únicamente la cesta de la compra de Pierre Pompidor.
ac
Esta selección se hace con la instrucción $match:
m
ra
db.Carts.aggregate([
og
{$match: {"email":"[email protected]”}}
pr
])
do
to
{ "_id": ObjectId("58d533262576fcbc8bb9aa82"),
w
"lastname":"Pompidor",
w
w
"firstname":"Pierre",
"email": "[email protected]”,
"order": [ ObjectId("58d3cd3cda8751c171a6606f"),
ObjectId("58d3cd3cda8751c171a66071")
]
}
Con los identificadores de los productos agrupados en una lista, distribúyalos en el objeto que
contiene esta lista para producir tantos objetos como productos:
db.Carts.aggregate([
{ $match: {"e-mail":"[email protected]”}},
{ $unwind: "$order" }
])
{ "_id": ObjectId("58d55d616d577b19d5946c1d"),
"lastname": "Pompidor",
"firstname": "Pierre",
"email": "[email protected]”,
"order": ObjectId("58d3cd3cda8751c171a6606f") }
{ "_id": ObjectId("58d55d616d577b19d5946c1d"),
"lastname": "Pompidor",
"firstname": "Pierre",
"email": "[email protected]”,
"order": ObjectId("")
}
org
n.
Ahora, aplique la unión (un left join) con la instrucción $lookup.
io
Las propiedades del objeto que configuran la unión, indican: ac
m
from: el nombre de la colección sobre la que se realiza la unión.
ra
la unión.
to
db.Carts.aggregate([
w
{ $match: {"email":"[email protected]”}},
w
{ $unwind: "$order" },
{ $lookup: { from: "Products",
localField: "order",
foreignField: "_id",
as: "product" }}
])
{ "_id": ObjectId("58d55d616d577b19d5946c1d"),
"lastname": "Pompidor",
"firstname": "Pierre",
rg
{ "_id": ObjectId("58d55d616d577b19d5946c1d"),
o
n.
"lastname": "Pompidor",
io
"firstname": "Pierre",
"email": "[email protected]”,
ac
m
"order": ObjectId("58d3cd3cda8751c171a66071"),
ra
"type": "headset",
"brand": "Earlid",
pr
"popularity": 5,
to
"price": 300,
e
.d
"picture": "beatspro.jpeg",
w
"stock": 21 }
w
]
w
Para volver a una estructura bastante similar en su cesta de la compra, que incluye una propiedad
order, que contiene la lista de los identificadores de los productos seleccionados, a la que le
añade una propiedad Products que contiene los objetos generados, ejecute este pipeline:
db.Carts.aggregate([
{ $match: {"email":"[email protected]”}},
{ $unwind: "$order" },
{ $lookup: { from: "Products",
localField: "order",
foreignField: "_id",
as: "product" }},
{ "_id": ObjectId("58d55d616d577b19d5946c1d"),
"order": [ ObjectId("58d55cd4dca60e1fe1f94d61"),
ObjectId("58d55cd4dca60e1fe1f94d63") ],
"Products": [
rg
{ "_id": ObjectId("58d55cd4dca60e1fe1f94d61"),
o
n.
"type": "phone",
io
"brand": "Threestars",
"name": "bigPhone",
ac
m
"popularity": 4,
ra
"price": 200,
og
"picture": "bigphone.jpeg",
"stock": 25 },
pr
{ "_id": ObjectId("58d55cd4dca60e1fe1f94d63"),
do
"type": "headset",
to
"brand": "Earlid",
e
.d
"popularity": 5,
w
"price": 300,
w
"picture": "beatspro.jpeg",
"stock": 21 }
]
}
la modificación del valor de una propiedad existente o la adición de una nueva propiedad,
se introducen con el operador \$set;
db.<nombreDeLaColeccion>
.update(<ObjetoFiltro>, <ObjetoQueEspecificaLasModificaciones>)
Por ejemplo, para bajar el precio del topPhone 7 64G, de 1.000 euros a 999 euros, escriba:
db.Products
.update({"name": "topPhone 7 64G"}, {$set: {"price": 999}})
org
n.
Observe que la siguiente consulta:
io
db.Products.update({"name": "topPhone 7 64G"}, {"price": 999})
ac
m
ra
no realizará completamente lo que se quiere hacer: todas las propiedades del documento seleccionado
og
Atención, la modificación solo afecta al primer documento que cumple los criterios. Si quiere que
to
todos los documentos que cumplen los criterios sean modificados, debe:
e
.d
db.Products
w
.update({"type": "tablet"},
{$set: {"type": "touch tablet"}, "multi": true})
b. Eliminación de un documento
La eliminación de uno o varios documentos, se implementa gracias al método remove():
db.<nombreDeLaColeccion>.remove(<ObjetoFiltro>)
db.<nombreDeLaColeccion>.drop()
rg
Este servidor será configurado por el archivo package.json.
o
n.
io
1. Instalación del módulo MongoDB para Node.js ac
m
ra
Primera etapa: cree el esqueleto del archivo package.json, con el comando npm init.
og
a las peticiones de información que provoca este comando, solo se guarda el nombre del
Respecto
pr
{
w
"name": "onlinesalesserver",
w
"versión": "1.0.0",
w
Como ha instalado Node.js globalmente, pregunte su versión con node -v, y complete este
archivo con la propiedad engines. Complete también el objeto scripts con la propiedad
rg
La opción --save permite completar las dependencias listadas en el archivo package.json.
o
n.
A continuación se muestra la versión (por el momento) final, de este archivo.
io
{ ac
m
"name": "onlinesalesserver",
ra
"versión": "1.0.0",
og
"scripts": {
do
},
.d
"license": "ISC",
w
Una variable con valor null si la conexión ha tenido éxito o si no, el error de
conexión.
Un objeto que sirve de interfaz con la conexión en el servidor Node.js (por supuesto, si
la conexión ha tenido éxito) y después, un segundo momento que permite la selección
de la base de datos.
org
Considere el programa onlinesalesserver.js siguiente:
n.
io
"use strict"; ac
var MongoClient = require("mongodb").MongoClient;
m
var assert = require("assert");
ra
let db = cliente.db("OnlineSales");
to
assert.equal(null, err);
e
cliente.close();
w
});
w
w
Si ejecuta este servidor a través de la instrucción npm start (por supuesto, puede ejecutarla
directamente con el comando node), el mensaje de conexión aparece en su terminal o su línea de
comandos.
A continuación se muestra el esquema de programación que permite insertar varios documentos, con
el método insertMany():
rg
MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {
o
let db = cliente.db("OnlineSales");
n.
assert.equal(null, err);
io
db.collection("Products") ac
.insertOne( {
m
"type":"phone",
ra
"brand":"Peach",
og
"name":"topPhone 8 32G",
pr
"popularity":4, "price":900,
do
"picture":"topphone.jpeg",
to
"stock":5 } );
e
cliente.close();
.d
})
w
w
w
Para que los códigos sean más legibles, estás diferentes selecciones se aislarán en una función
productResearch().
"use strict";
var MongoClient = require("mongodb").MongoClient;
var assert = require("assert");
var url = "mongodb://localhost:27017";
rg
};
o
n.
io
MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {
let db = cliente.db("OnlineSales");
ac
m
assert.equal(null, err);
ra
productResearch(db);
og
cliente.close();
});
pr
do
Si este código se ejecuta a través de la instrucción npm start, las propiedades/valores de cada
e to
_id: 58b3e2d440f60da0834601da
w
w
type: phone
brand: Peach
name: topPhone 8 32G
popularity: 4
price: 900
photo: topphone.jpeg
...
Ahora se trata de mostrar un mensaje una vez terminada la visualización en la consola de los
documentos seleccionados. Y también implementar un objeto que hace un filtro sobre los productos
"use strict";
var MongoClient = require("mongodb").MongoClient;
var assert = require("assert");
var url = 'mongodb://localhost:27017/';
rg
});
o
};
n.
io
MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => ac {
let db = cliente.db("OnlineSales");
m
productResearch(db, {"type": "phone"});
ra
cliente.close();
og
});
pr
do
'use strict';
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var url = 'mongodb://localhost:27017';
A continuación se muestra un ejemplo en el que un objeto hace un filtro sobre los teléfonos (tipo
phone) de marca Peach (se muestra un mensaje cuando termina la visualización de los documentos
seleccionados).
org
n.
var MongoClient = require('mongodb').MongoClient;
io
var assert = require('assert');
var url = 'mongodb://localhost:27017';
ac
m
ra
assert.equal(err, null);
do
if (documento != null)
to
console.log(prop+": "+documento[prop]);
w
else { callback(); }
w
console.log("\n");
w
});
};
MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {
let db = cliente.db("OnlineSales");
assert.equal(null, err);
ProductsResearch(db,
{"type": "phone", "brand": "Peach"},
() => {console.log("Fin del consulta"); });
cliente.close();
});
Por ejemplo, si quiere seleccionar los teléfonos de su tienda en línea que son más caros que la media
de todos los teléfonos que vende, pero no sabe realizar esta consulta en una única consulta. Va a
utilizar dos consultas.
org
{"type": "phone"}
n.
io
A continuación, almacene en una variable (llamada average) la media de los precios de los
ac
teléfonos que vende, y después coloque este resultado en el segundo objeto filtro, configurando la
m
ra
Un objeto compuesto:
El mensaje a mostrar una vez que la selección de los documentos está hecha.
El objeto filtro.
Después ejecute la consulta con el método find() del módulo mongodb, y vuelva a la función de
callback del método toArray():
org
"use strict";
n.
var MongoClient = require("mongodb").MongoClient;
io
var assert = require("assert"); ac
var url = "mongodb://localhost:27017";
m
ra
let db = cliente.db("OnlineSales");
assert.equal(null, err);
e to
.d
rg
Etapa 2 con 4 productos seleccionados
o
topPhone 8 32G: 900
n.
topPhone 8 64G: 1.000
io
topPhone 8 256G: 1.300 ac
Peach pro: 1300
m
ra
Esta primera solución de implementación es satisfactoria, pero si debe encadenar muchas consultas a
og
A continuación se muestra un ejemplo vertiginoso de lo que hemos llamado «el infierno de los
to
db.<nombreDeLaColeccion>.find(db,
w
<ObjetoFiltro>,
w
...
db.<nombreDeLaColeccion>.find(db,
ObjetoFiltro>,
(message, results) => {
...
db.<nombreDeLaColeccion>.find(db,
<ObjetoFiltro>,
(message, results) => {
...
db.<nombreDeLaColeccion>.find(db,
<ObjetoFiltro>,
(message, results) => {
...
Para instalar este módulo y referenciarlo en el archivo package.json, ejecute el siguiente comando:
rg
Entre los numerosos métodos que ofrece este módulo, hay dos que permiten sincronizar consultas:
o
n.
el método series();
io
el método waterfall(). ac
m
Los dos métodos permiten listar las funciones a encadenar, pero difieren respecto a la manera de
ra
...
w
async.series([
function (callback) { // primera función
callback();
},
function (callback) { // segunda función
callback();
},
...
],
function() { ... } // función final
);
Los datos a compartir entre las funciones, se deben declarar con antelación.
async.waterfall([
function (callback) { // primera función
callback(null, argumento1, ...);
},
function (argument1, ..., callback) { // segunda función
callback(null);
},
...
],
function() { ... } // función final
rg
);
o
n.
io
Los datos a compartir entre las funciones, se pasan como argumentos, es decir, cada función los
ac
transmite a la función siguiente, a través de la llamada a la función de callback. Una llamada
m
callback(true) detiene el encadenamiento de las funciones y llama a la función final si
ra
existe.
og
pr
c. El método async.series()
do
let db = cliente.db("OnlineSales");
w
assert.equal(null, err);
var average = 0;
async.series([
function (callback) {
productResearch(db, {"message":"Etapa 1",
filterObject": {"type":"phone"}},
(message, results) => {
console.log(message+": "
+results.length+" productos seleccionados");
if (results.length > 0) {
for (let documento of results) {
average += documento.price;
}
rg
}
o
n.
callback();
io
});
}],
ac
m
function () { db.close(); }
ra
);
og
});
pr
do
media = 780
w
d. El método async.waterfall()
A continuación se muestra la solución con el método async.waterfall():
...
var async = require("async");
rg
callback(null, average);
o
}
n.
io
else callback(true);
}); ac
},
m
function(average, callback) {
ra
productResearch(db,
og
{"message":"Etapa 2",
pr
console.log(message+": "+results.length
e
console.log(doc.name+": "+doc.price);
w
callback(null);
});
}],
function() { cliente.close(); }
);
});
org
1. La estructura de un servidor Node.js que consulta a
n.
io
MongoDB ac
m
Un servidor Node.js que consulta a MongoDB, implica las siguientes acciones:
ra
og
"use strict";
var express=require("express"); // Utilización del módulo express
var app=express(); // para crear una aplicación express
app.listan(8888); // que escucha en un puerto dado
var cors=require("cors"); // Utilización del módulo cors
app.use(cors()); // para permitir el cross-origin resource sharing
app.post(<route>,
rg
(req, res) => { // gestión de una ruta (método POST)
o
...
n.
io
});
ac
... // otras gestiones de rutas a través del método POST
m
ra
});
pr
do
to
El cross-origin resource sharing implica que un recurso solicitado por el cliente, se debe proporcionar
w
w
por un servidor que no es el que ha servido la página web que pide este recurso. Por defecto, un
mecanismo de seguridad impide al navegador acceder a este recurso.
No detallaremos esta problemática, pero para aumentar esta seguridad, le proponemos, a nivel del
servidor Node.js, implementar el módulo cors a través del código siguiente:
y también modificar el encabezado HTTP de las respuestas enviadas al cliente, con la instrucción
siguiente (res siendo el objeto «respuesta»):
res.setHeader('Access-Control-Allow-Origin', '*');
Los ejemplos relativos a las búsquedas aplicadas a la colección de productos a través de criterios de
búsqueda (excepto el identificador interno asignado por mongoDB a cada documento de la
colección).
org
el mensaje a mostrar al final de la selección;
n.
io
el objeto que especifica los criterios de búsqueda sobre la colección Products.
ac
m
y llama a la función de callback con:
ra
db.collection("Products").find(param["filterObject"])
w
app.get("/Products/brands",
(req, res) => {
productResearch(db,
{"message":"/Products/brands", "filterObject":{}},
(etapa, results) => {
console.log(etapa+": "+results.length
+" Productos seleccionados:");
var brands = [];
for (let doc of results)
rg
if (!brands.includes(doc.brand))
o
brands.push(doc.brand);
n.
brands.sort();
io
var json=JSON.stringify(brands); ac
console.log(json);
m
res.setHeader("Content-type",
ra
"application/json; charset=UTF-8");
og
res.end(json);
pr
});
do
});
to
});
e
.d
w
Por ejemplo, a continuación se muestra la gestión de la ruta que filtra los documentos de la colección
Products, una vez que la conexión a la base de datos se realiza con éxito. Cada argumento se
examina. Si su valor no es un asterisco (lo que significa que el criterio de búsqueda no se tiene en
cuenta), se convierte en el valor de una propiedad del objeto filtro filterObject.
A continuación, este objeto filtro se utiliza durante la consulta de la colección Products de la base
de datos MongoDB, a través de la función productResearch():
rg
(req,res) => {
o
var filterObject = {};
n.
if (req.params.type != "*") {
io
filterObject.type = req.params.type; } ac
if (req.params.brand != "*") {
m
filterObject.brand = req.params.brand; }
ra
if ( req.params.minprice != "*"
og
|| req.params.maxprice != "*") {
pr
filterObject.price = {};
do
if (req.params.minprice != "*")
to
filterObject.price.$gte =
e
parseInt(req.params.minprice);
.d
if (req.params.maxprice != "*")
w
filterObject.price.$lte =
w
w
parseInt(req.params.maxprice);
}
if (req.params.minpopularity != "*") {
filterObject.popularity
= {$gte: parseInt(req.params.minpopularity)};
}
productResearch(db, {"message":"/Products",
"filterObject": filterObject},
(etapa, results) => {
console.log(etapa+" con "+results.length
+" productos seleccionados:");
res.setHeader("Content-type",
"application/json; charset=UTF-8");
Este identificador se codifica en forma de una cadena de 24 caracteres que representa cada uno, un
rg
valor hexadecimal. La función ObjectId() proporcionada por el módulo mongodb, crea los 12
o
n.
bytes correspondientes.
io
Para utilizarla en este ejemplo, cree el siguiente acceso directo:
ac
m
ra
let id = req.params.id;
w
console.log("En /Product/id="+id);
w
if (/[0-9a-f]{24}/.test(id))
w
db.collection("Products")
.find({"_id": ObjectId(id)})
.toArray((err, documents) => {
let json = JSON.stringify({});
if ( documents !== undefined
&& documents[0] !== undefined )
json=JSON.stringify(documents[0]);
console.log(json);
res.end(json);
});
else res.end(JSON.stringify({}));
});
localhost:8888/Product/id=58e643742ba15f90d13cb87d
{"_id":"58e643742ba15f90d13cb87d",
"type":"phone",
"brand":"Peach"
"name":"topPhone 8 32G",
"popularity":4,
"price":900,
"picture":"topphone.jpeg",
"stock":5}
1. Creación de la colección
rg
El objetivo es crear una colección de prueba llamada Products, a partir del archivo Products.json
o
n.
siguiente:
io
[{
ac
m
"type":"phone", "brand":"Peach", "name":"topPhone 8 32G",
ra
"popularity":4, "price":900,
og
"picture":"topphone8.jpeg", "stock":5
pr
},
do
{
"type":"phone", "brand":"Peach", "name":"topPhone 8 64G",
to
"popularity":5, "price":1000,
e
.d
"picture":"topphone8.jpeg", "stock":20
w
},
w
{
w
rg
"type":"headset", "brand":"Earlid", "name":"Earlid Pro",
o
n.
"popularity":5, "price":300,
io
"picture":"earlidpro.jpeg", "stock":21
}, ac
m
{
ra
"popularity":4, "price":400,
"picture":"earlidstudio.jpeg", "stock":12
pr
},
do
{
to
"popularity":5, "price":100,
.d
"picture":"notendi3.jpeg", "stock":15
w
w
},
w
{
"type":"game console", "brand":"Notendi", "name":"4NTD",
"popularity":4, "price":150,
"picture":"notendi4.jpeg", "stock":5
},
{
"type":"game console", "brand":"Playgame", "name":"Playgame 5",
"popularity":5, "price":300,
"picture":"pg5.jpeg", "stock":15
},
{
"type":"cambiar", "brand":"Peach", "name":"Peachcambiar",
"popularity":3, "price":50,
"picture":"peachcambiar.jpeg", "stock":35
"use strict";
let express=require("express");
let cors=require("cors");
let app=express();
let assert = require("assert");
app.listan(8888);
app.use(cors());
});
org
app.get("/Products/criteria/:type/:brand/:minprice/:maxprice/
n.
:minpopularity",(req,res) => {
io
let filterObject = {}; ac
if (req.params.type != "*") {
m
filterObject.type = req.params.type; }
ra
if (req.params.brand != "*") {
og
filterObject.brand = req.params.brand; }
pr
filterObject.price = {};
if (req.params.minprice != "*")
to
filterObject.price.$gte = parseInt(req.params.minprice);
e
.d
if (req.params.maxprice != "*")
w
filterObject.price.$lte = parseInt(req.params.maxprice);
w
}
w
if (req.params.minpopularity != "*") {
filterObject.popularity
= {$gte: parseInt(req.params.minpopularity)};
}
productResearch(db,
{"message":"/Products", "filterObject": filterObject},
(step, results) => {
console.log(step+" con "+results.length
+" productos seleccionados:");
res.setHeader("Content-type",
"application/json; charset=UTF-8");
let json=JSON.stringify(results);
res.end(json);
app.get("/Product/id=:id",(req,res) => {
let id = req.params.id;
console.log("En /Product/id="+id);
if (/[0-9a-f]{24}/.test(id)) {
db.collection("Products")
.find({"_id": ObjectId(id)})
rg
.toArray((err, documents) => {
o
let json = JSON.stringify({});
n.
if ( documents !== undefined
io
&& documents[0] !== undefined ) { ac
json = JSON.stringify(documents[0]);
m
}
ra
res.end(json);
og
});
pr
}
do
else res.end(JSON.stringify({}));
to
});
e
.d
w
A continuación se muestra la ruta que invoca al servidor para solicitar todos los teléfonos:
localhost:8888/Products/phone/*/*/*/*
A continuación se muestra la ruta que invoca al servidor para solicitar todos los teléfonos, con un
precio inferior a 1.000 euros:
localhost:8888/Products/phone/*/*/1000/*
A continuación se muestra la ruta que invoca al servidor para acceder al documento de identificador
58e643742ba15f90d13cb87d:
http://localhost:8888/Product/id=58e643742ba15f90d13cb87d
rg
segundo es poder almacenar documentos heterogéneos, es decir, que no presentan necesariamente
o
entre ellos las mismas propiedades (los documentos son los objetos serializados JavaScript en un
n.
formato binario en las colecciones).
io
ac
Esta solución también es muy apreciada en el caso de una arquitectura MEAN, que implementa un
m
servidor Node.js y una aplicación Angular, ya que garantiza la homogeneidad de la representación de
ra
explicado cómo instalar MongoDB, insertar documentos en una colección y crear nuevas
Hemos
pr
especifica un objeto encargado de filtrar la colección destino (un determinado número de operadores
e to
de comparación, de conjunto y lógicos que se han revisado en esta ocasión), y permite a través de un
.d
del método distinct() que permite devolver solo los valores diferentes.
w
w
Finalmente se ha explicado la gestión de una base de datos MongoDB a partir de un servidor Node.js,
se ha realizado la consulta de datos finalmente invocando el servidor Node.js sobre las consultas http,
expresando las rutas soportadas por el framework Express.
Por otro lado, se ha hecho un apartado particular sobre la sincronización de las consultas, gracias al
módulo async y sobre los principales métodos que ofrece.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:18 p. m.]
Presentación de Angular
Angular es un framework aplicativo JavaScript desarrollado por Google, que permite crear
aplicaciones web mono página (es decir, completamente cargadas en el cliente antes de que el usuario
las utilice). Estas aplicaciones solo hacen peticiones al servidor para intercambiar los datos (y no para
cambiar las páginas web), a través de los servicios web. En el marco frecuente de una
rg
arquitectura MEAN, Angular está acoplado con un servidor Node.js (él mismo enriquecido con el
o
framework Express) y en unión con un sistema de gestión básico de datos NoSQL MongoDB.
n.
io
ac
1. Una evolución radical de AngularJS
m
ra
Angular se hizo público en marzo de 2017, y retoma grandes funcionalidades del framework
og
AngularJS creado en 2009 por Google, del cual es el heredero. Añade otras para crear un
Javascript
pr
framework mucho más potente. En el momento de redactar este libro, la versión actual de Angular es
do
Angular 8. A partir de la versión 4.3, Angular garantiza una compatibilidad ascendente del código
to
El desacoplamiento neto entre el lado cliente (por lo tanto, gestionado por Angular) y el lado
w
servidor (por ejemplo gestionado por Node.js en el marco de una arquitectura MEAN):
Angular permite crear aplicaciones mono página.
La extensión del lenguaje HTML con nuevas etiquetas y nuevos atributos, llamadas directivas:
los componentes Angular se pueden manipular como etiquetas.
La inyección de dependencias que permite a los servicios ser asignados y utilizados por
diferentes componentes (principalmente para dialogar con el servidor).
Un sistema de enrutado a través de un controlador llamado router.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:35 p. m.]
Nuevas directivas.
Un sistema de enrutado mucho más sofisticado y la posibilidad de controlar de manera más
fina, la activación de los componentes.
La utilización de extensiones en el lenguaje JavaScript, como TypeScript para programar
utilizando clases.
org
2. La modularidad de la aplicación: los módulos y los
n.
componentes
io
ac
m
La fuerza de una aplicación Angular es ser modular y esta modularidad se declina en dos niveles: los
ra
a. Los módulos
do
Angular utiliza un determinado número de módulos preexistentes, y crea otros nuevos que se pueden
.d
Una aplicación Angular siempre tiene un módulo principal, llamado root module, que alberga el
w
componente que juega el rol de «punto de entrada» en la aplicación (también puede contener otros
componentes). Los módulos adicionales creados en la aplicación que implementan grandes
funcionalidades, se llaman feature modules.
Las aplicaciones Angular utilizan los mismos módulos básicos que los de Node.js y los módulos
propios de Angular también se almacenan siguiendo las reglas introducidas por Node.js (por ejemplo,
un módulo por carpeta que forma un paquete). Es por esto que una aplicación Angular (en modo
desarrollo), busca los módulos que reutiliza en las subcarpetas de una carpeta llamada node_modules
(esta carpeta sería global en su sistema de archivos o local en la carpeta creada para su aplicación).
Los módulos propios de Angular, se almacenan en una subcarpeta llamada @angular
(<ruta>/node_modules/@angular). Evidentemente, esto puede crear cierta confusión, al decir que una
aplicación Angular es cualquier cosa que se describe como una aplicación Node.js: esto solo es
verdadero respecto a los módulos que utiliza.
rg
Por ejemplo, en el marco de El hilo rojo (la realización de una aplicación de e-commerce), va a crear
o
tres módulos.
n.
io
Dos módulos implementarán dos grandes funcionalidades que ofrecen cualquier sitio de e-commerce
que se precie (por lo tanto, son feature modules):
ac
m
Operar las búsquedas sobre los productos puestos a la venta (los seleccionados) y mostrarlos.
ra
og
El módulo principal supervisará todo y gestionará también la identificación de los internautas (es el
do
root module).
e to
También creará módulos «anexos», especificando las tablas de enrutado asociadas a los módulos
.d
mencionados anteriormente.
w
w
w
Hemos visto que «físicamente», un módulo está incorporado + por una carpeta. Excepto el
componente principal, los componentes también se implementan en carpetas específicas, incluso si
también se pueden «aplanar» en una única carpeta (pero esto no es del todo recomendable).
Piezas de software particulares, llamadas servicios, se pueden utilizar de manera transversal por
diferentes componentes. Los servicios se pueden considerar como componentes de herramientas que
La inyección de dependencias (ver el capítulo Angular y la conexión a Node.js: los servicios) permite
a los componentes no preocuparse de la instanciación de estas piezas de software (para determinar
qué componente se debe asignar inicialmente al servicio y en qué momento se debe hacer).
rg
través de una etiqueta cuyo nombre es el que se define por el selector del componente (propiedad
o
selector).
n.
io
Por ejemplo, si una aplicación tiene un componente llamado ResearchComponent que tiene
ac
una plantilla y cuyo selector se llama app-research, este componente se puede integrar en las
m
ra
<table>
.d
<tr>
w
w
...
</tr>
...
</table>
El intercambio de información en los dos sentidos es posible entre los dos componentes (lo que
integra en su plantilla otro componente y el que se muestra).
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:35 p. m.]
Angular respecto al framework MVC
(incluso VVM)
Angular (considerado aislado o de manera más natural, a través de su inmersión en una arquitectura
MEAN que lo asocia a un servidor Node.js), se inscribe en el paradigma de diseño modelo vista-
controlador (MVC). Este modelo garantiza el desacoplamiento de las interfaces de las operaciones,
org
principalmente poniendo en escena un controlador. Este controlador debe, a partir de una acción
n.
realizada sobre la vista, seleccionar la operación adecuada, operación que a su vez puede actualizar
io
los datos. Si los datos publicados se actualizan, el controlador debe alertar a la vista para que se
ac
refresque. En Angular, el controlador toma la forma de un enrutador, que vincula una acción a uno o
m
más componentes a activar, y la parte de la implementación de los componentes a cargo de la
ra
og
Este controlador permite también hacer evolucionar fácilmente una aplicación, que permite de una
do
manera sencilla la adición, eliminación o sustitución de una de sus partes constituyentes. En efecto, la
to
gestiona por parte del controlador que centraliza de esta manera los puntos de conexión entre los
.d
w
Por otro lado, este controlador también se puede utilizar para establecer una gestión de roles (para
autorizar una acción solo respecto al estado que tiene el usuario - administrador, cliente identificado,
visitante...).
A continuación se muestra el esquema que muestra las relaciones entre el modelo, la vista y el
controlador:
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:48 p. m.]
Encontramos las tres entidades que forman el modelo MVC en la arquitectura MEAN:
El modelo se forma por los datos que entrega el servidor Node.js de una base de datos
MongoDB o gestionados localmente por la aplicación, y las operaciones de negocio realizadas
sobre ellas.
rg
La vista de la aplicación se forma por diferentes «subvistas» asociadas a los componentes que
o
tienen una representación gráfica (una plantilla): por lo tanto, la aplicación es multivista de
n.
io
manera natural.
ac
El controlador principalmente toma la forma de un router, que permite invocar a los
m
componentes respecto a una solicitud de operación, realizada con anterioridad y especificado a
ra
través de una ruta (atención, este sistema de enrutado es interno a la aplicación Angular, no
og
hablamos aquí de las rutas entre la aplicación Angular y el servidor Node.js). Por otro lado, las
pr
operaciones internas a los componentes que permiten refrescar sus vistas, también son asunto
do
del controlador.
e to
Dicho esto, hay ciertas precisiones obligadas, ya que la arquitectura MEAN hace la lectura del
.d
La vista se corresponde con las plantillas. Las plantillas son las interfaces HTML asociadas a
w
los componentes, en los que algunos de sus atributos son visualizables y manipulables por el
internauta.
El controlador se implementa por un router que invoca a los componentes a través de las rutas
REST descritas en una o varias tablas de enrutado (según el número de módulos que forman la
aplicación).
El modelo se corresponde :
proporcionados por el servidor Node.js a los componentes que lo solicitan (el servidor
Node.js puede él mismo conectarse a un sistema de gestión básico de datos NoSQL,
como MongoDB);
La utilización del «two-way data binding» relaciona bidireccionalmente los datos entre las plantillas y
el modelo y acerca el modelo MVC al modelo MVVM (Modelo-Vista-Vista Modelo). Este
mecanismo solo es opcional en Angular, ya que implica costes bastante sustanciales.
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:48 p. m.]
Implementación de una aplicación
Angular
La implementación de una aplicación Angular se puede hacer de diferentes maneras:
rg
operaciones a realizar.
o
n.
Copiando el esqueleto de un proyecto existente, por ejemplo el quickstart de Google:
io
git clone https://github.com/angular/quickstart.git <nombreProjet>
ac
m
ra
Tampoco es aconsejable, porque las aplicaciones llamadas testigos como esta, son minimalistas y
og
será difícil entender cómo extenderlas.
pr
Usando una herramienta para iniciar un proyecto Angular: este es el camino que vamos a ver
do
Todos los módulos «básicos» de Node.js a instalar, lo son automáticamente durante la creación
w
w
de un nuevo proyecto.
El esqueleto de sus diferentes programas se crea a partir de numerosos comandos.
Las pruebas unitarias o de integración se pueden especificar y ejecutar.
Pasar de la versión de desarrollo a una versión de producción, es muy fácil de realizar.
Por lo tanto, todas las operaciones estructurales sobre las aplicaciones Angular descritas en este libro,
se realizarán a través de Angular CLI, que se puede considerar como un verdadero administrador de
proyectos Angular.
Una vez que se instala Angular CLI, ejecute sus acciones a través del comando ng, en la línea de
comandos.
org
2. Instalación de Angular CLI
n.
io
ac
La instalación Angular CLI se realiza por medio de los siguientes comandos:
m
ra
Globalmente:
og
Localmente:
e to
.d
(En Linux/Ubuntu, puede ser que tenga que pasar a modo administrador para utilizar el comando
anterior.)
Como se explicará en el capítulo Angular y la gestión de las rutas internas, las aplicaciones Angular
utilizan generalmente un router. Responder "sí" (y) a la pregunta "Would you like to add
Angular routing", permite hacer más legible la implementación de esta, creando un archivo
rg
separado para especificar la tabla de enrutado. Por otro lado, seleccione la opción por defecto de las
o
hojas de estilo CSS.
n.
io
ng new --routing <nombre del proyecto> ac
m
Cree el proyecto INTRODUCTION:
ra
og
ng new INTRODUCTION
pr
do
Una vez que termina la creación del proyecto (puede llevar cierto tiempo), pruebe la aplicación por
to
defecto asociada a cualquier nuevo proyecto (de hecho, un módulo que contiene un único
e
.d
componente que muestra el logo de Angular y los enlaces a su documentación en su navegador). Para
w
ng serve -o
Lanza un servidor local (el NG Live Developmente Server), que en submain llama al transpiler
TypeScript que transcodifica (transpila) en JavaScript todo el código TypeScript que crea o
modifica.
Ejecute la herramienta webpack, que relaciona el código JavaScript para formar diferentes
bundles (archivos que agrupan el código y los recursos y no se guardan en su disco, mientras
que la aplicación esté en modo desarrollo) y visualice su aplicación en la URL localhost:4200.
Estas dos operaciones se repiten tan pronto como modifique uno de sus códigos fuente. El código se
genera y la ventana de su navegador se actualiza, así que guarde sus archivos correctamente.
org
n.
io
ac
m
ra
og
pr
Ha creado una nueva aplicación Angular y como puede ver, se han generado muchas carpetas y
archivos. A continuación se muestran algunas explicaciones sobre estos (en negrita los archivos o las
carpetas más importantes):
rg
Una de las fortalezas de Angular respecto a su primera versión histórica (AngularJS), es basarse en la
o
utilización de componentes y a un nivel más global, en módulos.
n.
io
Nos interesamos por el contenido del directorio app, que contiene el código propio de la aplicación:
ac
m
introduction/src/app/app.component.ts: el componente principal de la aplicación.
ra
que lo compone).
.d
al módulo; esta solo se implementará si el router se utiliza (lo que no es el caso en esta primera
w
aplicación).
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
Un decorador (ver la sección Los decoradores) es una anotación que permite «in fine» a Angular,
generar el código JavaScript que tiene el comportamiento del que se especifica (aquí un componente).
Esta anotación toma la forma de una función que tiene como argumento un objeto de configuración.
Un selector que da el nombre a la etiqueta que hace de interfaz del componente en la página
rg
web index.html o en una plantilla de otro componente.
o
n.
Una plantilla externalizada (o descrita en el propio lugar «embedded», pero esto no se
io
recomienda), que define el código HTML asociado al selector. ac
Una o varias hojas de estilos.
m
ra
La clase declara un atributo llamado title, cuyo valor es el nombre del proyecto que ha creado.
og
el logo de Angular:
e to
.d
<div style="text-align:center">
w
<h1>
w
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,...">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener"
href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener"
href="https://angular.io/cli">CLI Documentation</a></h2>
</li>
<li>
Si solo queremos conservar de esta plantilla el código que muestra el nombre de nuestro proyecto, a
continuación se muestra lo que quedaría:
<h1>
{{title}}
</h1>
El selector declarado en la clase TypeScript se llama aquí app-root, lo que implica que la página
org
HTML de más alto nivel de la aplicación (src/index.html) incorpore esta plantilla (y por lo tanto, el
n.
del que es interfaz), utilizando las siguientes etiquetas:
io
ac
<app-root></app-root>
m
ra
Excepto para los componentes «puntos de entrada», Angular CLI llama a los selectores usando como
og
prefijo app y como sufijo, el nombre del componente: por ejemplo, si crea un componente llamado
pr
Por defecto, la clase es exportable, es decir que el componente se puede utilizar si el módulo en el que
w
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
rg
Como veremos más adelante, este módulo también podría declarar servicios (providers).
o
n.
Como los componentes, un módulo Angular se materializa también por una clase que tiene un
io
decorador: este es @NgModule(). Como sucede con el decorador @Component(),
ac
m
@NgModule() es una función que recibe como argumento un único objeto, cuyas principales
ra
declarations: declara todas las clases del módulo que se corresponde con los
pr
do
bootstrap: define el root component (el componente punto de entrada) cuya plantilla
w
w
Por otro lado, el archivo app-routing.module.ts especifica la tabla de enrutado utilizada por el router
de Angular si este está implementado, lo que no es el caso por el momento (este punto se aborda en el
capítulo Angular y la gestión de las rutas internas).
También puede actualizar todos los paquetes especificados en el archivo package.json, de la siguiente
manera:
ng update -all
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]
Generación del código JavaScript a partir
de TypeScript
Como hemos visto, el código de una aplicación Angular se escribe en TypeScript (y en HTML) y
utiliza módulos. Un transpiler (o transcompilador, pero este término es muy poco utilizado, también
nombrado de manera poco adecuada con el nombre de compilador), es necesario para traducir este
org
código en código JavaScript (y HTML), que posteriormente se agruparán después de ser minimizados
n.
(es decir empaquetados o «bundlelizados»), y finalmente enviados al navegador.
io
ac
A partir de la versión 8 de Angular, hay disponible una nueva cadena de producción (que incluye
m
varios transpilers) llamada Ivy, sustituyendo principalmente el transpiler original llamado tsc.
ra
En efecto, Ivy es mucho más eficaz que antes, ya que si su objetivo siempre es traducir en código
og
y HTML las clases TypeScript inherentes a los componentes, así como las plantillas
JavaScript
pr
asociadas, genera huellas de memoria más bajas (principalmente para mejorar la eficiencia de las
do
aplicaciones Angular sobre los aparatos móviles). También debe tenerse en cuenta que el código
to
generado por Ivy, va a crear un DOM que incrementa mucho más la eficacia, ya que vuelve a
e
.d
instanciar menos objetos en memoria del navegador. Por extensión del término, el DOM (Document
w
Object Model) es la jerarquía de los objetos instanciados en su navegador, para representar las
w
w
diferentes entidades a mostrar (en el marco de la implementación del lenguaje HTML, sus etiquetas,
la información textual, etc.).
En efecto, si las clases TypeScript de sus proyectos son compatibles con Ivy, deberá volver a
compilar los módulos que utilizan, esperando que sean compatibles de manera nativa: es el objetivo
del Angular Compatibility Compiler (ngcc).
En resumen, dos etapas se deben cruzar para construir aplicaciones Angular Ivy:
Ejecute el comando de Angular CLI que sirve de interfaz con ngcc: ivy-ngcc
org
n.
{
io
...
"angularCompilerOptions": {
ac
m
"enableIvy": true,
ra
"allowEmptyCodegenFiles": true
og
}
}
pr
do
Pida la presentación de su aplicación con la opción aot (para gestionar el lazy loading): ng
to
e
serve -o --aot
.d
w
w
w
rg
agrupan en un objeto y que definen el comportamiento de la clase. Estas funciones son muy
o
particulares: son las factorías las que producen «de segunda mano», las clases específicas de la
n.
naturaleza que le confiere el decorador.
io
ac
m
correspondiente será consumida por el transpiler tsc para producir código JavaScript
pr
Entre todos los decoradores propuestos por Angular, a continuación se muestran los siete más
.d
w
Otros cuatro decoradores tienen un impacto sobre las plantillas (ver la sección Las directivas de
atributos del capítulo Angular: las plantillas, bindings y directivas):
org
n.
1. Creación del componente
io
ac
m
Quiere crear un componente que muestre un mensaje, cuyo texto esté contenido en una variable
ra
Para
crear el componente, utilice el comando ng con la opción generate y el argumento
pr
do
component:
e to
ng g c Mycomponent
El efecto de este comando es crear una subcarpeta con el nombre del componente, en la carpeta app.
create src/app/Mycomponent/Mycomponent.component.css
create src/app/Mycomponent/Mycomponent.component.html
create src/app/Mycomponent/Mycomponent.component.spec.ts
create src/app/Mycomponent/Mycomponent.component.ts
update src/app/app.module.ts
Como ya ha comprobado, un componente tiene una triple faceta: la clase TypeScript que la
@Component({
selector: 'app-mycomponent',
templateUrl: './Mycomponent.component.html',
styleUrls: ['./Mycomponent.component.css']
})
export class MycomponentComponent implements OnInit {
constructor() {}
rg
ngOnInit() {}
o
}
n.
io
ac
Dos métodos aparecen en la clase: su constructor y el método ngOnInit(), que se llama durante
m
la implementación del componente.
ra
Modificamos este código, declarando una cadena de caracteres (un atributo de la clase
og
@Component({
e
selector: 'app-mycomponent',
.d
templateUrl: './Mycomponent.component.html',
w
w
styleUrls: ['./Mycomponent.component.css']
w
})
export class MycomponentComponent implements OnInit {
private message: string = "Un mensaje a mostrar";
constructor() {}
ngOnInit() {}
}
a. La plantilla HTML
Modificamos la plantilla HTML para que interpole la variable definida en la clase, entre las dos
b. La hoja de estilos
Para terminar, creamos la hoja de estilos para definir el tamaño de letra que muestra el mensaje:
rg
2. Interfaz de componentes en el componente raíz
o
n.
io
Ahora, nuestro nuevo componente se debe integrar en la plantilla del componente «punto de entrada»,
utilizando la etiqueta con el nombre del selector descrito en la clase TypeScript.
ac
m
Por lo tanto, modificamos el archivo app.component.htm para guardar solo el siguiente código:
ra
og
<h1>
pr
{{title}}
do
</h1>
to
<app-mycomponent></app-mycomponent>
e
.d
w
w
@NgModule({
declarations: [
AppComponent,
MycomponentComponent
],
imports: [
BrowserModule,
AppRoutingModule
rg
],
o
providers: [],
n.
io
bootstrap: [AppComponent]
}) ac
export class AppModule { }
m
ra
og
if (environment.production) { enableProdMode(); }
platformBrowserDynamic().bootstrapModule(AppModule);
.catch(err => console.error(err));
org
n.
La interfaz de su aplicación se refresca y a continuación, se muestra:
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
org
ngOnChanges(): este método se llamada durante la creación del componente, y después
n.
de cada cambio de valor de un atributo escalar decorado por @Input().
io
ac
ngOnInit(): este método se llama durante la creación del componente (justo después de
m
la primera llamada de ngOnChanges()).
ra
ngDoCheck(): este método se implementa para conocer los cambios de los valores
og
ngOnChanges().
do
to
ngOnDestroy(): este método se llama justo antes de que el componente sea designado.
e
.d
Para utilizar uno de estos hooks, es necesario importar la interfaz que implementa el método
w
correspondiente.
w
w
Por ejemplo, si quiere interceptar la inicialización del componente para ejecutar código durante su
creación, debe:
Existen otros cuatro hooks, pero dadas sus implementaciones raras, no se detallarán en este libro.
1. El hook ngOnChanges()
Este método no permite analizar los cambios de valores sobre las propiedades de un objeto o los
elementos de una lista: en estos casos, debe implementar el hook ngDoCheck().
org
export class <nombre del componente> implements OnChanges {
n.
io
...
} ac
m
ra
corresponden con los diferentes atributos para los que quiere trazar los valores cuando se modifican.
do
to
ngOnChanges(changes: SimpleChange})
.d
{
w
w
2. El hook ngOnInit()
El hook ngOnInit() se llama durante la inicialización del componente.
ngOnInit(): void {
rg
this.route.params.subscribe(params => { ... });
o
}
n.
io
params es un observable que crece con cada invocación del componente (ver el capítulo Angular y
ac
la gestión de las rutas internas). Permite acceder a los argumentos de la ruta que provoca, a través del
m
ra
3. El hook ngDoCheck()
do
to
Este método permite analizar los cambios de los valores internos de objetos o listas, cambios de
w
w
ngDoCheck() {
let changes
= this.<antiguo valor del atributo>.diff(<nueva valor>);
changes.forEachAddedItem(
ch => console.log('addition of: ' + ch.currentValue));
changes.forEachRemovedItem(
ch => console.log('remove of: ' + ch.currentValue));
}
}
rg
4. El hook ngOnDestroy()
o
n.
io
El hook ngOnDestroy() se llama justo antes de que se elimine la asignación del componente.
ac
A continuación se muestra un ejemplo en el que el hook se implementa para entregar el mensaje «Bye
m
bye».
ra
og
ngOnDestroy(): void {
console.log("Bye bye");
}
El primer punto fuerte, se centra en el hecho de que la parte cliente (la aplicación mono página
realizada con Angular) y la parte servidora, están totalmente desacopladas, el cliente y el servidor
rg
solo intercambian los datos (por lo tanto, en el marco de una arquitectura MEAN, el servidor asociado
o
n.
a Angular es un servidor Node.js, que gestiona las bases de datos MongoDB).
io
El segundo punto fuerte es la modularidad que este framework organiza a dos niveles: en el nivel de
ac
los módulos (que representan la aplicación - el root module -o la implementación de grandes
m
funcionalidades - los feature modules), y en el nivel de los componentes (que son las piezas de
ra
og
software). Cualquier feature module, se puede (¿debe?) diseñar para volver a importarse en otro
de otra aplicación.
módulo
pr
do
Angular (y su inmersión en el seno de una arquitectura MEAN), se ha situado respecto al prisma del
to
modelo MVC. Al acercarnos a este modelo, entendemos que Angular permite una muy buena
e
evolución estructural de una aplicación web, desacoplando las interfaces de las operaciones, y que
.d
w
Hemos detallado durante la creación de un nuevo componente, implementado por una clase
TypeScript. Esto nos ha permitido describir los decoradores, que permiten crear las funciones
JavaScript que encarnan las diferentes entidades de una aplicación Angular (módulos, componentes,
servicios, etc.).
Un componente atraviesa diferentes etapas durante su ciclo de vida y esta evolución puede ser
analizada, en particular para configurar un observable en la invocación del componente (ver el
capítulo Angular y la conexión a Node.js: los servicios), y para reaccionar a un cambio de valor de
uno de sus atributos.
Esta plantilla se puede codificar en el componente (lo calificaremos como plantilla insertada, en
inglés «embedded»), o externalizada en un archivo separado. Por supuesto, es realmente preferible
rg
crear plantillas externalizadas, que permiten una mejor organización y por lo tanto, una mejor
o
n.
legibilidad del código.
io
La plantilla de un componente se integra en la página web única de la aplicación, de dos maneras
ac
diferentes:
m
ra
De manera estática, en el lugar donde las etiquetas de apertura y cierre definidas por su
og
rutas internas).
w
w
Nos interesamos en primer lugar por la integración de las plantillas de manera estática. Suponga un
w
@Component({
selector: "app-mycomponent",
templateUrl: "Mycomponent.component.html",
styleUrls: [ "Mycomponent.component.css" ]
})
export class MycomponentComponent {
...
}
Por lo tanto, Angular extiende el lenguaje HTML con sus propias etiquetas y sus propios atributos.
Observe que es posible insertar datos (textos o etiquetas) entre las dos etiquetas del component
directive, pero que por defecto no se tienen en cuenta. Por ejemplo, si la plantilla del componente
Mycomponent es esta:
rg
<p> Soy la plantilla del componente Mycomponent </p>
o
n.
io
y la plantilla del componente que la integra es esta:
ac
m
<app-mycomponent>
ra
</app-mycomponent>
pr
do
final.
e
.d
La aplicación de e-commerce, El hilo rojo de este libro, se diseña con tres módulos (veremos su
integración más adelante):
rg
El componente «punto de entrada» AppComponent bootstrapado por el módulo raíz, de
o
selector app-root.
n.
io
El componente AuthComponent de selector app-auth, que gestiona la ac
autenticación del internauta.
m
ra
siguientes.
e
.d
Esta composición de las vistas (de las plantillas) de los diferentes componentes, se ilustra en la
rg
siguiente gráfica:
o
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
Ahora vamos a bosquejar las diferentes plantillas de estos componentes y examinar su integración,
que creará la vista final en el inspector del DOM de un navegador (por supuesto, la vista final será
mucho más compleja que la que se simulará aquí).
A continuación se muestra en primer lugar, un fragmento de la página HTML de más alto nivel
(src/index.html). Esta página integra el conjunto de las plantillas a partir de la directiva app-root
<!doctype html>
<html>
<head> ... </head>
<body>
<app-root> </app-root>
</body>
</html>
<table>
rg
<tr>
o
<td> <span class="title"> Ventas en línea / Online sales
n.
</span>
io
</td> ac
<td> <app-auth></app-auth> </td>
m
</tr>
ra
<tr>
og
</tr>
<tr>
to
<td colspan="2">
e
.d
<router-outlet name="display"></router-outlet>
w
</td>
w
</tr>
w
<tr> <-- Outlet por defecto para mostrar los mensajes -->
<td colspan="2"> <router-outlet></router-outlet> </td> </tr>
</table>
Y ahora examinamos las plantillas de los otros tres componentes del módulo (llamado research)
de selección.
rg
La plantilla del componente principal ProductselectionbykeywordsComponent, está
o
n.
contenida en el archivo product-selectionbykeywords.component.html:
io
<p> Buscar por palabra clave: </p>
ac
m
<input ...> </input>
ra
og
selectionbycriteria.component.html:
e to
...
w
w
w
Examinamos ahora las plantillas de los componentes del módulo que gestiona la cesta de la compra.
rg
A continuación se muestra el «resultado virtual» generado por Angular, una vez que todas las
o
plantillas se integren las unas en las otras:
n.
io
<!doctype html> ac
<html>
m
<head> ... </head>
ra
<body>
og
<table>
do
<tr>
to
</span>
.d
</td>
w
<td>
w
</td>
</tr>
<tr>
<td> <!-- "integración" de app-research -->
<p> Busque su producto </p>
<!-- "integración" de app-product-selectionbykeywords-->
<p> Buscar por palabra clave: </p>
<input ...> </input>
<!--integración de app-product-selectionbycriteria-->
<p> Criterios de búsqueda: </p>
...
</td>
<td> <!-- "integración" de app-cart -->
rg
</tr>
o
...
n.
io
</table>
</body> ac
</html>
m
ra
og
@Component({
selector: "app-mycomponent",
template: `
<cuerpo de la plantilla>
`,
styleUrls: [ "Mycomponent.component.css" ]
})
export class MycomponentComponent { ... }
org
@Component({
n.
selector: "app-mycomponent",
io
templateUrl: "app.Mycomponent.html", ac
styleUrls: [ "Mycomponent.component.css" ]
m
})
ra
De nuevo aquí, se pueden asociar a la plantilla cero, una o varias hojas de estilo.
do
to
Ahora vamos a explorar los enlaces entre los atributos de las clases TypeScript, que implementa los
e
rg
La interpolación: una variable escalar del componente se inyecta en la plantilla.
o
n.
El property binding: Angular tiene en cuenta un atributo de una etiqueta, que se corresponde
io
con un atributo existente de una etiqueta HTML o con un nuevo atributo.
ac
El event binding: un método del componente se llama a través de un evento soportado por
m
Angular.
ra
og
El two-way data binding: una variable del componente se relaciona de manera bidireccional a
una interfaz de entrada o de selección de la plantilla.
pr
do
to
Antes de cualquier cosa, veamos cómo Angular permite hacer referencia simplemente a los elementos
w
El DOM representa la jerarquía de los objetos creados por las etiquetas HTML, así como sus atributos
y la información textual que circunscribe (y otra información) en memoria del navegador. Los objetos
se corresponden con las etiquetas HTML se llaman elementos.
Angular ofrece la posibilidad de hacer aparecer en una etiqueta HTML, una referencia al elemento del
DOM con el que se corresponde. A continuación, esta referencia se podrá utilizar en el código. De
manera sintáctica, una referencia se prefija por una almohadilla.
Para ilustrar esto, a continuación se muestra la creación de una referencia llamada image asociada a la
imagen que se muestra en la plantilla (con Angular CLI, los recursos, entre ellos las imágenes, se
deben almacenar en la carpeta src/assets):
rg
import {Component} from "@angular/core";
o
n.
@Component({
io
selector: "app-root", ac
templateUrl: "app.component.html",
m
styleUrls: ['./app.component.css']
ra
})
og
}
e to
posibilidades:
og
O delegar a Angular la gestión de este atributo: esta gestión se llama property binding.
to
atributo.
.d
w
AppComponent.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private imageSrc: string = "assets/angular-js.png";
}
Por otro lado, en el caso de la implementación de una directiva de atributo, el valor de este atributo
(en este ejemplo imageSRC), se evalúa en JavaScript. Esto implica que:
org
JavaScript.
n.
Pero [src]="{{imageSrc}}" es incorrecta, ya que {{imageSrc}} no es
io
evaluable en JavaScript. ac
m
Si la plantilla se asocia a la siguiente hoja de estilos:
ra
og
esto produce el siguiente resultado (las dos imágenes son las que se corresponden con las dos sintaxis,
to
4. Event binding
Angular soporta diferentes listener de eventos, para permitirle llamar a un método del componente
con información diferente. Esta gestión se llama event binding.
org
Los dos listeners utilizados de manera más frecuente son:
n.
io
click: se podrían asociar a cualquier elemento pulsable. ac
keyup: asociado a una zona de entrada de datos (el evento se despacha después de cada
m
ra
El método llamado sobre el evento (por ejemplo, sobre un clic), puede tener como argumento al
pr
sobre él. imageRef es la referencia de la imagen en el DOM que se debe modificar (haciendo en
.d
w
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private srcImage: string = "assets/angular-js.png";
changeImage(imageRef:any) {
imageRef.src = "assets/Angular.png";
}
}
o
n.
cambio de la imagen con un clic (solos se muestran extractos de código).
io
La plantilla se puede codificar de esta manea (con el enlace «duro» de la primera imagen):
ac
m
<img src="assets/angular-js.png" (click)="changeImage($event)" />
ra
og
changeImage(event:any) {
to
event.target.src = "assets/Angular.png";
e
}
.d
w
w
w
El two-way data binding no está activo por defecto en Angular, mientras que lo está en AngularJS (a
A continuación se muestra un componente que hace eco del valor introducido en la plantilla (aquí
«embedded»):
@Component({
selector: 'app-root',
template: `
<input [(ngModel)]="value"/> → {{value}}
`
})
export class AppComponent {}
Después de introducir la palabra Pierre (la actualización se realiza tan pronto como se introduce o
elimina un carácter):
Globalmente hay dos tipos de directivas (el segundo tipo se puede dividir en subtipos):
org
Las directivas «componentes» (directivas de componentes), representadas por nuevas
n.
io
etiquetas, que insertan las vistas de los componentes en el flujo HTML: se fusionan con la
implementación de los componentes.
ac
m
Las directivas se dan en los atributos a una etiqueta y que condicionan su visualización (y más
ra
generalmente, su comportamiento).
og
pr
Las directivas dadas como atributos a una etiqueta, se declinan en diferentes facetas:
do
Las directivas estructurales (structural directives), que permiten mostrar cero, una o varias
to
Las otras directivas, llamadas globalmente directivas de atributo (attribute directives), que
w
permiten:
w
w
Establecer una operación particular asociada a la etiqueta, cuando esta se activa (por
ejemplo, la directiva [(ngModel)], que permite gestionar el two-way data binding, o
la directiva [routerLink], que permite invocar el controlador en la activación de un
ancla).
Antes de examinar estas directivas más en detalle, a continuación se muestra un pequeño aperitivo de
las diferentes sintaxis:
rg
*ngIf: controla la visualización de una etiqueta.
o
n.
io
a. La directiva *ngFor
ac
La directiva *ngFor insertada en una etiqueta HTML, permite (por defecto) producir esta etiqueta
m
ra
Supongamos un componente que dispone de una lista de objetos (que representa los productos a la
pr
En primer lugar, quiere simplemente mostrar estos productos y después enumerarlos y finalmente,
e to
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
rg
},
o
n.
{ "type":"phone",
io
"brand":"Peach",
"name":"topPhone 8 256G", ac
"popularity":4, "price":1300,
m
ra
"picture":"topphone.jpeg",
og
"stock":100
},
pr
{ "type": "phone",
do
"brand": "Konia",
to
"name": "4000",
e
"price": 200,
.d
"picture": "konia4000.jpeg",
w
w
"stock":0
w
}
];
}
Quiere mostrar en la plantilla una lista HTML, en la que cada ítem tiene como valor el de la
propiedad name de los objetos de esta colección.
A continuación se muestra cómo codificar esto con la directiva estructural *ngFor en la plantilla:
<ul>
<li *ngFor="let product of Products">{{product.name}}</li>
</ul>
org
n.
io
La directiva *ngFor produce automáticamente las variables que se pueden utilizar localmente:
ac
m
index: el iterador del bucle (inicializado a 0).
ra
<ul>
<li *ngFor="let product of Products; let idx = index">
{{idx+1}}: {{product.name}}
</li>
</ul>
rg
La directiva *ngIf insertada en una etiqueta, condiciona la visualización de esta.
o
n.
Por ejemplo, para que la plantilla anterior, que muestra una lista de nombres de productos a partir de
io
una colección llamada Products, muestra su indisponibilidad cuando su stock está agotado,
ac
modifíquelo como sigue:
m
ra
<ul>
og
<label>
do
{{product.name}}
to
</label>
e
(not available)
w
</label>
w
</li>
w
</ul>
org
<etiqueta *ngIf="...; else ..."> ... </etiqueta>
n.
io
ac
2. Las directivas de atributos
m
ra
og
Las directivas de atributos que permiten modificar la apariencia de un elemento, modificando sus
pr
atributos en el DOM o de manera más general, establecer una operación particular asociada a la
do
Va a crear un nuevo atributo que se puede aplicar a los elementos del DOM que muestran texto. Esta
w
(directiva) atributo llamado whiteOnBlack mostrará el texto en blanco sobre un fondo negro (en
w
«vídeo inverso»).
w
ng g d whiteOnBlack
Se crean dos archivos (el segundo es el archivo de especificación de las pruebas unitarias):
white-on-black.directive.ts;
La especificación del módulo se modifica automáticamente para declarar esta nueva directiva.
@Directive({
selector: '[whiteOnBlack]'
})
export class whiteOnBlackDirective {
constructor(el: ElementRef) { }
}
org
El decorador @Directive() permite a Angular generar el código JavaScript que implementa
n.
esta nueva directiva.
io
ac
Modifique este esqueleto para que las propiedades style.background Color (color en segundo plano) y
m
style.color (color del texto) del elemento implicado por la directiva, sean respectivamente negro y
ra
blanco:
og
pr
@Directive({
e
selector: '[whiteOnBlack]'
.d
})
w
constructor(el: ElementRef) {
w
el.nativeElement.style.backgroundColor = "black";
el.nativeElement.style.color = "white";
}
}
Esto le ofrece el código que permite acceder al DOM y por lo tanto, a las propiedades de
visualización del elemento (en este ejemplo, la etiqueta que gestiona un texto que se debe mostrar en
blanco sobre negro).
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
Ahora va a ir más lejos, configurando la directiva con un color en segundo plano particular: la
w
directiva llamada whiteOnAColor, aplicará al elemento al que afecte este color, a través de la
siguiente sintaxis, que utiliza los corchetes:
[nombreDeLaDirectiva] = "valorDeLaDirectiva"
De esta manera, en la siguiente plantilla, la etiqueta se mostrará en blanco sobre rojo y el botón en
blanco sobre azul.
Utilizará el decorador @Input() para asociar a la directiva un valor: este decorador permite
producir el código JavaScript necesario para el uso de este valor. En este ejemplo, este valor se
almacenará en la variable color.
Por otro lado, la modificación de los colores de primer y segundo plano, se realiza en el método
ngOnInit(), que se llama después de que el atributo color se haya definido.
A continuación se muestra el código:
org
import { Directive, ElementRef, Input } from '@angular/core';
n.
io
@Directive({ ac
selector: '[whiteOnAColor]'
m
})
ra
ngOnInit() {
w
w
this.el.nativeElement.style.backgroundColor = this.color;
w
this.el.nativeElement.style.color = "white";
}
}
n.
modificar el código de tal manera que el cambio de color del texto sea efectivo en el momento en el
io
que el internauta pulse en él (y que el internauta pueda eliminar este efecto en un nuevo clic).
ac
m
Para hacer esto, utilice el decorador @HostListener(), que permite asociar una operación a un
ra
evento.
og
pr
'@angular/core';
e
.d
w
@Directive({
w
selector: '[whiteOnAColorOnClick]'
w
})
export class WhiteOnAColorOnClickDirective {
private clicked: boolean = false;
@HostListener('click') onClick() {
if (this.clicked) {
this.el.nativeElement.style.backgroundColor = "white";
this.el.nativeElement.style.color = "black";
this.clicked = false;
}
else {
this.el.nativeElement.style.backgroundColor = this.color;
rg
integre en su plantilla con el decorador @Input, también es posible en el otro sentido transmitir un
o
valor de un componente «hijo» hacia su «padre». Esta transmisión se realiza emitiendo eventos. Para
n.
implementar este mecanismo, el decorador @Output() decora una función que emite eventos, que
io
se pueden captar por el componente de más alto nivel.
ac
m
A continuación, se muestra el esquema de programación que permite definir un emisor de eventos:
ra
og
@Loutput()
pr
Para ilustrar este punto, a continuación se muestra el código de una aplicación que implementa un
.d
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
time: number;
countChange(event) {
this.time = event;
}
rg
}
o
n.
io
A continuación se muestra la plantilla del componente AppComponent ac
(archivo app.component.html).
m
ra
El componente «cuenta atrás» admite como argumentos un atributo time, que ajusta el número de
og
segundos a descontar y un atributo tick, que especifica el listener de eventos que capta los eventos
pr
<div>
e
<app-countdown
w
[time]="10"
w
(tick)="countChange($event)">
w
</app-countdown>
</div>
Este componente recibe el número de segundos a descontar y cada segundo, emite un evento que
contiene el número de segundos restante a descontar.
Este descuento se implementa por el método interval(), que alimenta un observable de eventos
temporales. El método subscribe() permite suscribirse y el método unsubscribe()
eliminar la suscripción.
@Component({
selector: 'app-countdown',
templateUrl: './countdown.component.html'
})
export class CountdownComponent implements OnInit {
@Input()
time: number;
@Output()
tick: EventEmitter<number> = new EventEmitter<number>();
rg
timer: any;
o
n.
io
ngOnInit() {
this.timer = interval(this.time*100); ac
this.timer.subscribe(e => this.countdown());
m
}
ra
og
countdown() {
pr
console.log(this.time);
do
this.tick.emit(this.time);
to
if (this.time == 0) this.timer.unsubscribe();
e
this.time--;
.d
}
w
w
}
w
<datoDeEntrada> | <datoDeSalida>
rg
Solo hay algunos pipes ofrecidos por Angular, principalmente UpperCasePipe y
o
n.
LowerCasePipe.
io
ac
Por ejemplo, en una plantilla, puede transformar en mayúsculas todos los caracteres de la cadena
m
contenida en la variable interpolada nombre:
ra
og
{{nombre | UpperCase}}
pr
do
Pero los pipes propuestos por Angular (en el momento en el que se escribe este libro), son muy
to
escasos. También puede crear sus propios pipes (por ejemplo, un pipe muy útil que ordena los
e
Para hacer esto, utilice el comando de Angular CLI que produce el esqueleto de un pipe:
w
w
o su versión abreviada:
ng g p <nombreDelPipe>
A continuación se muestra el resultado de este comando, usando como nombre de pipe, sort:
@Pipe({
name: 'sort'
})
En este esqueleto, la transformación operada por el pipe se gestiona por el método transform(),
que recibe como argumentos:
org
Por lo tanto, quiere crear un pipe llamado sort, que ordene los elementos de una colección sobre
n.
io
cualquier propiedad de esta.
ac
Sea la colección Products, que contiene los siguientes objetos:
m
ra
[
og
{ "type":"phone", "brand":"Peach",
pr
"name":"topPhone 8 32G",
do
"popularity":4, "price":900,
to
"picture":"topphone.jpeg", "stock":80
e
},
.d
{ "type":"phone", "brand":"Threestars",
w
"name":"bigPhone 9",
w
"popularity":4, "price":700,
w
"picture":"bigPhone.jpeg", "stock":150
},
{ "type":"phone", "brand":"Peach",
"name":"topPhone 8 256G",
"popularity":4, "price":1300,
"picture":"topPhone.jpeg", "stock":100
},
{ "type": "phone", "brand": "Konia",
"name": "4000",
"price": 200,
"picture": "konia4000.jpeg", "stock":0
}
]
<ul>
<li *ngFor="let product of (Products | sort:'name')">
{{product.name}}
</li>
</ul>
Observe que:
Los paréntesis son necesarios para que en un primer momento, el pipe pueda transformar la
colección ordenándola y que, solamente en un segundo momento, el resultado se explote para
instanciar la variable product.
org
Los argumentos dados en el pipe, se separan por dos puntos (podría haber varios).
n.
La cadena de caracteres name se debe rodear por comillas.
io
ac
Los ítems de la lista HTML se deberían mostrar en este orden:
m
ra
<ul>
og
</ul>
.d
w
w
@Pipe({
name: 'sort'
})
export class SortPipe implements PipeTransform {
org
La colección (una tabla de objetos).
n.
io
El nombre de la propiedad de los objetos de esta colección que se utiliza como criterio de
ordenación.
ac
m
ra
Utiliza el método sort() de Array, que recibe como argumento su propia función de
og
comparación.
pr
A continuación se muestra un ejemplo de otro pipe, que será útil en la aplicación El hilo rojo de e-
do
commerce.
e to
Este pipe recibe como entrada una colección y devuelve, según el argumento que se le pasa, una
.d
Este pipe utiliza el método filter() de Array, que filtra los elementos de una tabla, según la
w
@Pipe({
name: "evenOdd"
})
export class EvenOddPipe implements PipeTransform {
transform(array:Array<Object>, rank:string):Array<Object> {
if (array === undefined || (rank !== "even" && rank !== "odd"))
return null;
A continuación se muestra la plantilla que combina los dos pipes vistos anteriormente, mostrando
solo los nombres de los productos pares en la colección Products, en la que los objetos se han
ordenado por su propiedad name:
<ul>
<li *ngFor="let product of (Products | sort:'name'
| evenOdd:'even')">
{{product.name}}
</li>
</ul>
rg
A continuación se muestran los archivos que componen este ejemplo:
o
n.
AUTH/src/app/app.component.ts (no presentado): el componente principal.
io
AUTH/src/app/app.component.html: la plantilla del componente principal. ac
m
AUTH/src/app/auth/auth.component.ts: el componente de autenticación.
ra
aplicación.
do
to
<app-auth></app-auth>
w
w
w
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css']
})
export class AuthComponent {
private login: string;
private pass: string;
private isLoggedIn: boolean = false;
onSubmit() {
// Sin verificación...
this.isLoggedIn = true;
rg
}
o
n.
io
logout() {
this.isLoggedIn = false; ac
}
m
}
ra
og
<div class="form-group">
e
.d
<input type="text"
w
class="form-control"
w
id="login" required
placeholder="email"
[(ngModel)]="login"
name="login" />
</div>
<div>
<label for="pass"> Password </label>
<input type="password"
class="form-control"
id="pass" required
placeholder="password"
[(ngModel)]="pass"
name="pass" />
</div>
rg
ng new OnlineSalesStep1
o
n.
io
Angular CLI crea un módulo que inicia («bootstrapping») un primer componente, que es el
ac
componente principal de la aplicación (en esta etapa).
m
ra
(ver la sección Anidación de las plantillas).
pr
componente principal.
.d
w
vista).
w
Ahora va a elaborar el componente que muestra la información de los productos a vender, contenidos
en una colección creada en un primer momento «en duro (hardware)» en el código (como verá en el
capítulo Angular y la conexión a Node.js: los servicios, cómo Angular puede acceder a una base de
datos MongoDB en la que se gestionará esta colección).
ng g c productDisplay
Ahora va a completar los tres primeros códigos (las pruebas unitarias solo se abordarán al final del
libro), y después va a integrar este componente en el componente principal del módulo único de la
aplicación Angular.
@Component({
selector: 'app-product-display',
templateUrl: './product-display.component.html',
styleUrls: ['./product-display.component.css']
})
export class ProductDisplayComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
Modifíquelo para crear «en duro (hardware)» una colección de algunos productos en venta:
rg
{"type":"phone", "brand":"Apple",
o
"name":"iPhone 7 32G",
n.
io
"popularity":4, "price":900,
"picture":"iphone7.jpeg", "stock":80 ac
},
m
{"type":"phone", "brand":"Apple",
ra
"name":"iPhone 7 256G",
og
"popularity":4, "price":1300,
pr
"picture":"iphone7.jpeg",
do
"stock":100
to
},
e
"name": "3310",
w
w
"price": 200,
w
"picture": "nokia3310.jpeg","stock":0
}];
constructor() {}
ngOnInit() {}
}
b. La plantilla HTML
El esqueleto de la plantilla gestionada por Angular CLI, es esta:
<p>
product-display works!
</p>
<p>
Products offered for sale:
</p>
<table>
<tr>
<td>
<ul>
<li *ngFor="let product of (Products | evenOdd:'even')">
<label *ngIf="product.stock > 0" class="available">
rg
{{product.name}},
o
</label>
n.
<label *ngIf="product.stock == 0">
io
{{product.name}} (not available),
</label>
ac
m
brand: {{product.brand}},
ra
price: {{product.price}}
og
</li>
pr
</ul>
do
</td>
<td>
to
<ul>
e
.d
{{product.name}}, </label>
w
Cree con Angular CLI el esqueleto del pipe que filtra una colección sobre el rango par o impar de sus
elementos:
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]
ng g p evenOdd
@Pipe({
name: 'evenOdd'
})
export class EvenOddPipe implements PipeTransform {
transform(array:Array<Object>, rank:string):Array<Object> {
if (array === undefined || (rank !== "even" && rank !== "odd")) {
return null;
rg
}
o
n.
if (rank == "even")
io
return array.filter(function(item, index) {
return índices % 2 === 0; });
ac
m
else
ra
}
do
to
Para terminar, integre esta plantilla en la del componente principal de su único módulo:
e
.d
w
<app-product-display></app-product-display>
w
c. La hoja de estilos
Cree la hoja de estilos (inicialmente vacía):
* { font-size: 18pt; }
p { font-style: italic; font-weight: bold; }
table { width: 100%; }
tr { width: 100%; }
td { width: 50%; padding: 10px; }
label.available { font-weight: bold; color: red; }
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]
2. El módulo que especifica el componente
Angular CLI ha actualizado el módulo src/app/app.module.ts por medio de la creación del
componente ProductDisplayComponent y del pipe EvenOddPipe:
rg
@NgModule({
o
declarations: [
n.
AppComponent,
io
ProductDisplayComponent, ac
EvenOddPipe
m
],
ra
imports: [
og
BrowserModule,
pr
FormsModule,
do
],
to
providers: [],
e
bootstrap: [AppComponent]
.d
})
w
import { platformBrowserDynamic }
from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environmente } from './environments/environment';
import { AppModule } from './app/app.module';
if (environment.production) {
platformBrowserDynamic().bootstrapModule(AppModule);
<!doctype html>
<html>
<head>
<meta charset="utf-8">
rg
<title>OnlineSalesStep1</title>
o
<base href="/">
n.
io
<meta name="viewport" ac
content="width=device-width, initial-scale=1">
m
<link rel="icon" type="image/x-icon" href="favicon.ico">
ra
</head>
og
<body>
pr
<app-root>Loading...</app-root>
do
</body>
to
</html>
e
.d
w
w
5. Lanzamiento de la aplicación
w
Lance ahora la transformación de su código TypeScript en código JavaScript y la creación del bundle,
a través de Webpack. Para esto, ejecute el comando Angular CLI siguiente:
ng serve -o
Una pestaña se abre en su navegador. Si no es el caso, escriba la URL localhost:4200 en una nueva
pestaña.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]
Conocimientos adquiridos en este capítulo
En este capítulo, hemos estudiado la construcción de las vistas de los componentes, es decir las
plantillas. En primer lugar, hemos visto cómo estás plantillas se integran las unas en los otras, a partir
de la página HTML de más alto nivel src/index.html, para formar la única página de la aplicación
mono página.
rg
Después hemos descrito el mecanismo que permite relacionar un atributo o un método de la clase
o
n.
TypeScript, que implementa un componente en su plantilla. Por lo tanto, este enlace (este «binding»)
io
relaciona la vista (la plantilla) con el modelo (la implementación en una clase TypeScript de las
operaciones de negocio del componente).
ac
m
Hemos declinado este data binding según diferentes facetas:
ra
og
etiqueta o para gestionar un nuevo atributo) por Angular (el property binding).
to
En relación con el data binding, hemos visto cómo extender el lenguaje HTML gracias a:
Nuevas etiquetas que se corresponden con las plantillas de los componentes (las directivas de
componentes).
Nuevos atributos que se corresponde con:
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:38 p. m.]
Se ha estudiado la transferencia de información entre dos componentes, donde uno integra al otro en
su plantilla, y ha permitido entender mejor la misión de los decoradores @Input() y
@Output().
Para terminar, hemos analizado la creación de pipes (filtros de datos que se pueden encadenar),
insertables en las plantillas y que permiten transformar y devolver el contenido de una variable
(escalar o compuesta, como una colección).
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:38 p. m.]
Introducción
Dentro de una arquitectura MEAN, el servidor Node.js solo intercambia datos con la aplicación
cliente (esto permite al servidor ser particularmente eficaz). A nivel del servidor, el intercambio de
datos se organiza por los servicios web, que se invocan en las rutas REST. Estos servicios web hacen
consultas a una base de datos MongoDB, a través del módulo mongodb, y devuelven datos
rg
generalmente formateados en JSON.
o
n.
Dentro de la aplicación Angular, la conexión al servidor Node.js y por lo tanto, a los servicios web
io
que este gestiona, se delega a los servicios (declarados en los módulos como providers). Un servicio
ac
es un componente de herramienta, sin plantilla, que se puede compartir por varios componentes.
m
Angular ofrece servicios de «bajo nivel», como el servicio HTTP proporcionado por el módulo HTTP
ra
(representado por una carpeta) cuyos métodos de la clase HTTP permiten invocar a los servicios web
og
De esta manera, una aplicación Angular utiliza no solamente los servicios ya proporcionados, sino
que también crea los suyos propios. Esto es lo que vamos a ver en este capítulo, principalmente a
e to
También introduciremos muy brevemente la librería NgRx, que permite centralizar todos los datos
w
que definen el estado de una aplicación Angular en un administrador (un «store») único.
w
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:54 p. m.]
Inyección de dependencias
En Angular, la inyección de dependencias permite inyectar una misma clase en los constructores de
diferentes componentes, sin preocuparse de determinar en qué momento se deberá instanciar esta
clase. Este potente mecanismo se implementa por ejemplo, para utilizar los servicios.
Los servicios Angular son código compartido por diferentes componentes, entre otros para
rg
proporcionar a estos los mecanismos de acceso a los datos (principalmente aquellos entregados por el
o
n.
servidor o que actualizan el servidor a través del envío de datos).
io
Por lo tanto, en el contexto de una arquitectura MEAN, son los intermediarios imprescindibles entre
ac
los componentes de Angular y los servicios web gestionados por el servidor Node.js.
m
ra
En Angular, un servicio es una clase inyectada en los componentes que lo necesitan. Las clases que
og
@Injectable()
w
...
w
Un servicio se puede explotar en un componente (o en otro servicio), declarando una variable (tipada
con el nombre de la clase definida por el servicio), como argumento del constructor de este
componente (o del servicio).
this.<servicio>.<método>
Durante la especificación del módulo, el servicio generalmente se declara como valor de la propiedad
providers, lo que lo define como un singleton, es decir, un objeto que solo se instanciará una
única vez.
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [ <declaración del servicio> ],
bootstrap: [ ... ]
rg
})
o
n.
export class AppModule { }
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:06 p. m.]
Utilización de los servicios para la
transferencia de datos
De manera más frecuente, los servicios Angular se utilizan para gestionar las transferencias de datos
hacia el servidor (esto no es su única utilidad, por ejemplo se pueden simplemente implementar para
compartir variables entre los componentes).
org
n.
1. Recuperación de datos formateados en JSON
io
ac
m
Un uso muy frecuente de los servicios Angular, es la gestión de los datos con el servidor Node.js
ra
(tanto para acceder a los datos del servidor, como para enviarle datos).
og
pr
rojo, que utiliza el servicio HTTP propuesto por Angular, e implementa un observable para recuperar
to
@Injectable()
export class ResearchService {
constructor(private http: HttpClient) {}
Integrándolos en la ruta.
rg
Integrándolos en el cuerpo (body) del mensaje HTTP.
o
n.
Evidentemente, es más coherente elegir la segunda solución que detallaremos a continuación,
io
tomando como ejemplo el envío de datos a través del método POST y la utilización del servicio
ac
HttpClient, proporcionado por el módulo HTTP de Angular.
m
ra
HttpHeaders(), también se importa del módulo HTTP (que está representado por una carpeta).
to
Esta es la función que permite modificar los encabezados del mensaje HTTP con destino el servidor.
e
.d
w
Para enviar datos, puede utilizar el método post() del servicio HttpClient, que acepta tres
argumentos:
La URL formada por el dominio (el host) sobre el que el servidor responde y la ruta (el path)
seleccionado.
Los datos del cuerpo del mensaje.
Los encabezados que deben obligatoriamente indicar al servidor que el mensaje contiene los
datos formateados en JSON.
org
Si el servidor devuelve un resultado (en el siguiente ejemplo, también formateado en JSON), se debe
n.
acoger en un observable. Entonces, la última instrucción del ejemplo anterior se sustituiría por esto:
io
ac
let observable: Observable<any>
m
= this.http
ra
Query string (si el método HTTP es GET, ya que con POST puede pasar estos datos en el cuerpo del
w
w
mensaje).
let url
= "htt:p//localhost:8888/Products/keywords?phone&64G";
return this.http.get(url);
= this.http
.get(url);
A nivel del servidor web, la Query string es accesible gracias a la propiedad query del objeto que
representa la consulta:
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:18 p. m.]
Implementación de los servicios en El hilo
rojo
La aplicación de e-commerce utiliza dos servicios:
rg
productos.
o
n.
El servicio CartService (archivo cart/cart.service.ts) para la gestión de las cestas de la
io
compra. ac
m
Estos dos servicios se van a utilizar en el servicio HttpClient proporcionado por Angular y que
ra
permite enviar consultas HTTP a un servidor HTTP (en este caso, el servidor Node.js).
og
A
continuación, vamos a listar los servicios web gestionados por el servidor Node.js, y después
pr
declinar cada operación de la aplicación que implementa un servicio, presentando el componente que
do
implementa una funcionalidad particular, el servicio que utiliza como intermediario entre él y el
to
Los servicios de Angular invocan a los servicios web gestionados por el servidor Node.js.
Estos servicios web que permiten, por un lado la selección de los productos y por otro, la gestión de
la cesta de la compra.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]
A continuación se muestra la lista de las acciones relacionadas con la selección de los productos que
implican la invocación de un servicio web, a partir del servicio ResearchService:
Recuperación de todos los selectores (valores diferentes de las propiedades de los productos),
que permiten construir listas desplegables de búsqueda:
método HTTP: GET
ruta genérica: /Products/selectors
Recuperación de todos los productos que se corresponden con los criterios de búsqueda:
método HTTP: GET
ruta genérica: Products/criteria/:type/:brand/:minprice/:maxprice/:minpopularity
A continuación se muestra la lista de los servicios web invocados a partir del servicio
CartService, que permite la gestión de la cesta de la compra.
En todas las acciones descritas a continuación, el cliente se identifica por su dirección e-mail, que es
la que se pasa como argumento a la ruta.
Los valores diferentes de las propiedades que determinan los productos, se deben copiar a la
colección selectors que se utiliza en la plantilla del componente.
...
export class ProductselectionbycriteriaComponent implements OnInit {
private selectors: Object[];
ngOnInit() {
this.research
.getProducts("selectors")
.subscribe(res => this.selectors = res);
}
}
rg
(selectors) => {
o
distinctValuesResearch(db, selectors, "popularity",
n.
io
(selectors) => {
let json=JSON.stringify(selectors); ac
res.setHeader("Content-type",
m
"application/json; charset=UTF-8");
ra
res.end(json);
og
});
pr
});
do
});
to
});
e
});
.d
w
w
como argumento un nuevo objeto que contiene todos los diferentes valores de la propiedad actual,
buscada en los documentos de la colección Products de la base de datos MongoDB. Para la
propiedad price, se crean intervalos numéricos de precios.
[
{"name": "type",
"values": ["cambiar", "computer", "game console",
"headset","phone","tablet"]}
[{"name": "type",
"values": ["cambiar","computer","game console",
"headset","phone","tablet"]}]
{"name": "brand",
"values": ["Earlid","Konia","Notendi",
"Peach","Playgame","Threestars","Vale"]}
]
rg
y property vale price.
o
n.
A continuación se muestra el código de la función que construye los diferentes valores de las
io
propiedades que determinan los productos: ac
m
function distinctValuesResearch(db, selectors, property, callback) {
ra
db.collection("Products")
og
.distinct(property,
pr
if (err)
selectors.push({"name": property, "values": []});
to
else {
e
.d
if (property == "price") {
w
En este código, se hace una operación particular para presentar los intervalos de precio.
rg
implementa en el componente ProductDisplayComponent. Este componente utiliza el
o
servicio ResearchService, que enlaza la aplicación Angular y el servidor Node.js para todo lo
n.
io
que tiene que ver con la búsqueda de productos. En el capítulo Angular y la gestión de las rutas
internas, veremos sobre qué rutas se invoca este componente.
ac
m
ra
og
pr
do
e to
.d
w
w
w
...
export class ProductDisplayComponent implements OnInit {
private Products: Object[]; // La lista de productos a mostrar
private subscribe: any;
ngOnInit() {
this.route.params.subscribe((params: Params) => {
let subroute = "";
if (params["type"] !== undefined) {
// Caso de una búsqueda sobre criterios
subroute
= "criteria/"+params['type']
+"/"+params['brand']
+"/"+params['minprice']
+"/"+params['maxprice']
+"/"+params['minpopularity'];
}
rg
else {
o
... // Caso de una búsqueda con palabras clave,
n.
io
// ver la siguiente sección
} ac
m
this.research
ra
.getProducts(subroute)
og
});
do
}
to
}
e
.d
w
productos.
w
La palabra Products que indica que los datos por los que se pregunta son productos.
La palabra criteria que indica que esta consulta se realizada a través de los criterios de
búsqueda (tipo, marca...).
Y para terminar estos criterios.
A continuación se muestra el servicio web gestionado por Node.js. Los argumentos de la ruta (que se
corresponde con los diferentes criterios de búsqueda), se analizan para crear al objeto que filtra la
colección MongoDB (llamado filterObject en el código). Este objeto se pasa como
argumento a la función productResearch(), que pregunta por la colección MongoDB que
almacena los productos y devuelve los documentos que se corresponden con la búsqueda.
app.get("/Products/criteria/:type/:brand/:minprice/:maxprice/
:minpopularity",
rg
(req,res) => {
o
let filterObject = {};
n.
io
if (req.params.type != "*") {
filterObject.type = req.params.type; } ac
if (req.params.brand != "*") {
m
filterObject.brand = req.params.brand; }
ra
if ( req.params.minprice != "*"
og
|| req.params.maxprice != "*") {
pr
filterObject.price = {};
do
if (req.params.minprice != "*")
to
filterObject.price.$gte
e
= parseInt(req.params.minprice);
.d
if (req.params.maxprice != "*")
w
w
filterObject.price.$lte
w
= parseInt(req.params.maxprice);
}
if (req.params.minpopularity != "*") {
filterObject.popularity
= {$gte: parseInt(req.params.minpopularity)};
}
productResearch(db,
{"message":"/Products",
"filterObject": filterObject},
(step, results) => {
res.setHeader("Content-type",
"application/json; charset=UTF-8");
let json=JSON.stringify(results);
org
n.
io
ac
m
ra
og
pr
do
e to
.d
búsqueda realizada sobre los criterios de búsqueda o sobre una lista de términos libremente elegidos
w
w
por el internauta. Aquí, se trata del segundo caso: los términos separados por los espacios que forman
una Query string. Por ejemplo, si el internauta ha introducido los dos términos phone y 64G, se crea
la siguiente Query string: ?phone&64G.
...
ngOnInit() {
this.route.params.subscribe((params: Params) => {
let subroute = "";
if (params["type"] !== undefined) {
// Caso de una búsqueda sobre criterios
// (ver la sección anterior)
...
}
else {
org
La palabra Products, que indica que los datos por lo que se pregunta son documentos de la
n.
colección Products de la base de datos de MongoDB.
io
ac
La palabra keywords, que indica que esta consulta se realizada respecto a una lista de
m
términos.
ra
Por ejemplo, si los términos son phone y 64G, la ruta es: http://localhost:8888/Products/keywords?
do
phone&64G
to
return this.http.get(url);
= this.http
.get(url)
}
Y a continuación se muestra el servicio web gestionado por Node.js. Las palabras clave se buscan a
través de las expresiones regulares en los valores de las propiedades de todos los documentos de la
colección Products, devueltos por Node.js (excepto en la propiedad interna _id). Para que un
documento sea devuelto por el servidor, es necesario que todos los términos de la búsqueda se
encuentren como valores en las propiedades de este.
rg
if ( !found ) match = false;
o
n.
}
io
if ( match ) { results.push(product); }
}); ac
res.setHeader("Content-type",
m
ra
"application/json; charset=UTF-8");
og
});
do
});
e to
.d
find({}, {"_id":0})
org
n.
A continuación se muestra el servicio web gestionado por Node.js, que reacciona sobre las rutas de
io
selección de un producto, por su identificador:
ac
m
app.get("/Product/id=:id",(req,res) => {
ra
let id = req.params.id;
og
db.collection("Products")
do
.find({"_id": ObjectId(id)})
to
json = JSON.stringify(documents[0]);
w
});
}
res.end(json);
});
Una expresión regular comprueba que el argumento de la ruta que se debe corresponder con un
identificador de un documento MongoDB, está formado con 24 caracteres que representan cada uno
un valor hexadecimal.
A continuación se muestra el servicio web gestionado por Node.js, que reacciona sobre la selección
de una ruta que solicita el acceso a los identificadores de los productos de la cesta de la compra de un
rg
cliente:
o
n.
io
app.get("/CartProducts/productIds/email=:email",
(req,res) => { ac
let email = req.params.email;
m
ra
db.collection("Carts")
og
.find({"email":email})
.toArray((err, documents) => {
pr
res.setHeader("Content-type",
.d
"application/json; charset=UTF-8");
w
w
let json=JSON.stringify(order);
w
res.end(json);
}
});
});
...
export class CartDisplayComponent implements OnInit {
rg
}
o
...
n.
io
}
ac
m
Cada vez que se invoca el componente CartDisplayComponent, se llama al método
ra
"http://localhost:8888/CartProducts/"+parameters;
.d
return this.http.get(url);
w
}
w
w
A continuación se muestra el servicio web gestionado por Node.js, que reacciona sobre la selección
de una ruta que solicita el acceso a los productos de la cesta de la compra de un cliente:
app.get("/CartProducts/Products/e-mail=:e-mail",(req,res) => {
let e-mail = req.params.e-mail;
let pipeline = [
{ $match: {"e-mail": e-mail}},
{ $unwind: "$order" },
{ $lookup: { from: "Products",
localField: "order",
foreignField: "_id",
as: "product" }},
{ $unwind: "$product" },
rg
ProductsInI[product._id].nb++;
o
else
n.
io
ProductsInI[product._id]
= {"_id": product._id, ac
"type": product.type,
m
"brand": product.brand,
ra
"name": product.name,
og
"popularity": product.popularity,
pr
"price": product.price,
do
"nb": 1};
to
}
e
productList.push(ProductsInI[productId]);
w
}
json=JSON.stringify(productList);
}
else json=JSON.stringify([]);
res.setHeader("Content-type",
"application/json; charset=UTF-8");
res.end(json);
});
});
org
CartManagementComponent. Este componente utiliza el servicio CartService, que
n.
hace el enlace entre la aplicación Angular y el servidor Node.js, para todo lo que respecta a las cestas
io
de la compra (visualización, gestión, validación, etc.). ac
m
El método cartProductManagement() se utiliza para añadir (o eliminar) un producto en la
ra
Como hemos visto anteriormente, la implementación del servicio pasa por su inyección en el
w
...
export class CartManagementComponent implements OnInit {
constructor( private cart: CartService, ... ) {}
ngOnInit() {
this.route.params.subscribe(params => {
this.cartProductManagement(params["action"],
params["id"]);
});
}
cartProductManagement(action, productId) {
Este método invoca dos métodos de la clase CartService, que implementa el servicio:
Por otro lado, la ruta /cart/display/<incremento>, se emite para mostrar la cesta de la compra. El
incremento que acompaña a la ruta, le permite reactivarlo en caso de adiciones sucesivas del mismo
rg
producto a la cesta de la compra (ver el capítulo Angular y la gestión de las rutas internas).
o
n.
A continuación, se muestra el código completo del método cartProductManagement():
io
cartProductManagement(action, productId) {
ac
m
// Visualización de un mensaje en la interfaz de la cesta de la compra
ra
this.cart.getProductById(productId)
do
// y visualización de esta
w
.subscribe(res => {
w
this.router.navigate(['/cart',
{outlets:{'cartDisplay': ['display',
this.numAction]}}
]);
this.numAction++; });
}
modifyCart(action: string,
productId: string,
email: string): Observable<any> {
const httpOpciones = {
org
n.
Y a continuación se muestra el servicio web gestionado por Node.js que se invoca al otro extremo de
io
la cadena: ac
m
app.post("/CartProducts", (req,res) => {
ra
db.collection("Carts")
do
.find({"email":email})
.toArray((err, documents) => {
to
let json;
e
.d
order.push(ObjectId(productId));
db.collection("Carts")
.update({"email":email},
{$set: {"order": order}});
json = JSON.stringify(order);
}
else json = JSON.stringify([]);
res.setHeader("Content-type",
"application/json; charset=UTF-8");
res.end(json);
});
});
cartProductManagement(action, productId) {
// Visualización de un mensaje en la interfaz de la cesta de la compra
if (action == "add") this.action = "Adición";
if (action == "remove") this.action = "Eliminación";
rg
this.cart.getProductById(productId)
o
.subscribe(res => this.product = res);
n.
io
// Modificación de la cesta de la compra almacenada en el servidor ac
// y visualización de esta
m
this.cart.modifyCart(action, productId, this.email)
ra
this.numAction]}}]);
do
this.numAction++;
to
});
e
}
.d
w
app.delete("/CartProducts/productId=:productId/e-mail=:email",
(req,res) => {
let email = req.params.e-mail;
let productId = req.params.productId;
db.collection("Carts").find({"email":email}).toArray((err,
documents) => {
let json = [];
if ( documents !== undefined && documents[0] !== undefined ) {
rg
let posición = order.map(function(e) {return
o
n.
e.toString();}).índicesOf(productId);
io
if (posición != -1) {
order.splice(position, 1);
ac
m
db.collection("Carts")
ra
json = order;
}
pr
}
do
res.setHeader("Content-type",
to
"application/json; charset=UTF-8");
e
.d
res.end(JSON.stringify(json));
w
});
w
});
w
La lista de los identificadores de los productos se copia en la lista local order: let
order = documents[0].order.
En esta lista, los objetos identificadores se sustituyen por cadenas de caracteres:
order.map(function(e) { return e.toString(); })
Se encuentra la posición del identificador buscado: indexOf(productId)
Y este identificador se elimina a través de esta posición: order.splice(position,
1);
CartReset() {
this.cart
.cartReset(this.email)
.subscribe(res => this.router.navigate(['/cart',
{outlets:{'cartDisplay': ['display']}}]));
rg
"http://localhost:8888/Cart/reset/email="+email;
o
n.
return this.http.get(url);
io
}
ac
m
A continuación se muestra el servicio web gestionado por Node.js:
ra
og
app.get("/Cart/reset/e-mail=:email",
pr
(req,res) => {
do
res.setHeader("Content-type",
w
"application/json; charset=UTF-8");
w
});
rg
Este objeto se encapsula en una clase llamada el store que es la única que permite acceder a él y
o
n.
puede modificarla a través de las acciones tipadas. El acceso a las propiedades del store, se realiza por
io
los selectores que toman la forme de observables (como los que hemos visto descritos anteriormente
ac
en este libro, durante la presentación de la programación reactiva con RxJs) e informan de esta
m
manera a todos los componentes que se suscriben a un cambio de estado de la aplicación.
ra
og
implementados, pero solamente como interfaz del store. También es importante observar que cada
do
feature module de la aplicación Angular, se puede asociar de manera independiente a una propiedad
e to
del objeto gestionado por el store y de esta manera, gestionar una parte del estado de la aplicación.
.d
w
NgRx implementa una simplificación del pattern de diseño Redux, que es similar a una simplificación
w
La utilización de NgRx sobrepasa los objetivos de nuestro libro, pero podría interesar al desarrollador
que quisiera centralizar todos los datos que definen el estado de una aplicación Angular,
principalmente con el objetivo de programar de manera más sencilla la sincronización de la
visualización de los datos, en las plantillas de los diferentes componentes de esta.
rg
argumento del constructor de la clase que implementa el componente. Además, se ha analizado el
o
esquema de programación de un servicio implementado por una clase TypeScript, decorado por el
n.
decorador @Injectable().
io
ac
Usando el servicio HTTP ofrecido por Angular (módulo HTTP), y principalmente los métodos GET,
m
POST y DELETE que este servicio ofrece, puede desarrollar sus propios servicios para intercambiar
ra
pr
Este intercambio de datos toma, de manera natural, la forma de datos formateados en JSON, se ha
do
ilustrado el envío de tales datos a través de los métodos HTTP POST y DELETE (estos datos se
to
incluyen en el cuerpo del mensaje HTTP), así como la recuperación de datos formateados en JSON a
e
Para esto, en primer lugar hemos listado los servicios web gestionados por el servidor, y después,
para cada servicio web, reproducido el camino por el que se implementa:
De esta manera, hemos presentado todas las funcionalidades implementadas en la aplicación El hilo
Para terminar, se ha ofrecido una presentación resumida de la librería NgRx, que permite centralizar
todos los datos que definen el estado de una aplicación Angular en un store.
rg
acción (solo se solicita al servidor cuando es necesario intercambiar datos con la aplicación Angular).
o
n.
Por lo tanto, la vista global de la aplicación está formada por una composición de vistas (las plantillas
io
de los componentes), y algunas de estas vistas solo deben aparecer temporalmente, durante la
ac
activación de algunos componentes. Estas activaciones se hacen generalmente en respuesta a una
m
acción del internauta (por supuesto también es posible que una acción del internauta no active una
ra
vista, permaneciendo sin efecto en la interfaz, pero este caso es poco frecuente).
og
pr
Por lo tanto, se implementa este esquema transaccional de manera más frecuente: una acción del
do
En el marco de la aplicación El hilo rojo de e-commerce, el internauta puede realizar las siguientes
w
acciones:
w
w
Definir los criterios (tipo, marca, precio, popularidad, etc.) de los productos de los que
queremos información (a través de la vista del componente
selectionbycriteriaComponent): los productos que responden a estos criterios,
se buscan en la base MongoDB, y después se muestran en una vista activada para este
momento (y gestionada por el componente productDisplayComponent).
Seleccionar un producto entre los que se muestran en la vista del componente
productDisplayComponent, para añadirlo a su cesta de la compra: este producto se
añade a la lista de los productos de la cesta de la compra de este internauta, almacenada en una
colección MongoDB (componente cartManagementComponent) y aparece la vista de
los productos de la cesta de la compra (componente cartDisplayComponent).
La segunda solución, que tiene que ver con el paradigma modelo vista-controlador, es evidentemente
mucho más satisfactoria, ya que desacopla las conexiones entre componentes, permitiendo:
Una mejor legibilidad de los resultados de una acción (se destacan todos sus detalles).
Una mejor evolución de la aplicación, que permite sustituir fácilmente la invocación de un
rg
componente por otro.
o
n.
Utilizar este desacoplamiento para establecer una gestión de los roles (comprobando que el
io
internauta tiene todos los permisos para realizar la acción descrita, por ejemplo un internauta
ac
no identificado, puede no tener los permisos para insertar productos en una cesta de la
m
compra).
ra
og
Este acoplamiento entre componentes se define por una o varias tablas de enrutado, que gestiona el
pr
router (el controlador). Veremos pronto que una arquitectura sana implica que cada módulo tiene su
do
Lo que acabamos de mencionar es la gestión de las rutas internas a la aplicación mono página, creada
e
.d
con Angular. En el marco de una aplicación MEAN, que asocia Angular (por el lado cliente) con un
w
A nivel interno en el seno de la aplicación mono página: generalmente, las plantillas (a través
de las acciones del internauta) hacia el controlador, y después del controlador hacia el o los
componentes destino.
A nivel externo, entre los servicios de Angular y el servidor Node.js.
A nivel interno, si se debe expresar una acción (por ejemplo, eliminar o retirar un producto de la cesta
de la compra), se hace por un argumento de la ruta.
A nivel externo, los puristas expresan esta acción por el método HTTP utilizado: POST (o PUT) para
añadir un producto, DELETE para eliminarlo y PUT (o POST) para actualizarlo.
Esta ruta «específica» se inscribe en una acción más genérica (por ejemplo, conocer todos los
productos que responden a algunos de los criterios de búsqueda), que se declara en una o varias tablas
de enrutado.
Por lo tanto, las tablas de enrutado establecen la correspondencia entre una acción «genérica» y un
componente que se invoca para resolver la acción. También es posible que un módulo se invoque
globalmente por la acción, su finalidad precisa se debe tener en cuenta en un segundo momento, por
un componente específico del módulo.
El router establece la correspondencia entre la ruta específica y la ruta genérica (entre la selección y
rg
la declaración de la ruta) e invoca al componente destino. También puede activar los
o
comportamientos por defecto, si esta correspondencia no se ha podido establecer.
n.
io
Para poder utilizar un router en una aplicación Angular creada con Angular CLI, es preferible crear su
ac
proyecto respondiendo sí (y) a la pregunta o con la opción --routing:
m
ra
Esta opción permite crear el módulo app-routing.module.ts, que acompaña al root module y que
do
Una tabla de enrutado se presenta como una lista de objetos (una colección), donde las rutas se
.d
En este caso, una primera parte de la ruta (la ruta primaria) invoca al módulo y la segunda parte de la
ruta (la ruta secundaria), se tiene en cuenta gracias a la tabla de enrutado asociada al módulo, para
rg
invocar el componente específico de este.
o
n.
En este esquema, el módulo se carga en modo «lazy loading», es decir durante la primera invocación
io
de un componente del mismo, y no durante la carga inicial de la aplicación. ac
m
Se dice que una ruta específica está seleccionada, cuando se realiza la acción que describe, y la ruta se
ra
llama declarada cuando se corresponde con una ruta genérica especificada en una table de enrutado.
og
pr
selección de una ruta, por un componente iniciador, el acoplamiento entre los dos componentes se
w
w
garantiza por el router. En la gran mayoría de los casos, el componente invocado ejecuta una
w
operación que conduce (a través de su plantilla), a la visualización de resultados. Esta vista tiene dos
características particulares.
Al contrario de lo que sucede con las vistas permanentes, creadas por las etiquetas relativas a los
componentes, una vista activada por una ruta es temporal: aparece durante la selección de la ruta y
desaparece con la activación de otra ruta. En otros términos, un componente «visual» en un outlet,
permanece hasta que la navegación se dirija hacia otro componente.
Si se localizan en el mismo lugar las vistas de todos los componentes invocados en las rutas, solo
necesita crear una única directiva <router-outlet>: por lo tanto, esta directiva única define la
Si, por el contrario, quiere diferenciar las ubicaciones de las vistas de los componentes respecto a las
diferentes rutas que las invocan, tiene dos posibilidades:
Puede crear directivas <router-outlet> adicionales (hasta una por componente), los
outlets activados en un momento dado se definen por una configuración particular de la tabla
de enrutado.
Puede crear directivas <router-outlet>, nombradas asociando un atributo name a
estas, para identificarlas. Los identificadores creados de esta manera, se utilizan en la selección
de las rutas.
Una directiva <router-outlet> sin nombre se designa con el nombre de primary outlet. Una
directiva <router-outlet> con nombre se designa con el nombre de auxiliary outlet.
rg
Después de un primer ejemplo sencillo de activación de un componente por medio de una ruta y la
o
n.
visualización de su vista en un outlet, presentaremos la utilización de los primary y auxiliary outlets.
io
ac
m
4. Ejemplo de enrutado
ra
og
Empecemos con un primer ejemplo muy sencillo de enrutado. Para esto, cree con Angular CLI una
pr
Lance los comandos Angular CLI que crean la aplicación y los dos componentes:
rg
creación ha estado provocada por la opción --routing que se pasa al comando ng).
o
n.
Modifique la plantilla del componente principal (AppComponent) para hacer aparecer la vista
io
de Component1Component y la vista de los componentes invocados por una ruta ac
(solamente Component2Component en este caso):
m
ra
<app-component1></app-component1>
og
<br/>
pr
<router-outlet></router-outlet>
do
<br/><br/>
to
«app works!».
w
rg
}
o
n.
];
io
@NgModule({
ac
m
imports: [RouterModule.forRoot(routes)],
ra
exports: [RouterModule],
og
providers: []
})
pr
ng serve -o
w
w
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
Angular permite, en la tabla de enrutado, definir un «árbol de vistas» e indicar sobre qué ruta se va a
activar una rama del árbol (esta activación se llama estado).
Imaginemos que la aplicación incluye además del componente principal, otros cuatro componentes.
A continuación se muestra (disposición horizontal) el árbol de los estados de las vistas, para el
modelo de navegación:
vista del componente principal < vista del component1 < vista del component3
vista del componente principal < vista del component1 < vista del component4
Para definir este árbol en la tabla de enrutado, dispone de la propiedad children. Esta propiedad
rg
especifica las vistas a integrar en la vista del componente padre.
o
n.
A continuación se muestran los archivos que definen los componentes y la tabla de enrutado del
io
proyecto ENRUTADO2, que ilustra este punto: ac
m
ENRUTADO2/src/app/app.component.ts: componente de más alto nivel AppComponent.
ra
AppComponent.
to
Component1Component.
w
Component2Component.
w
rg
component: Component3Component
o
},
n.
io
{
path: 'component4', ac
component: Component4Component
m
}
ra
]
og
},
pr
{
do
path: 'component2',
to
component: Component2Component
e
}
.d
];
w
w
w
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }
La ubicación de las vistas (las plantillas) de los componentes activados por el router, que se define
por la etiqueta <router-outlet>:
<div>
<p> Inside component1 </p>
<router-outlet></router-outlet>
</div>
org
n.
Esta plantilla se asocia a la siguiente hoja de estilos, que crea un borde alrededor de la vista de este
io
componente: ac
m
div { width: 200px;
ra
border-width: 1px;
og
border-style: solid;
pr
}
do
to
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
/component1/component3:
org
n.
En los ejemplos anteriores, los componentes invocados sobre una ruta solo se muestran en
io
ubicaciones únicas. También es posible mostrar un componente en diferentes ubicaciones. Para esto,
ac
puede utilizar uno o varios outlets con nombre, llamados auxiliary outlets.
m
ra
Para utilizar un outlet con nombre, es necesario por un lado hacer que aparezca una propiedad
og
path: <ruta>,
e
.d
}
w
];
y por otra parte, durante la selección de la ruta utilizar un objeto específico que lista los auxiliary
outlets, asociados a las rutas:
[routerLink] =
"[{'outlets': {<nombre del outlet>: <link parameters array>}}]"
Para ilustrar la utilización de los outlets con nombre, volvemos al primer ejemplo. Quiere invocar el
componente 2 sobre dos rutas diferentes:
Para esto, cree un outlet con nombre (auxiliary outlet), gracias a una directiva router-outlet
con nombre.
A continuación se muestra los archivos que definen los componentes y la tabla de enrutado del
proyecto ENRUTADO_outlet con nombre que ilustra este punto:
rg
Component1Component.
o
n.
ENRUTADO3/src/app/component1/component2.component.ts / html: code y plantilla de
io
Component2Component. ac
m
ENRUTADO3/src/app/app-routing.module.ts: la tabla de enrutado.
ra
app.component.html) para:
pr
do
añadir un ancla para hacer aparecer la vista del componente 2 en "modo VIP".
e
.d
w
<div>
w
Ubicación VIP:
w
<router-outlet name="specialPlace"></router-outlet>
</div>
<app-component1></app-component1>
<router-outlet></router-outlet>
Esta plantilla se asocia a la siguiente hoja de estilos, que crea un borde alrededor de la ubicación VIP:
y:
org
n.
Configure la tabla de enrutado utilizada por el router, para gestionar las dos rutas (una nueva
io
propiedad outlet debe especificar a este nivel el nombre del outlet): ac
m
import { NgModule } from '@angular/core';
ra
'./component2/component2.component';
do
{
e
.d
path: 'forthecomponent2',
w
component: Component2Component
w
},
w
{
path: 'fortheVIPcomponent2',
component: Component2Component,
outlet: 'specialPlace'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }
org
Cuando se selecciona la primera ancla, la vista del componente 2 se activa después de la del
n.
componente 1:
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
Cuando el ancla VIP se selecciona, la vista del componente 2 se activa en la parte superior de la
página:
Al más alto nivel, la selección de una ruta interna en Angular generalmente se especifica por dos
rg
datos, el segundo es opcional:
o
n.
La ruta propiamente dicha (que se corresponde con el path de una URL), que llamaremos ruta.
io
ac
Esta ruta absoluta o relativa, incluye uno o varios segmentos (algunas pueden recibir
argumentos).
m
ra
Un objeto JavaScript que especifica los outlets con nombre (los auxiliary outlets), que definen
og
las ubicaciones específicas (identificadas por un nombre), donde se activan las vistas de los
pr
componentes invocados por la ruta. Si este objeto no está presente, las vistas se redirigen hacia
do
La sintaxis de la selección de las rutas queda de esta manera condicionada respecto a varios factores:
e
.d
En adelante, vamos a introducir el conjunto de elementos que influyen sobre la sintaxis de las rutas.
Si varios elementos constituyen la ruta (a causa de la presencia de varios segmentos que forman la
ruta y/o del objeto que especifica los auxiliary outlets), estos elementos se agrupan en una lista del
que se muestra e a continuación el esquema de programación:
[ <segmentos de la ruta>,
<objeto que especifica los outlets con nombre> ]
rg
A continuación se muestra un ejemplo en el que la ruta está compuesta por dos segmentos, que se
o
enumeran en una lista:
n.
io
<a [routerLink]="['/forthecomponent2', 'VIP']"> ac
Llamada VIP al component2
m
</a>
ra
og
Las rutas se definen por una serie de segmentos separados por barras oblicuas. Estos segmentos deben
pr
Por ejemplo, el segmento Products insertado en una ruta de la aplicación El hilo rojo indica que afecta
e to
a los productos puestos en venta, mientras que el segmento cart recuerda a la cesta de la compra de un
.d
cliente.
w
w
Como hemos visto en el capítulo La plataforma Node.js, la filosofía de las rutas, orienta su expresión
w
hacia los datos y no hacia las operaciones. Pero aquí, discutimos de las rutas internas dentro de la
aplicación mono página, y no de las rutas externas de la aplicación Angular, con destino el servidor
Node.js, donde las acciones se pueden definir por el método HTTP. De esta manera, los segmentos de
la ruta pueden indicar una acción que es necesario realizar.
La raíz se corresponde con el directorio src/app definido por la directiva siguiente, que se debe
escribir en el encabezado del archivo HTML src/index.html:
<base href="/">
En el capítulo Prueba y despliegue, veremos con qué objetivos se puede modificar esta raíz.
Las rutas relativas, directamente se prefijan por un segmento, un argumento o una referencia relativa
(./, ../). Las rutas relativas se soportan directamente por la tabla de enrutado asociada al módulo actual
(se darán ejemplos a continuación).
org
n.
3. Configuración de las rutas
io
ac
En la mayor parte de los casos, las rutas incluyen argumentos que se corresponden con el valor de
m
variables del componente que selecciona la ruta.
ra
og
En las tablas de enrutado gestionadas por el router, los argumentos aparecen precedidos por el
pr
Por ejemplo, si la ruta seleccionada en la barra de URL del navegador es esta (solicita buscar y
to
/research/display/:type/:brand/:minprice/:maxprice/:minpopularity
w
Por supuesto, este ejemplo es muy reducido, ya que las rutas no se seleccionan desde la barra de
URL, sino a través de un componente que las construye a partir de una acción del internauta.
Veremos en la sección Selección de las rutas, cómo crear rutas como esta, a partir de acciones
realizadas a través de las plantillas.
<a [routerLink]="['/',{'outlets':
{'specialPlace': ['fortheVIPcomponent2']}}]">
rg
Llamada VIP del component2
o
n.
</a>
io
ac
Por el contrario, la sintaxis siguiente es incorrecta:
m
ra
<a [routerLink]="['/fortheVIPcomponent2',
og
{'outlets':{'specialPlace':[]}}]">
pr
</a>
e to
En efecto, esta última sintaxis se corresponde con la separación de la ruta, en una ruta principal
.d
(/fortheVIPcomponent2) y una ruta secundaria (vacía). Esta separación que nos devuelve a una
w
w
gestión que se debería compartir entre la tabla de enrutado asociada en el root module y una tabla de
w
enrutado, asociada a un feature module (veremos esta configuración más adelante), lo que no se
corresponde con la arquitectura elegida.
Usando la directiva routerLink, que esta asocia con elementos HTML en los que se
puede hacer clic (por ejemplo, las anclas).
rg
Usando el método Router.navigate(), este método se llamada normalmente directa o
o
n.
indirectamente a partir de un listener de eventos.
io
La directiva routerLink y el método navigate() de la clase Router, provienen del
ac
m
paquete @angular/router.
ra
og
1. La directiva routerLink
pr
do
to
La directiva routerLink, que permite seleccionar una ruta, se asocia a los elementos HTML
e
Como hemos visto, si varios elementos forman la ruta (presencia de argumentos y/o del objeto que
w
w
definen los outlets de salida), estos elementos se agrupan en una lista. En este caso, el valor de la
directiva es código JavaScript, el nombre de la directiva se enmarca por corchetes:
[routerLink].
A continuación se muestra el esquema de programación que se debe implementar:
<a [routerLink]="['/research',
{outlets: {'display': ['display',
this.params.type,
Índice
this.params.brand]}}]">
Si la ruta es un segmento representado por una simple cadena de caracteres, entonces la directiva
routerLink se puede utilizar:
La directiva routerLink entonces puede estar acompañada por las siguientes directivas:
rg
durante la activación de la ruta.
o
La directiva [routerLinkActiveOptions] permite aplicar las clases CSS,
n.
io
únicamente para la ruta estrictamente definida o en caso contrario, para aquellas para las que
es prefijo.
ac
m
La directiva [queryParams] permite transmitir la Query string (cadena de consulta) en forma
ra
og
</a>
e
.d
w
<a [routerLink]="['/home]"
[routerLinkActive]="routeactive"
[routerLinkActiveOptions]="{ exact: true }">
Inicio
</a>
A continuación se muestra el esquema de programación que permite importar este servicio y utilizarlo
a través de su inyección en el constructor de una clase:
@Component({
...
})
export class ... {
rg
constructor (private router: Router) {}
o
...
n.
}
io
ac
Ilustramos la implementación de este método indirectamente, a partir de una plantilla. Supongamos el
m
siguiente código:
ra
og
</button>
do
to
cartValidation() {
w
this.router.navigate(['/cart', 'validation']);
w
this.router.navigateByUrl(<URL>);
3. Ejemplo de ruta
Tomamos como ejemplo la visualización de los productos seleccionados por diferentes criterios (tipo,
marca, precio, etc.).
A continuación se muestra un ejemplo de la selección de una ruta, que permite buscar y mostrar todos
Esta selección se corresponde con esta definición genérica de la ruta que describe una búsqueda de
productos: /research/display/:type/:brand/:minprice/:maxprice/:minpopularity
Dicho esto, respecto a la aplicación El hilo rojo de e-commerce, soportan la selección dos tablas de
enrutado y no una sola. De esta manera, la ruta /research/display/phone/*/*/*/* se descompone en una
ruta primaria /research reconocida por la tabla de enrutado del root module y en una ruta secundaria
display/phone/*/*/*/*, reconocida por la tabla de enrutado asociada en el feature module
research especializado en la selección y visualización de los productos.
Este punto se detallará en la sección Soporte de una ruta por varios módulos/tablas de enrutado.
org
externalización hace más fácil el mantenimiento del proyecto). El módulo AppRoutingModule
n.
que acompaña al root module, se crea con el comando ng new --routing (este módulo
io
ac
aparece en el archivo app-routing.module.ts). Este módulo utiliza (y por lo tanto, importa) el servicio
m
RouterModule (del paquete nodes_modules/@angular/router).
ra
og
1.Configuración
de la tabla de enrutado
pr
do
La aplicación El hilo rojo puede utilizar una o varias tablas de enrutado. En efecto, diferente a la tabla
e to
de enrutado asociada al root module, habrá sin duda una tabla de enrutado por feature module.
.d
w
Una tabla de enrutado se define por la colección (una lista de objetos) de tipo rutas. A continuación se
w
muestra el esquema de programación (básico), donde cada objeto de esta colección enlaza una ruta
w
const routesRoutes = [
{
path: <ruta>,
component: <componente a invocar>
},
{
path: <ruta>,
component: <componente a invocar>
},
rg
...
o
];
n.
io
@NgModule({ ac
imports: [RouterModule.forRoot(routes)],
m
// o RouterModule.forChild(routes)],
ra
exports: [RouterModule],
og
providers: []
pr
})
do
outlet.
w
const routesRoutes = [
{ path: 'display/:type/:brand/:minprice/:maxprice/:minpopularity',
component: ProductDisplayComponent,
outlet: 'display'
}
];
rg
La propiedad pathfull, que indica si la descripción de la ruta es completa o solo forma un
o
n.
prefijo.
io
ac
A continuación se muestra un ejemplo en el que las rutas vacías se redirigen hacia el componente que
m
gestiona la bienvenida del sitio web. La propiedad pathMatch con valor full, aquí es
ra
Observe en este ejemplo la utilización del joker (el wildcard), como valor de la propiedad path para
pr
do
interceptar las URL inválidas. Este joker toma la forma de dos asteriscos.
to
También es posible pasar datos adicionales a transmitir al componente que se invoca. Para esto, se
puede utilizar la propiedad data.
La utilización de estos datos por el componente, se explica en la sección Captura de una ruta durante
la invocación de un componente.
rg
el método forRoot(), lo de los feature modules por el método forChild().
o
n.
A continuación se muestra el esquema de programación (archivo src/app/app-routing.module.ts):
io
que define la tabla de enrutado del módulo principal; ac
m
y la asocia al router.
ra
og
];
w
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }
En el caso de una ruta expresada de manera absoluta (por lo tanto, soportada por la tabla de enrutado
rg
del módulo principal) y que debe invocar a un componente de un feature module, esta incluye una
o
ruta primaria y una ruta secundaria.
n.
io
La ruta primaria (que puede comprender varios segmentos), especifica semánticamente la
ac
funcionalidad global expresada por uno de los feature modules. La ruta secundaria permite a la tabla
m
de enrutado del feature module seleccionado, invocar el componente destino.
ra
Ilustremos este punto con un ejemplo que se basa en la estructura de tres módulos, de la aplicación El
og
hilo rojo:
pr
do
El módulo principal (el root module), que implementa la vista global de la aplicación y la
to
El módulo cart (un segundo feature module), que implementa la funcionalidad de gestión y
visualización de la cesta de la compra.
En este caso, puede modelizar las rutas usando como prefijo /research en todas aquellas
relativas a una acción relacionada con la selección de los productos y /cart en aquellas relativas a
una acción relacionada con la cesta de la compra. Las rutas primarias research y cart se
reconocen por el router, gracias a la tabla de enrutado asociada al root module.
org
4. Control de las rutas: guardián
n.
io
ac
Durante la implementación de los ejemplos de código de este capítulo, ha podido comprobar que las
m
URL de su navegador reflejan de manera fiel su navegación. Por lo tanto, es posible navegar en la
ra
aplicación mono página especificando directamente una ruta en la barra de URL. Para controlar estas
og
Este control se realiza por las rutas guardián (los vigilantes de las rutas).
e to
Por supuesto, este mecanismo se puede utilizar para la gestión de los roles.
.d
w
Entre los diferentes controles posibles, el control CanActivate es el más útil: requiere una
w
w
o
autenticación:
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
o
n.
GUARD/src/app/app.component.ts: el componente principal.
io
GUARD/src/app/app.component.html: la plantilla del componente principal.ac
GUARD/src/app/test/test.component.ts: el componente (y su plantilla) cuya invocación se
m
controla.
ra
og
consulta el servidor.
w
Por otro lado, se crea un servidor Node.js HTTPS para preguntar a una colección llamada Users de
la base de datos MongoDB.
https.createServer(opciones, app).listen(8443);
rg
let db = cliente.db("OnlineSales");
o
n.
io
assert.equal(null, err);
ac
app.get("/auth/login=:login/password=:password",
m
function(req, res){
ra
res.setHeader("Content-type",
do
"text/plain; charset=UTF-8");
to
db.collection("Users")
e
.find({"email":login, "password":password})
.d
&& doc.length == 1)
res.end("1");else res.end("0");
});
});
});
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
<router-outlet name="login"></router-outlet>
<br/><br/>
<a [routerLink]="['/test']" > test </a>
<br/><br/>
<router-outlet></router-outlet>
rg
import { Component } from '@angular/core';
o
@Component({
n.
selector: 'app-test',
io
template: '<p> Test works </p>' ac
})
m
export class TestComponent { }
ra
og
servicio AuthService.
e to
.d
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css']
})
export class AuthComponent {
private login: string;
private password: string;
private nombreYApellido: string[] = [];
onSubmit() {
console.log(this.login+" "+this.password);
this.auth.isLoggedIn = false;
this.auth.authentication(this.login, this.password).
subscribe(res => this.nombreYApellido = res);
if ( this.nombreYApellido.length > 0 ) {
this.auth.isLoggedIn = true;
this.auth.firstname = this.nombreYApellido[0];
this.auth.lastname = this.nombreYApellido[1];
this.auth.email = this.login;
}
}
rg
logout() {
o
this.auth.isLoggedIn = false;
n.
io
}
ac
}
m
ra
og
{{ mensaje }}
.d
#loginForm="ngForm">
w
<div class="form-group">
w
rg
import { AuthService } from './auth.service';
o
n.
@Injectable()
io
export class AuthGuardService implements CanActivate { ac
m
constructor(public authService:AuthService,
ra
public router:Router) {}
og
pr
canActivate(): boolean {
do
return false;
.d
}
w
}
w
w
@Injectable()
rg
import { AuthComponent } from './auth/auth.component';
o
import { TestComponent } from './test/test.component';
n.
io
const routes Routes = [ ac
{ path: 'test',
m
component: TestComponent,
ra
canActivate: [AuthGuardService]
og
},
pr
{ path: 'login',
do
component: AuthComponent,
outlet: 'login'
to
}
e
.d
];
w
w
@NgModule({
w
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule {}
A continuación se muestra el módulo AppModule, que especifica todos los actores de este ejemplo
(archivo app.module.ts):
@NgModule({
declarations: [
AppComponent,
TestComponent,
AuthComponent
],
imports: [
rg
BrowserModule,
o
FormsModule,
n.
io
HttpClientModule,
AppRoutingModule ac
],
m
providers: [AuthService, AuthGuardService],
ra
bootstrap: [AppComponent]
og
})
pr
5. Invocación de un componente
w
w
w
La invocación del componente por el router activa la vista del componente en el outlet que se le
asocia (un primary outlet o un auxiliary outlet si se ha especificado).
En la gran mayoría de los casos, el componente se invoca un número cualquiera de veces, sus
invocaciones se acompañan de argumentos. Para acceder a estos argumentos, Angular ofrece utilizar
un objeto creado según la interfaz ActivatedRoute. Este objeto contiene la información sobre
los argumentos propiamente dichos de la ruta, la Query string (cadena de consulta), los fragmentos de
la URL y los datos adicionales añadidos en la tabla de enrutado. Para poder reaccionar a cada
invocación del componente, es necesaria una suscripción al observable params, proporcionado por
la interfaz ActivatedRoute.
@Component({
...
})
export class <nombre del componente> implements OnInit {
constructor (private route: ActivatedRoute) {}
...
}
Los argumentos de la ruta son accesibles a través del observable params. La suscripción a este
rg
observable se realiza en el método ngOnInit():
o
n.
ngOnInit(): void {
io
this.route.params.subscribe(params: Params => { ac
...
m
});
ra
}
og
pr
los criterios de los productos a mostrar. Durante cada invocación del componente (en una ruta
.d
diferente a la anterior), el observable params recibe nuevos valores. La suscripción realizada con el
w
w
a los productos devueltos por el servidor Node.js, que se corresponde con los criterios de búsqueda a
través de un nuevo observable. Una nueva suscripción a este segundo observable permite acceder a
estos datos y rellenar una lista Products, cuyos elementos se muestran en la plantilla del
componente.
ngOnInit(): void {
this.route.params.subscribe(params: Params => {
this.research
.getProducts("criteria/"+params['type']+"/"+params['brand']
+"/"+params['minprice']
+"/"+params['maxprice']
+"/"+params['minpopularity'])
. subscribe(res => this.Products = res);
Los datos de la URL son accesibles siguiendo el mismo esquema, pero utilizando el observable url:
ngOnInit(): void {
this.route.url.subscribe(data => { ... });
}
rg
url permiten acceder a los argumentos de la ruta y al camino completo.
o
n.
Cuando el componente solo se invoca una sola vez o quiere capturar los valores de los argumentos de
io
la primera invocación del componente, puede utilizar al objeto snapshot. Este objeto
ac
snapshot también permite acceder a los datos que se han añadido a la ruta, en la tabla de
m
enrutado.
ra
og
Estudiamos un ejemplo que invoca un componente llamado TestComponent una primera vez en
pr
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
org
A continuación se muestra la configuración de la ruta, activando el componente (archivo app-
n.
io
routing.module.ts). Un objeto data se añade a la configuración de la ruta. Este objeto contiene
propiedades que se pueden asociar a cualquier tipo de valor.
ac
m
ra
{
w
path: 'test/:param',
w
component: TestComponent,
w
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }
@Component({
selector: 'app-test',
template: '<p> Test works </p>'
})
export class TestComponent implements OnInit {
constructor(private ruta: ActivatedRoute) {}
ngOnInit() {
console.log(this.route.snapshot.params['param']);
console.log(this.route.snapshot.data['data1']);
console.log(this.route.snapshot.data['data2'][0]);
this.route.params.subscribe(function(params:Params) {
rg
console.log('Argumento = '+params['param']);
o
n.
});
io
}
}
ac
m
ra
paramValue1
do
v1
1
to
Argumento = paramValue1
e
.d
w
directamente:
Argumento = paramValue2
Las operaciones relativas al objeto snapshot, solo se realizan durante la primera invocación del
componente. El observable params solo se «renueva» durante las invocaciones siguientes.
rg
La gestión de la cesta de la compra (módulo cart).
o
n.
io
Como se ha mencionado, cada módulo debe tener su propia tabla de enrutado, lo que permite a los
ac
feature modules ser reutilizables. Para mejorar la legibilidad del código, las tablas de enrutado se
m
gestionan en los módulos específicos que acompañan a estos dos módulos y el root module.
ra
Por lo tanto, el router gestiona tres tablas de enrutado: una definida a nivel del root module, dos
og
A nivel del root module, la tabla de enrutado del módulo AppRoutingModule (archivo
to
A nivel de los feature modules, las tablas de enrutado administran las rutas secundarias:
El lazy loading permite cambiar los componentes del módulo designado por la ruta primaria, solo
cuando el router tiene en cuenta esta ruta por primera vez.
A continuación se muestra la estructura del código de la aplicación de prueba que gestiona las tablas
de enrutado:
Los componentes de los feature modules research y cart se cargan durante su primera
activación.
o rg
Por otro lado, con anterioridad a la invocación de los componentes que gestiona la cesta de la compra,
n.
un «guardián» comprueba que el usuario se ha autenticado correctamente.
io
ac
import { NgModule } from '@angular/core';
m
import { Routes, RouterModule } from '@angular/router';
ra
{
w
w
path: 'research',
loadChildren: './research/research.module#ResearchModule'
},
{
path: 'cart',
loadChildren: './cart/cart.module#CartModule',
canActivate: [AuthGuardService]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
rg
import { ProductDisplayComponent }
o
from './product-display/product-display.component';
n.
import { ModuleWithProviders } from '@angular/core';
io
const routes Routes = [
ac
m
{ path: 'display/:type/:brand/:minprice/:maxprice/:minpopularity',
ra
component: ProductDisplayComponent,
og
outlet: 'display'
pr
},
do
{ path: 'display/:terms',
component: ProductDisplayComponent,
to
outlet: 'display'
e
.d
}
w
];
w
w
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: []
})
export class ResearchRoutingModule { }
La tabla de enrutado establece un enlace entre las rutas secundarias prefijadas por el segmento display
y seguido por otros cuatro segmentos cualesquiera o por uno único, hacia el componente
ProductDisplayComponent.
Por ejemplo, si se selecciona la siguiente ruta (y esto se corresponde con la primera activación de una
ruta prefijada por la ruta primaria research): /research/display/phone/*/*/*/*
import { CartManagementComponent }
rg
from './cart-management/cart-management.component';
o
n.
import { CartDisplayComponent }
io
from './cart-display/cart-display.component';
import { CartValidationComponent }
ac
m
from './cart-validation/cart-validation.component';
ra
og
component: CartManagementComponent,
do
outlet: 'cartManagement'
to
},
e
.d
{ path: 'display/:numAction',
w
component: CartDisplayComponent,
w
outlet: 'cartDisplay'
w
},
{ path: 'validation',
component: CartValidationComponent,
outlet: 'cartValidation'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: []
})
export class CartRoutingModule { }
rg
inserta directamente en una plantilla (es posible, aunque incómodo, condicionar un visualización
o
«permanente» como esta a través de la directiva estructural *ngIf).
n.
io
Por otro lado, según la ruta seleccionada, la vista del componente activado en una ruta, puede
ac
aparecer en la ubicación por defecto (el primary outlet) o en una ubicación específica (en un outlet
m
con nombre o auxiliary outlet). Además, el router también puede comprobar que las condiciones
ra
pr
De esta manera, vemos toda la flexibilidad que procura la implementación del router. Y por supuesto,
do
el desacoplamiento fuerte que se establece entre la expresión de una acción y su realización facilita la
evolución de las aplicaciones y confiere en parte al router el papel de controlador del paradigma
e to
modelo vista-controlador.
.d
w
Este capítulo ha detallado la estructura de una ruta (tal y como se «emite», es decir seleccionada), que
w
se puede definir directamente por una cadena de caracteres o por medio de una lista que incluye todos
w
De esta manera, hemos visto cómo se configura una ruta y cómo se puede asociar a un primary outlet
o a un auxiliary outlet. También hemos estudiado la sintaxis de la ruta, que cuando esta viene
soportada por dos tablas de enrutado sucesivas se corresponde con el root module y un feature
module (la descripción de la ruta hace aparecer una ruta primaria y una ruta secundaria).
A continuación hemos estudiado cómo se realiza la selección de una ruta, esta selección se puede
hacer a través de la directiva routerLink, que se puede asociar a los elementos HTML pulsables
o a través del método navigate() (que ella misma se puede invocar indirectamente a partir de un
evento).
Además, en este capítulo se ha descrito la configuración de las tablas de enrutado. Las propiedades
Para terminar, este capítulo se ha abordó el mecanismo de control de las rutas propuesto por Angular
para establecer las condiciones (el guardián), que hace que la activación de un componente sea
posible o imposible. Este es un poderoso mecanismo que permite, por ejemplo, someter operaciones a
la identificación de un usuario y más generalmente, a la gestión de los roles.
o rg
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:26 p. m.]
Introducción
Del amplio dominio de la visualización de información y de la integración de componentes de
interfaz (GUI components), en este capítulo presentamos tres aspectos particulares centrados en un
proyecto Angular:
rg
La interfaz de mapas geográficos (en este caso de Google Maps), con multitud de información
o
n.
gráfica.
io
La integración de componentes gráficos pertenecientes a diferentes librerías de componentes.
ac
m
La creación de gráficas se ilustrará por medio de la utilización de las librerías gráficas D3.js y dc.js,
ra
que se basan en el lenguaje SVG, de la que se aportará una pequeña muestra. También se
og
implementará la librería Crossfilter, que permite analizar grandes volúmenes de datos con el objetivo
pr
La cobertura mediática de un mapa geográfico, se realizará por medio del uso del API Google Map a
to
Al final de capítulo, se presentará una aplicación Angular que sintetiza estos dos puntos: un Google
w
Map que ubicará (con círculos de diámetros más o menos grandes) las ciudades con pedidos
(números más o menos importantes) realizados en el sitio web El hilo rojo de e-commerce. La
selección de uno de estos círculos mostrará un histograma, en el que se presentará las ventas de los
diferentes productos.
Para expandir nuestros horizontes sobre las librerías de componentes de interfaz más populares, dos
ejemplos adicionales nos permitirán descubrir la librería Material (también con un datepicker) y la
librería ngx-bootstrap (para la creación de una barra de navegación).
rg
objetos JavaScript. Estos objetos se podrían, en el caso de una utilización en Angular, entregar por los
o
observables.
n.
io
La librería dc.js utiliza la librería D3.js para ofrecer funciones que permiten crear gráficos estáticos
ac
(charts). Normalmente se utilizada con la librería Crossfilter, que permite además (pero siempre en el
m
cliente), un análisis multidimensional de los datos.
ra
og
1. Instalación de D3.js
pr
do
to
Fuera de Angular, la librería D3.js está relacionada con su código a través de la etiqueta <script>
e
2. El lenguaje SVG
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]
Material para descargar
El lenguaje de marcado SVG, normalizado por el W3C, permite crear gráficas 2D vectoriales.
También permite controlar (a través de los eventos) y animar los elementos gráficos, así como
crearlos. Es un lenguaje muy completo. Las interacciones entre elementos gráficos se realizan en
JavaScript.
rg
Un círculo por la etiqueta <circle>:
o
n.
<circle cx="<abscisa del centro>"
io
cy="<ordenada del centro>" ac
r="<radio>"
m
stroke="<color del borde>"
ra
en el que:
Hay otros elementos de control como C o A, que permiten interpolar una curva que pasa por las
rg
coordenadas que se listan. Por otro lado, la utilización de estos caracteres en minúsculas, implicaría la
o
utilización de coordenadas relativas.
n.
io
La etiqueta <g> permite agrupar elementos gráficos para crear un elemento gráfico compuesto, que
ac
se puede reutilizar con la etiqueta <use>.
m
ra
Por supuesto, todos los elementos gráficos creados por las etiquetas SVG, se pueden identificar con
og
un atributo id.
pr
do
Hay operaciones de cambio de escala, de translación y de rotación, que se pueden aplicar gracias al
atributo transform, cuyo valor se rige por las siguientes funciones:
e to
.d
mirroring.
w
w
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
La selección (o la no selección) de elementos gráficos del DOM (ya creados). Esta etapa se
implementa con el método selectAll(<selector>).
Los selectores tienen una sintaxis cercana a la de los CSS: si no se rellena ningún selector
como argumento de este método, no se selecciona ningún elemento gráfico.
La asociación lógica entre los elementos seleccionados del DOM y una colección de objetos
JavaScript (un dataset). Esta asociación se realiza rango a rango (primer elemento del DOM
con el primer datum del dataset, etc.). Esta etapa se implementa con el método data().
Respecto a la asociación realizada anteriormente, se debe gestionar la selección de los objetos.
Esta etapa se implementa con los métodos:
exit(): devuelve los objetos del dataset que se corresponden con (rango a rango) los
rg
elementos del DOM seleccionados.
o
n.
enter(): devuelve los objetos del dataset que no han encontrado lugar en los elementos
io
seleccionados del DOM. Estos elementos quieren entrar en escena.
ac
m
La estrategia de asociación entre los elementos gráficos seleccionados y la colección. Esta
ra
nuevos elementos asociados a los datos del dataset de los mismos rangos.
w
w
w
(El radio de los círculos se fija en el código, pero sus posicionamientos dependen del dataset.)
org
var circulos = [{"x": 10, "y": 30},
n.
{"x": 10, "y": 60},
io
{"x": 10, "y": 90}]; ac
svg.selectAll()
m
.data( circulos )
ra
.enter()
og
.append("circle")
pr
.attr({
do
r: 10;
to
});
w
w
w
A continuación se muestra otro ejemplo con la creación de textos (etiquetas <h3>) en dos
momentos:
d3.select("body")
.selectAll() // ninguna selección de elementos del DOM (lista vacía)
.data(data) // asociación de ningún elemento en el dataset
// ["texto1", "texto2", "texto3"]
.enter() // devuelve los objetos adicionales,
// es decir ["texto1", "texto2", "texto3"]
.append("h3") // añade tres elementos h3
d3.select("body")
.selectAll("h3") // selección de los tres elementos h3
.data(data) // asociación de los tres elementos h3 al dataset
// ["texto4", "texto5", "texto6", ..., "texto9"]
.enter() // devuelve los data adicionales,
// es decir ["texto7", "texto8", "texto9"]
.append("h3") // añade tres elementos h3 para las cadenas
// de "texto7" hasta "texto9"
.text((d) => {return d;});
org
texto1
n.
io
texto2 ac
m
texto3
ra
texto7
og
pr
texto8
do
texto9
e to
.d
Una fortaleza de D3 es poder sustituir «a voluntad», los elementos gráficos respecto a la llegada de
w
w
nuevos datos.
texto4
org
texto5
n.
io
texto6
ac
m
El método on() permite adjuntar listeners de eventos a uno o varios elementos del DOM.
do
function update() {
selección = d3.select("body")
.selectAll("h3")
.data(data);
selection.exit().remove();
selection.text((d) => {return d; });
}
org
A continuación se muestra la organización del código principal de la aplicación de prueba:
n.
io
CHARTwithD3/dataServer.js: servidor Node.js. ac
CHARTwithD3/src/app/app.service.ts: servicio Angular para acceder a los servicios REST
m
gestionados por Node.js.
ra
og
componente.
.d
w
D3.js.
w
"use strict";
let express = require("express");
let cors = require("cors");
let app = express();
app.use(cors());
app.listen(8889);
let data = [
{ "place": "place1",
"sales": [{"name": "product1", "sales": 50},
rg
{"name": "product2", "sales": 20},
o
n.
{"name": "product3", "sales": 35},
io
{"name": "product4", "sales": 75},
{"name": "product5", "sales": 12}
ac
m
]
ra
},
og
{
"place": "place2",
pr
]
w
}
w
];
rg
muestra a continuación (archivo CHARTwithD3/src/app/data.service.ts):
o
n.
import { Injectable } from '@angular/core';
io
import { HttpClient } from '@angular/common/http'; ac
import { Observable } from 'rxjs326
m
ra
';
og
pr
@Injectable()
do
getPlaces(): Observable<any> {
w
return this.http.get(url);
}
<select (change)="displaySalesOfAPlace($event)">
<opción *ngFor="let place of places">
{{place}}
</option>
</select>
<br/><br/>
<div id="divchart"></div>
org
Este componente utiliza una zona de visualización SVG, cuyo tamaño se calcula según las
n.
dimensiones elegidas para las barras del histograma y los márgenes que las separan. Los ejes y las
io
ac
barras del histograma se eliminan y se vuelve a crear durante cada selección de un nuevo lugar.
m
A continuación se muestra la lista de los módulos necesarios (observe la importación de los
ra
@Component({
w
selector: 'app-chart',
w
templateUrl: 'chart.component.html',
w
styleUrls: ['chart.component.css']
})
export class ChartComponent implements OnInit {
constructor(...) {}
ngOnInit() { ... }
displaySalesOfAPlace(...) { ... }
createAxis(...) { ... }
rg
createBars() { ... }
o
}
n.
io
ac
Los colores de las barras del histograma se toman de una lista de cuatro colores, que se utiliza
m
circularmente si se visualizan más de cuatro productos.
ra
El servicio que accede a los servicios REST del servidor Node.js, se inyecta en el constructor.
og
pr
El método ngOnInit() crea la etiqueta <svg>, que reúne todos los elementos gráficos
do
generados por D3.js y llama al servicio para construir la lista desplegable de los lugares de venta:
e to
ngOnInit() {
w
w
this.svg = d3.select("#divchart")
.append("svg")
.attr("width", "100%")
.attr("height", 500);
this.chart = this.svg.append("g");
this.dataService.getPlaces()
.subscribe(res => this.places = res);
}
displaySalesOfAPlace($event) {
let selectElement = $event.target;
let optionIndex = selectElement.selectedIndex;
let place = selectElement.options[optionIndex].text;
console.log("Visualización de las ventas de "+place);
this.chartService
.getSalesDataOfaPlace(place)
.subscribe(res => {
let salesMax: number = 0;
this.data = [];
for (let product of res) {
this.data.push([product.name, product.sales]);
if (product.sales > salesMax)
rg
salesMax = product.sales;
o
this.places.push();
n.
}
io
this.chart.remove(); ac
this.chart = this.svg.append("g");
m
this.createAxis(this.data.length, salesMax);
ra
this.createBars();
og
this.chart.attr("transform",
pr
`translate(0,${this.margin}) scale(1.5)`);
do
});
to
}
e
.d
w
El método createAxis() crea los dos ejes del histograma: el eje de abscisas (cantidades de
w
A continuación, el eje se debe posicionar (en la parte superior, inferior, izquierda o derecha) y
agrupar en una etiqueta «grupo» (<g>), a la que se le permite aplicar una operación de
transformación (por ejemplo, un desplazamiento hacia la derecha).
this.chart
.append("g")
.attr("transform", `translate(${this.margin}, ${this.yAxisHeight})`)
.call(d3.axisBottom(this.xScale));
this.chart.append("g")
.attr("transform", `translate(${this.margin}, 0)`)
.call(d3.axisLeft(this.yScale));
rg
}
o
n.
io
El método createBars() crea las barras del histograma. Los colores de las barras se toman
ac
circularmente de la lista colors.
m
ra
CreateBars() {
og
this.chart.selectAll().data(this.data)
pr
.enter()
do
.append('rect')
to
+ i*(this.margin+this.barWidth))
.d
.attr('width', this.barWidth)
w
Va a retomar el ejemplo anterior, que muestra el histograma de las ventas realizadas en un lugar dado,
utilizando una librería JavaScript de visualización de gráficos de más alto nivel que D3.js: dc.js
pr
(Dimensional Charting JavaScript library). Dc.js se basa en D3.js, que ofrece funciones que se
do
corresponden con los diferentes gráficos estadísticos clásicos (en este ejemplo, utilizará la
e to
La utilización de dc.js normalmente está muy conectada con la de la librería Crossfilter, que permite
w
manipular muy rápidamente grandes volúmenes de datos en el cliente, para realizar operaciones
w
analíticas en línea (OnLine Analytical Processing = OLAP). De esta manera, los datos se representan
en un hipercubo multidimensional, que cruza varias dimensiones (podemos hablar de vista
multidimensional de los datos).
En este ejemplo, Crossfilter permite acceder a los datos creando los dominios de valores necesarios
para el histograma, es decir, el escenario de los nombres de productos en el eje de abscisas, y el
número de sus ventas en el de ordenadas.
En este ejemplo, solo presentamos el código de la clase que implementa el componente gráfico
CHARTwithDC/src/app/chart/chart.component.ts, el resto de código es idéntico al presentado
anteriormente.
rg
a. Instalación de dc.js y de Crossfilter
o
n.
Para instalar dc.js y Crossfilter, utilice el comando npm, como sigue:
io
ac
npm install --save dc
m
npm install --save crossfilter2 @tipos/crossfilter
ra
og
de Crossfilter (por lo tanto, se recomienda eliminar la instalación de la versión actual de D3.js, antes
do
de instalar dc.js).
e to
.d
@Component({
selector: 'app-chart',
templateUrl: 'chart.component.html',
styleUrls: ['chart.component.css']
})
export class ChartComponent implements OnInit {
private places: Array<string>;
private chart: any;
ngOnInit() {
this.chart = dc.barChart("#divchart");
this.chartService.getPlaces()
.subscribe(res => this.places = res);
}
displaySalesOfAPlace($event) {
let selectElement = $event.target;
let optionIndex = selectElement.selectedIndex;
let place = selectElement.options[optionIndex].text;
this.chartService.getSalesDataOfaPlace(place)
rg
.subscribe(res => this.createChart(res));
o
}
n.
io
createChart(data) { ac
let ndx = crossfilter(data);
m
ra
return d.name;});
pr
.reduceSum(function(d) {
to
return d.sales;
e
));
.d
w
w
this.chart
w
.width(data.length*120)
.height(300)
.x(d3.scale.ordinal())
.xUnits(dc.units.ordinal)
.xAxisLabel('Products')
.yAxisLabel('Quantities')
.dimension(productDimension)
.barPadding(0.2)
.group(sumGroup);
this.chart.render();
}
}
rg
o
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
Anterior
Introducción
Siguiente
Integración de mapas Google…
|
Condiciones generales de uso Copyright - © Éditions ENI
|
Política de protección de datos del grupo ENI
org
Map con Angular, y de manera más específica, utilizando la librería de componentes PrimeNG, y
n.
después vamos a presentar el código que realiza esta integración. Este ejemplo le aportará un resumen
io
de la utilización de componentes gráficos (UI componentes) en Angular. ac
m
Va a reutilizar el servidor de prueba que ha creado, para alimentar con datos los histogramas. A los
ra
datos asociados a los lugares a partir de los cuales se realizan los pedidos, va a añadir sus
og
coordenadas geográficas para mostrar en el mapa Google Map, círculos de diámetros proporcionales
pr
al número de productos solicitados. Estos círculos se mostrarán «en overlay», es decir en una capa
do
Para gestionar los mapas Google Maps, debe instalar por un lado los tipos TypeScript necesarios y
por otro, instalar en la librería de módulos Node.js aquellos que forman parte de la librería PrimeNG
(puede utilizar otra librería de componentes, incluso crear su componente, pero esto sería un poco
más complejo).
Utilice la aplicación typings para instalar los tipos relativos para la utilización de Google Maps:
{
"globalDependencies": {
"google.maps": "registry:dt/google.maps#3.25.0+20161208205818"
}
}
rg
Edite el archivo tsconfig.json, añada la propiedad include si no existe y asigne el valor
o
typings/*.d.ts, que hace referencia a la carpeta local typings, así como el valor
n.
io
"src/**/*.ts":
ac
"include": [ "typings/*.d.ts", "src/**/*.ts" ]
m
ra
og
Es posible la utilización anónima del API Google Maps, pero es preferible solicitar a Google una
pr
clave de identificación (esta última permite utilizar de manera más frecuente este API y tener acceso a
do
servicios adicionales). A continuación se muestra la URL en la que se puede hacer esta petición:
https://developers.google.com/maps/documentation/javascript/get-api-key?hl=es
e to
.d
Para utilizar esta librería, instale los paquetes que se corresponden con npm:
Según los componentes PrimeNG que instale, es probable que también deba especificar las hojas
de estilos según el código del componente que integra el componente en su template:
"styleUrls": [
"../node_modules/primeng/resources/themes/omega/theme.css",
"../node_modules/font-awesome/css/font-awesome.min.css",
"../node_modules/primeng/resources/primeng.min.css"
]
2. Presentación de un mapa Google Map estático
Antes de gestionar un mapa Google Map con la librería de componentes PrimeNG, presentamos un
ejemplo de creación de un mapa centrado en la ciudad de Madrid directamente a través de un código
JavaScript. No olvide insertar su clave en la URL del API Google Map.
<!DOCTYPE html>
<html>
<head>
<title> Mapa Google Map </title>
<meta charset="utf-8" />
<style type="text/css">
rg
html { height: 100%; }
o
n.
body { height: 100%; }
io
</style>
<script src="https://maps.googleapis.com/maps/api/js?
ac
m
key=<YourKey>
ra
<script type="text/javascript">
pr
function initMap() {
do
center: latlng,
w
mapTypeId:
w
google.maps.MapTypeId.ROADMAP
w
};
var map
= new google.maps.Map(documento
.getElementById("map_canvas"),
myOptions);
El argumento callback de la URL del Google API referencia la función que inicia el mapa.
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
También añadirá un servicio REST que devuelve a la ruta /placeInfos, una lista de listas que contiene
el nombre, las coordenadas y el número total de productos vendidos en cada lugar.
(Por supuesto, este servidor debería recuperar sus datos a partir de una base de datos.)
"use strict";
rg
let cors = require("cors");
o
let app = express();
n.
app.use(cors());
io
app.listen(8889); ac
m
let data = [
ra
{ "place": "Madrid",
og
"lat": 40.4167047,
pr
"lng": -3.7035825,
do
...
e
.d
]
w
},
w
...
w
];
node GOOGLEMAP/dataServerForGMap.js
A continuación se muestra el código del servicio Angular, que consulta los servicios REST del
servidor Node.js a través del servicio HTTP propuesto por Angular (archivo app.service.ts):
@Injectable()
export class DataService {
org
constructor(private http: HttpClient) {}
n.
io
getPlaceInfos(): Observable<any> { ac
let url: string = "http://localhost:8889/placeInfos";
m
return this.http.get(url);
ra
}
og
}
pr
do
app.component.html).
e
.d
overlays de anotaciones.
w
A continuación se muestra el código del componente AppComponent host que utiliza el servicio
para posicionar el mapa en las coordenadas del primer lugar, y después crea tantos marcadores y
overlays como lugares. Al hacer clic en uno de los círculos que decoran una ciudad donde se realizan
pedidos, se llama al método handleOverlayClick() para mostrar un mensaje (el método
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public overlays: any[];
public options: any;
org
ngOnInit() {
n.
this.dataService.getPlaceInfos()
io
.subscribe(res => { ac
let latLng = new google.maps.LatLng(res[0][1], res[0][2]);
m
this.options = { center: latLng, zoom: 10 };
ra
this.overlays = [];
og
.LatLng(infos[1], infos[2]);
to
this.overlays
e
.push(new google.maps
.d
this.overlays
w
w
handleMapClick(event) {}
handleOverlayClick(event) {
console.log("Clic sur "+event.overlay.name);
}
org
n.
io
4. Gestión de un chart asociado a un Google Map ac
m
Finalmente, va a combinar el componente que muestra un histograma con el mapa Google Map, de tal
ra
manera que la selección de una ciudad en el Google Map genere el gráfico de las ventas. Solo se
og
En la ruta /place=:place, el servicio que devuelve los datos de las ventas relativas a un lugar
dado como argumento.
"use strict";
let express = require("express");
let cors = require("cors");
let app = express();
app.use(cors());
app.listen(8889);
let data = [
{ "place": "Madrid",
"lat": 40.4167047,
"lng": -3.7035825,
rg
"sales": [{"name": "product1", "nb": 50}, ... ]
o
},
n.
...
io
]; ac
m
app.get(" /place=:place", function(req, res){
ra
if (datum.place == place) {
sales = datum.sales;
e to
break;
.d
}
w
}
w
res.setHeader('Access-Control-Allow-Origin', '*');
w
node GOOGLEMAP_CHART/dataServerForGMapAndChart.js
El servicio Angular (archivo DataService) alberga los dos métodos que invocan los dos servicios
REST vistos anteriormente:
rg
import { Injectable } from '@angular/core';
o
import { HttpClient } from '@angular/common/http';
n.
import { Observable } from 'rxjs/';
io
ac
@Injectable()
m
export class DataService {
ra
og
return this.http.get(url);
.d
}
w
w
getPlaceInfos(): Observable<any> {
w
<app-chart [place]="place"></app-chart>
@Component({
rg
selector: 'app-root',
o
templateUrl: './app.component.html',
n.
styleUrls: ['./app.component.css']
io
}) ac
export class AppComponent {
m
public overlays: any[];
ra
...
e
.d
handleOverlayClick(event) {
w
this.place = event.overlay.name;
w
w
}
}
...
@Input() place: string;
org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w
org
De una manera general, sea cual sea el componente implementado, la component directive (la etiqueta
n.
que lleva como nombre el selector del componente) se utiliza en la plantilla del componente que la
io
integra (este componente «host» se puede activar en una ruta). ac
m
A continuación se muestra el esquema de programación de utilización de un componente de
ra
PrimeNG:
og
pr
(La clase que implementa el componente host, puede por supuesto especificar los valores de las
e
<p-calendar [(ngModel)]="date"
@Component({
selector: 'app-root',
rg
templateUrl: './app.component.html',
o
styleUrls: ['./app.component.css',
n.
"../../node_modules/primeng/resources/themes/omega/theme.css",
io
"../../node_modules/font-awesome/css/font-awesome.min.css", ac
"../../node_modules/primeng/resources/primeng.min.css"
m
]
ra
})
og
}
e
}
.d
w
w
w
rg
Esta librería necesita instalar los siguientes módulos:
o
n.
npm install --save @angular/material
io
npm install --save @angular/cdk ac
m
A continuación se muestra la lista del código principal de la aplicación de prueba:
ra
og
fecha seleccionada.
e
.d
app.component.html). Como hemos visto en la sección anterior, las component directives (las
etiquetas que tienen como nombre el selector del componente), se utilizarán en las plantillas del
componente que las integra. Con Material, las component directives se introducen con el prefijo
mat-.
<mat-form-field>
<input matInput [matDatepicker]="picker"
placeholder="Elija una fecha"
onSelect)="onChange($event.value)">
<mat-datepicker-toggle matSuffix [for]="picker">
</mat-datepicker-toggle>
rg
import { MatFormFieldModule, MatInputModule,
o
MatDatepickerModule, MatNativeDateModule
n.
io
} from '@angular/material';
ac
@Component({
m
selector: 'app-root',
ra
templateUrl: './app.component.html',
og
styleUrls: ['./app.component.css']
pr
})
do
}
w
La librería ngx-bootstrap es una variante de la célebre librería bootstrap, cuyo objetivo original es
og
gestionar el web responsive (la adaptación de una interfaz a los diferentes tamaños de pantallas). A lo
pr
largo del tiempo, esta librería también se ha hecho muy popular, con una oferta ampliada de
do
componentes gráficos.
to
Vamos a descubrirla implementando el componente Navbar, que permite crear una barra de
e
.d
navegación horizontal.
w
w
Nuestra aplicación de prueba va a permitir presentar varios enlaces que invocan a diferentes
componentes a través del router.
rg
La etiqueta <router-outlet> para hacer de interfaz con la visualización de los
o
componentes invocados por el router.
n.
io
La utilización de las clases definidas por ngx-bootstrap.
ac
m
<app-menu></app-menu>
ra
og
<div class="container-fluid">
<div id="main" class="row justify-content-center">
pr
<router-outlet></router-outlet>
do
</div>
to
</div>
e
.d
w
A continuación se muestra el código del componente de más alto nivel AppComponent (archivo
w
app.component.ts):
w
rg
<a class="nav-link" routerLink="/component2">
o
Component2
n.
</a>
io
</li> ac
</ul>
m
ra
...
pr
</ul-->
do
to
</div>
e
</nav>
.d
w
w
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.css']
})
export class MenuComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
rg
@NgModule({
o
declarations: [
n.
AppComponent,
io
Component1Component,
Component2Component,
ac
m
MenuComponent
ra
],
og
imports: [
pr
BrowserModule,
do
AppRoutingModule,
FormsModule
to
],
e
.d
providers: [],
w
bootstrap: [AppComponent]
w
})
w
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
La aplicación presenta una barra de navegación que incluye un enlace de conexión (no
implementado) y dos enlaces que permiten invocar a dos componentes. La conexión podría modificar
los enlaces presentados en este ejemplo.
rg
anotación de mapas geográficos, a través de capas superpuestas de anotaciones gráficas. De esta
o
manera, puede integrar en una aplicación Angular, los Google Maps sobrecargados por overlays.
n.
io
Un gran número de librerías ofrece componentes gráficos (los UI componentes), listos para usar.
ac
Hemos elegido presentarle las librerías PrimeNG y Material, cuya oferta es particularmente rica.
m
En un primer momento, hemos explicado cómo crear una gráfica (en este caso, un histograma)
ra
og
utilizando la librería JavaScript gráfica D3.js. D3.js inserta en el DOM elementos del lenguaje SVG,
quees un lenguaje de etiquetas que permite realizar gráficos vectoriales y animarlos. Las bases de la
pr
librería D3.js, así como del lenguaje SVG, también se han presentado. Hemos insistido en la
do
capacidad de D3.js de reutilizar o crear elementos gráficos, asociando elementos del DOM que
e to
existen, a un nuevo flujo de datos que proviene del servidor. Para ilustrar la utilización de D3.js,
.d
hemos creado un proyecto (llamado CHARTwithD3), para el que un servidor envía los datos (se
w
corresponde con las ventas realizadas a partir de diferentes lugares), a un componente que
w
w
permite seleccionar uno de estos lugares y mostrar un histograma que representa las ventas asociadas.
También hemos introducido la librería de más alto nivel dc.js aquí asociada a Crossfilter, que permite
mostrar gráficos estadísticos después de una operación analítica de datos (el proyecto
CHARTwithDC ilustra este punto).
También hemos presentado un ejemplo de publicidad en mapas geográficos, con el API Google
Maps. Se han introducido las bases de la creación de un mapa y de marcadores. Un primer proyecto
llamado GOOGLEMAP utilizando el módulo gmap de la librería PrimeNG, ha permitido manipular
un mapa a través de un componente. Este proyecto también ha ilustrado la creación de overlays que
permiten sobrecargar un mapa de elementos gráficos (en nuestro ejemplo, de círculos centrados en las
ciudades donde se realizan los pedidos).
Para implementar las pruebas, Angular tiene un módulo específico mientras que Angular CLI ofrece
rg
tres herramientas que permiten especificar y ejecutar las pruebas.
o
n.
El módulo de prueba de Angular @angular/core/testing, ofrece un componente llamado TestBed
io
(testbed significa «banco de prueba»), que permite crear un módulo de prueba que admite como
ac
argumento el componente a probar.
m
ra
Angular CLI ofrece el framework Jasmine y las herramientas de prueba Karma y Protractor:
og
La herramienta Karma permite ejecutar las pruebas unitarias especificadas con Jasmine.
to
Angular realmente se ejecuta (la carpeta e2e se destina a la utilización de esta herramienta, que
.d
A continuación, es suficiente con especificar el atributo del componente o el sub-elemento del DOM
a probar, así como el valor esperado.
rg
de referencia.
o
toBeGreaterThan(<valor de referencia>): el valor debe ser superior al
n.
io
valor de referencia.
ac
toEqual(<valor de referencia>): el valor debe ser igual al valor de
m
referencia.
ra
og
referencia.
do
AppComponent). Este archivo especifica tres pruebas unitarias que son explícitas.
w
w
El código siguiente retoma el archivo de prueba creado para el primer proyecto de ejemplo, del
capítulo Introducción):
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
A continuación se muestra una primera prueba unitaria sobre la creación del componente:
rg
A continuación se muestra una segunda prueba unitaria sobre la creación de un atributo de la clase
o
que implementa el componente:
n.
io
it(`should have as title 'INTRODUCTION', ac
awosync(() => {
m
const fixture = TestBed.createComponent(AppComponent);
ra
expect(app.title).toEqual('INTRODUCTION');
pr
}));
do
to
async(() => {
w
Para ejecutar estás pruebas con Karma, es suficiente con ejecutar el comando npm test o el
comando ng test (el primer comando ejecuta el segundo).
Se abre el navegador (Chrome por defecto) y se puede visualizar los resultados de las pruebas
unitarias:
(Los servidores Node.js están en producción, no olvide modificar las direcciones IP de los servidores
que los albergan, implementados en sus servicios Angular, lo mejor es factorizarlos en un servicio
rg
Angular ad hoc.)
o
n.
Para esto, utilice el comando ng buid -prod, que genera una carpeta llamada dist, que puede
io
opcionalmente renombrar y llevar tal cuál a una de las carpetas gestionadas por su servidor preferido
ac
(Apache, Node.js, http-server...). También puede (según los casos), utilizar la opción --bh para
m
ra
Enejemplos de despliegue en Apache, Node.js y http-server que siguen, tomamos como ejemplo
los
pr
Para simplificar los ejemplos que siguen, consideremos que el servidor físico de desarrollo es el
.d
mv dist OnLineSales
sudo cp -R OnLineSales/ /var/www/html
<direccionIP:port>/OnLineSales/index.html
rg
Es preferible crear un servidor distinto del que sirve los datos de su base MongoDB, a través de
o
servicios web REST. Este primer servidor que responde en el puerto 8888, elige que haga que su
n.
nuevo servidor en el puerto 8880. Por otro lado, dado que este servidor que gestiona exclusivamente
io
su aplicación, no es útil modificar su raíz. ac
m
Como en el primer servidor, utilice el framework Express para crear un servidor HTTP.
ra
pr
ng build --prod
do
to
despliegue:
w
w
mv dist OnLineSales
w
app.use(express.static(__dirname + '/OnLineSales'));
pp.listen(8880);
Ejecute el servidor:
<direcciónIP:8880>
rg
A continuación se muestran cuatro opciones entre las que se ofrecen:
o
n.
-p: especificación del puerto.
io
-S: activa HTTPS.
ac
m
--cors: activa cors manipulando el encabezado Access-Control-Allow-Origin.
ra
og
ng build --prod
e
.d
w
despliegue:
w
mv dist OnLineSales
cp -R OnLineSales/ <carpeta de despliegue>
E invoque a su aplicación de la siguiente manera (por defecto, http-server ocupa el puerto 8080):
<direcciónIP:port>/index.html
rg
excelente en la gestión de los datos, gracias a servicios web gestionados por el framework Express.
o
n.
Angular es el framework aplicativo JavaScript más exitoso, pero también es el más complejo. Este
io
libro es una introducción a su implementación dentro de una arquitectura MEAN. A lo largo de las
ac
páginas, hemos presentado numerosas facetas de este ecosistema exuberante y las hemos ilustrado
m
con numerosos casos concretos.
ra
og
Hemos discutido los entornos de desarrollo más adecuados para escribir código JavaScript y de
específica, código Angular. Utilizar uno u otro entorno de desarrollo normalmente es muy
manera
pr
subjetivo, pero le recomendamos Visual Studio, que ofrece una facilidad remarcable en la
do
Para terminar, le hemos invitado a optimizar sus técnicas de análisis y visualización de información,
.d
w
explorando las ventajas de la librería Crossfilter, que permite analizar y filtrar grandes volúmenes de
w
Y para aquellos que se interesan por la portabilidad de las webs apps Angular en teléfonos móviles, el
framework Ionic le dará todas las claves para generar aplicaciones Android, Chrome, iOS y Windows
Phone.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:56:14 p. m.]
Dogram Code
https://dogramcode.com/biblioteca
https://dogramcode.com/programacion