100% encontró este documento útil (1 voto)
1K vistas

Angular y Nodejs

desarrollo de software

Cargado por

hemerson
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
100% encontró este documento útil (1 voto)
1K vistas

Angular y Nodejs

desarrollo de software

Cargado por

hemerson
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 370

Visita la Biblioteca Online Gratis!

+300 Libros PDF

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

implementación de un servidor Node.js extremadamente reactivo y por otro lado, el framework de


og

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

Siendo la visualización de información un área emblemática de Angular, también se estudia la


.d

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

2. ¿ Dónde escribir código JavaScript ?


3. Las herramientas del navegador y la depuración
4. Los elementos de programación básicos
4.1 Las variables
4.1.1 Los tipos internos
4.1.2 El transtipado
4.2 Las estructuras de datos comunes
4.3 Aplicación de expresiones regulares
4.4 Los bloques de instrucciones
4.5 Las estructuras condicionales
4.5.1 La estructura if ... else ...
4.5.2 La estructura switch de conmutador múltiple
4.6 Las estructuras iterativas
4.6.1 Las estructuras iterativas con índices de bucle
4.6.2 Las estructuras iterativas sin índices de bucle

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.1 Los principios de la programación orientada a objetos con JavaScript


og

6.2 Los objetos literales


pr

6.3 La herencia por encadenamiento de prototipos


do

6.3.1 La propiedad __proto__ del objeto del que heredan


to

6.3.2 La propiedad prototype


e

6.4 La creación de un objeto por medio de la llamada de una función constructora


.d

6.5 Ejemplos de implementaciones de un método


w

6.6 La problemática del objeto actual (this)


w
w

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

8.2 Segundo ejemplo: un observable sobre un entero


8.3 Tercer ejemplo: un observable sobre un timer
8.4 Las promises
 Extensiones JavaScript para las clases
1. Presentación de las extensiones de JavaScript
2. El lenguaje TypeScript
2.1 El transpiler tsc
2.2 Tipado estático de las variables
2.2.1 Los tipos básicos
2.2.2 Tipado de variables no escalares
2.2.3 El tipo enum
2.2.4 El tipo genérico any
2.2.5 Tipado de las funciones
2.3 Las clases
2.3.1 La herencia

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

3.3.2 Las interfaces


pr

 La plataforma Node.js
do

1. Presentación de Node.js
to

2. Instalación y prueba de Node.js


e

2.1 Creación del archivo de prueba


.d

2.2 Instalación y prueba de Node.js en Ubuntu


w
w

2.3 Instalación y prueba de Node.js en Windows


w

2.4 Instalación y prueba de Node.js en Mac OS


3. La modularidad de Node.js
3.1 Los módulos y los paquetes
3.1.1 El administrador de módulos de Node.js: npm
3.1.2 Especificación de las dependencias: el archivo package.json
3.2 Creación de un primer servidor Node.js de prueba
3.3 Creación y reutilización de un módulo
3.4 Creación de un servidor que devuelva los datos
3.5 El módulo express
3.5.1 Gestión de rutas REST con el módulo express
3.5.2 Gestión de las plantillas con el módulo express
3.5.3 Especificación de las dependencias en un archivo package.json
3.5.4 Instalación del módulo express
3.6 El módulo fs (FileSystem)

https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN

3.7 Prueba de un servidor Node.js


3.7.1 Creación de un archivo de datos JSON
3.7.2 La problemática del control de acceso HTTP
3.7.3 Retorno al cliente de un archivo JSON
3.7.4 Configuración de las rutas
3.7.5 Gestión de los argumentos

4. Securización de un servidor Node.js (protocolo HTTPS)


4.1 Creación de la autoridad de certificación
4.2 Creación del certificado
4.3 Creación del servidor
5. Conocimientos adquiridos en este capítulo
 El SGBD NoSQL MongoDB
1. Introducción
2. ¿ Por qué utilizar una base de datos NoSQL ?
3. Presentación de MongoDB

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

4.1.3 Utilización de MongoDB en línea de comandos


og

4.2 Visualización de la lista de las bases de datos


pr

4.3 Creación de una base de datos


do

4.4 Visualización de la lista de las colecciones


to

4.5 Creación de una colección


e

4.5.1 Inserción de los documentos en una colección


.d

4.5.2 Importación de documentos a partir de un archivo


w

4.5.3 Exportación de los documentos de una colección en un archivo JSON


w
w

4.6 Interrogación de una colección


4.6.1 Interrogación a través de un objeto "filtro"
4.6.2 Los operadores de comparación, los operadores de conjuntos y lógicos
4.6.3 El operador $exists
4.6.4 El operador $in
4.6.5 El operador $nin
4.6.6 El operador $or
4.6.7 El operador $not
4.6.8 El operador $nor
4.7 Aplicación de expresiones regulares
4.8 Las proyecciones y el método distinct()
4.8.1 Las proyecciones
4.8.2 El método distinct()
4.9 Referenciado los documentos y joins

https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN

4.9.1 Los objetos anidados (nested objects)


4.9.2 Los objetos referenciados
4.9.3 Los joins
4.10 Actualización y eliminación de un documento
4.10.1 Actualización de un documento
4.10.2 Eliminación de un documento
4.11 Eliminación de una colección
5.Utilización de MongoDB a través de Node.js
5.1 Instalación del módulo MongoDB para Node.js
5.2 Conexión al servidor MongoDB
5.3 Inserción de datos desde un servidor Node.js
5.4 Consultar datos desde un servidor Node.js
5.4.1 Explotación del resultado del método find()
5.4.2 Utilización del método toArray()
5.5 Sincronización de las consultas

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

6.1 La estructura de un servidor Node.js que consulta a MongoDB


og

6.2 La problemática del cross-origin resource sharing


6.3 Ejemplos de gestión de rutas
pr

6.3.1 Gestión de una ruta para listar las marcas


do

6.3.2 Gestión de una ruta para filtrar los productos


to

6.3.3 Búsqueda de un producto a partir de su identificador interno


e
.d

7.El hilo rojo del lado servidor


7.1 Creación de la colección
w
w

7.2 Implementación de dos búsquedas sobre los productos


w

7.2.1 La superestructura del servidor


7.2.2 Gestión de la ruta que filtra los documentos usando diferentes criterios
7.2.3 Gestión de la ruta que devuelve un documento a través de su identificador interno
7.2.4 Ejemplos de consulta en el servidor

8. Conocimientos adquiridos en este capítulo


 Introducción al framework aplicativo Angular
1. Presentación de Angular
1.1 Una evolución radical de AngularJS
1.2 La modularidad de la aplicación: los módulos y los componentes
1.2.1 Los módulos
1.2.2 Los componentes y los servicios
1.3 Manipulación de los componentes como etiquetas
1.4 Utilización de una extensión en JavaScript (TypeScript o Dart)
2. Angular respecto al framework MVC (incluso VVM)

https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN

3.Implementación de una aplicación Angular


3.1 Presentación de Angular CLI
3.2 Instalación de Angular CLI
3.3 Creación de un proyecto Angular con Angular CLI
3.4 Estructura de las carpetas de un proyecto Angular CLI
3.5 Un primer componente creado por Angular CLI
3.6 El root module creado por Angular CLI
3.7 Actualización de Angular a través de Angular CLI
4. Generación del código JavaScript a partir de TypeScript
5. Los decoradores
6.Creación de un nuevo componente que muestra un mensaje
6.1 Creación del componente
6.1.1 La plantilla HTML
6.1.2 La hoja de estilos
6.2 Interfaz de componentes en el componente raíz

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

7.3 El hook ngDoCheck()


og

7.4 El hook ngOnDestroy()


pr

8. Conocimientos adquiridos en este capítulo


do

 Angular: las plantillas, bindings y directivas


to

1. Las plantillas
e

1.1 Anidación de las plantillas


.d

1.2 Las plantillas insertadas (embedded templates)


w

1.3 Las plantillas externalizadas


w
w

2. Data bindings entre el componente y la plantilla


2.1 Acceso a los elementos del DOM
2.2 Interpolación de una variable en una plantilla
2.3 Property binding
2.4 Event binding
2.5 Two-way data binding
3. Las directivas
3.1 Las directivas estructurales
3.1.1 La directiva *ngFor
3.1.2 La directiva *ngIf
3.2 Las directivas de atributos
3.3 Envío de información de un componente a su padre (@Output())
4. Los pipes
5. Ejemplo de resumen: un formulario de autenticación

https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN

6. El hilo rojo: creación de un componente que muestra los productos


6.1 El componente
6.1.1 La clase que implementa el componente
6.1.2 La plantilla HTML
6.1.3 La hoja de estilos
6.2 El módulo que especifica el componente
6.3 Activación del módulo
6.4 La página web front
6.5 Lanzamiento de la aplicación
7. Conocimientos adquiridos en este capítulo
 Angular y la conexión a Node.js: los servicios
1. Introducción
2. Inyección de dependencias
3. Utilización de los servicios para la transferencia de datos
3.1 Recuperación de datos formateados en JSON

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

4.2.2 Visualización de los productos según los criterios de búsqueda


og

4.2.3 Visualización de los productos asociados a las palabras clave


pr

4.2.4 Acceso a un producto por su identificador


do

4.3 Gestión de la cesta de la compra


to

4.3.1 Visualización de los identificadores de los productos de la cesta de la compra


e

4.3.2 Visualización de todos los productos de la cesta de la compra


.d

4.3.3 Adición de un producto en la cesta de la compra


w

4.3.4 Eliminación de un producto de la cesta de la compra


w
w

4.3.5 Reinicialización de la cesta de la compra

5. La librería NgRx y los stores


6. Conocimientos adquiridos en este capítulo
 Angular y la gestión de las rutas internas
1. Principio general del enrutado
1.1 ¿ Por qué establecer un enrutado ?
1.2 Las rutas, el router, las tablas de enrutado
1.3 Las vistas activadas por las rutas
1.4 Ejemplo de enrutado
1.5 Definición de un árbol de vistas
1.6 Utilización de los outlets con nombre
2. La sintaxis de las rutas
2.1 Las dos sintaxis de una ruta: cadena o link parameters array
2.2 Las rutas absolutas y las rutas relativas

https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN

2.3 Configuración de las rutas


2.4 Asociación de una ruta a un auxiliary outlet
3. Selección de las rutas
3.1 La directiva routerLink
3.2 El método navigate()
3.3 Ejemplo de ruta
4. Gestión de las rutas del controlador hacia el componente destino
4.1 Configuración de la tabla de enrutado
4.2 Las propiedades de una ruta
4.3 Soporte de una ruta por varios módulos/tablas de enrutado
4.4 Control de las rutas: guardián
4.5 Invocación de un componente
4.6 Captura de una ruta durante la invocación de un componente
5. Gestión de rutas en El hilo rojo
5.1 El módulo de enrutado asociado al root module

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

2. Creación de charts con D3.js y dc.js


2.1 Instalación de D3.js
og

2.2 El lenguaje SVG


pr

2.3 Generación de elementos gráficos asociados a los objetos de una colección


do

2.4 Selección y modificación de elementos del DOM


to

2.4.1 Adición de elementos gráficos


e

2.4.2 Sustitución de elementos gráficos


.d

2.5 Implementación de listeners de eventos


w
w

2.6 Integración de D3.js en Angular


w

2.6.1 Un servidor virtual de datos comerciales


2.6.2 El servicio que accede a los datos del servidor
2.6.3 La plantilla del componente que muestra un histograma
2.6.4 Implementación del componente que muestra un histograma
2.7 Las librerías dc.js y Crossfilter
2.7.1 Instalación de dc.js y de Crossfilter
2.7.2 Implementación del componente que muestra el histograma

3. Integración de mapas Google Map en un proyecto Angular


3.1 Instalación de los requisitos previos técnicos
3.1.1 Instalación de los tipos TypeScript
3.1.2 Instalación de la librería PrimeNG
3.2 Presentación de un mapa Google Map estático
3.3 Un componente PrimeNG para gestionar un mapa Google Map
3.4 Gestión de un chart asociado a un Google Map

https://dogramcode.com/programacion
Libro Angular y Node.js - Optimice el desarrollo de sus aplicaciones web con una arquitectura MEAN

4. Implementación de componentes gráficos


4.1 Otro ejemplo de componente PrimeNG (Calendar)
4.2 La librería de componentes Material
4.3 La librería ngx-bootstrap
5. Conocimientos adquiridos en este capítulo
 Prueba y despliegue
1. Test
2. Despliegue
2.1 Despliegue con Apache
2.2 Despliegue con Node.js
2.3 Despliegue con http-server (acceso directo a Node.js)
3. Para ir más lejos

í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

Nivel Número de páginas Publicación


Medio a Experto 370 páginas Junio 2020

DESCARGAS

Descargar los ejemplos del libro (1.75 Mo)

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

gráficos muy populares (PrimeNG, Material y ngx-bootstrap).


w
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

implementen servicios web, utilizando el framework Express.

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

export class <nombre del servicio> {


og

...
pr

}
do

<nombre del servicio> concierne a diferentes nombres posibles de servicios y los puntos
e to

suspensivos, el bloque de instrucciones que implementa este servicio.


.d
w
w
w

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

Por lo tanto, la problemática consiste en crear aplicaciones web:


w
w

inicialmente cargadas en el cliente;


escritas en JavaScript/HTML/CSS para ejecutarse de manera natural en el ecosistema del
navegador;
construidas modularmente;
que dialoga con uno o varios servidores a través de los servicios web.

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

a la flexibilidad del servidor de bases de datos NoSQL MongoDB.


og

Crear páginas web de manera modular y reutilizable (con componentes y módulos), gracias al
pr

framework aplicativo Angular.


do

Homogenizar el desarrollo alrededor del lenguaje JavaScript y de su extensión TypeScript, que


to

permite una programación orientada a objetos con clases (también es posible utilizar el lenguaje
e

Dart).
.d
w
w

1. El principio de las aplicaciones mono página (single page


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.

Los beneficios de una arquitectura como esta, son numerosos:

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:

que implican frecuentes transferencias de datos entre el cliente y el servidor;


pero que tienen una complejidad funcional relativamente limitada (es decir, no gestionan un número
excesivo de vistas).

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.

2. El paradigma de diseño modelo vista-controlador


Angular (esté o no sumergido en una arquitectura MEAN), se proyecta en el bien conocido paradigma de
diseño modelo vista-controlador (MVC). Este paradigma se ilustra de manera clásica por el gráfico
siguiente:

org
n.
io
ac
m
ra
og
pr
do
to

De una manera clásica:


e
.d

El modelo agrupa los datos (tanto si se gestionan por el servidor o el cliente) y las operaciones
w
w

realizadas sobre ellos.


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

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:37:59 p. m.]


operaciones asociadas (lo que permite hacer fácilmente evolucionar las operaciones sustituyendo un
componente por otro).

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.

A continuación se muestra el esquema que representa el paradigma MVVM:

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

bases de datos NoSQL MongoDB.


w
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

A continuación se muestra el esquema que enfatiza la administración de rutas dentro de la aplicación

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

Una colección Users que memoriza los identificadores de los clientes.


og


pr

Una colección Products que memoriza los productos en venta.


do

Una colección Carts que memoriza las cestas de la compra en curso.


e to

Un servidor Node.js que gestiona los servicios web que intercambian datos entre la base de datos
.d

MongoDB y la aplicación Angular.


w
w

Una aplicación mono página Angular modular que gestiona, a diferencia del módulo principal, dos
w

módulos (susceptibles de ser reutilizados en otras aplicaciones):

Un módulo que permite seleccionar los productos aplicando cualquier combinación de criterios
de búsqueda sobre:

sus tipos;

sus marcas;

sus precios (a través de los intervalos de precio);

su popularidad.

o realizando una búsqueda sobre palabras clave. 


Un módulo que permite añadir o eliminar productos en la cesta de la compra y visualizar su
contenido.
https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:38:34 p. m.]
La autenticación de un usuario (que condiciona la manipulación de la cesta de la compra), se tendrá en
cuenta por parte de un componente directamente gestionado por el módulo principal.

A continuación se muestran algunas vistas de esta aplicación.

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

Se identifica y abre su cesta de la compra, que está vacía:

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:

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:38:34 p. m.]


Se muestra la información relativa a los teléfonos:

org
n.
io
ac
m
ra
og

El internauta selecciona el famoso bigPhone 9 de Threestars y lo añade a la cesta de la compra (después de


pr

haberse identificado):
do
e to
.d
w
w
w

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:38:34 p. m.]


Introducción a JavaScript
En este capítulo, describiremos rápidamente el lenguaje JavaScript, suponiendo que domina al menos un
lenguaje informático y los conceptos básicos (tipado, variables, estructuras condicionales y iterativas, etc..)
de la programación.

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

A continuación, JavaScript evolucionó mucho funcionalmente (por ejemplo, permitiendo el acceso


do

asíncrono a los datos proporcionados por el servidor) e incluso ha invertido recientemente en el «lado
to

servidor», con el entorno Node.js.


e
.d

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

El lenguaje JavaScript se sitúa en la confluencia de dos paradigmas de programación: el paradigma de la


programación funcional y el de la programación orientada a objetos y esto lo convierte en un lenguaje muy
difícil de dominar. Detallaremos estas dos facetas en este capítulo.

2. Panorama de la utilización de JavaScript


A continuación se muestra un resumen, no exhaustivo, de la utilización de JavaScript del lado cliente y del
lado servidor.

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.

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:38:58 p. m.]


Acceder a los elementos del DOM y llegado el caso, modificarlos con el API DOM de JavaScript o
con las librerías de más alto nivel como jQuery. El DOM representa la jerarquía de los objetos
creados por las etiquetas (HTML...) en la memoria del navegador.
Gestionar los flujos de datos asíncronos con el servidor, a través de la arquitectura AJAX
(Asynchronous JavaScript and XML) (normalmente con la utilización de la librería jQuery).
Gestionar una comunicación bidireccional y full-duplex entre clientes y servidor(es), gracias a los
web sockets (en el caso de la programación de juegos, mensajes instantáneos, etc..).
Crear interfaces gráficas utilizando la etiqueta <canvas> o las librerías D3.js o dc.js, que
encapsulan el lenguaje SVG (Scalable Vector Graphics).
Implementar un almacenamiento de datos en el sistema de archivos local, a través de los objetos
JavaScript sessionStorage y localStorage.

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

3. Las librerías y los frameworks aplicativos JavaScript


e
.d
w

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

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:38:58 p. m.]


pantallas).

 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.

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:38:58 p. m.]


¿Dónde escribir código JavaScript?
Para una utilización del lado cliente fuera del «bundlelización» (veremos más adelante qué es un bundle), el
código JavaScript se debe externalizar en archivos de extensión .js y relacionar con el código HTML a
través de la etiqueta <script>, como en el siguiente.

Consideremos el script JavaScript llamado hola.js:

console.log("Hola");

Y la página HTML test.html, que implementa este script:

<html>
<head>
<script src="hola.js"
 type="text/javascript" language="javascript">
</script>
</head>
<body> JavaScript le dice hola en la consola </body>
</html>

Este ejemplo de código se ejecuta habiendo sido cargado (abierto) en su navegador.

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.

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:39:14 p. m.]


Las herramientas
 del navegador y la
depuración
Las herramientas del navegador (pulse la tecla [F12] para mostrar un número determinado), le permite entre
otras cosas:

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

pestañas del navegador.


e
.d

Algunos navegadores ofrecen dos consolas:


w

La consola web que muestra los mensajes (emitidos por los scripts JavaScript o relativos a las cargas
w
w

de los recursos inducidos), relativos al recurso cargado en la pestaña actual.


La consola del navegador, que muestra los mensajes relativos a los recursos cargados en todas las
pestañas.

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:39:32 p. m.]


Los elementos de programación básicos
La sintaxis «básica» de la programación en JavaScript, es la del lenguaje C, que a su vez fue adoptada por
el lenguaje Java, pero esta sintaxis se ha estandarizado por ECMAScript, que define un conjunto de normas
de programación.

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

La palabra reservada const: la variable solo es accesible en modo lectura.


e to

a. Los tipos internos


.d
w

JavaScript tipa (de manera interna) las variables, según seis tipos primitivos:
w
w

Un tipo booleano: true y false.


Un tipo nul: null.
Un tipo indefinido (resultado de los accesos a una variable que no existe o no tiene valor):
undefined.
Ejemplo de variable creada, pero de valor indefinido: var i.
Un tipo para los números que sean enteros o reales: number.
Un tipo para las cadenas de caracteres: string.
Las cadenas de caracteres se pueden rodear por comillas simples o dobles; veremos en nuestros
ejemplos que generalmente los rodeamos por dobles comillas.

Un tipo para los símbolos (este tipo se introdujo por ECMAScript 6 y no se desarrollará aquí).

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


Y el tipo Object en el resto de casos. Por lo tanto, JavaScript es un lenguaje orientado a objetos.
Material para descargar
A continuación se muestran algunos ejemplos de creación de variables escalares (simplificando las cadenas
de caracteres en esta categoría):

var raro; // su valor es: undefined


var i = 0;
let bool = true;
const nombre = "Pierre";

El tipo de una variable se puede probar por la función typeof().

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

parseFloat(<string>) extrae (si existe) el valor flotante utilizado como prefijo de la


og

cadena:
pr

parseFloat("123.45") devuelve 123.45.


do

parseFloat("123.45bla") devuelve 123.45.


e to

El operador unario + extrae el valor numérico que debe representar la cadena:


.d
w

+"123.45" devuelve 123.45.


w

+"123.45bla" devuelve NaN.


w

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

2. Las estructuras de datos comunes


En JavaScript, dos estructuras de datos son omnipresentes:

Los objetos.

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


Las listas (que se corresponden con las tablas dinámicas y que también son 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»:

let miLista = new Array(1, 2, 3, "ya");


let miLista = [1, 2, 3, "ya"];

El número de elementos de una lista se da por el atributo length:

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

indexOf(<elemento>), para conocer la posición de la primera ocurrencia de un elemento


ra
og

en la lista (devuelve -1 si el elemento no se encuentra).


pr

push(<elemento>), para añadir un elemento al final de la lista.


do

splice(...), para añadir o eliminar elementos a una posición dada en una lista:
e to

<lista>.splice(<posición>,
.d

<número de elementos a eliminar>,


w

[elementos a añadir])
w
w

Por ejemplo:

Añadir el valor 4 en la posición 3 en miLista:

miLista.splice(3, 0, 4)

Eliminar el valor 4 que acaba de añadirse:

miLista.splice(3, 1)

sort(), para ordenar los elementos de la lista.

Para aplicar una operación sobre cada elemento de una lista, hay dos métodos principales:

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


forEach(<función de callback>), que permite aplicar una operación sobre cada
elemento.
map(<función de callback>), que hace lo mismo, pero devuelve la lista modificada.

Estos dos métodos utilizan funciones de callback (consulte la sección Una función que se pasa como
argumento (función de callback)).

3. Aplicación de expresiones regulares


Aunque la aplicación de expresiones regulares no sea un elemento de la programación básica, la
presentamos aquí, ya que está relacionada con las cadenas de caracteres y normalmente son muy útiles.

 No se detallará aquí la sintaxis de las expresiones regulares.

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

El método exec(), para extraer datos de la cadena.


pr
do

Las dos sintaxis que utilizan test() o exec() son:


e to

/<expresión regular>/.test(<cadena de caracteres>)


.d
w
w

/<expresión regular>/.exec(<cadena de caracteres>)


w

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):

let mail = "[email protected]";


if (/[\w.]+@[\w.]+\.\w+/.test(mail)) console.log("Formato de email");

A continuación se muestra un ejemplo que extrae el primer y el último número de la cadena:

let string = "atención 1 2 3 ya";


let data = /(\d+).+(\d+)/.exec(string);
if (data.length == 3) console.log(data[1]+" "+data[2]);

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


En este último ejemplo, data[0] contiene la parte de la cadena de caracteres cubierta por la expresión
regular (aquí 1 2 3).

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:

let string = "atención 1 2 3 ya!";


let start = "atención";
let end = "ya";
let regexp = new RegExp(start+"(.+)"+end);
let data = regexp.exec(string);
if (data.length == 2) console.log(data[1]);

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

5. Las estructuras condicionales


e
.d
w

En JavaScript encontrará estructuras condicionales básicas, heredadas del lenguaje C.


w
w

a. La estructura if ... else ...


La estructura condicional básica es if ... else ...:

if (stock == 0) {
console.log("El producto ya no está disponible");
}

El bloque «sino» se introduce por la instrucción else:

if (stock == 0) {
console.log("El producto ya no está disponible");
}
else {

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


console.log("Date un capricho, compra todo");
}

El encadenamiento de condiciones no se introduce por una instrucción concreta:

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

console.log("acceso no permitido ");


et

break;
...
default:
console.log("Código no identificado");
w

6. Las estructuras iterativas

a. Las estructuras iterativas con índices de bucle


Las tres estructuras iterativas clásicas while (...)..., do ... while (...) ... y for
(...; ...; ...) ... existen en JavaScript.
Para ilustrar rápidamente estas estructuras iterativas, a continuación se muestra cómo implementar la
visualización del valor de una variable inicializada en 0 e incrementada, mientras que su valor sea inferior a
10:

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


Con la estructura while (...) ...:
La estructura iterativa básica creada sobre un «mientras que» existe en JavaScript:

var i=0;
while (i < 10) {
console.log(i); i++;
}

Con la estructura do ... while (...) ...:


El control del bucle se puede dar la vuelta usando la estructura «hacer ... mientras que ...»:

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

for (let i=0; i < 10; i++) {


og

console.log(i);
pr

i++;
}
do
e to

b. Las estructuras iterativas sin índices de bucle


.d
w

Se pueden implementar dos estructuras iterativas «automáticas», es decir, que no usan índices de bucle:
w
w

la estructura for (... in ...) ...


la estructura for (... of ...) ...

Es necesario también observar el método forEach(), que se aplica a las listas.

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):

let brands = ["Konia", "Peach", "Threestars"]


for (let i in brands) {
console.log(i +": "+brands[i]); // 0: Konia

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


// 1: Peach
// 2: Threestars
}

Este otro ejemplo permite acceder a las propiedades de un objeto:

let product = {"type": "phone",


"brand": "Peach",
"name": "topPhone"};
for (let p in product) {
console.log(p +": "+product[p]); // type: phone
// brand: Peach
// name: topPhone
}

La estructura for (... of ...) (introducida a través de la norma ECMAScript 6), permite acceder
directamente a los elementos de la lista.

let brands = ["Konia", "Peach", "Threestars"];


for (let e of brands) {
console.log(e); // Konia
// Peach
// Threestars
}

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:14 p. m.]


La programación funcional en JavaScript
JavaScript se sitúa en el paradigma de la programación funcional: en este paradigma, las funciones son
elementos de primer plano del lenguaje.

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

La utilización de las funciones de callback, es un elemento esencial del lenguaje JavaScript.



pr

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

que consume los datos producidos por la función.


w
w

Utilizar funciones de callback es un esquema de programación muy frecuente en JavaScript, durante el


w

acceso asíncrono a los datos.

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.

a. Ejemplo con el método forEach()


A continuación se muestra un ejemplo de utilización de una función de callback con el método
forEach(), que itera sobre los elementos de una lista.

let brands = ["Konia", "Peach", "Threestars"];


brands.forEach(function(e) { 
console.log(e); // Konia
// Peach
// Threestars

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:32 p. m.]


});

Observe que la función de callback aquí es anónima.

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.

A continuación se muestra su utilización en el ejemplo anterior:

let brands = ["Konia", "Peach", "Threestars"];


brands.forEach((e) => {
console.log(e); // Konia
// Peach
// Threestars
});

En la sección La programación orientada a objetos con JavaScript, verá cómo se implementa la función
forEach().

b. Ejemplo con el método map()


A continuación se muestra otro ejemplo de utilización de una función de callback con el método map(),
que permite aplicar una función a cada elemento de una lista y después devolver esa lista modificada.

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.

A continuación se muestra el código:

function subTotal(e) {
e.subtotal = e.price * e.quantity;
return e;

var cart = [{"name":"topPhone", "price":1000, "quantity":2},


{"name":"bigPhone", "price":700, "quantity":1}];
cart.map(subTotal).forEach(function(e) {
console.dir(e); // Muestra cada objeto modificado de la lista
});

Este ejemplo provoca la siguiente visualización en la consola del navegador:

{ name: 'topPhone', price: 1000, quantity: 2, subtotal: 2000 }

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:32 p. m.]


{ name: 'bigPhone', price: 700, quantity: 1, subtotal: 700 }

En la sección La programación orientada a objetos con JavaScript, verá cómo se implementa la función
map().

2. Una función devuelve una función (factory)


Una función que devuelve una función se llama factory.

En el siguiente ejemplo (factory.html), la función manufacture() le permite crear («industrializar»,


de ahí el nombre de factory), las funciones phoneManufacture() y tabletManufacture()
que permiten insertar un teléfono o una tablet en la lista adecuada.

(Para entender este ejemplo, diríjase si necesita a la sección La programación orientada a objetos con
JavaScript.)

var ProductType = function (type) {


this.type = type;
this.lista = new Array();

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

var phones = new ProductType('phone');


phones.lista.push({"name":'topPhone', "brand":"Peach"});

var phoneManufacture = manufacture(phones);


phoneManufacture({"name":"bigPhone", "brand":"Threestars"});
phones.productDisplay();

var tablets = new ProductType('tablet');


var tabletManufacture = manufacture(tablets);
tabletManufacture({"name":'bigTablet', "brand":"Threestarts"});
tablets.productDisplay();

Este ejemplo muestra el siguiente resultado en la consola del navegador:

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:32 p. m.]


phones:
Object { name: "topPhone", brand: "Peach" }
Object { name: "bigPhone", brand: "Threestars" }
tablets:
Object { name: "bigTablet", brand: "Threestarts" }

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:32 p. m.]


La programación
 orientada a objetos con
JavaScript
1. Los principios de la programación orientada a objetos con
JavaScript

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 gestión clásica de los objetos, sin utilización de clases para instanciarlas.


og

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

De manera literal, utilizando las llaves.


w
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.

2. Los objetos literales


JavaScript se distingue de muchos otros lenguajes de programación, por el hecho de que los objetos se
pueden crear de manera literal (y no son instancias de clase). La sintaxis que permite formatear estos
objetos cuando se serializan, se llama JSON (JavaScript Object Notation). Esta sintaxis se ha convertido en
algo extremadamente popular en el intercambio de datos (en detrimento de XML).

La gestión de las clases en JavaScript se ha introducido a través de la norma ECMAScript 6 y sobre todo,
por las extensiones del lenguaje JavaScript como TypeScript o Dart (ver la webografía).

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


Suponga un objeto topPhone que se define como sigue:

let topPhone = {"type": "phone", "brand": "Peach", "name":


"topPhone"};

Usando las llaves, el objeto creado hereda el «meta-objeto» Object (este punto se detallará más
adelante).

Una propiedad de un objeto puede tener cualquier valor:

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

Una lista de objetos se llama colección.


pr
do

3. La herencia por encadenamiento de prototipos


e to
.d

a. La propiedad __proto__ del objeto del que heredan


w

Un objeto puede heredar propiedades de otro objeto, a través de la propiedad __proto__.


w
w

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 PeachPhone = {"brand": "Peach" };

let topPhone = {
name: "topPhone",
__proto__: PeachPhone
};

console.dir(topPhone); // Muestra:
// name: "PeachPhone"
// __proto__: Object PeachPhone

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


console.log(topPhone.brand); // Muestra: Peach

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 PeachPhone = { "brand": "Peach" };

let topPhone = {
name: "topPhone",
__proto__: PeachPhone
};

PeachPhone.brand = "Pear";

console.log(topPhone.brand); // Muestra: Pear (y no Peach)

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.

Esta parte heredable es el objeto que se define por la propiedad prototype.

En lo sucesivo, el objeto PeachPhone «localiza» en la propiedad prototipo aquello que se puede


heredar (así como la propiedad comment, que no se debe poder heredar).

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

console.log(topPhone.brand); // Muestra: Peach

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


Vamos a ver que la creación de un objeto por medio de la llamada a una función constructora, que utiliza de
manera sistemática la propiedad prototype del objeto heredado (que toma la forma de una función).

4. La creación de un objeto por medio de la llamada de una función


constructora
En JavaScript (antes de la aplicación de la norme ECMAScript 6, que crea las clases), es más habitual crear
objetos a través de las funciones constructoras y utilizando el operador new, que crear objetos de manera
literal.

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:

function phone (name, brand) {


this.name = name;
this.brand = brand;
}
phone.prototype.whoAmI = function() {
console.log("Soy el "+this.name+" de marca "+this.brand);
}

var topPhone = new phone("topPhone", "Peach");


var bigPhone = new phone("bigPhone", "Threestars");

topPhone.whoAmI(); // Soy el topPhone de marca Peach


bigPhone.whoAmI(); // Soy el bigPhone de marca Threestars

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 :

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


5. Ejemplos de implementaciones de un método
Los siguientes ejemplos vuelven a implementar los métodos forEach() y map(), que se aplican a las
listas.

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

6. La problemática del objeto actual (this)


La manipulación de this, que hace referencia al objeto actual puede reservar algunas sorpresas,
principalmente cuando el script JavaScript se ejecuta dentro de un navegador.

Es necesario recordar tres principios fundamentales que permiten evitarlos:

Inicialmente, this hace referencia al objeto Window.


En una función constructora invocada por new, this hace referencia al objeto que acaba de ser
creado por este. Esto es igualmente cierto en un método añadido al prototipo de esta función

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


constructora.
En un método de un objeto, this hace referencia a este.
Pero en una función de callback de un método de un objeto, this hace referencia a Window, al
menos que se use la sintaxis basada en la fat arrow (ver más adelante).

En el código siguiente cargado en un navegador, this hace referencia a Window:

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

var objeto = new funcion('objeto1');


// Muestra: Object { name: "objeto1" }
objeto.method(); // Muestra: Object { name: "objeto1" }

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 funcion(name) { this.name = name; }

function.prototype.method = function(callback) {
console.log(this); // Muestra: Object { name: "objeto1" }
callback();
};

var objeto = new funcion('objeto1');


objeto.method(function() {
console.log(this); // Muestra: Window
});

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


Para paliar este problema, va a utilizar una de las tres funciones call(), apply() y bind().

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:

Con call(), los argumentos de la función se listan como argumentos de call():

function.call(objeto [, argumento1, argumento2, ...])

Con apply(), los argumentos de la función se pasan en una tabla:

function.apply(objeto [, [argumento1, argumento2, ...]]);

bind(), por el contrario, permite crear una nueva función:

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.

Con el método call():

function funcion(name) { this.name = name; }

function.prototype.methode = function(callback) {
console.log(this); // Muestra: Object { name: "objeto1" }
callback.call(this, "un argumento");
};

var objeto = new funcion('objeto1');


objeto.method(function(param) {
console.log("callback con "+param+" y "+this);
// Muestra: callback con un argumento y Object { name:
"objeto1" }
});

Con el método apply():

function funcion(name) { this.name = name; }

function.prototype.method = function(callback) {

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


console.log(this); // Muestra: Object { name: "objeto1" }
callback.apply(this, ["un argumento"]);
};
var objeto = new funcion('objeto1');
objeto.method(function(param) {
console.log("callback con "+param+" y "+this);
// Muestra:
// callback con un argumento y Object { name: "objeto1" }
});

Con el método bind():

function funcion(name) { this.name = name; }


function.prototype.method = function(callback) {
console.log(this); // Muestra: Object { name: "objeto1" }
let newFunction = callback.bind(this, "un argumento");
newFunction();
};

var objeto = new funcion('objeto1');


objeto.method(function(param) {
console.log("callback con "+param+" y "+this);
// Muestra: callback con un argumento
// y Object { name: "objeto1" }
});

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().

function phone(name) { this.name = name; }

phone.prototype.whoAmI = function() {
console.log("Soy el "+this.name+" de marca "+this.brand);
}

function PeachPhone (name) {


this.brand = "Peach";
phone.call(this, name);
}

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


PeachPhone.prototype = phone.prototype;

var topPhone = new PeachPhone("topPhone");


topPhone.whoAmI(); // Muestra: Soy el topPhone de marca Peach

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:

object.method1().method2(). ... .methodn();

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

var inicialValue = new create("2");


pr

console.log(inicialValue.append('Fast').append('4U').value); //
do

2fast4U
e to
.d
w
w
w

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:40:49 p. m.]


Las principales
 aportaciones de la norme
ECMAScript 6
1. La norma ECMAScript

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 palabra reservada let;


la interpolación de las variables en las cadenas de caracteres;
pr
do

los argumentos por defecto;


to

la manipulación más cómoda de las listas con:


e
.d

la estructura iterativa for (... of ...)...;


w
w

el método includes() para probar la presencia de un elemento;


w

la utilización de las fat arrows para simplificar la escritura de las funciones de callback;
las clases.

2. La palabra reservada let


La palabra reservada let limita el ámbito de la variable que se introduce en el bloque de
instrucciones actual.

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

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:08 p. m.]


Es posible que haya tenido que utilizar la directiva use strict para utilizar esta palabra reservada.

3. La interpolación de variables en las cadenas


Para manipular las cadenas de caracteres que se extienden en varias líneas y/o interpolan variables, es sin
embargo posible utilizar las backquotes y de esta manera, crear plantillas de strings.

A continuación se muestra un ejemplo:

let v1 = 1; let v2 = 2; let v3 = 3;


let v4 = "ya";
console.log(`${v1} ${v2} ${v3} ... ${v4} ! `);

4. Los argumentos por defecto


Durante la definición de una función, se pueden definir los valores por defecto de argumentos opcionales:

function test(a, b=0) {


console.log(a+" y "+b);
}
test(1); // muestra: 1 y 0

5. Una manipulación más cómoda de las listas

a. La estructura for (... of …) ...


La estructura iterativa for (let <variable> of <lista>) { ... } permite acceder
directamente a los elementos de una lista, mientras que la estructura for (let <variable> in
<lista>) { ... } solo instancia la variable local al bucle con los índices de los elementos de la
lista.

var brands = ["Peach", "Threestars", "Sony Ericsson"];


for (let e of brands) {
console.log(e); // Peach
// Threestars
// Sony Ericsson
}

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:08 p. m.]


b. El método includes()
El método includes() permite probar si una lista contiene un elemento particular (el método
indexOf() solo permite conocer el índice del elemento):

if (brands.includes("Peach")) {
console.log("Vendemos también marcas fruta...");
}

6. El operador "fat arrow" (=>)


No solamente la fat arrow (=>) es un acceso directo sintáctico para las funciones de callback, sino que
también permite conservar el contexto del objeto actual (this) en la función de callback.

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:

var lista = [1, 2, 3];


lista.forEach(function (valor) {
console.log(valor);
});

se convierte en:

lista.forEach((valor) => { console.log(valor) });

incluso en este caso (un único argumento):

lista.forEach(valor => { console.log(valor) });

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

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:08 p. m.]


ECMAScript 6 define las clases (con la palabra clave class) y una herencia simple.

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

let topPhone = new Phone("topPhone", "Peach");


topPhone.whoAmI(); // Soy el topPhone de marca Peach

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:08 p. m.]


La programación reactiva, observables y
promises (promesas)
Los observables son los elementos clave de la programación reactiva.

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

de este libro, en curso de normalización bajo ECMAScript).


do
to

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):

npm install rxjs --save


npm install rxjs-compat --save

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.

A continuación se muestra la creación de un observable, en un código ejecutado con Node.js:

var Rx = require('rxjs-compat');
var source = Rx.Observable;

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:24 p. m.]


Los métodos siguientes (entre otros muchos) se podrían utilizar en un observable:

subscribe(): «suscribe» una operación al observable.


source.subscribe(value => console.log(value)): muestra cada valor
recibido por el observable.

unsubscribe(): «elimina la suscripción» de la operación al observable.


source.unsubscribe().

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.

take(): permite solo retener valores entre los generados.


source.interval(1000).take(10): el observable recibe una señal cada segundo,
durante 10 segundos.

Los tres ejemplos siguientes programan:

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

Por supuesto, la utilización de observables es omnipresente en Angular.

1. Primer ejemplo: un observable en un botón


Este ejemplo implementa la librería RxJS, relacionándola directamente con un código HTML. También
utiliza la librería JQuery para acceder a un elemento del DOM (en este caso un botón).

<html>
<head>
<meta charset="utf-8" />
<script src="rx.lite.min.js"></script>
<script src="jquery.min.js"></script>
<script>
$().ready(function(){

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:24 p. m.]


var button = $('#testButton');
Rx.Observable.fromEvent(button, 'click')
.subscribe(e => console.log("click"));
});
</script>
</head>
<body>
<button id="testButton"> Test button </button>
</body>
</html>

Cada vez que se seleccione el botón, se muestra el mensaje en la consola.

2. Segundo ejemplo: un observable sobre un entero

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

Un observador que muestra un mensaje al final del flujo.


pr

var Rx = require('rxjs-compat');
do

var source = Rx.Observable.range(1, 5);


e to

var subscription = source.subscribe(


.d

value => console.log(value),


w

err => console.log("Error: "+e),


w

() => console.log("And that's all folks !"));


w

Este código muestra:

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 subscription = source.subscribe(

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:24 p. m.]


function(value) { console.log(value); },
function(err) { console.log("Error "+e); },
function() { console.log("And that'all folk !"); });

3. Tercer ejemplo: un observable sobre un timer


Este ejemplo crea un observable que cuenta los 10 primeros segundos, mostrando «Top» en la consola:

var Rx = require('rxjs-compat');

function operation() { console.log("Top"); }

const sub = Rx.Observable.interval(1000)


.subscribe(e => operation());
setTimeout(() => sub.unsubscribe(), 10000);

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.

Es necesario también observar las siguientes diferencias:

En caso de error, la suscripción a un observable se renueva.


Y los observables también se pueden cancelar (la eliminación de la suscripción es posible a través
del método unsubscribe()); las promises en su implementación ES6, no lo son (pero lo
pueden ser en otras implementaciones).

A continuación se muestra un ejemplo de creación de un objeto promise:

let p = new Promise(function(resolve, reject){ resolve(1); })


p.then(function(valor){ console.log("etapa 1: "+valor); return
valor+1; } )
.then(function(valor){ console.log("etapa 2: "+valor); });

Este código muestra "Etapa 1: 1", y después "Etapa 2: 2".

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:24 p. m.]


A continuación se muestra el mismo código utilizando las fat arrows:

let p = new Promise((resolve, reject) => resolve(1) )


p.then(valor => { console.log("etapa 1: "+valor); return
valor+1; } )
.then(valor => console.log("etapa 2: "+valor) );

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

Y utilizando las fat arrows, esta:

getBrand(productName,
brand => { getLastPurchaseByBrand(brand,
purchase => { ... }) });

Y para terminar, a continuación se muestra utilizando una promise:

getBrand(productName)
.then(function(brand) {
return getLastPurchaseByBrand(brand);
})
.then(function(purchase) {
...
});

https://www.eni-training.com/portal/client/mediabook/home[8/05/2022 11:41:24 p. m.]


Presentación de las extensiones de
JavaScript
Como hemos visto anteriormente, el lenguaje JavaScript es un lenguaje de programación orientado a
objetos, pero que no gestiona clases (antes de su normalización con ECMAScript 6): es un lenguaje
de programación orientado a prototipo, donde los objetos se crean a partir de otros objetos, de los que

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

sintaxis de una clase.


do

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

código de terceros no controlado (incluso código propio) «hacer castillos de arena».


w
w

Por otro lado, el tipado de las variables se hace dinámicamente en JavaScript y no aporta la seguridad
w

del tipado estático.

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:44:00 p. m.]


El lenguaje
 TypeScript
TypeScript es una capa añadida al lenguaje JavaScript y creada por Microsoft en 2012. Permite
principalmente:

Tipar opcionalmente las variables durante sus declaraciones (tipado estático).


Gestionar los argumentos opcionales (añadiendo un punto de consulta junto a sus nombres).

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

El código TypeScript se transforma en código JavaScript a través de un transpiler (o


.d

transcodificador), llamado tsc.


w
w
w

A continuación se muestra un ejemplo de llamada de este transpiler en línea de comandos, donde


code.ts es un código TypeScript:

tsc code.ts

tsc va a generar código.js, que será un código JavaScript «puro».


Por lo tanto, puede mezclar código TypeScript con código JavaScript y esto puede conducir a picos
de no legibilidad.


2. Tipado estático de las variables
A continuación se muestra la especificación formal del tipado de una variable:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:19 p. m.]


const|let|var <variable>: <type> = <valor>;

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.

a. Los tipos básicos


Los tipos básicos de TypeScript retoman los de JavaScript:

let entero: number = 1;


let pi: number = 3.14;
let bool: boolean = true;
let nombre: string = "Soy una cadena de caracteres";
let objeto: Object = {"name": "topPhone"};

b. Tipado de variables no escalares


Las variables no escalares también se pueden tipar.

El tipado de las listas puede utilizar tanto el objeto Array, como los corchetes utilizados como
accesos directos sintácticos:

let lista1: Array<number> = [1, 2, 3];


let lista2: number[] = [1, 2, 3];

Las colecciones (las listas de objetos) se tipan de la siguiente manera:

let coleccion1: Array<Object>


= [{"name":"topPhone"}, {"name":"bigPhone"}];
let coleccion2: Object[]
= [{"name":"topPhone"}, {"name":"bigPhone"}];

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:19 p. m.]


enum brands {Peach, Threestars};
let brand = brands.Peach;

d. El tipo genérico any


Un tipo genérico existe, es any:

let miVariable: any;

e. Tipado de las funciones


Por supuesto, los argumentos y el valor de retorno de una función, también se pueden tipar:

function Hola(apellido: string): string {


return "Hola "+apellido;
}

Una función que no devuelve ningún valor (de hecho, un procedimiento), puede utilizar el tipo
void:

function Hola(apellido: string):void {


console.log("Hola "+apellido);
}

Un argumento puede ser opcional (entones se sigue por un punto de interrogación):

function Hola(apellido: string, apodo?: String):void {


let texto = "Hola "+apellido;
if (apodo) texto += "(debería decir "+apodo+"?)";
console.log(texto);
}

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:

A continuación se muestra el esquema de programación de una clase:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:19 p. m.]


class <nombreDeLaClase> {
// Declaración de los atributos
...

constructor( ... ) { ... }

// Métodos de la clase
...
}

A continuación se muestra el ejemplo anterior formateado para TypeScript (archivo


ejemplo_clase.ts):

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

+" de marca "+this.brand);


do

}
}
e to
.d

let topPhone = new Phone("topPhone", "Peach");


w

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.

var Phone = (function () {


function Phone(name, brand) {
this.name = name;
this.brand = brand;
}
Phone.prototype.whoAmI = function () {
console.log("Soy el " + this.name + " de marca "
+ this.brand);
};
return Phone;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:19 p. m.]


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

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;

constructor(name) { this.name = name; }

whoAmI() {
console.log("Soy el "+this.name
+" de marca "+this.brand);
}
}

class PeachPhone extends Phone {

constructor(name) {
super(name);
this.brand = "Peach";
}
}

let topPhone = new PeachPhone("topPhone");


topPhone.whoAmI();

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).

Las interfaces en TypeScript se implementan a través de la palabra reservada implements.

interfaz forThePublic {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:19 p. m.]


nickname: string;
whoAmI(): void;
}
class Phone implements forThePublic {
private name: string;
private brand: string;
private nickname: string;

constructor(name, brand, nickname) {


this.name = name;
this.brand = brand;
this.nickname = nickname;
}

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

durante la implementación de la clase.


w
w

A continuación se muestra un ejemplo simple de esta programación genérica en TypeScript (un


w

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:19 p. m.]


El lenguaje Dart
Esta sección solo presenta un breve resumen del lenguaje Dart. Este lenguaje no se usa para
implementar nuestros ejemplos de código Angular.

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

para hacer constantes los valores de variables.

1. Instalación y prueba de Dart


Los procedimientos de instalación se describen en el sitio oficial de Dart.

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

2. Generación del código JavaScript con Dart


Para generar el código JavaScript a partir de un código escrito en Dart, utilice dart2js.

A continuación se muestra la sintaxis de utilización de dart2js:

dart2js --out=<code JavaScript> <code dart>

A continuación se muestra la generación del código JavaScript a partir de hello.dart en Linux:

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

clase, el antepasado último de todas las clases es Object).


do

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

A diferencia de TypeScript, el constructor de la clase lleva el mismo nombre que este.


w
w

A continuación se muestra el esquema de programación de una clase:

class <nombreDeLaClase> {
// Declaración de las propiedades
...

<nombreDeLaClase>( ... ) { ... } // Constructor

// Métodos de la clase
...
}

A continuación se muestra el ejemplo anterior formateado para Dart (archivo ejemplo_clase.dart).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:34 p. m.]


Debe existir un punto de entrada que se define por la función main().

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;

Phone(name) { this.name = name; }

whoAmI() { print("Soy el $name de marca $brand"); }


}

class PeachPhone extends Phone {


PeachPhone(name): super(name) {
this.brand = "Peach";
}
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:34 p. m.]


main() {
var topPhone = new PeachPhone("topPhone");
topPhone.whoAmI();
}

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:

abstract class forThePublic {

rg
String nickname;

o
void whoAmI();

n.
}

io
class Phone implements forThePublic {
ac
m
String name;
ra

String brand;
og

String nickname;
pr
do

Phone(name, brand, nickname) {


this.name = name;
to

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:34 p. m.]


Presentación de Node.js
Node.js es un entorno que permite ejecutar código JavaScript, fuera de un navegador. Cuando se está
redactando este libro, se basa en el motor JavaScript V8 desarrollado por Google para sus
navegadores Chrome y Chromium.

Su arquitectura es modular y orientada a eventos. Está fuertemente orientado a la red. Tiene

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

dos cualidades principales:



pr

Su ligereza (resultado de su modularidad).


do

Su eficacia, provocada por su arquitectura monothread (resultado de la gestión orientada a


to

eventos que ofrece de manera nativa el entorno JavaScript).


e
.d

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:

Instalar y probar Node.js en Linux, Windows o macOS.


Crear un servidor HTTP que devuelva una cadena de caracteres.
Implementar un módulo.
Crear un servidor HTTP utilizando el módulo express, invocado en una ruta REST y

devolviendo los datos formateados en JSON, inicialmente en su totalidad y después, filtrados
por una propiedad.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:45:47 p. m.]


En este capítulo, solo introduciremos algunos módulos (y funciones) de Node.js.

La documentación completa de los módulos, está disponible en esta dirección: https://nodejs.org/api/


Instalación y prueba de Node.js
1. Creación del archivo de prueba
Para probar Node.js, en primer lugar va a crear un código JavaScript que va a ser lo más sencillo
posible y lo va a ejecutar con Node.js.

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

2.Instalación y prueba de Node.js en Ubuntu


og


pr
do

 Para instalar Node.js en Ubuntu, lo más sencillo es utilizar el comando curl y el administrador
to

de paquetes en línea de comandos (apt-get):


e
.d

curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash


w

-sudo apt-get install nodejs


w
w

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:

sudo ln -s /usr/bin/nodejs /usr/bin/node

 Abra un terminal (shell) y ejecute el archivo de prueba:

node pruebaDeNode.js 

Se muestra la cadena de caracteres «Prueba de Node».

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:00 p. m.]


Un procedimiento completo está en línea en la dirección:
https://wiki.ubuntu.com/SpanishDocumentation

3. Instalación y prueba de Node.js en Windows


La instalación de Node.js y su prueba en Windows, se van a desarrollar en cuatro etapas:

 Descargue el instalador Windows Installer, yendo al sitio oficial de Node.js:


https://nodejs.org/en/download

 Ejecute el instalador (el archivo .msi anteriormente descargado), aceptando las condiciones de
utilización y la configuración por defecto.

 Vuelva a arrancar su ordenador.

 Abra la línea de comandos y ejecute el archivo de prueba:

node pruebaDeNode.js

Se muestra la cadena de caracteres «Prueba de Node».

4. Instalación y prueba de Node.js en Mac OS


La instalación de Node.js y su prueba en Mac Os, se van a hacer en tres etapas:

 Descargue el paquete de instalación para Mac Os (Macintosh Installer), yendo al sitio oficial de
Node.js: https://nodejs.org/en/download/

 Abra un terminal e instale el paquete:

pkg install nombrePaquete.pkg

 Ejecute el archivo de prueba:

node pruebaDeNode.js

Se muestra la cadena de caracteres «Test de Node».


La modularidad de Node.js
1. Los módulos y los paquetes
Una de las principales fortalezas de Node.js es ser modular (y principalmente ofrecer numerosos
módulos de red). Si algunos de estos módulos se instalan directamente al mismo tiempo que Node.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

Indirectamente (pero siempre con npm), a través de la especificación de las dependencias de la


aplicación (es decir, los módulos necesarios para esto), en un archivo llamado package.json.
pr
do

Un módulo se utiliza en una aplicación, con la función require():


e to

var moduloEnsuAplicacion = require('<nombreDelModulo>');


.d
w
w

Su aplicación Node.js se puede reutilizar a su vez como módulo en determinadas condiciones, que se
w

presentarán más adelante.

Detengámonos en un punto un poco sutil: la distinción entre módulos y paquetes.

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. 

a. El administrador de módulos de Node.js: npm

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


npm (Node.js Package Manager), es el administrador de módulos de Node.js (se instala con éste).

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:

npm install -g <module>

o en caso contrario (sin la opción g) en el directorio actual (pero también en una carpeta llamada
node_modules).

b. Especificación de las dependencias: el archivo package.json


Para especificar las dependencias necesarias para la creación de una aplicación Node.js (es decir los
módulos asociados a los paquetes necesarios), se recomienda crear un archivo llamado package.json.

En el contexto de un archivo package.json, solo hablamos más de paquetes (y de módulos).

A continuación se muestra un esquema mínimo de este archivo:

{
"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>",
...
}
}

Para instalar los módulos necesarios, se debe ejecutar el siguiente comando:

npm install

Y a continuación se muestra el que va a lanzar el servidor:

npm start

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


Para crear un esqueleto de archivo package.json, utilice el siguiente comando:

npm init --yes

En este caso, el valor de la propiedad main se inicializa a index.js. Explicaremos un poco el interés
de esta propiedad:

Si su aplicación se convierte en un paquete (incluyendo uno o varios códigos reutilizables), la


propiedad main hace referencia al código, que es el punto de entrada en el paquete durante la
ejecución de la instrucción require().

2. Creación de un primer servidor Node.js de prueba


Va a escribir su primer servidor (el clásico «Hello World»), utilizando el módulo HTTP que ofrece
Node.js.

Escriba el código de este servidor en el archivo helloConNode.js:

var http = require('http');

var server = http.createServer((request, => response){


response.end('Hello World de Node.js');
});

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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


localhost hace referencia a la dirección IP de su ordenador (es un alias para 127.0.0.1).

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.

 Para probar su servidor, escriba:

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:

curl -X GET localhost:8888

Para enviar datos a través del método POST con curl, utilice las opciones -X y --data:

curl -X POST --data "datos" localhost:8888

3. Creación y reutilización de un módulo


Ahora va a crear un módulo llamado saludo. Este módulo se corresponde con una carpeta paquete,
que se debe encontrar a su vez en la carpeta node_modules.

Su paquete solo contiene dos códigos:

Un punto de entrada normalmente llamado index.js.


El código saludo.js, que contiene la función que devuelve la cadena de caracteres «hola de

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


Node a través de un módulo».

(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.

En el archivo principal index.js y el resto de archivos de código, todos los


objetos/funciones exportables van prefijadas (directa o indirectamente) con module.exports.

A continuación se muestra el archivo index.js:

/*!
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/"

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


]
}

En la carpeta lib, el archivo saludo.js:

module.exports.saludo = function() {
return "Hola de Node a través de un módulo";
}

 Utilice su módulo en holaDeNodeViaUnModulo.js:

var http = require('http');


var saludo = require('saludo');

var server = http.createServer((request, response) => {

rg
response.end(saludo.saludo());

o
n.
});

io
server.listen(8888);
ac
m
ra

 Lance el servidor:
og
pr

node holaDeNodeatravesdeUnModulo.js
do
to

En su navegador, invóquelo a través de localhost:8888.


e
.d

Y el módulo de saludo le responde:


w
w
w

4. Creación de un servidor que devuelva los datos

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


Ahora va a pasar a una nueva etapa más interesante: crear un servidor que devuelva los datos.

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).

Utilizará en un primero momento dos módulos de Node.js:

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

El módulo express permite simplemente añadir potentes funcionalidades a un servidor Node.js,


pr

principalmente la gestión de rutas REST (Representational State Transfer) y la gestión de plantillas.


do

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

incomparablemente más potente).


w
w
w

a. Gestión de rutas REST con el módulo express


La gestión de rutas REST permite asociar consultas HTTP (para las que el request path se expresa en
una sintaxis orientada a datos), a una acción determinada por un controlador (y opcionalmente por el
método HTTP utilizado).

Por lo tanto, la información es transmitida por:

El request path descrito por la siguiente formalización de las URL: scheme:


[//host[:port]][/]path[?query][#fragment]
A este request path también se asocia la querystring.

El método HTTP (GET, POST, PUT, DELETE) utilizado.

Por ejemplo, si la URL es www.mitiendaenlinea.net/products/telefono/Peach y si el método HTTP es

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


GET, esto implica el desencadenamiento por el controlador de una acción que tiene por objetivo
mostrar todos los productos de tipo teléfono y de marca Peach (la acción de visualización se incluye
por el método GET).

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

(Discutiremos más adelante la utilización de caracteres acentuados en las URL y su reconocimiento


og

en las rutas gestionadas por el módulo express).


pr
do

Para crear su servidor a partir del módulo express, va a:


to

importar el módulo express por la función require(): var express =


e
.d

require(’express’);
w
w

crear su aplicación servidora con la función express() ofrecida, por este módulo: var
w

app = express();

Las rutas serán gestionadas según los métodos HTTP utilizados.

Por ejemplo, quiere que su servidor le salude: en una ruta vacía y a través del método GET.

Escriba el código completo en (holaDeNodeConExpress.js):

var express = require('express');


var app = express();

app.get('/', (request, response) => {


response.setHeader('Content-Type', 'text/plain');
response.end('Hola de Node.js ');
});

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


app.listen(8888);

La función de callback exhibe dos argumentos:

request (normalmente llamado req), se corresponde con el request path (y le permite


acceder a sus argumentos).
response (normalmente llamado res), se corresponde con la respuesta HTTP enviada por
el servidor; en el encabezado de esta, se fija el Content-type y en su cuerpo, el mensaje en
bruto.
Lance su servidor:

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

A continuación se muestra la declinación de app.METHOD(path, callback) según el


método HTTP utilizado:

Nombre
Método HTTP Acción a realizar Método del framework Express
corto

método POST creación de los datos create app.post(path,


callback)

método GET acceso a los datos read app.get(path, callback)

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


método PUT modificación de los datos update app.put(path, callback)

método DELETE eliminación de los datos delete app.delete(path,


callback)

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).

b. Gestión de las plantillas con el módulo express


Durante un uso exclusivo del módulo express (es decir, sin utilizar otros frameworks como Angular),
esto ofrece la posibilidad de servir plantillas (es decir, de pre-formatear el código HTML en el que se

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

posibilidades infinitamente más potentes), a continuación se muestra un ejemplo de templating


e
.d

(creación de una plantilla) con EJS.


w
w

En una plantilla EJS, las variables se interpolan con la siguiente sintaxis:


w

<%= variable %>

 Cree una carpeta de prueba TemplatingConNodeYExpress y cree en ella los módulos express y
EJS con npm:

npm install express ejs

Estos módulos se crean en la carpeta node_modules.

 Cree una carpeta views y en ella, una página HTML llamada testNodeExpress.ejs:

<!DOCTYPE html>
<html>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


<head>
<style>
.message { font-size: 14pt; color: red; }
</style>
</head>
<body>
<span class='message'>
<%= mensaje %>
</span>
</body>
</html>

Esta página es su primera (y última para este capítulo) plantilla.

 Para terminar, cree la aplicación servidora templatingConNodeYExpress.js:

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

{ message: 'Hola de Node.js/express/EJS'});


do

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

 En su navegador o con el comando curl, invoque su servidor: localhost:8888.

Y el saludo de Node.js con express y EJS se muestra:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


c. Especificación de las dependencias en un archivo package.json
Como hemos visto, es posible especificar las dependencias necesarias para la aplicación Node.js, es
decir los módulos express y EJS, gracias al archivo de configuración package.json:

{
"name": "templatingConEjs",
"scripts": {
"start": "node templatingConNodeYExpress.js"
},
"dependencies": {
"ejs": "^2.6.1",
"express": "^4.16.4"
}
}

 Para instalar estos módulos, ejecute el comando:

npm install

 Para lanzar el servidor, ejecute el comando:

npm start

d. Instalación del módulo express


 Para instalar el módulo express, ejecute lo siguiente en la línea de comandos:

npm install -g express

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


O bien, indíquelo como dependencia en el archivo package.json relacionado a su aplicación, y
después ejecute lo siguiente en la línea de comandos:

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.

Suponga un archivo llamado Products.json localizado en la misma carpeta que el código de su


servidor. El módulo fs permite, a través de diferentes funciones, acceder al contenido de este archivo.

Introduciremos aquí solo tres funciones de este módulo:

fs.readFile(file[, opciones], callback)

fs.readFileSync(file[, opciones])

fs.createReadStream(path[, opciones])

La función fs.readFile() permite leer de manera no síncrona el contenido de un archivo. La


función fs.readFileSync() es la versión síncrona de fs.readFile().

Son dos funciones básicas, pero normalmente es más adecuado utilizar la


función fs.createReadStream(). Permite manipular el contenido de un archivo como un
flujo (un stream). De esta manera, los datos de este flujo se pueden consumir progresivamente (en
nuestros primeros ejemplos, esto no tiene mucho interés, sino que satura la memoria central si es
demasiado restringido).

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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


Los flujos son tipados: se pueden leer (readable), «scriptables» (writable) o los dos.
En un flujo legible (readable), es capaz de controlar tres eventos con el método on():

La apertura exitosa del flujo (evento open).

El error de este (evento error).

La lectura progresiva de bloques de datos (chunk) en el evento data:


readable.on(’data’, (chunk) => { ... }).

Sobre un flujo scriptible (writable), los datos generalmente se proporcionan:

por un pipe abundado por un flujo entrante (el método pipe() se aplica al flujo en
entrada),

directamente por el método end().

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.

Si el flujo no se puede abrir, se devuelve una colección al cliente:

var readable = fs.createReadStream('Products.json');


readable.on('open', () => {
readable.pipe(res);
console.log('Lista devuelta de los productos ');
});
readable.on('error', () => { res.end('[]'); });

 Para instalar el módulo fs, ejecute lo siguiente en la línea de comandos:

npm install -g fs

O bien, indíquelo como dependencia en el archivo package.json relacionado con su aplicación.

7. Prueba de un servidor Node.js


a. Creación de un archivo de datos JSON
 Cree por ejemplo una mini-colección (una colección que sea una lista de objetos) que almacene
información de los productos en venta en un sitio de e-commerce.

Por el momento, es suficiente con la descripción de cuatro productos:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


[
{ type: "phone", brand: "Peach", name: "topPhone"},
{ type: "phone", brand: "Threestars", name: "bigPhone"},
{ type: "tablet", brand: "Threestars", name: "bigTablet"},
{ type: "phone", brand: "Konia", name: "4000"}
]

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.

La colección se parece a esto:

[
{ "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

o una llave), le recomendamos los dos validadores siguientes: jsonlint.com y


e
.d

jsonformatter.org.
w
w
w

b. La problemática del control de acceso HTTP


Antes de ir más lejos, es necesario abordar la problemática del control de accesos HTTP.

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):

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


resp.setHeader('Access-Control-Allow-Origin', '*');

Esta autorización muy/demasiado general, se podría afinar a continuación.

c. Retorno al cliente de un archivo JSON


Disponiendo de todas las piezas que necesita, va a crear un primer servidor Node.js que devuelva su
archivo Products.json, que contiene los datos formateados en JSON.

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

app.get('/Products', (req, res) => {


pr

res.setHeader('Content-Type',
do

'application/json; charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
e to
.d

var readable = fs.createReadStream('Products.json');


w

readable.on('open', () => {
w

readable.pipe(res);
w

console.log('Lista de los productos devuelta'); });


readable.on('error', () => { res.end('[]'); });
});

app.listen(8888);

Y su archivo package.json:

{
"name": "devuelveLosDatos",
"description": "devuelve datos JSON leídos en un archivo",
"scripts": {
"start": "node devuelveLosDatos.js"

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


},
"dependencies": {
"express": "^4.16.4"
}
}

 Lance el servidor:

npm start

 En su navegador, invoque el servidor en la ruta /Products: localhost:8888/Products.

Y su navegador muestra entonces los datos deserializados:

org
n.
io
ac
m
ra
og
pr
do
e to
.d

d. Configuración de las rutas


w
w

Ahora vamos a introducir un nuevo elemento esencial: la configuración de la ruta.


w

En el ejemplo anterior, va a añadir a la ruta un argumento brand. De esta manera, el servidor va a


tener que analizar (parser) la colección Products deserializada a partir del archivo Products.json,
para devolver solo los objetos cuyo valor de la propiedad brand se corresponde con el argumento.

En una ruta, se introduce un argumento por medio del carácter dos puntos (:).

A continuación se muestra la ruta modificada:

app.get('/Products/:brand', (req, res) => {


...
}

Esta ruta se va a corresponder, por ejemplo, con la invocación: localhost:8888/Products/Peach.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


Los argumentos son accesibles gracias al objeto params asociado a la consulta, y a continuación se
muestra la línea de código que se debe escribir, si quiere aislar en una nueva variable el argumento
brand:

var marcaABuscar = req.params.brand;

Una vez que este argumento esté aislado, será necesario:

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

var Products = [];


og

for (var i=0; i < ProductsArray.length; i++) {


pr

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.

A continuación se muestra el código completo del ejemplo (devuelveLosDatosFiltrados.js):

var express = require('express');


var fs = require('fs');

var app = express();

app.get('/Products/:brand', (req, res) => {


var searchedBrand = req.params.brand;
console.log('Ruta invocada: /Products/'+searchedBrand);

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


var ProductsArray=JSON.parse(fs.readFileSync('Products.json',
'UTF-8'));
var results = [];
for (var i=0; i < ProductsArray.length; i++) {
if (ProductsArray[i].brand == searchedBrand) {
results.push( ProductsArray[i] ); }
}

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type',
'application/json;charset=utf-8');

var json = JSON.stringify(results);


res.end(json);

rg
});

o
n.
io
app.listen(8888);
ac
m
Y su archivo package.json:
ra
og

{
"name": "devuelveLosDatosFiltrados",
pr

"description":"devuelve datos JSON filtrados con un argumento",


do

"scripts": {
to

"start": "node devuelveLosDatosFiltrados.js"


e
.d

},
w

"dependencies": {
w

"express": "^4.16.4"
w

}
}

 Lance el servidor:

npm start

 En su navegador, invoque el servidor en la ruta /Products: http://localhost:8888/Products/Peach.

Y su navegador muestra los datos deserializados:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


org
Estudiamos ahora el caso de un archivo muy voluminoso, cuyos datos se deben consumir

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

datos línea por línea.


og
pr

A continuación se muestra un ejemplo de código (devuelveLosDatosFiltrados_v2.js) utilizando esta


do

estrategia:
e to

var express = require('express');


.d

var fs = require('fs');
w

var split = require('split');


w
w

var app = express();

app.get('/Products/:brand', (req, res) => {


var searchedBrand = req.params.brand;
console.log('Ruta invocada: /Products/'+searchedBrand);

var readable = fs.createReadStream('Products.json');


var objectStream = readable.pipe(split());
var ProductsDeLaMarca = [];

readable.on('error', () => { res.end('[]'); });


objectStream.on('data', (chunk) => {
try {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


chunk = chunk.replace(/,$/, '');
var product = JSON.parse(chunk);
if (product.brand == searchedBrand)
ProductsDeLaMarca.push( product );
} catch (e) {};
});

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

los datos de un archivo JSON, siguiendo un motivo de análisis (de parsing).


pr

 Instale el módulo split con npm e incluya esta dependencia en el archivo package.json, utilizando
do

su opción --save:
e to

npm install split --save


.d
w
w

 A continuación, lance el servidor e invóquelo como anteriormente.


w

e. Gestión de los argumentos


Vamos a profundizar en el paso de argumentos en las rutas REST, que el módulo express gestiona.

Suponga el método app.get() que gestiona la ruta /Products, con la variable request que
representa la consulta:

app.get('/Products', (request, response) => {


...
});

request.url va a contener la URL completa.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


request.query va a contener la cadena de consulta (la query string) si existe, es decir, los
argumentos situados después del punto de consulta.

De esta manera, si el código es este:

app.get('/Products', function(request, response) => {


for (let e in request.query) {
console.log(e+": "+request.query[e]);
}
});

y si lo invoca en su navegador con la URL siguiente: localhost:8888/Products?


type=phone&brand=Peach&brand=Threestars

Obtiene esta visualización:

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

devolver sus partes en forma de objeto.


og

Con este código:


pr
do

var express = require('express');


to

var querystring = require('querystring')


e

var app = express();


.d
w

app.get('/Products', (request, response) => {


w

let query = querystring.parse(request.url.split('?')[1]);


w

console.log(query);
});

app.listen(8888);

el mismo ejemplo de invocación que antes, devuelve:

{type: "phone", brand: ["Peach", "Threestars"]}

El módulo body-parser permite acceder a los argumentos enviados a través del método POST.

De esta manera, si el código es el siguiente:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/Products', (request, response) => {


for (let e in request.body) {
console.log(e+": "+request.body[e]);
}
});

app.listen(8888);

y si lo invoca gracias a curl (en un terminal o una línea de comandos):

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:15 p. m.]


Securización de un servidor Node.js
(protocolo HTTPS)
Si quiere establecer un servidor Node.js HTTPS, el procedimiento es un poco más complicado (en
esta sección, las etapas se ilustrarán por medio de los comandos ejecutados en Linux/Ubuntu).

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

2. el certificado que entrega el servidor (server certificate).


do
to

Estas operaciones necesitan la creación de una passphrase:


e
.d

pwgen 20 1 -s > passphrase


w
w
w

1. Creación de la autoridad de certificación


Una autoridad de certificación es un tercero de confianza que permite autenticar la identidad de
aquellos con quién se relaciona.

En este ejemplo, usted será su propia autoridad de certificación (lo que por supuesto, es criticable).

 Genere una clave privada (debe rellenar dos veces la passphrase):

openssl genrsa -des3 -out ca.key 1024

 Genere el certificado signing request (debe rellenar la passphrase):

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:27 p. m.]


openssl req -new -key ca.key -out ca.csr

 Firme el certificado (debe rellenar la passphrase):

openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key

2. Creación del certificado


 Genere una clave privada (para esto, debe rellenar dos veces la passphrase):

openssl genrsa -des3 -out server.key 1024

 Genere el certificado signing request (debe rellenar la passphrase):

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

openssl rsa -in server.key.passphrase -out server.key


pr

Firme el certificado (debe rellenar la passphrase):


do


to

openssl x509 -req -days 365 -in server.csr -signkey server.key -out
e

server.crt
.d
w
w
w

3. Creación del servidor


Todos los recursos que hayan sido generados, ahora pueden hacer funcionar su servidor en el puerto
convencional 443 o en otro puerto (por ejemplo, 8443).

A continuación se muestra el ejemplo de un servidor que recibe de manera cifrada los


logins/contraseñas:

const https = require('https');


const fs = require('fs');
const express = require('express');
const app = express();

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:27 p. m.]


const opciones = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt'),
ca: fs.readFileSync('./ca.crt'),
requestCert: true,
rejectUnauthorized: false
};

https.createServer(opciones, app).listen(8443);

app.get('/login=:login/password=:password', (req, res) => {


let login = req.params.login;
let password = req.params.password
console.log("Ok con login = "+login+" password = "+password);
});

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:27 p. m.]


Conocimientos adquiridos en este capítulo
A partir de ahora, usted sabe:

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

explotar archivos que contienen datos formateados en JSON:


do

Accediendo globalmente a los datos de estos archivos.


e to

Accediendo progresivamente a los datos de estos archivos, a través de la utilización de


.d
w

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. 

La opción se toma a favor de MongoDB, cuyo funcionamiento y acoplamiento con un servidor

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:40 p. m.]


Node.js se discutirán en el capítulo siguiente.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:40 p. m.]


Introducción
MongoDB es un sistema de gestión básico de datos, creado en 2007 por la empresa con el mismo
nombre (y en el momento en que se escribe este libro, en versión 4.0). Este SGBD forma parte de la
familia NoSQL (Not only SQL), que agrupa a los SGBD no relacionales de diferentes tipos. La
principal motivación para separarse del modelo relacional, es la voluntad de deshacerse de las

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

 los objetos JavaScript se implementan con tablas asociativas). La palabra BSON se


clave/valor,
pr

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

orientados a documentos (principalmente con el SGBD Redis). Por lo tanto, esquemáticamente un


.d

documento es la representación de un objeto JavaScript en una colección (una lista de objetos).


w
w
w

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:46:53 p. m.]


¿Por qué utilizar una base de datos
NoSQL?
MongoDB tiene como principal ventaja la capacidad de poder gestionar un volumen muy importante
de datos, ya que una base de datos se puede repartir en varios servidores, garantizando los accesos
eficaces (el SGBD Redis, conserva los datos en memoria RAM, garantiza los accesos todavía más

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

mayoría de estos atributos no tendrá valores para numerosos artículos).


e
.d

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:03 p. m.]


Presentación de MongoDB
1. Las colecciones y los documentos
Si está acostumbrado a un SGBD relacional, una primera aproximación (muy acertada) es considerar
que las tablas se van a corresponder con las colecciones, y las tuplas con los documentos. Dicho esto

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

debe llevarse a cabo de la lógica empresarial.


pr

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

La duplicación de las propiedades del documento destino en el documento origen, lo que


w

puede parecer incómodo, pero facilitará el tiempo de acceso.


La identificación del documento destino en el documento origen, por el identificador único
asignado por MongoDB (de hecho, como si manipulara una clave extranjera), pero esto le
obligará a producir algo de código.

Este punto se detalla en la sección Referencia a los documentos y joins.

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:15 p. m.]


otro índice sobre uno o varios otros atributos (creando un índice compuesto).

Para ver los índices que tiene una colección, aplique el método getIndexes() a la colección:

db.<nombreDeLaColeccion>.getIndexes()

Para crear un índice sobre una colección, utilice el método createIndex():

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:15 p. m.]


Implementación de MongoDB
1. Instalación de MongoDB
a. Instalación de MongoDB en Linux

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

administrador de servicios desde Ubuntu 16.04):


og

sudo

pr

systemctl enable mongodb


do
to

Para verificar cuál es el puerto ocupado por MongoDB (funciona por defecto en el puerto 27017):
e
.d

sudo netstat -antup


w
w
w

b. Instalación de MongoDB en Windows o en Mac OS


Toda la información útil está en el sitio web: https://docs.mongodb.com/manual/installation/

c. Utilización de MongoDB en línea de comandos


En línea de comandos, la gestión de las bases de datos de MongoDB y de las colecciones contenidas
en ella, se realiza gracias al mongo shell, que es una interfaz JavaScript interactiva. Para esto, es
suficiente con lanzar el ejecutable mongo en su terminal o su línea de comandos:

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)

La función print() permite mostrar mensajes en el terminal o la línea de comandos.

 En los ejemplos que siguen, las palabras rodeadas por < y > se sustituyen por sus
propios datos:

<nombreDeLaBase>: nombre de su base MongoDB.


<nombreDeLaColeccion>: nombre de su colección MongoDB.
<Documento>: un documento MongoDB, es decir, un objeto almacenado en una
colección.
<Colleccion>: una colección, es decir una lista de objetos: [Objeto1, ...,
Objeton]
<ObjetoFiltro>: un objeto que permite filtrar una colección MongoDB para
seleccionar los documentos. Se corresponde con los criterios de búsqueda.
<ArchivoJSON>: un archivo en el formato JSON.

2. Visualización de la lista de las bases de datos


La lista de las bases de datos se obtiene con la siguiente instrucción:

show dbs

3. Creación de una base de datos


La creación de una base de datos se obtiene con la siguiente instrucción:

use <nombre de la base de datos>

Pero atención, esta creación solo será efectiva si se crea una colección en la base de datos.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


4. Visualización de la lista de las colecciones
La lista de las colecciones se obtiene con una de las siguientes instrucciones:

show colecciones

show tables

5. Creación de una colección


Para gestionar una colección en mongo shell, va a aplicar métodos al objeto db, que representa la
base de datos actual.

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

Esta base de datos se creará definitivamente durante la creación de su primera colección.


pr
do

a. Inserción de los documentos en una colección


to

La colección se creará cuando se inserten los primeros documentos. La colección se puede considerar
e
.d

(aproximadamente) como una tabla y un documento como una tupla de ella.


w

Puede insertar un único documento o una colección de documentos:


w
w

db.<nombreDeLaColeccion>.insert(<Document>)

db.<nombreDeLaColeccion>.insert(<Collection>)

Ejemplos:

db.Products.insert({"type": "phone", "brand": "Peach", "name":


"topPhone"})

db.Products.insert([
{"type": "phone", "brand": "Peach", "name": "topPhone 8 64G"},
{"type": "phone", "brand": "Peach", "name": "topPhone 8 256G"},

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


{"type": "phone", "brand": "Threestars", "name": "bigPhone"},
{"type": "phone", "brand": "Konia", "name": "Konia 4000"},
{"type": "headset", "brand": "Earlid", "name": "Earlid pro"}
])

 La inserción de una fecha se hace a través del método Date() de mongo shell.

b. Importación de documentos a partir de un archivo


Los documentos también se pueden insertar a partir de un archivo JSON, gracias al comando
mongoimport.
Por defecto, los documentos se añaden a los documentos existentes. Si quiere eliminar los
documentos existentes, utilice la opción --drop.

Atención, debe haber un objeto por línea (y por lo tanto, los objetos no están separados por comillas).

mongoimport --db <nombreDeLaBase>


--collection <nombreDeLaColeccion>
--file <ArchivoJSON>

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:

mongoimport --db <nombreDeLaBase>


--collection <nombreDeLaColeccion>
--file <ArchivoJSON> --jsonArray --drop

Si la base de datos no existe, directamente se crea.

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:

mongoimport --db OnlineSales


--collection Products
--file listaProductos.json
--jsonArray --drop

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


c. Exportación de los documentos de una colección en un archivo JSON
Los documentos de una colección se pueden exportar (por ejemplo, con el objetivo de hacer una
copia de seguridad) en un archivo JSON, gracias al comando mongoexport.

mongoexport --db <nombreDeLaBase>


--collection <nombreDeLaColeccion>
--out <ArchivoJSON>

6. Interrogación de una colección


a. Interrogación a través de un objeto "filtro"
Para seleccionar todos los documentos de la colección, utilice el método find() sin argumento:

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.

De esta manera, db.Products.find()[0] devuelve el primer documento de la colección


Products (si existe).

db.Products.find()[0].<nombre de la propiedad> devuelve una propiedad


de este documento.

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()

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


Para mostrar el valor de algunas propiedades de los documentos de la colección, programe el
JavaScript utilizando el método forEach() en el resultado devuelto por find():

db.<nombreDeLaColeccion>
.find()
.forEach((doc) => { <codeJavaScript> })

Por ejemplo:

db.Products.find().forEach((doc) => { print(doc.name); })

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

Es posible en el objeto filtro especificar varios criterios de búsqueda:

db.Products.find({"brand": "Peach", "type": "phone"})

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

b. Los operadores de comparación, los operadores de conjuntos y lógicos

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


Los siguientes operadores de comparación, se pueden utilizar en el objeto filtro:

$eq: devuelve verdadero si el valor de la propiedad es igual al valor especificado.


$ne: devuelve verdadero si el valor de la propiedad es diferente del valor especificado.
$lt: devuelve verdadero si el valor de la propiedad es inferior al valor especificado.
$lte: devuelve verdadero si el valor de la propiedad es superior o igual al valor especificado.
$gt: devuelve verdadero si el valor de la propiedad es superior al valor especificado.
$gte: devuelve verdadero si el valor de la propiedad es superior o igual al valor especificado.

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

(sea cual sea el valor de esta).


og

Por ejemplo, a continuación se muestra una búsqueda de los productos de la colección Products,
pr

que se asocian a la propiedad popularity:


do
to

db.Products.find({"popularity": {$exists: true}})


e
.d
w
w

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:

db.Products.find({"type":{$in: ["computer", "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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


db.Products
.find({"type": "phone",
"brand": {$nin: ["Konia", "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).

db.Products.find({"popularity": {$not: {$lt: 4}}})

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


7. Aplicación de expresiones regulares
Es posible aplicar una expresión regular al valor de una propiedad (por ejemplo, para buscar en ella
una subcadena):

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.

A continuación se muestra la búsqueda de la subcadena «phone», como valor de la propiedad name

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

8. Las proyecciones y el método distinct()


pr
do
to

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.

 Es imposible simultáneamente conservar algunas propiedades y excluir otras (excepto


excluir la propiedad _id, que se devuelve por defecto).

Por ejemplo, a continuación se muestra la búsqueda de los nombres de teléfonos:

db.Products.find({"type": "phone"}, {"name": 1})

o la búsqueda de toda la información de los productos, salvo su popularidad:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


db.Products.find({}, {"popularity": 0})

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:

la propiedad cuyos únicos valores distintos se devolverán;


el objeto filtro.

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

9. Referenciado los documentos y joins


do
e to

Le proponemos descubrir con ejemplos, el referenciado de documentos a través de los identificadores


.d

y la implementación de joins (uniones).


w
w

En el marco de su aplicación de comercio en línea, quiere gestionar dos colecciones:


w

La colección Products que lista los productos puestos a la venta.


La colección Carts que memoriza las cestas de la compra (es decir los pedidos en
constitución y todavía no validados) de sus diferentes clientes.

(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).

Tomemos como ejemplo los dos documentos siguientes de la colección Products:

{ "_id": ObjectId("58d3cd3cda8751c171a6606f"),
"type":"phone",
"brand":"Threestars", "name":"bigPhone", "price":200, "stock":25 }
{ "_id": ObjectId("58d3cd3cda8751c171a66071"),

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


"type":"headset",
"brand":"Earlid", "name":"Earlid Pro", "price":300, "stock":10 }

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

a. Los objetos anidados (nested objects)


og

La primera solución es volver a copiar (totalmente o en parte) las propiedades de los productos
pr

implicados en los objetos insertados en la lista order.


do
to

A continuación se muestra cómo se puede escribir:


e
.d

{ "_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}
]
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


A continuación se muestra la consulta que permite conocer los nombres de los productos de la cesta
de la compra de Pierre Pompidor:

db.Carts.find({"email":"[email protected]"})[0]
.order
.forEach((doc) => {
print(doc.name);
})

En conclusión, la duplicación de los datos es criticable, pero el acceso es relativamente simple.

b. Los objetos referenciados


La segunda solución es volver a copiar solo los identificadores de los productos en la lista de los
productos del pedido:

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.

A continuación se muestra la primera solución, la segunda se ilustrará en la siguiente sección.

var IDS = db.Carts.find({"email":"[email protected]”})[0].order


db.Products.find({_id: {$in: ids}}).forEach((doc)=>{print(doc.name);});

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


La unión es una acción definida en un conjunto de acciones (un pipeline), gestionado por el método
aggregate() aplicado a una colección.
A continuación se muestra el esquema de programación de un pipeline:

db.<nombreDeLaColeccion>.aggregate([
{$<acción>: },
{$< acción >: },
...
])

Las acciones implementadas en el ejemplo siguiente son:

$match: selecciona los documentos de una colección.


$unwind: distribuye los elementos de una lista en el objeto que la contiene.

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

A continuación se muestra el resultado de las acciones del pipeline:


e
.d

{ "_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" }
])

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


A continuación se muestra el resultado de las acciones del pipeline:

{ "_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

localField: el nombre de la propiedad local que crea el enlace de la unión.


og
pr

foreignField: el nombre de la propiedad de la segunda colección que crea el enlace de


do

la unión.
to

as: el nombre de la nueva propiedad que recibe a los documentos.


e
.d
w

db.Carts.aggregate([
w

{ $match: {"email":"[email protected]”}},
w

{ $unwind: "$order" },
{ $lookup: { from: "Products",
localField: "order",
foreignField: "_id",
as: "product" }}
])

A continuación se muestra el resultado de las acciones del pipeline:

El primero objeto producido:

{ "_id": ObjectId("58d55d616d577b19d5946c1d"),
"lastname": "Pompidor",
"firstname": "Pierre",

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


"email": "[email protected]”,
"order": ObjectId("58d3cd3cda8751c171a6606f"),
"product": [ { "_id": ObjectId("58d3cd3cda8751c171a6606f"),
"type": "phone",
"brand": "Threestars",
"name": "bigPhone",
"popularity": 4,
"price": 200,
"picture": "bigphone.jpeg",
"stock": 25 }
]
}

Y el segundo objeto producido:

rg
{ "_id": ObjectId("58d55d616d577b19d5946c1d"),

o
n.
"lastname": "Pompidor",

io
"firstname": "Pierre",
"email": "[email protected]”,
ac
m
"order": ObjectId("58d3cd3cda8751c171a66071"),
ra

"product": [ { "_id": ObjectId("58d3cd3cda8751c171a66071"),


og

"type": "headset",
"brand": "Earlid",
pr

"name": "Earlid Pro",


do

"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" }},

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


{ "$unwind": "$product" },
{ "$group": {"_id": "$_id",
"order": { "$push": "$order" },
"Products": { "$push": "$product" }
}
}
]
)

A continuación se muestra el resultado de las acciones del pipeline:

{ "_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

"name": "Earlid Pro",


w

"popularity": 5,
w

"price": 300,
w

"picture": "beatspro.jpeg",
"stock": 21 }
]
}

10. Actualización y eliminación de un documento


a. Actualización de un documento
La actualización de un documento se implementa gracias al método update() que recibe dos
argumentos:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


El objeto filtro;
un objeto que agrupa las modificaciones a realizar:

la modificación del valor de una propiedad existente o la adición de una nueva propiedad,
se introducen con el operador \$set;

la eliminación de una propiedad se introduce con el operador $unset.

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

se habrán eliminado, y solo se inserta ({"price": 999}) en el objeto.


pr
do

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

o añadir el operador $multi con el valor true:


w
w

db.Products
w

.update({"type": "tablet"},
{$set: {"type": "touch tablet"}, "multi": true})

o utilizar el método updateMany().

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


11. Eliminación de una colección
La eliminación de una colección se implementa gracias al método drop():

db.<nombreDeLaColeccion>.drop()

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:28 p. m.]


Utilización de MongoDB a través de
Node.js
Para ilustrar la asociación entre Node.js y MongoDB, va a crear diferentes versiones de un servidor
Node.js, llamado onlinesalesserver.

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

servidor, su descripción, el archivo que lo contiene y su nombre.


do

A continuación se muestra el archivo package.json, así como el producto:


e to
.d

{
w

"name": "onlinesalesserver",
w

"versión": "1.0.0",
w

"description": "servidor de venta de productos high tech",


"main": "onlinesalesserver.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Pierre Pompidor",
"license": "ISC"
}

 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

start, lo que permite utilizar npm para lanzar


Índiceel servidor:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node onlinesalesserver.js"
},
...
"engines": { "node": ">= 8.10.0" }
}

 Segunda etapa: instale los módulos express, cors y mongodb:

npm install express cors mongodb --save

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

"description": "servidor de productos high tech",


"main": "onlinesalesserver.js",
pr

"scripts": {
do

"test": "echo \"Error: no test specified\" && exit 1",


to

"start": "node onlinesalesserver.js"


e

},
.d

"author": "Pierre Pompidor",


w
w

"license": "ISC",
w

"engines": { "node": ">= 8.10.0" },


"dependencies": {
"cors": "^2.8.5",
"express": "^4.16.4",
"mongodb": "^3.2.1"
}
}

2. Conexión al servidor MongoDB


Para conectar al servidor MongoDB desde Node.js, va a:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


Crear una aplicación cliente MongoDB, gracias al módulo mongodb de Node.js.
Conectarse al servidor MongoDB gracias al método connect(), que recibe como
argumentos:

La URL de conexión: el protocolo de red y la dirección IP del servidor;

Un objeto de configuración (tipo de analizador de la URL);

Una función de callback que tiene dos argumentos:

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

var url = "mongodb://localhost:27017/";


og
pr

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


do

let db = cliente.db("OnlineSales");
to

assert.equal(null, err);
e

console.log("Conexión al servidor MongoDB realizada con éxito");


.d

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.

(Esta primera versión del servidor se conserva con el nombre de conexion-MongoDB.js.)

3. Inserción de datos desde un servidor Node.js


Hay dos métodos, insertOne() y insertMany(), que permiten insertar documentos en una
colección MongoDB.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


Si la colección no existe, se crea.

A continuación se muestra el esquema de programación que permite insertar un único documento,


con el método insertOne():

db.collection("<nombre de la colección>").insertOne( { ... } );

A continuación se muestra el esquema de programación que permite insertar varios documentos, con
el método insertMany():

db.collection("nombre de la colección").insertMany( [ ... ] );

A continuación se muestra un ejemplo de inserción de un documento en la colección Products:

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

4. Consultar datos desde un servidor Node.js


Después de la conexión al servidor MongoDB, va a realizar diferentes selecciones sobre los
documentos contenidos en la colección Products de su base de datos OnlineSales.

Para que los códigos sean más legibles, estás diferentes selecciones se aislarán en una función
productResearch().

a. Explotación del resultado del método find()


El método find() devuelve un objeto promise, sobre el que se aplica el método each() que
permite acceder a cada documento.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


Considere el código del servidor que muestra todos los documentos de la colección Products:

"use strict";
var MongoClient = require("mongodb").MongoClient;
var assert = require("assert");
var url = "mongodb://localhost:27017";

var productResearch = function(db) {


var cursor = db.collection("Products").find();
cursor.each((err, doc) => {
assert.equal(err, null);
if (doc != null)
for (let p in doc) console.log(p+": "+doc[p]);
console.log("\n");
});

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

documento de la colección Products, se muestran en su terminal o su línea de comandos.


.d
w

_id: 58b3e2d440f60da0834601da
w
w

type: phone
brand: Peach
name: topPhone 8 32G
popularity: 4
price: 900
photo: topphone.jpeg

...

(Esta versión del servidor se guarda con el nombre de consultaMongoDB_visualizacionConsola.js.)

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


de tipo phone.

"use strict";
var MongoClient = require("mongodb").MongoClient;
var assert = require("assert");
var url = 'mongodb://localhost:27017/';

var productResearch = function(db, ObjetoFiltro, callback) {


var cursor = db.collection("Products").find(ObjetoFiltro);
cursor.each((err, doc) => {
assert.equal(err, null);
if (doc != null)
for (let p in doc) console.log(p+': '+doc[p]);
else console.log("Final de la operación");
console.log("\n");

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

(Esta versión del servidor se guarda con el nombre de consultaMongoDB_visualizaciónConsole.js.)


e to
.d

b. Utilización del método toArray()


w
w

Es realmente práctico utilizar el método toArray() sobre el resultado de find(), para


w

almacenar en una lista los documentos devueltos por la consulta.

A continuación se muestra un ejemplo donde los nombres de los productos almacenados en la


colección Products, se muestran en la consola:

'use strict';
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var url = 'mongodb://localhost:27017';

var ProductsResearch = function(db, search) {


db.collection('Products').find()
.toArray((err, documents) => {
for (let doc of documents) console.log(doc.name);

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


});
};

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


let db = cliente.db("OnlineSales");
assert.equal(null, err);
ProductsResearch(db);
cliente.close();
});

(Esta versión del servidor se guarda con el nombre de consultaMongoDB_toArray.js.)

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

var ProductsResearch = function(db, objetoFiltro, callback) {


og

var cursor = db.collection('Products').find(objetoFiltro);


cursor.each((err, documento) => {
pr

assert.equal(err, null);
do

if (documento != null)
to

for (let prop in documento)


e
.d

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

(Esta versión del servidor se guarda con el nombre de consultaMongoDB_filtro.js.)

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


5. Sincronización de las consultas
En el universo de JavaScript, las operaciones de importación y de consultas de datos (ya sea a través
de Ajax o a través de las consultas que solicitan una base de datos NoSQL como MongoDB), son por
defecto asíncronas. Este modelo, natural con la gestión orientada a eventos del lenguaje, es muy
potente, ya que evita que las páginas web y las acciones de usuario se fijen, esperando que los datos
sean importados. Pero en algunos casos, es necesario sincronizar los accesos a los datos.

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.

 En primer lugar, filtre la colección Products usando el siguiente objeto de filtro:

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

segunda consulta que pregunta de nuevo a la colección Products:


og

{"price": {$gte: average}}


pr
do

 Para unificar el resultado de sus consultas, cree su propia función productResearch().


e to

Recibe como argumentos:


.d
w

El enlace a la base de datos.


w
w

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():

El mensaje de fin de operación a mostrar.


Y la colección de los documentos seleccionados (o una colección vacía si no se ha
seleccionado ningún documento).

function productResearch(db, param, callback) {


db.collection("Products").find(param["filterObject"])

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


.toArray((err, documents) => {
if (err) callback(err, []);
if (documents !== undefined)
callback(param["message"], documents);
else callback(param["message"], []);
});
};

a. Utilización de las funciones de callback


La solución natural para sincronizar las consultas en la base de datos NoSQL, es encapsularlas en las
funciones de callback.

A continuación se muestra una implementación posible del ejemplo anterior:

org
"use strict";

n.
var MongoClient = require("mongodb").MongoClient;

io
var assert = require("assert"); ac
var url = "mongodb://localhost:27017";
m
ra

... // function productResearch


og
pr

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


do

let db = cliente.db("OnlineSales");
assert.equal(null, err);
e to
.d

find(db, {"message":"Etapa 1", "ObjetoFiltro": {"type": "phone"}},


w

(etape, resultados) => {


w

console.log(etape+" con "+resultados.length


w

+" productos seleccionados");


if (resultats.length > 0) {
let average = 0;
for (let documento of resultados)
if (documento.price !== undefined)
average += documento.price; }
average /= resultados.length;
console.log("average = "+average);
find(db,
{"message":"Etapa 2",
"ObjetoFiltro": {"price": {$gte: average}}},
(etapa, resultados) => {
console.log(etape+" con "+resultados.length
+" productos seleccionados");

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


for (let documento of resultados)
console.log(documento.name+": "
+documento.price);
cliente.close();
});
}
else cliente.close();
});
});

A continuación se muestra el resultado respecto a la colección Products:

Etapa 1 con 5 productos seleccionados


average = 780

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

sincronizar, se convierte en un problema a nivel de la legibilidad del programa.


pr
do

A continuación se muestra un ejemplo vertiginoso de lo que hemos llamado «el infierno de los
to

callbacks» (callback hell):


e
.d

db.<nombreDeLaColeccion>.find(db,
w

<ObjetoFiltro>,
w

(message, results) => {


w

...
db.<nombreDeLaColeccion>.find(db,
ObjetoFiltro>,
(message, results) => {
...
db.<nombreDeLaColeccion>.find(db,
<ObjetoFiltro>,
(message, results) => {
...
db.<nombreDeLaColeccion>.find(db,
<ObjetoFiltro>,
(message, results) => {
...

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


});
});
});
});

b. Utilización del módulo async


Para evitar «el infierno de los callbacks», puede utilizar un módulo muy potente de gestión de
operaciones asíncronas, ejecutadas en Node.js: el módulo async.

Para instalar este módulo y referenciarlo en el archivo package.json, ejecute el siguiente comando:

npm install --save async

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

compartir los datos entre funciones.


og

En los ejemplos siguientes, la conexión a la base de datos MongoDB y la


pr
do

función productResearch(), no son reproducibles.


to

El método async.series() tiene la siguiente estructura:


e
.d
w

// declaraciones de los datos a compartir entre las funciones


w

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


Una llamada callback(true) detiene el encadenamiento de las funciones y llama a la función
final si existe.

El método async.waterfall() tiene la siguiente estructura:

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

A continuación se muestra la solución con el método async.series():


e to
.d

var async = require("async");


w

MongoClient.connect(url, , {useNewUrlParser: true}, (err, cliente) => {


w

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


average /= results.length;
console.log("media = "+average);
callback();
}
else callback(true);
});
},
function (callback) {
productResearch(db, {"message":"Etapa 2",
"filterObject": {"price":{$gte: average}}},
(message, results) => {
console.log(message+": "+results.length
+" productos seleccionados");
for (let doc of results) {
console.log(doc.name+": "+doc.price);

rg
}

o
n.
callback();

io
});
}],
ac
m
function () { db.close(); }
ra

);
og

});
pr
do

A continuación se muestra el resultado respecto a la colección Products:


e to

Etapa 1: 5 productos seleccionados


.d

media = 780
w

Etapa 2: 4 productos seleccionados


w

topPhone 8 32G: 900


w

topPhone 8 64G: 1000


topPhone 8 256G: 1300
Peach pro: 1300

d. El método async.waterfall()
A continuación se muestra la solución con el método async.waterfall():

...
var async = require("async");

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


let db = cliente.db("OnlineSales");

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


assert.equal(null, err);
async.waterfall([
function(callback) {
productResearch(db, {"message":"Etapa 1",
"filterObject": {"type": "phone"}},
(message, results) => {
console.log(message+": "+results.length
+" productos seleccionados");
if (results.length > 0) {
var average = 0;
for (let doc of results)
if (doc !== undefined)
average += doc.price;
average /= results.length;
console.log("media = "+average);

rg
callback(null, average);

o
}

n.
io
else callback(true);
}); ac
},
m
function(average, callback) {
ra

productResearch(db,
og

{"message":"Etapa 2",
pr

"filterObject": {"price": {$gte: average}}},


do

(message, results) => {


to

console.log(message+": "+results.length
e

+" productos seleccionados");


.d

for (let doc of results)


w
w

console.log(doc.name+": "+doc.price);
w

callback(null);
});
}],
function() { cliente.close(); }
);
});

A continuación se muestra el resultado respecto a la colección Products:

Etapa 1: 5 productos seleccionados


media = 780
Etapa 2: 4 productos seleccionados
topPhone 8 32G: 900
topPhone 8 64G: 1.000

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


topPhone 8 256G: 1.300
Peach pro: 1300

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:42 p. m.]


Interrogación de MongoDB con las rutas
gestionadas por express
Ahora va a preguntar a su base de datos MongoDB, invocando al servidor Node.js con consultas http,
que expresan rutas. Estas rutas se gestionan por el módulo express de Node.js.

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

Utilización del módulo express:


pr

para crear una aplicación que gestione las rutas;


do
to

y que escuche en un puerto dado.


e
.d

Utilización del módulo cors para permitir el cross-origin resource sharing.


w

Creación del cliente MongoDB que:


w
w

se conecta a una base de datos gestionada por el servidor MongoDB;

establece los «listeners» sobre las diferentes rutas.

En el ejemplo estructural siguiente, <route> hace referencia a una ruta cualquiera.

"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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


var MongoClient = require("mongodb").MongoClient;
var url = "mongodb://localhost:27017";
var assert = require("assert");

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


let db = cliente.db("OnlineSales");
assert.equal(null, err);
app.get(<route>,
(req, res) => { // gestión de una ruta (método GET)
...
});

... // otras gestiones de rutas a través del método GET

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

... // gestiones de las rutas para los métodos PUT y DELETE


og

});
pr
do
to

2. La problemática del cross-origin resource sharing


e
.d
w

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:

var cors=require("cors"); // Utilización del módulo cors


app.use(cors()); // permite el cross-origin resource sharing

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', '*');

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


3. Ejemplos de gestión de rutas
Todos los ejemplos de gestión de rutas se relacionan con la colección Products, que agrupa los
productos puestos a la venta.

Estos códigos van a formar de la base del servidor de la aplicación de e-commerce.

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).

Para estructurar el código, utilice de nuevo la función productResearch() que:

recibe como argumento un objeto que contiene:

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

el mensaje a mostrar (que se puede sustituir por un mensaje de error);


og
pr

la lista de los documentos filtrados.


do

A continuación se muestra el código de la función productResearch():


e to
.d

function productResearch(db, param, callback) {


w

db.collection("Products").find(param["filterObject"])
w

.toArray((err, documents) => {


w

if (err) callback(err, []);


else if (documents !== undefined)
callback(param["message"], documents);
else callback(param["message"], []);
});
});

a. Gestión de una ruta para listar las marcas


En el marco de El hilo rojo (la aplicación de e-commerce), quiere gestionar una ruta para listar todas
las marcas de los productos que vende. Este servicio permitirá a sus futuros clientes definir sus
criterios de búsqueda.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


Por lo tanto, a continuación se muestra el código que gestiona la ruta /Products/brands:

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


let db = cliente.db("OnlineSales");
assert.equal(null, err);

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

b. Gestión de una ruta para filtrar los productos


w
w

Ahora quiere gestionar una ruta mucho más ambiciosa:

siempre sobre la colección Products;


pero que presentará, en este orden, cinco argumentos que definen:

el tipo de los productos buscados: :type;

la marca de los productos buscados: :brand;

el intervalo de precio de los productos buscados: :minprice y :maxprice;

y la popularidad mínima de los productos buscados: :minpopularity.

Por lo tanto, la declaración de la ruta es:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


/Products/:type/:brand/:minprice/:maxprice/:minpopularity

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():

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


let db = cliente.db("OnlineSales");
assert.equal(null, err);
app.get("/Products/:type/:brand/:minprice/:maxprice/:minpopularity",

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


var json=JSON.stringify(results);
console.log(json);
res.end(json);
});
});
});

c. Búsqueda de un producto a partir de su identificador interno


En el marco de la aplicación de e-commerce, es útil encontrar un producto a partir de su identificador.

MongoDB asocia cada documento de una colección a un identificador interno en función de la


propiedad _id.

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 ObjectId = require("mongodb").ObjectId;


og
pr

En el código propuesto a continuación, si el identificador está mal formado o es desconocido en la


do

colección, se devuelve un objeto vacío.


to

app.get("/Product/id=:id", (req, res) => {


e
.d

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


Por ejemplo, la ruta:

localhost:8888/Product/id=58e643742ba15f90d13cb87d

devuelve el documento correspondiente:

{"_id":"58e643742ba15f90d13cb87d",
"type":"phone",
"brand":"Peach"
"name":"topPhone 8 32G",
"popularity":4,
"price":900,
"picture":"topphone.jpeg",
"stock":5}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:47:55 p. m.]


El hilo rojo del lado servidor
A continuación se muestra el momento de crear algunos productos para vender.

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

"type":"phone", "brand":"Peach", "name":"topPhone 8 256G",


"popularity":4, "price":1300,
"picture":"topphone8.jpeg", "stock":18
},
{
"type":"phone", "brand":"Threestars", "name":"bigPhone 9",
"popularity":4, "price":700,
"picture":"bigphone9.jpeg", "stock":10
},
{
"type": "phone", "brand": "Konia", "name": "Konia4000",

"picture": "konia4000.jpeg", "stock":0
},
{

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:07 p. m.]


"type":"computer", "brand":"Vale", "name":"Vale 3000",
"popularity":4, "price":700,
"picture":"vale3000.jpeg", "stock":32
},
{
"type":"computer", "brand":"Peach", "name":"Peach pro",
"popularity":5, "price":1300,
"picture":"peachpro.jpeg", "stock":8
},
{
"type":"tablet", "brand":"Threestars", "name":"bigTablet",
"popularity":4, "price":200,
"picture":"tbigablet.jpeg", "stock":25
},
{

rg
"type":"headset", "brand":"Earlid", "name":"Earlid Pro",

o
n.
"popularity":5, "price":300,

io
"picture":"earlidpro.jpeg", "stock":21
}, ac
m
{
ra

"type":"headset", "brand":"Earlid", "name":"Earlid Studio",


og

"popularity":4, "price":400,
"picture":"earlidstudio.jpeg", "stock":12
pr

},
do

{
to

"type":"game console", "brand":"Notendi", "name":"3NTD",


e

"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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:07 p. m.]


}
]

Importe los documentos en la colección Products, gracias al siguiente comando:

mongoimport --db OnlineSales --collection Products


--file Products.json --jsonArray

La base de datos OnlineSales se crea automáticamente.

2. Implementación de dos búsquedas sobre los productos


Cree un servidor Node.js (archivo OnlineSales.js) para gestionar dos rutas sobre la colección
Products():

Una ruta para filtrar los productos según cinco argumentos:

El tipo de los productos buscados: type.

La marca de los productos buscados: brand.

El intervalo de precio de los productos buscados: minprice y maxprice.

La popularidad mínima de los productos buscados: minpopularity.

Una ruta para encontrar un producto a partir de su identificador interno.

En la versión final del servidor, varias otras rutas se gestionarán para:

Las listas de selección.


La actualización interactiva de las listas de selección.
La búsqueda por palabras clave.
Y todas las rutas que gestiona la cesta de la compra...

a. La superestructura del servidor


La creación del servidor implica:

La importación de los módulos necesarios:

El módulo express para gestionar fácilmente las rutas REST.

El módulo cors para superar los problemas de acceso a un servidor de terceros.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:07 p. m.]


El módulo assert para gestionar los problemas de accesos al servidor MongoDB.

El módulo mongodb para acceder al servidor MongoDB.

La creación de una aplicación express.


La creación de una función productResearch() para gestionar limpiamente los errores
y la visualización de mensajes, durante la realización de las acciones.
La conexión al servidor MongoDB.

"use strict";

let express=require("express");
let cors=require("cors");
let app=express();
let assert = require("assert");

app.listan(8888);
app.use(cors());

let MongoClient = require("mongodb").MongoClient;


let ObjectId = require("mongodb").ObjectId;
let url = "mongodb://localhost:27017/OnlineSales";

function productResearch(db, param, callback) {


db.collection("Products")
.find(param["filterObject"])
.toArray((err, documents) => {
if (documents !== undefined)
callback(param["message"], documents);
else callback(param["message"], []);
});
}

MongoClient.connect(url, {useNewUrlParser: true}, (err, cliente) => {


let db = cliente.db("OnlineSales");
assert.equal(null, err);

// Gestión de la ruta que filtra los productos


// según 5 argumentos
// (ver a continuación)

// Gestión de la ruta que encuentra un producto


// a partir de su identificador interno

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:07 p. m.]


// (ver a continuación)

});

b. Gestión de la ruta que filtra los documentos usando diferentes criterios


A continuación se muestra la gestión de la ruta que filtra los documentos de la colección
Products, con los cinco criterios opcionales siguientes:

El tipo del producto.


La marca del producto.
El precio mínimo y el precio máximo del producto.
La popularidad del producto.

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

if (req.params.minprice != "*" || req.params.maxprice != "*") {


do

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:07 p. m.]


});
});

c. Gestión de la ruta que devuelve un documento a través de su identificador interno


A continuación se muestra la gestión de la ruta que accede a un documento a través de su
identificador interno:

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

d. Ejemplos de consulta en el servidor


w
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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:07 p. m.]


Conocimientos adquiridos en este capítulo
Utilizar un sistema de gestión básico de datos NoSQL orientado a documento, y más particularmente
MongoDB, aporta dos grandes beneficios. El primero es poder gestionar grandes volúmenes de datos
de manera eficaz (gracias, entre otras, a la creación de índices, pero la verificación de las restricciones
de integridad y el respeto a las propiedades ACID de las transacciones no están garantizado), el

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

datos (objetos JavaScript) y de su manipulación.


og

 explicado cómo instalar MongoDB, insertar documentos en una colección y crear nuevas
Hemos
pr

colecciones. La consulta de las colecciones se ha estudiado con cuidado: el método find()


do

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

segundo argumento opcional, el establecimiento de proyecciones. También se ha abordado el empleo


w

del método distinct() que permite devolver solo los valores diferentes.
w
w

El referenciado de los documentos a través de sus identificadores internos y la implementación de


joins, se han ilustrado con ejemplos. Entre ellos, hemos presentado un pipeline muy complejo de
acciones a realizar.

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

creado con Angular.


e
.d

Angular retoma las siguientes grandes funcionalidades de AngularJS:


w
w

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.

Trae las siguientes mejoras notables:



Una modularidad perfecta de las aplicaciones que se construyen sobre una arquitectura de dos
niveles: módulos que integran componentes.

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.

 El «two-way data binding» (ver la sección Two-way data binding en el capítulo


Angular: las plantillas, bindings y directivas), que permite el acoplamiento bidireccional
de datos entre la vista y el modelo, ya no se implementa de forma predeterminda en
Angular cuando estaba en AngularJS (ya que este acoplamiento automático era muy
costoso en términos de recursos).

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

módulos, que a su vez se componen de componentes.


og
pr

a. Los módulos
do

Los módulos se corresponden con la implementación de grandes funcionalidades. Una aplicación


e to

Angular utiliza un determinado número de módulos preexistentes, y crea otros nuevos que se pueden
.d

reutilizarse en otras aplicaciones.


w
w

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:35 p. m.]


Inevitablemente, una aplicación Angular está formada por varios «nuevos» módulos, tan pronto como
alcanza cierta envergadura:

Los módulos que responden a necesidades funcionales claramente identificadas y reutilizables


(los feature modules).
El módulo principal (el root module), que es el módulo que contiene el componente «punto de
entrada» de la aplicación, e incluye también los componentes no encapsulados en los feature
modules.
Y los módulos (anexos) que acompañan a los módulos anteriores, para especificar las tablas de
enrutado necesarias para el router. En efecto, para ser autónomos, los feature modules deben
tener su propia tabla de enrutado (por lo tanto, el router gestiona el conjunto de las tablas de
enrutado, la tabla del root module y aquellas asociadas a los feature modules).

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

Gestionar una cesta de la compra.


pr

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

b. Los componentes y los servicios


Los componentes son piezas de software básicas. Durante la utilización de los módulos pre-existentes
ofrecidos por Angular, la aplicación solo importa los componentes que le interesan. En el marco de
este libro, utilizamos la extensión de JavaScript TypeScript para crear estos componentes (lo que es
de lejos la solución más frecuente) y de esta manera, cada componente se implementa por una clase
TypeScript.

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:35 p. m.]


no tienen plantillas. Por ejemplo, en este libro encontrará normalmente el servicio HTTP,
proporcionado por el módulo HTTP, contenido en la carpeta <ruta>/node_modules/@angular/http y
que ofrece métodos que permiten enviar consultas HTTP para dialogar con el servidor.

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).

3. Manipulación de los componentes como etiquetas


Angular permite extender el lenguaje HTML con las directivas. Estas directivas se corresponden con
las etiquetas y los atributos. Como ya sucedía con AngularJS, todo componente que tiene una
plantilla (es decir una interfaz en HTML), se puede utilizar en las plantillas de otros componentes, a

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

plantillas de otros componentes con la etiqueta <app-research> (eventualmente acompañado


og

de atributos que configuran el componente).


pr

A continuación se muestra un fragmento de la plantilla del componente de más alto nivel de la


do

aplicación El hilo rojo, que integra el componente ResearchComponent:


e to

<table>
.d

<tr>
w
w

<td> <app-research></app-research> </td>


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).

4. Utilización de una extensión en JavaScript (TypeScript o


Dart)
Una aplicación Angular se puede desarrollar en JavaScript «puro» (habiendo seguido las evoluciones

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:35 p. m.]


dictadas por ECMAScript), con Dart (un lenguaje concurrente de JavaScript desarrollado por Google)
o con TypeScript (una extensión de JavaScript), pero es realmente TypeScript el que se ha convertido
en imprescindible por su popularidad para realizar aplicaciones Angular. El código TypeScript
contiene decoradores que permiten en el «backoffice» de Angular, producir código JavaScript que se
corresponde con las diferentes entidades de la aplicación que desarrolla (módulos, componentes,
servicios, etc.). Por otro lado, la potente herramienta Angular CLI, que permite crear y gestionar
fácilmente proyectos Angular, se basa en TypeScript (lo que ha contribuido a popularizar este
lenguaje).

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

interpolación de datos en las plantillas.



pr

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

invocación de un componente a partir de una acción realizada en un primer componente, se


e

gestiona por parte del controlador que centraliza de esta manera los puntos de conexión entre los
.d
w

componentes: esta arquitectura evita también el síndrome del plato de espaguetis!.


w
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

modelo MVC más complicada:


w
w

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 :

Con los datos:

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:48:48 p. m.]


o gestionados localmente por los componentes Angular (datos temporales, ya que no se
guardan en una base de datos).

A las operaciones de negocio:

realizadas en el seno de los componentes Angular;

además, en el nivel del servidor Node.js (que, en el marco de una programación


racional se deben restringir a la implantación de filtros sobre los datos proporcionados
por el SGBB o a formateados concretos).

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:

Creándola de la nada, pero esto es totalmente desaconsejable debido a la complejidad de las

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

aquí, utilizando Angular CLI.


e to

Las ventajas de esta solución son múltiples:


.d
w

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.

1. Presentación de Angular CLI 

Angular CLI es una herramienta que permite:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


Instalar los módulos imprescindibles de Node.js (módulos adicionales deberán ser instalados
según sus necesidades).
Crear la estructura de un proyecto Angular (especificando las dependencias relacionadas con
este, creando las carpetas necesarias, etc.).
Crear los esqueletos de las diferentes entidades que componen una aplicación Angular y que
necesitará: módulos, componentes, servicios, directivas, pipes, guardián y enums.
Especificar y ejecutar las pruebas unitarias o de integración.
Crear la versión de producción de su aplicación.
Etc.

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

npm install -g @angular/cli@latest


pr
do

Localmente:
e to
.d

npm install --save-dev @angular/cli@latest


w
w
w

La opción --save-dev guarda una dependencia en el archivo de configuración package.json.

Por lo tanto, Angular CLI se gestiona a través de un módulo Node.js...

(En Linux/Ubuntu, puede ser que tenga que pasar a modo administrador para utilizar el comando
anterior.)

 Se podía instalar una versión obsoleta (angular-cli) con:

npm install -g angular-cli@latest.

Si ya la tiene instalada (aquí de manera local), puede desinstalarla (e instalar la nueva


versión), de la siguiente manera:

npm uninstall --save-dev angular-cli

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


npm install --save-dev @angular/cli@latest

3. Creación de un proyecto Angular con Angular CLI


La creación de un proyecto Angular con Angular CLI, se realizada a través del comando ng new:

ng new <nombre del proyecto>

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

esto, utilice el siguiente comando:


w
w

ng serve -o

De hecho, este comando realiza dos cosas esenciales:

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


En su navegador, invoque al servidor local en el puerto 4200: localhost:4200.

Y verá aparecer el resultado:

org
n.
io
ac
m
ra
og
pr

Además de un mensaje al de bienvenida y el logo de Angular, tres enlaces que le conducen a un


do

quickstart, un tutorial y un blog dedicados a Angular.


e to
.d

4. Estructura de las carpetas de un proyecto Angular CLI


w
w
w

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):

Archivo angular.json: especificación del contexto de desarrollo del proyecto (herramientas de


desarrollo, jerarquía de carpeta).
Carpeta e2e: recursos necesarios para Protractor, que es una herramienta de prueba completa
(protractor.conf.js especifica su configuración).
Carpeta node_modules: módulos Node.js instalados localmente.
Archivo package.json: configuración del proyecto (dependencias).
Carpeta src: todo el código y recursos de su aplicación...

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


Carpeta app: las entidades (módulos, componentes, servicios, etc.) de su aplicación.

Carpeta assets: recursos (imágenes...) necesarias en el proyecto.

Archivo index.html: página HTML raíz de su aplicación.

Archivo main.ts: código TypeScript que especifica el módulo raíz de su aplicación.

Archivo styles.css: hoja de estilos de más alto nivel.

Archivo tsconfig.app.json: configuración del transpiler TypeScript (tsc o ngtsc).


Archivo tslint.json: configuración del analizador de código TypeScript.

5. Un primer componente creado por Angular CLI

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

introduction/src/app/app.component.html: la plantilla del componente principal.


og

introduction/src/app/app.component.css: la hoja de estilos asociada a la plantilla.


pr

introduction/src/app/app.component.spec.ts: la especificación de las pruebas unitarias.


do

introduction/src/app/app.module.ts: la especificación del módulo (y principalmente del código


e to

que lo compone).
.d

introduction/src/app/app-routing.module.ts: la especificación de la tabla de enrutado asociada


w
w

al módulo; esta solo se implementará si el router se utiliza (lo que no es el caso en esta primera
w

aplicación).

El componente AppComponent (archivo app.component.ts), es el componente que juega el rol de


punto de entrada a la aplicación. Esto significa que es la vista de este componente (su plantilla), la
que se integra en la página HTML de más alto nivel de la aplicación.

A continuación se muestra el código que se ha generado:

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


})
export class AppComponent {
title = 'introducción';
}

Por lo tanto, un componente es una clase TypeScript que se decora con el


decorador @Component().

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.

El decorador @Component() especifica (todos estos elementos son opcionales):

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

A continuación se muestra el código de la plantilla, asociada a esta clase


pr

(archivo app.component.html), que ha sustituido el contenido en base 64 de la imagen que representa


do

el logo de Angular:
e to
.d

<div style="text-align:center">
w

<h1>
w

Welcome to {{ title }}!


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>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


<h2><a target="_blank" rel="noopener"
href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>

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>

Precisamos varios puntos muy importantes.

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

Mycomponent, su selector se llama app-mycomponent y las etiquetas que permite insertar su


do

plantilla en el código HTML, serán <app-mycomponent></app-mycomponent>.


e to
.d

Por defecto, la clase es exportable, es decir que el componente se puede utilizar si el módulo en el que
w

está contenido, se importa por otro módulo de otra aplicación.


w
w

6. El root module creado por Angular CLI


Nos interesamos ahora por el root module AppModule, creado por Angular CLI en el archivo
app.module.ts. Este módulo declara los componentes que agrupa (aquí uno solo) y especifica los
módulos del que depende.

A continuación se muestra el código que se genera:

import { BrowserModule } from '@angular/platform-browser';


import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


import { AppComponent } from './app.component';

@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

propiedades son las siguientes:


og

declarations: declara todas las clases del módulo que se corresponde con los
pr
do

componentes, las directivas y los pipes.


to

imports: declara los módulos de los que depende este módulo.


e
.d

providers: declara los servicios creados en este módulo.


w

bootstrap: define el root component (el componente punto de entrada) cuya plantilla
w
w

integrará en cascada el resto de componentes.


exports: esta propiedad, que no existe en este código, declara las clases visibles por el
resto de módulos que importarán este módulo.

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).

7. Actualización de Angular a través de Angular CLI


Angular CLI permite actualizarse, y lo más importante, también permite actualizar la versión de
Angular que utiliza, a través de su opción update:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:03 p. m.]


ng update @angular/cli @angular/core

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, Ivy tiene dos transpilers:

El transpiler propiamente dicho de Ivy (ngtsc), que compila el código TypeScript.


Un transpiler de compatibilidad (ngcc), que convierte los antiguos módulos (los almacenados
en una carpeta node_modules), para hacerlos compatibles con Ivy.

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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:17 p. m.]


Recompilar los antiguos módulos de la carpeta
Material node_modules, utilizando ngcc (y esto
para descargar
usando el comando de Angular CLI ivy-ngcc como vamos a ver).
Bascular sobre el nuevo transpiler ngtsc (en sustitución de tsc) a través de la configuración de
su proyecto.

A continuación se muestran las etapas que hay que seguir:

 Actualice Angular CLI hacia su última versión con: ng update @angular/cli

 Actualice Angular a la versión 8 o superior con: ng update @angular/core

 Ejecute el comando de Angular CLI que sirve de interfaz con ngcc: ivy-ngcc

 Modifique el archivo de configuración de su proyecto llamado src/tsconfig.app.json como sigue


(la propiedad allowEmptyCodegenFiles se valida para permitir el «lazy loading»,
concepto de carga «perezoso» de componentes, que se ha mencionado más atrás en este libro):

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:17 p. m.]


Los decoradores
Un componente o un módulo se implementa a través de una clase TypeScript. En el mundo de
Angular, hay otras piezas de software implementadas por una clase: por ejemplo, los servicios, las
directivas o los pipes. Todas estas clases son codificadas con decoradores. Los decoradores son
funciones que utilizan la arroba (@) como prefijo, que admiten como argumentos los metadatos que se

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

 TypeScript es una extensión de JavaScript, antes de cualquier implementación efectiva


ra

de la lógica de negocios de un componente (o de un módulo, un servicio, etc.), la clase


og

 correspondiente será consumida por el transpiler tsc para producir código JavaScript
pr

«puro» (esta fase se cachea durante la utilización de Angular CLI).


do
e to

Entre todos los decoradores propuestos por Angular, a continuación se muestran los siete más
.d
w

normales (veremos otros en este libro).


w
w

Tres decoradores estructuran las operaciones de negocio:

El decorador @NgModule() crea un módulo.


El decorador @Component() crea un componente.
El decorador @Injectable() crea una clase inyectable, es decir, que se podrá inyectar al
constructor de un componente. Veremos que este mecanismo permite crear servicios, es decir,
componentes particulares cuyos métodos se podrían utilizadas por diferentes componentes (y
que no tiene vista - plantilla - asociada).

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): 

El decorador @Directive() normalmente se utiliza para crear un nuevo atributo que

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:30 p. m.]


podrá acompañar a las etiquetas a las que se puede aplicar (veremos el ejemplo de un atributo
que modifica el texto de una etiqueta mostrándolo en vídeo inverso).
El decorador @Pipe() permite crear un filtro que modifica los datos interpolados en una
plantilla.
El decorador @Input() permite proporcionar a un componente un valor que se le transmite
a través de su etiqueta: de esta manera, permite establecer una comunicación de datos entre un
componente padre (el componente que utiliza el selector) y un componente hijo.
El decorador @Output() permite, a la inversa del decorador anterior, proporcionar a un
componente un valor que se emite por un componente que integra en su plantilla (la captura
del valor emitido se realiza por un listener de eventos).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:30 p. m.]


Creación de un nuevo componente que
muestra un mensaje
Quiere crear un nuevo componente que se integrará en la plantilla del componente de más alto nivel,
ya creada por Angular CLI (por lo tanto, la plantilla del componente AppComponent se modificará en
consecuencia).

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

declarada en la clase que la implementa.


og

Para
 crear el componente, utilice el comando ng con la opción generate y el argumento
pr


do

component:
e to

ng generate componente Mycomponent


.d
w

A continuación se muestra el mismo comando en modo escritura abreviada:


w
w

ng g c Mycomponent

El efecto de este comando es crear una subcarpeta con el nombre del componente, en la carpeta app.

A continuación se muestra lo que Angular CLI muestra:

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:43 p. m.]


implementa, su plantilla y la hoja de estilos asociada a esta (el archivo con extensión .spec.ts es un
archivo de especificación de pruebas unitarias, para el framework JavaScript Jasmine a ejecutar con
la herramienta Karma).

A continuación se muestra el código del archivo Mycomponent.component.ts, que se ha creado:

import { Component, OnInit } from '@angular/core';

@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

TypeScript), llamada message:


pr
do

import { Component, OnInit } from '@angular/core';


to

@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() {}
}

Esta variable se interpola en la plantilla que se presenta a continuación.

a. La plantilla HTML
Modificamos la plantilla HTML para que interpole la variable definida en la clase, entre las dos

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:43 p. m.]


etiquetas <label> y </label>:

<label> {{message}} </label>

La noción de plantilla y la interpolación de variables, se detallarán en el capítulo Angular: las


plantillas, bindings y directivas.

b. La hoja de estilos
Para terminar, creamos la hoja de estilos para definir el tamaño de letra que muestra el mensaje:

label { font-size: 18pt; }

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

3. Especificación de los componentes en el módulo


w

En el root module, el nuevo componente se ha especificado automáticamente. Por lo tanto, ahora


tiene dos componentes declarados:

AppComponent, asociado a la clase almacenada en el archivo app.component.ts.


Mycomponent, asociado a la clase almacenada en el archivo
Mycomponent/Mycomponent.component.ts.

 En el código, el sufijo .ts de los archivos se omite.

import { BrowserModule } from '@angular/platform-browser';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:43 p. m.]


import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';


import { MycomponentComponent } from
'./Mycomponent/Mycomponent.component';

@NgModule({
declarations: [
AppComponent,
MycomponentComponent
],
imports: [
BrowserModule,
AppRoutingModule

rg
],

o
providers: [],

n.
io
bootstrap: [AppComponent]
}) ac
export class AppModule { }
m
ra
og

4. Activación del módulo


pr
do
to

La activación del módulo se realiza por el archivo src/main.ts siguiente:


e
.d

import { enableProdMode } from '@angular/core';


w

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';


w
w

import { environmente } from './environments/environment';


import { AppModule } from './app/app.module';

if (environment.production) { enableProdMode(); }

platformBrowserDynamic().bootstrapModule(AppModule);
.catch(err => console.error(err));

La puesta en producción de una aplicación se explica en el capítulo Prueba y despliegue.

5. La página web front


La página web front, es la raíz de su interfaz:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:43 p. m.]


<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title> INTRODUCTION </title>
<base href="/">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root> </app-root>
</body>
</html>

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:43 p. m.]


Material para los amantes de la
Programación Java,
C/C++/C#,Visual.Net, SQL,
Python, Javascript, Oracle,
Algoritmos, CSS, Desarrollo
Web, Joomla, jquery, Ajax y
Mucho Mas…
El ciclo de vida de un componente
Un componente pasa por diferentes etapas, desde su nacimiento a su muerte. Es posible interceptar
estas etapas y asociar código a través de los métodos predefinidos (y se declaran en las interfaces de
programación). Estos métodos se llaman hooks.

A continuación se muestra la lista de los cuatro hooks más importantes:

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

internos de objetos o listas, cambios de valores no identificables por el hook


pr

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:

importar la interfaz de programación: import { OnInit } from


’@angular/core’;
implementar el método ngOnInit().

Existen otros cuatro hooks, pero dadas sus implementaciones raras, no se detallarán en este libro.

1. El hook ngOnChanges() 

El hook ngOnChanges() se llama durante la creación del componente, y después de cada

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:55 p. m.]


cambio de un atributo, cuyo valor se ha dado a través de la etiqueta que se corresponde con en el
componente (los atributos decorados por el decorador @Input(): ver la sección Las directivas de
atributos del capítulo Angular: las plantillas, bindings y directivas).

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().

A continuación se muestra un ejemplo en el que el hook ngOnChanges() se implementa para


mostrar el antiguo y el nuevo valor de un atributo, cuyo valor acaba de cambiar.

Se deben importar la interfaz OnChanges y el objeto SimpleChanges.

import { OnChanges, SimpleChanges } from '@angular/core';

A continuación se muestra el esquema de programación de la clase que implementa la interfaz:

org
export class <nombre del componente> implements OnChanges {

n.
io
...
} ac
m
ra

Y para terminar, el método se implementa.


og

El argumento changes que recibe ngOnChanges(), es un objeto cuyas propiedades se


pr

corresponden con los diferentes atributos para los que quiere trazar los valores cuando se modifican.
do
to

@Input() <nombre del atributo>: <tipo del atributo>


e

ngOnChanges(changes: SimpleChange})
.d

{
w
w

// changes[<nombre del atributo>).previousValue


w

// contendrá el valor antiguo

// changes[<nombre del atributo>).currentValue


// contendrá el valor nuevo
...
}

2. El hook ngOnInit()
El hook ngOnInit() se llama durante la inicialización del componente.

A continuación se muestra un ejemplo muy común, en el que el hook ngOnInit() se implementa

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:55 p. m.]


para conocer los argumentos de la ruta que ha provocado la invocación del componente por el router
(ver el capítulo Angular y la gestión de las rutas internas).

Se debe importar la interfaz OnInit:

import { OnInit } from '@angular/core';

La clase implementa la interfaz:

export class <nombre del componente> implements OnInit

Y para terminar, el método ngOnInit() se implementa:

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

router, la invocación del componente para activarlo temporalmente.


og
pr

3. El hook ngDoCheck()
do
to

El hook ngDoCheck() se llama cada vez que el componente se revisa.


e
.d

Este método permite analizar los cambios de los valores internos de objetos o listas, cambios de
w
w

valores que el hook ngOnChanges() no puede identificar.


w

Se deben importar la interfaz DoCheck y los servicios KeyValueDiffers y


IIterableDiffers: permiten comparar en profundidad dos versiones de un objeto o de una
lista.

La clase implementa la interfaz:

export class <nombre del componente> implements DoCheck

Y para terminar, el método ngDoCheck() se implementa utilizando un servicio:

ngDoCheck() {
let changes
= this.<antiguo valor del atributo>.diff(<nueva valor>);

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:55 p. m.]


if (changes) {
changes.forEachChangedItem(
ch => console.log('modification of: '+ch.currentValue));

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

Se debe importar la interfaz OnDestroy:


pr
do

import { OnDestroy } from '@angular/core';


e to

La clase implementa la interfaz:


.d
w

export class <nombre del componente> implements OnDestroy


w
w

Y para terminar, el método se implementa:

ngOnDestroy(): void {
console.log("Bye bye");
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:49:55 p. m.]


Conocimientos adquiridos en este capítulo
En este capítulo, después de haber explicado la filiación de Angular, hemos examinado los dos
principales puntos fuertes del framework Angular.

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

permite desacoplar las conexiones entre componentes.


w
w

A continuación hemos mostrado cómo crear un proyecto Angular, utilizando el administrador de


proyectos Angular CLI. Gracias a esta herramienta, es posible generar el entorno completo de un
proyecto, que comprende numerosas subcarpetas (que se corresponde con los módulos Node.js
necesarios por la aplicación Angular).

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:08 p. m.]


Los siguientes capítulos ilustraran abundantemente, todos los conceptos que se acaban de abordar.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:08 p. m.]


Las plantillas
Cada componente que produce un resultado visualizable en una página web, lo hace a través de su
plantilla. Esta plantilla se corresponde con su vista en la página única de la aplicación mono página.

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

selector aparecen en otra plantilla o en la página raíz src/index.html: su selector se declara en


pr

la propiedad selector del objeto que configura (que decora) el componente.


do

O de manera dinámica, cuando el componente se invoca por el controlador y su vista se asocia


to

a una etiqueta llamada <router-outlet> (ver el capítulo Angular y la gestión de las


e
.d

rutas internas).
w
w

Nos interesamos en primer lugar por la integración de las plantillas de manera estática. Suponga un
w

componente llamado MycomponentComponent, creado con Angular CLI.

A continuación se muestra el esqueleto de este componente:

import { Component } from '@angular/core';

@Component({
selector: "app-mycomponent",
templateUrl: "Mycomponent.component.html",
styleUrls: [ "Mycomponent.component.css" ]
})
export class MycomponentComponent { 
...
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


Material para descargar

Su plantilla (es decir, su vista), que se define en el archivo Mycomponent.component.html, se


integrará en el lugar en el que aparecerán las etiquetas <app-mycomponent></app-
mycomponent> (forman un component directive) en otra plantilla o en la página src/index.html.
Por lo tanto, este funcionamiento implica que las etiquetas propias de Angular se van a integrar en
diferentes códigos HTML de su aplicación (página src/index.html de más alto nivel, plantillas de los
componentes). Estas etiquetas (y también los atributos propios de Angular) se llaman directivas.

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

<p> Y te doy la bienvenida </p>


og

</app-mycomponent>
pr
do

la cadena de caracteres «Y te doy la bienvenida», se ocultará durante la representación


to

final.
e
.d

Para que permanezca, es necesario utilizar la etiqueta <ng-content>, en la plantilla del


w

componente que se inserta:


w
w

<p> Soy la plantilla del componente Mycomponent </p>


<ng-content></ng-content>

Lo que producirá el resultado final siguiente:

<p> Soy la plantilla del componente Mycomponent </p>


<p> Y te doy la bienvenida </p>

Este mecanismo se llama proyección de contenido.

1. Anidación de las plantillas

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


Las plantillas de los diferentes componentes de los que se forma la aplicación, se anidan siguiendo la
arquitectura propia de esta.

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):

El módulo raíz (automáticamente creado por Angular CLI).


Un módulo llamado research, que permite seleccionar los productos en venta respecto a
diferentes criterios de búsqueda y mostrarlos.
Un módulo llamado cart, que permite gestionar el contenido de la cesta de la compra.

A continuación se muestran algunos componentes contenidos en estos módulos (habrá más):

Dos componentes en el módulo principal (el root module):

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

Cuatro componentes contenidos en el módulo research:


og
pr

El componente principal de este módulo, llamado ResearchComponent, de selector


do

app-research y que integra en su plantilla, las plantillas de los tres componentes


to

siguientes.
e
.d

El componente ProductselectionbykeywordsComponent de selector


w

app-product-selectionbykeywords, que permite seleccionar los productos


w
w

a partir de una o varias palabras clave.

El componente ProductselectionbycriteriaComponent de selector


app-product-selectionbycriteria, que permite seleccionar los productos
a partir de diferentes criterios de búsqueda (tipo, marca, precio, etc.).

El componente ProductDisplayComponent, cuyo selector app-product-


display no se utilizará directamente: en nuestro ejemplo, la plantilla de este
componente se integra dinámicamente en la interfaz de la aplicación a través de la etiqueta
<router-outlet name="display">. Este componente lista los productos
seleccionados y permite al internauta seleccionar algunos, para añadirlos a la cesta de la
compra.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


Tres componentes contenidos en el módulo cart:

El componente principal de este módulo, llamado CartComponent, de selector app-


cart.

El componente CartManagementComponent de selector app-cart-


management y cuya vista (la plantilla), se integra dinámicamente a través de la etiqueta
<router-outlet name="cart">. Este componente permite modificar la
cantidad de los productos de la cesta de la compra.

El componente CartValidationComponent, de selector app-cart-


validation. Este componente permite validar la cesta de la compra y hacer el pedido
de los productos que se encuentran en ella.

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


(se corresponde con el componente AppComponent bootstrapado por el módulo raíz):

<!doctype html>
<html>
<head> ... </head>
<body>
<app-root> </app-root>
</body>
</html>

A continuación se muestra la plantilla del componente AppComponent, contenido en el archivo


src/app/app.component.html de selector app-root:

<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

<td> <app-research></app-research> </td>


pr

<td> <app-cart></app-cart> </td>


do

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

Los selectores app-research y app-cast se corresponden con los componentes principales


de los dos módulos research y cart.

La etiqueta <router-outlet name="display"> acoge las plantillas de los componentes


invocados a través del controlador, para integrarse en la salida (el outlet) display. Es el caso del
componente ProductDisplayComponent: la lista de productos que muestra este
componente se puede redirigir dinámicamente a otras zonas de visualización. A continuación se
muestra su plantilla:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


<p> Lista de los productos: </p>
...

Y ahora examinamos las plantillas de los otros tres componentes del módulo (llamado research)
de selección.

A continuación se muestra la plantilla del componente ResearchComponent (contenido en el


archivo src/app/research/research.component.html):

<p> Busque sus productos </p>


<app-product-selectionbykeywords></app-product-selectionbykeywords>
<br/><br/>
<app-product-selectionbycriteria></app-product-selectionbycriteria>

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

Y para terminar, la plantilla del componente


pr

ProductselectionbycriteriaComponent, está contenido en el archivo product-


do

selectionbycriteria.component.html:
e to

<p> Criterios de búsqueda: </p>


.d

...
w
w
w

Examinamos ahora las plantillas de los componentes del módulo que gestiona la cesta de la compra.

A continuación se muestra la plantilla del componente CartComponent contenido en el archivo


src/app/cart/Cart.component.html:

<p> Su cesta de la compra </p>


<router-outlet name="cart"></router-outlet>
<br/><br/>
<app-cart-validation></app-cart-validation>

La etiqueta <router-outlet name="cart"> acoge las plantillas de los componentes


invocados a través del controlador, para ser integrados en la salida (el outlet) cart. Es el caso del
componente CartManagementComponent: la lista de productos de la cesta de la compra que

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


muestra este componente, se puede redirigir de esta manera dinámicamente a diferentes zonas de
visualización.

A continuación se muestra la plantilla del componente CartManagementComponent,


contenida en el archivo src/app/cart/cart-management.component.html:

<p> Su selección </p>


...

Y a continuación se muestra la plantilla del componente CartValidationComponent,


contenida en el archivo src/app/cart/cart-validation.component.html:

<button ...> Validación de la cesta de la compra </button>

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

<!-- "integración" de app-root -->


pr

<table>
do

<tr>
to

<td> <span class="title"> Ventas en línea / Online sales


e

</span>
.d

</td>
w

<td>
w

... <!-- plantilla del componente de autenticación -->


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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


<!-- integración de app-cart-management
a través de la etiqueta router-outlet name="cart" -->
<p> Su selección: <p>
...
<br/><br/>
<!-- integración de app-cart-validation -->
<button ...> Validación de la cesta de la compra </button>
</td>
</tr>
<tr>
<td colspan="2"> <!-- "integración" de app-product-display
a través de la etiqueta router-outlet name="display" -->
<p> Lista de los productos: </p>
...
</td>

rg
</tr>

o
...

n.
io
</table>
</body> ac
</html>
m
ra
og

2. Las plantillas insertadas (embedded templates)


pr
do
to

Un «embedded» template se codifica entre dos backquotes en el valor de la propiedad template


e

del objeto que configura el componente:


.d
w
w

import { Component } from '@angular/core';


w

@Component({
selector: "app-mycomponent",
template: `
<cuerpo de la plantilla>
`,
styleUrls: [ "Mycomponent.component.css" ]
})
export class MycomponentComponent { ... }

Se pueden asociar a la plantilla cero, una o varias hojas de estilos.

Esta manera de codificar las plantillas no es aconsejable.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


3. Las plantillas externalizadas


De manera convencional, una plantilla externalizada se codifica en un archivo del mismo nombre,
que codifica la clase asociada al componente, pero con la extensión .html. De esta manera, si la clase
TypeScript que implementa el componente se escribe en el archivo Mycomponent.component.ts, la
plantilla se almacena en el archivo Mycomponent.component.html. Pero esto solo es una convención,
el nombre del archivo plantilla se da como valor de la propiedad templateUrl del objeto que
configura el componente:

import { Component } from '@angular/core';

org
@Component({

n.
selector: "app-mycomponent",

io
templateUrl: "app.Mycomponent.html", ac
styleUrls: [ "Mycomponent.component.css" ]
m
})
ra

export class MycomponentComponent { ... }


og
pr

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

componentes y sus plantillas.


.d
w
w
w

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:23 p. m.]


Data bindings entre el componente y la
plantilla
Para relacionar una variable (o un método en el caso de la gestión de un listener de eventos) del
componente en la plantilla, existen varios tipos de unión (de data bindings):

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

1. Acceso a los elementos del DOM


e
.d
w

Antes de cualquier cosa, veamos cómo Angular permite hacer referencia simplemente a los elementos
w

del DOM, a fin de acceder a ellos y eventualmente, modificarlo.


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):

<img #imageRef src="assets/Angular.pn www.detodoprogramacion.org


>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


Por otro lado, gracias a las directivas de atributo, es posible acceder a los atributos de un elemento del
DOM (se corresponde con una etiqueta HTML), para modificarlos (ver la sección Las directivas de
atributo).

2. Interpolación de una variable en una plantilla


La interpolación de una variable de un componente (de hecho, una propiedad de una clase
TypeScript) en una plantilla HTML, se realiza gracias a las dobles llaves.

A continuación se muestra un ejemplo en el que el componente AppComponent tiene una


variable llamada who, cuyo valor se inicializa a there:

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

export class AppComponent {


pr

private who:string = "there";


do

}
e to

Esta variable se interpola en la plantilla (app.component.html):


.d
w

<h2> Data binding - interpolation: </h2>


w
w

<label> Hello {{who}} <label>

asociado a la siguiente hoja de estilos (app.component.css):

label { font-size: 18pt; font-weight: bold; }

Esto produce el siguiente resultado:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


org
n.
3. Property binding
io
ac
m
Si la variable a interpolar en la plantilla es el valor de un atributo de una etiqueta HTML, tiene dos
ra

posibilidades:
og

Utilizar la interpolación con las dobles llaves.


pr
do

O delegar a Angular la gestión de este atributo: esta gestión se llama property binding.
to

Sintácticamente, el atributo HTML se enmarca por corchetes y se convierte en una directiva de


e

atributo.
.d
w

A continuación se muestra un ejemplo de la visualización de una imagen (que simboliza el anterior


w

framework AngularJS), cuyo origen se da por la variable imageSrc del componente


w

AppComponent.

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private imageSrc: string = "assets/angular-js.png";
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


Entonces, la etiqueta <img> utilizada en la plantilla se puede escribir en una de las dos formas
sintácticas siguientes:

<img src="{{imageSrc}}" /> // Interpolación con las dobles


llaves
<img [src]="imageSrc" /> // Property binding: [src] es una
directiva atributo

La segunda forma es preferible, ya que si la variable imageSrc por ejemplo se define


temporalmente, la imagen no aparecería.

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:

[src]="imageSrc" es correcta, ya que imageSrc es una variable evaluable en

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

img { width:100px; height:100px; }


pr
do

esto produce el siguiente resultado (las dos imágenes son las que se corresponden con las dos sintaxis,
to

utilizando la interpolación o el propertybinding):


e
.d
w
w
w

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


A diferencia de los atributos clásicos de las etiquetas HTML, Angular ofrece gestionar nuevos
atributos (por supuesto puede crear los suyos), como el atributo routerLink asociado a las anclas
(<a [routerLink]="..."> ... </a>) y que invoca al controlador en la ruta
especificada.

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.

Sintácticamente, el listener se enmarca por paréntesis:

<etiquetaHTML (listener)=método(...)> ... </etiquetaHTML>

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

adición o eliminación de un carácter).


og

El método llamado sobre el evento (por ejemplo, sobre un clic), puede tener como argumento al
pr

objeto $event, que da toda la información sobre él.


do
to

A continuación se muestra un ejemplo de modificación dinámica de una imagen, durante un clic


e

sobre él. imageRef es la referencia de la imagen en el DOM que se debe modificar (haciendo en
.d
w

este caso, que aparezca el logo de Angular en lugar del de AngularJS).


w
w

import { Component } from '@angular/core';

@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";
}
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


A continuación, la plantilla del componente (archivo app.component.html) permite:

Asignar una referencia al elemento del DOM (aquí #imageRef).


Pasar esta referencia al método changeImage() del componente, que se llama sobre el
evento clic.

<img #imageRef [src]="srcImage"


(click)="changeImage(imageRef)" />

Y a continuación se muestra la hoja de estilos asociada a la plantilla (archivo app.component.css):

img { width:100px; height:100px; }

Lo que produce el siguiente resultado:

Y después de hacer clic en la imagen:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


rg
Para ilustrar la utilización del objeto $event, a continuación se muestra otra manera de codificar el

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

El método como este:


pr
do

changeImage(event:any) {
to

event.target.src = "assets/Angular.png";
e

}
.d
w
w
w

5. Two-way data binding


El two-way data binding permite relacionar una propiedad de la clase TypeScript que implementa el
componente, con una interfaz de entrada/selección de la plantilla (etiquetas <input>,
<textarea>, <select>), de tal manera que:

La modificación en la plantilla del valor de la zona de entrada/selección,


actualiza inmediatamente el valor de la propiedad de la clase.
La actualización de la variable dentro de la propiedad, se repercute inmediatamente en la
plantilla.

El two-way data binding no está activo por defecto en Angular, mientras que lo está en AngularJS (a

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


través del scope). En efecto, este mecanismo de unión automático de datos entre la vista y el modelo,
es muy costoso en términos de recursos. Esta unión se puede implementar gracias a una directiva
específica: [(ngModel)] (ver la sección Las directivas).

A continuación se muestra un componente que hace eco del valor introducido en la plantilla (aquí
«embedded»):

import {Component} from '@angular/core';

@Component({
selector: 'app-root',
template: `
<input [(ngModel)]="value"/> &rarr; {{value}}
`
})
export class AppComponent {}

Lo que produce el siguiente resultado:

Después de introducir la palabra Pierre (la actualización se realiza tan pronto como se introduce o
elimina un carácter):

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]


https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:36 p. m.]
Las directivas
Una directiva es una nueva etiqueta o un nuevo atributo soportado por Angular e insertado
respectivamente en el código HTML o en una etiqueta.

Por lo tanto, las directivas extienden el lenguaje HTML.

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

veces las etiquetas en las que aparezcan.


e
.d

Las otras directivas, llamadas globalmente directivas de atributo (attribute directives), que
w

permiten:
w
w

Modificar la apariencia del elemento asociado a la etiqueta, modificando sus atributos en el


DOM (con la condición o no de que exista un evento, y por ejemplo, en el marco del
propertybinding).

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: 

Los ángulos enmarcan una directiva de componente: <Directiva>.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


Un asterisco utiliza como prefijo de una directiva estructural: *Directiva="...".
Material para descargar
Los corchetes enmarcan una directiva de atributo, con un valor a evaluar:
[Directiva]="..." (y si no hay valor a evaluar, el nombre de la directiva se
especifica directamente).

1. Las directivas estructurales


Las directivas estructurales que permiten controlar la producción de las etiquetas HTML en una
plantilla.

Sintácticamente, se introducen por un asterisco:

*ngFor: controla la iteración de una etiqueta.

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

para cada elemento (ítem) de una lista que se itera.


og

Supongamos un componente que dispone de una lista de objetos (que representa los productos a la
pr

venta), en una variable llamada Products (evidentemente, es solo para probar).


do

En primer lugar, quiere simplemente mostrar estos productos y después enumerarlos y finalmente,
e to

modificar el aspecto de la visualización según el stock disponible.


.d
w

A continuación se muestran los archivos que componen este ejemplo:


w
w

directivasEstructurales/src/app/app.component.ts (no presentado): el componente principal.


directivasEstructurales/src/app/app.component.html: la plantilla del componente principal.
directivasEstructurales/src/app/app.module.ts (no presentado): el módulo que lista el código de
la aplicación.

A continuación se muestra la clase TypeScript que implementa este componente:

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


export class AppComponent {
private Products: Object[] = [
{ "type":"phone",
"brand":"Peach",
"name":"topPhone 8 32G",
"popularity":4, "price":900,
"picture":"topphone.jpeg",
"stock":80
},
{ "type":"phone",
"brand":"Threestars",
"name":"bigPhone 9",
"popularity":4, "price":700,
"picture":"bigphone.jpeg",
"stock":150

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>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


Esta plantilla se asocia a la siguiente hoja de estilos:

* { font-size: 14pt; font-weight: bold; }

Y a continuación se muestra lo que obtiene:

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

first: booleano a verdadero si el ítem actual es el primero.


og

last: booleano a verdadero si el ítem actual es el último.


pr
do

even: booleano a verdadero si el ítem actual es par.


to

odd: booleano a verdadero si el ítem actual es impar.


e
.d

A continuación se muestra una plantilla que enumera los productos mostrados:


w
w
w

<ul>
<li *ngFor="let product of Products; let idx = index">
{{idx+1}}: {{product.name}}
</li>
</ul>

Con el siguiente resultado:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


b. La directiva *ngIf

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

<li *ngFor="let product of Products">


pr

<label>
do

{{product.name}}
to

</label>
e

<label *ngIf="product.stock == 0" class="warning">


.d

(not available)
w

</label>
w

</li>
w

</ul>

Esta plantilla se asocia a la siguiente hoja de estilos:

* { font-size: 14pt; font-weight: bold; }


.warning { color: red; }

Obtiene el siguiente resultado:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


Desde Angular 4, es posible utilizar un bloque de instrucciones else asociado a *ngIf, según el
esquema de programación:

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

etiqueta. Sintácticamente, se pueden enmarcar o no con corchetes; lo son cuando se acompañan de un


to

valor que JavaScript debe evaluar.


e
.d

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

Gracias a Angular CLI y su opción generate (o g en escritura abreviada), cree el esqueleto de la


directiva:

ng generate directive whiteOnBlack

A continuación se muestra el mismo comando, en escritura abreviada:

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;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


white-on-black.directive.spec.ts.

La especificación del módulo se modifica automáticamente para declarar esta nueva directiva.

A continuación se muestra el esqueleto que proporciona Angular CLI (archivo white-on-


black.directive.ts):

import { Directive, ElementRef } from '@angular/core';

@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

import { Directive, ElementRef } from '@angular/core';


do
to

@Directive({
e

selector: '[whiteOnBlack]'
.d

})
w

export class whiteOnBlackDirective {


w

constructor(el: ElementRef) {
w

el.nativeElement.style.backgroundColor = "black";
el.nativeElement.style.color = "white";
}
}

Un mecanismo potente de inyección de dependencias (ver el capítulo Angular y la conexión a


Node.js: los servicios), permite inyectar el componente inyectable ElementRef, en el componente
que lo utiliza (la inyección de dependencias se realiza pasando un objeto como argumento del
constructor de la clase TypeScript).

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).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


Pruebe la directiva sobre una etiqueta y un botón:

<h2> Directive - atribute directive: </h2>


Label: <label whiteOnBlack> whiteOnBlack </label>
<br/><br/>
button: <button whiteOnBlack> My button </button>

con la siguiente hoja de estilos:

* { font-size: 18pt; font-weight: bold; }

Y a continuación se muestra lo que esto produce:

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.

Etiqueta: <label [whiteOnAColor]="'red'"> whiteOnAColor </label>


<br/><br/>
Botón: <button [whiteOnAColor]="'blue'"> My button </button>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


 Atención [whiteOnAColor]="red" no es correcto, ya que JavaScript
interpreta red como una variable, que no es el caso.

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.

@Input('whiteOnAColor') color: string;

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

export class whiteOnBlackDirective {


og
pr

@Input('whiteOnAColor') color: string;


do
to

constructor(private el: ElementRef) {}


e
.d

ngOnInit() {
w
w

this.el.nativeElement.style.backgroundColor = this.color;
w

this.el.nativeElement.style.color = "white";
}
}

Y a continuación se muestra lo que esto produce:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


org
Para terminar, para probar los mecanismos de control que se puede establecer, de nuevo va a

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

A continuación se muestra el código:


do

import { Directive, ElementRef, Input, HostListener } from


to

'@angular/core';
e
.d
w

@Directive({
w

selector: '[whiteOnAColorOnClick]'
w

})
export class WhiteOnAColorOnClickDirective {
private clicked: boolean = false;

@Input('whiteOnAColorOnClick') color: string;

@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;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


this.el.nativeElement.style.color = "white";
this.clicked = true;
}
}

constructor(private el: ElementRef) {}


}

3. Envío de información de un componente a su padre


(@Output())
Igual que es posible configurar un componente con un valor que provenga de un componente que lo

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

<nombre del evento>: EventEmitter<tipo del evento>


do

= new EventEmitter<tipo del evento>();


e to

Para ilustrar este punto, a continuación se muestra el código de una aplicación que implementa un
.d

componente «cuenta atrás» llamado CountdownComponent.


w
w
w

Un componente padre debe mostrar el descuento calculado por CountdownComponent y por lo


tanto, este componente debe transmitirlo.

Este ejemplo agrupa el código siguiente:

COUNTDOWN/src/app/app.component.ts: el componente principal.


COUNTDOWN/src/app/app.component.html: la plantilla del componente principal.
COUNTDOWN/src/app/countdown/countdown.component.ts: el componente «cuenta atrás».
COUNTDOWN/src/app/app.module.ts (no presentado): el módulo que especifica todos los
actores de este ejemplo.

A continuación se muestra el código del componente AppComponent


(archivo app.component.ts). Este componente muestra en su plantilla el valor de su atributo time,

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


que indirectamente se actualiza por los eventos que el componente CountdownComponent
emite.

import { Component } from '@angular/core';

@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

que el componente emite.


do
to

<div>
e

Cuenta atrás: {{ time }}


.d

<app-countdown
w

[time]="10"
w

(tick)="countChange($event)">
w

</app-countdown>
</div>

A continuación se muestra el código del componente CountdownComponent (se corresponde


con el archivo countdown.component.ts).

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


import { Component, OnInit, Input, Output, EventEmitter }
from '@angular/core';
import { interval } from 'rxjs';

@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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:50:49 p. m.]


Los pipes
Dentro de una plantilla, los pipes (los filtros) permiten transformar un dato: el dato inicial se
proporciona en la entrada del pipe, que genera como salida el dato transformado según la sintaxis:

<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

elementos de una colección en una de sus propiedades).


.d
w

 Para hacer esto, utilice el comando de Angular CLI que produce el esqueleto de un pipe:
w
w

ng generate pipe <nombreDelPipe>

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:

import { Pipe, PipeTransform } from '@angular/core';


@Pipe({
name: 'sort'
})

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:02 p. m.]


export class SortPipe implements PipeTransform {

transform(value: any, args?: any): any {


return null;
}

En este esqueleto, la transformación operada por el pipe se gestiona por el método transform(),
que recibe como argumentos:

La entrada dada en el pipe (por defecto no importa qué).


Una lista indeterminada de argumentos.

Y no devuelve nada por el momento.

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
}
]

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:02 p. m.]


Va a ordenar los objetos que contiene sobre su propiedad name, utilizando el pipe que quiere
implementar:

<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

<li> 4000 </li>


pr

<li> bigPhone 9 </li>


do

<li> topPhone 8 32G </li>


to

<li> topPhone 8 256G </li>


e

</ul>
.d
w
w

A continuación se muestra el código completo del pipe:


w

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'sort'
})
export class SortPipe implements PipeTransform {

transform(array: Array<Object>, property: string): Array<Object> {

if (array !== undefined) {


array.sort(function (a: Object, b: Object) {
if (a[property] === undefined && b[property] === undefined)
return 0;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:02 p. m.]


if (a[property] === undefined) return -1;
if (b[property] === undefined) return 1;

if (a[property] < b[property]) return -1;


if (a[property] > b[property]) return 1;
return 0;
});
}
return array;
}
}

Este ejemplo de código merece las siguientes observaciones:

La transformación operada por el pipe, utiliza dos argumentos:

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

(sub) colección compuesta por los objetos pares o impares.


w
w

Este pipe utiliza el método filter() de Array, que filtra los elementos de una tabla, según la
w

condición que se debe verificar en la función de callback que gestiona.

import { Pipe, PipeTransform } from "@angular/core";

@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;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:02 p. m.]


if (rank == "even")
return array.filter(function(item, index) {
return index % 2 === 0;
});
else
return array.filter(function(item, index) {
return index % 2 === 1; });
}
}

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>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:02 p. m.]


Ejemplo de resumen: un formulario de
autenticación
Para ilustrar de manera resumida la mayor parte de los bindings que hemos descrito, a continuación
se muestra el ejemplo del componente AuthComponent. Este es un esqueleto posible.

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

AUTH/src/app/auth/auth.component.html: la plantilla del componente de autenticación.


og

AUTH/src/app/app.module.ts (no presentado): el módulo que agrupa el código de la


pr

aplicación.
do
to

La plantilla del componente principal (archivo app.component.html) es muy sencilla:


e
.d

<app-auth></app-auth>
w
w
w

A continuación se muestra el esqueleto del componente (archivo auth/auth.component.ts), que


gestiona un formulario de autenticación.

Este componente implementa:

Las cadenas de caracteres de identificación login y password.


El booleano isLoggedIn (gestionado en otro lugar), que guarda si el internauta ya está
autenticado.
El método onSubmit() se llama cuando el formulario se envía para su validación.
El método logout() que guarda la desconexión del internauta.

import { Component } from '@angular/core';
import { BrowserModule } from latform-browser';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:13 p. m.]


import { FormsModule } from '@angular/forms';

@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

A continuación se muestra la plantilla de este componente (archivo auth/auth.component.html).


pr
do

<form *ngIf="!isLoggedIn" (ngSubmit)="onSubmit()" #loginForm="ngForm">


to

<div class="form-group">
e
.d

<label for="login"> Login </label>


w

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:13 p. m.]


<button type="submit"> Submit </button>
</form>
<div *ngIf="isLoggedIn">
{{login}} is logged...
<br/><br/>
<button (click)="logout()"> Logout </button>
</div>

Esta plantilla moviliza diferentes tipos de bindings vistos anteriormente:

El property binding por la directiva estructural *ngIf: si el valor del


booleano isLoggedIn (gestionado en otro lugar) es true, se muestra el formulario pero
en caso contrario, se muestra el botón de desconexión.
El two-way data binding por la directiva [(ngModel)].
El event binding por las directivas (ngSubmit) (envío y validación del formulario) y
(click) (activación del botón de desconexión).

A continuación se muestra el formulario de autenticación:

El internauta está conectado:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:13 p. m.]


Este embrión de componente se reutiliza en la sección Control de las rutas: guardián del capítulo
Angular y la gestión de las rutas internas.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:13 p. m.]


El hilo rojo: creación de un componente
que muestra los productos
 Para empezar a desarrollar el lado cliente (el «cliente-side») de la aplicación de e-commerce, cree
con Angular CLI un proyecto llamado OnlineSalesStep1.

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

Este componente se corresponde con la primera versión de ProductDisplayComponent


og


(ver la sección Anidación de las plantillas).
pr

Se generan diferentes archivos, y más particularmente (por orden de creación):


do
to

src/app/app.component.css: este archivo contiene la hoja de estilos asociada a la plantilla del


e

componente principal.
.d
w

src/app/app.component.html: este archivo contiene la plantilla del componente principal (su


w

vista).
w

src/app/app.component.ts: este archivo contiene la clase TypeScript que implementa las


operaciones de negocio del componente principal.
src/app/app.module.ts: este archivo especifica la lista de los componentes gestionados por el
módulo actual.
src/index.html: la página web de la aplicación en la que se incluyen las diferentes plantillas de
los componentes en una estructura de árbol.
src/main.ts: el código que inicia (bootstrapper) el módulo raíz de la aplicación.

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).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]


1. El componente
Cree el esqueleto del componente ProductDisplayComponent con el comando ng de
Angular CLI:

ng g c productDisplay

se crean cuatro archivos en la carpeta src/app/product-display:

product-display.component.ts: este archivo contiene la clase TypeScript que implementa el


componente.
product-display.component.html: este archivo contiene la plantilla del componente.
product-display.component.css: este archivo contiene la hoja de estilos asociada a la plantilla.
product-display.component.spec.ts: este archivo contiene la especificación de las pruebas
unitarias.

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.

a. La clase que implementa el componente


El esqueleto de la clase TypeScript generado por Angular CLI, es este:

import { Component, OnInit } from '@angular/core';

@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:

import { Component, OnInit } from '@angular/core';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]


@Component({
selector: 'app-product-display',
templateUrl: './product-display.component.html',
styleUrls: ['./product-display.component.css']
})
export class ProductDisplayComponent implements OnInit {

private Products: Object[] = [


{"type":"phone", "brand":"Samsung",
"name":"Galaxy Edge S7",
"popularity":4, "price":700,
"picture":"galaxyS7edge.jpeg",
"stock":150
},

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

{"type": "phone", "brand": "Nokia",


.d

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]


 Modifíquelo para mostrar en dos columnas los productos (si el stock está agotado, se menciona
una nota):

<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

<li *ngFor="let product of (Products | evenOdd:'odd')">


w

<label *ngIf="product.stock > 0" class="available">


w

{{product.name}}, </label>
w

<label *ngIf="product.stock == 0">


{{product.name}} (not available), </label>
brand: {{product.brand}},
price: {{product.price}}
</li>
</ul>
</td>
</tr>
</table>

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

Después complete este esqueleto:

import { Pipe, PipeTransform } from '@angular/core';

@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

return array.filter(function(item, index) {


og

return índices % 2 === 1; });


}
pr

}
do
to

Para terminar, integre esta plantilla en la del componente principal de su único módulo:
e
.d
w

<h1> OnLine Sales (first step) </h1>


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:

import { BrowserModule } from '@angular/platform-browser';


import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';


import { ProductDisplayComponent }
from './product-display/product-display.component';
import { EvenOddPipe } from './even-odd.pipe';

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

export class AppModule { }


w
w

3. Activación del módulo


El módulo se «bootstrapea» por el archivo src/main.ts, que permanece sin cambios:

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) {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:51:26 p. m.]


enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

4. La página web front


La página web src/index.html también permanece sin cambios:

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

Y a continuación se muestra el resultado:

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

La interpolación de una variable en la plantilla.


pr

La gestión de un atributo de una etiqueta (ya sea sobrecargando un atributo existente de la


do

etiqueta o para gestionar un nuevo atributo) por Angular (el property binding).
to

La llamada de un método del componente (más exactamente, de la clase que la implementa), a


e
.d

partir de un evento capturado por la plantilla (el event binding).


w

El acoplamiento bidireccional de una variable entre la vista y el modelo de un componente de


w
w

tal tipo, que la actualización de esta variable en la clase TypeScript se repercute


automáticamente en la plantilla, y viceversa (two-way data binding).

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:

El control de la producción de las etiquetas HTML (las directivas estructurales *NgIf y


*NgFor).

La modificación del aspecto visual de los elementos HTML y de su comportamiento (las
directivas de atributos).

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 los diferentes métodos (verbos) HTTP (GET, POST, PUT y DELETE).


a través
pr
do

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

través de la implementación de la aplicación El hilo rojo de e-commerce.


.d
w

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

 los servicios se deben decorar con el decorador @Injectable().


constituyen
pr

A continuación se muestra el esquema de programación de un servicio:


do
to

import { Injectable } from '@angular/core';


e
.d

@Injectable()
w

export class <nombre del servicio> {


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).

A continuación se muestra el esquema de utilización de un servicio en un componente:

import { Injectable } from '@angular/core';


@Injectable()
export class <nombre del servicio> {

...
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:06 p. m.]


Para utilizar los métodos del servicio en los métodos del componente, es suficiente con utilizar este
servicio como un atributo de la clase:

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

A continuación se muestra el código del servicio ResearchService de la aplicación El hilo


do

rojo, que utiliza el servicio HTTP propuesto por Angular, e implementa un observable para recuperar
to

los datos formateados en JSON, que le devuelve el servidor Node.js:


e
.d

import { Injectable } from '@angular/core';


w

import { HttpClient } from '@angular/common/http';


w

import { Observable } from 'rxjs';


w

@Injectable()
export class ResearchService {
constructor(private http: HttpClient) {}

getProducts(arguments: string): Observable<any> {


let url: string = "http://localhost:8888/Products/"+arguments;
let observable: Observable<any> =
this.http.get(url);
return observable;
} 
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:18 p. m.]


Los datos devueltos por el servidor en JSON, se deserializanr automáticamente para crear los objetos
JavaScript correspondientes.

2. Envío de datos JSON al servidor


El envío de datos al servidor, se puede corresponder con una creación o una actualización de datos.
En la norma REST (ver el capítulo La plataforma Node.js), los métodos HTTP asociados
generalmente son POST (o PUT) para una creación de datos y PUT (o POST) para una
actualización.

El envío de datos al servidor, se puede realizar de diferentes maneras:

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

Para utilizar el servicio HttpClient de Angular, es necesario importarlo e inyectarlo en el constructor


og

de su servicio, como acabamos de ver.


pr

A continuación se muestra un esquema de programación en el que la función constructora


do

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

import { HttpClient, HttpHeaders } from '@angular/common/http';


w

export class <nombreDelServicio> {


w

constructor(private http: HttpClient) {}


...
}

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:18 p. m.]


A continuación se muestra un ejemplo de envío de un objeto, que contiene el identificador de un
producto a añadir en la cesta de la compra de un cliente identificado por su dirección e-mail. El
servidor Node.js funciona aquí en el puerto 8888 de la máquina local y la ruta significa que la
operación afecta a los productos de la cesta de la compra.

let url = "http://localhost:8888/CartProducts";


let data = { "productId": productId, "email": email };
const httpOpciones = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};

this.http.post(url, data, HttpOpciones);

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

.post(url, data, HttpOpciones);


og
pr
do

3. Envío de datos a través de Query string


e to
.d

Es posible transmitir al servidor Node.js un número indeterminado de argumentos, a través de una


w

Query string (si el método HTTP es GET, ya que con POST puede pasar estos datos en el cuerpo del
w
w

mensaje).

A continuación se muestra un ejemplo (que se desarrollará un poco más adelante) en el que un


servicio web debe recibir una lista indeterminada de términos (aquí, phone y 64G).

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://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:18 p. m.]


app.get("/Products/keywords",
(req, res) => {
for (let keyword in req.query)
console.log(keyword);
});

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:

El servicio ResearchService (archivo research/research.service.ts) para la búsqueda de

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

servidor y para terminar, el servicio web invocado sobre el servidor.


e
.d
w
w

1. Declaración de las rutas del lado servidor


w

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.

Según la acción deseada, el método HTTP es diferente:

El método GET para recuperar datos.


El método POST (o PUT) para añadir datos.
El método DELETE para eliminar datos.
El método PUT (o POST) para modificar datos.

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

Recuperación de todos los productos asociados a algunas palabras clave:


método HTTP: GET
ruta genérica: /Products/keywords?<querystring>

Recuperación de un producto por su identificador:


método HTTP: GET
ruta genérica: /Product/id=:id

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.

Recuperación de todos los identificadores de los productos de la cesta de la compra de un


cliente:
método HTTP: GET
ruta genérica: /CartProducts/productIds/email=:email

Recuperación de todos los productos de la cesta de la compra de un cliente:


método HTTP: GET
ruta genérica: /CartProducts/Products/email=:email

Adición de un producto (a través de su identificador) en la cesta de la compra de un cliente:


método HTTP: POST
datos subidos en JSON: {"productId": ..., "email": ...}
ruta genérica: /CartProduct

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


Eliminación de un producto (a través de su identificador) de la cesta de la compra de un
cliente:
método HTTP: DELETE
ruta genérica: /CartProduct/productId=:productId/email=:email

Reinicialización de la cesta de la compra:


método HTTP: DELETE
ruta genérica: /Cart/reset/e-mail=:e-mail

Las siguientes secciones listan las funcionalidades implementadas en la aplicación de e-commerce y


que, para la mayor parte, actualizan una plantilla después de haber recuperado los datos del servidor.
Como ilustra el siguiente esquema, estas funcionalidades se describen presentando el componente que
implementa una funcionalidad particular, el servicio que la utiliza como intermediario entre él y el
servidor y para terminar, el servicio web invocado en el servidor.

2. Gestión de los productos


a. Visualización de los selectores
Por medio del módulo de búsqueda, y más particularmente del componente
SelectionbycriteriaComponent, la aplicación presenta al internauta las listas
desplegables que contienen los diferentes valores de las propiedades de los productos puestos a la
venta (estos productos son los documentos de la colección Products de la base de datos
MongoDB). Estas propiedades son el tipo, la marca, el precio y la popularidad del producto. Para el
precio, la lista desplegable exhibe los intervalos de precio.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


A continuación se muestra la estructura del componente
SelectionbycriteriaComponent y la inyección del servicio ResearchService
que lo utiliza.

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[];

constructor(private research: ResearchService,


private router: Router) {}

ngOnInit() {
this.research
.getProducts("selectors")
.subscribe(res => this.selectors = res);
}
}

El método getProducts() del servicio ResearchService que usa como argumento


selectors, es el que se implementa para preguntar al servidor:

getProducts(arguments: string): Observable<any> {


let url: string = "http://localhost:8888/Products/"+arguments;
return this.http.get(url);

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


}

Y al final de la cadena, se invoca el servicio web gestionado por Node.js en la ruta


/Products/selectors. Este servicio web utiliza la función
distinctValuesResearch(), que permite acumular valores distintos de cada criterio de
selección (tipo de producto, marca, precio y popularidad), en las sublistas que se corresponden con
los diferentes criterios.

app.get("/Products/selectors", (req,res) => {


distinctValuesResearch(db, [], "type",
(selectors) => {
distinctValuesResearch(db, selectors, "brand",
(selectors) => {
distinctValuesResearch(db, selectors, "price",

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

La función distinctValuesResearch() añade a la colección selectors, que recibe


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.

A continuación se muestra un ejemplo de su funcionamiento:

Durante la primera llamada, selectors vale [] y property vale type.


Durante la segunda llamada, selectors vale:

[
{"name": "type",
"values": ["cambiar", "computer", "game console",
"headset","phone","tablet"]}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


]

y property vale brand.

Durante la tercera llamada, selectors vale:

[{"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

(err, documents) => {


do

if (err)
selectors.push({"name": property, "values": []});
to

else {
e
.d

if (documents !== undefined) {


w

let values = [];


w

if (property == "price") {
w

let min = Math.min.apply(null, documents);


let max = Math.max.apply(null, documents);
let minSlice = Math.floor(min / 100)*100;
let maxSlice = minSlice + 99;
values.push(minSlice+" - "+maxSlice);
while (max > maxSlice) {
minSlice += 100;
maxSlice += 100;
values.push(minSlice+" - "+maxSlice);
}
selectors.push({"name": property,
"values": values});
}
else selectors.push({"name": property,

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


"values": documentos.sort()});
}
else
selectors.push({"name": property, "values":[]});
}
callback(selectors);
});
};

En este código, se hace una operación particular para presentar los intervalos de precio.

b. Visualización de los productos según los criterios de búsqueda


La visualización de los productos según diferentes criterios de búsqueda por palabras clave, se

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

A continuación se muestra la estructura del componente ProductDisplayComponent y la


inyección del servicio ResearchService que utiliza:

...
export class ProductDisplayComponent implements OnInit {
private Products: Object[]; // La lista de productos a mostrar
private subscribe: any;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


constructor (private research: ResearchService,
private ruta: ActivatedRoute) {}

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

.subscribe(res => this.Products = res);


pr

});
do

}
to

}
e
.d
w

Es el método getProducts() del servicio ResearchService, el que recupera una lista de


w

productos.
w

Durante la invocación del componente ProductDisplayComponent con una lista de


argumentos, que se corresponden con los diferentes criterios de búsqueda, se llama al método
getProducts() con estos criterios, que añade en la ruta prefijada por Products.
Por lo tanto, la ruta completa está formada finalmente por:

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.

getProducts(arguments: string): Observable<any> {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


let url: string = "http://localhost:8888/Products/"+arguments;
return this.http.get(url);
= this.http
.get(url)
}

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


res.end(json);
});
});

c. Visualización de los productos asociados a las palabras clave


La visualización de los productos según diferentes palabras clave (una sucesión de términos
introducidos en una zona de entrada de datos), se implementa en el componente
ProductDisplayComponent. Como anteriormente, este componente utiliza el servicio
ResearchService, que enlaza la aplicación Angular y el servidor Node.js.

org
n.
io
ac
m
ra
og
pr
do
e to
.d

Este componente se invoca en cada visualización de productos, como consecuencia de una


w

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 {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


// Caso de una búsqueda con palabras clave
subroute = "keywords?"+params["terms"]
.split(" ").join("&");
}
this.research.getProducts(subroute)
.subscribe(res => this.Products = res);
});
}
...

Es el método getProducts() del servicio ResearchService, el que se encarga de


recuperar una lista de productos del servidor Node.js. Este método se llama con una cadena que
utiliza como prefijo keywords y la lista de términos, esta cadena se inyecta en una ruta después de
la palabra clave Products. Por lo tanto, la ruta completa está formada finalmente por:

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

Y para terminar, los términos introducidos componen la Query string.


og
pr

Por ejemplo, si los términos son phone y 64G, la ruta es: http://localhost:8888/Products/keywords?
do

phone&64G
to

A continuación se muestra el método getProducts() del servicio ResearchService:


e
.d
w

getProducts(arguments: string): Observable<any> {


w

let url: string = "http://localhost:8888/Products/"+arguments;


w

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.

app.get("/Products/keywords", (req,res) => {


let keywords = [];

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


for (let keyword in req.query) keywords.push(keyword);
db.collection("Products").find({}, {"_id":0})
.toArray((err, documents) => {
let results = [];
documentos.forEach((product) => {
let match = true;
for (let k of keywords) {
let found = false;
for (let p in product) {
let regexp = new RegExp(k, "i");
if (regexp.test(product[p])) {
found = true;
break;
}
}

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

let json = JSON.stringify(results);


res.end(json);
pr

});
do

});
e to
.d

La consulta MongoDB devuelve todos los documentos de la colección Products privados de su


w

propiedad _id, que podrían contaminar la búsqueda:


w
w

find({}, {"_id":0})

d. Acceso a un producto por su identificador


La recuperación de un producto por su identificador (es decir, la recuperación de todas las
propiedades del producto), es una operación que se puede encontrar en diferentes lugares en una
aplicación de e-commerce. En El hilo rojo, que solo es el embrión de una aplicación completa, no
solo se utiliza cuando un producto añadido o eliminado de la cesta de la compra de un cliente, quiere
impactar sobre la interfaz el nombre de este producto que se manipula por su identificador.

A continuación se muestra el método cartProductManagement() del componente


CartManagementComponent en la que se realiza esta acción:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


cartProductManagement(action, productId) {
this.research
.getProductById(productId)
.subscribe(res => this.product = res);
...
}

Es el método getProductById() del servicio ResearchService, el que se implementa


para recuperar todas las propiedades de un producto:

getProductById(id: string): Observable<any> {


let url: string = "http://localhost:8888/Product/id="+id;
return this.http.get(url);
}

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

let json = JSON.stringify({});


if (/[0-9a-f]{24}/.test(id)) {
pr

db.collection("Products")
do

.find({"_id": ObjectId(id)})
to

.toArray((err, documents) => {


e
.d

if ( documents !== undefined


w

&& documents[0] !== undefined )


w

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.

3. Gestión de la cesta de la compra


a. Visualización de los identificadores de los productos de la cesta de la compra

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


La recuperación de los identificadores de los productos de la cesta de la compra de un cliente no está
por el momento en el embrión de aplicación.

El método getCartProductIds() del servicio CartService se creó (anticipando su


utilización futura) y se llama enviando como argumento el identificador del cliente (su dirección de e-
mail):

getCartProductIds(email: string): Observable<any> {


let url: string = "http://localhost:8888/CartProductIds/"+email;
return this.http.get(url);
}

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

if ( documents !== undefined


do

&& documents[0] !== undefined ) {


to

let order = documents[0].order;


e

res.setHeader("Content-type",
.d

"application/json; charset=UTF-8");
w
w

let json=JSON.stringify(order);
w

res.end(json);
}
});
});

b. Visualización de todos los productos de la cesta de la compra


Necesita recuperar todos los productos de la cesta de la compra de un cliente, durante la visualización
de este. Esta visualización se implementa en el componente CartDisplayComponent. Este
componente utiliza el servicio CartService.

...
export class CartDisplayComponent implements OnInit {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


private Products: Object[];
private email: string;
private total: number = 0;
constructor( private cart: CartService, ... ) { ... }
ngOnInit() {
this.route.params.subscribe(params => {
this.cart.getCartProducts("Products/email="+this.email)
.subscribe(res => {
this.Products = res;
this.total = 0;
for (let p of res) {
this.total += p.price * p.nb;
}
});
});

rg
}

o
...

n.
io
}
ac
m
Cada vez que se invoca el componente CartDisplayComponent, se llama al método
ra

getCartProducts() del servicio CartService, usando como argumento el identificador


og

del cliente (su dirección de e-mail):


pr
do

getCartProducts(parameters: string): Observable<any> {


to

let url: string =


e

"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" },

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


{ $group: {"_id": "$_id",
"order": { "$push": "$order" },
"Products": { "$push": "$product" }
}
}
];
db.collection("Carts")
.aggregate(pipeline).toArray((err, documents) => {
let json;
if ( documents !== undefined
&& documents[0] !== undefined ) {
let ProductsInE = documents[0].Products;
let ProductsInI = {};
for (let product of ProductsInE) {
if (product._id in ProductsInI)

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

let productList = [];


.d

for (let productId in ProductsInI) {


w
w

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

Este servicio web merece algunas explicaciones.

Se define un pipeline de acciones para una consulta MongoDB. Permite:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


Filtrar ($match) las cestas de la compra sobre una dirección de e-mail, para conservar solo
uno.
Crear una unión ($lookup) entre cada identificador de producto del pedido actual y la
colección de los productos.
Crear una lista ($group) llamada Products y formada por los productos (con todas sus
propiedades), de la cesta de la compra.

Estas acciones se aplican posteriormente a la colección Carts y los productos de la cesta de la


compra devueltos. Los mismos productos (pedidos una o varias veces), se agrupan y se crea una
propiedad nb.

c. Adición de un producto en la cesta de la compra


La gestión de las cestas de la compra se implementa en el componente

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

cesta de la compra del cliente.


og
pr

Este método se llama con dos argumentos:


do

El argumento que describe la acción a realizar: add en este caso.


to

El identificador del cliente (su dirección de e-mail).


e
.d
w

Como hemos visto anteriormente, la implementación del servicio pasa por su inyección en el
w

constructor del componente.


w

A continuación, se muestra la estructura del componente CartManagementComponent y la


inyección del servicio que utiliza:

...
export class CartManagementComponent implements OnInit {
constructor( private cart: CartService, ... ) {}
ngOnInit() {
this.route.params.subscribe(params => {
this.cartProductManagement(params["action"],
params["id"]);
});
}
cartProductManagement(action, productId) {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


...
}
...
}

Este método invoca dos métodos de la clase CartService, que implementa el servicio:

El método getProductById() para recuperar la información relativa al producto y de


esta manera, mostrar un mensaje en la interface.
El método modifyCart() para añadir el identificador del producto en la cesta de la
compra, almacenada en la colección MongoDB.

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

if (action == "add") this.action = "Adición";


og

if (action == "remove") this.action = "Eliminar";


pr

this.cart.getProductById(productId)
do

.subscribe(res => this.product = res);


to

// Modificación de la cesta de la compra almacenada en el servidor


e
.d

// y visualización de esta
w

this.cart.modifyCart(action, productId, this.email)


w

.subscribe(res => {
w

this.router.navigate(['/cart',
{outlets:{'cartDisplay': ['display',
this.numAction]}}
]);
this.numAction++; });
}

A continuación se muestra el método modifyCart() del servicio CartService:

modifyCart(action: string,
productId: string,
email: string): Observable<any> {
const httpOpciones = {

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};

let observable: Observable<any>;


if (action == "add") {
observable =
this.http.post("http://localhost:8888/CartProducts",
{ "productId": productId, "email": email },
HttpOpciones);
}
... // caso de eliminación de un artículo de la cesta de la compra
return observable;
}

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

let e-mail = req.body.e-mail;


og

let productId = req.body.productId;


pr

db.collection("Carts")
do

.find({"email":email})
.toArray((err, documents) => {
to

let json;
e
.d

if ( documents !== undefined


w

&& documents[0] !== undefined ) {


w

let order = documents[0].order;


w

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


La extracción de los datos del cuerpo del mensaje HTTP es directa, los datos transmitidos al cuerpo
del mensaje, son accesibles desde el objeto de la consulta req.body.

d. Eliminación de un producto de la cesta de la compra


Durante la eliminación de un producto de la cesta de la compra de un cliente, también se utiliza el
método cartProductManagement() del componente
CartManagementComponent, usando como argumentos la acción a realizar (remove en
este caso) y la dirección de e-mail del cliente (su identificador).

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

.subscribe(res => {this.router.navigate(


og

['/cart', {outlets:{'cartDisplay': ['display',


pr

this.numAction]}}]);
do

this.numAction++;
to

});
e

}
.d
w

A continuación se muestra el método del servicio CartService:


w
w

modifyCart(action: string, productId: string, email: string):


Observable<any> {
... // caso de la adición de un producto a la cesta de la compra

let observable: Observable<any>;


if (action == "remove") {
observable =
this.http.delete("localhost:8888/CartProducts/productId="
+productId+"/email="+email,
HttpOpciones);
}
return observable;
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


A continuación se muestra el servicio web gestionado por Node.js (la cesta de la compra de un cliente
se almacena como valor de la propiedad order en forma de una lista de identificadores):

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 ) {

let order = documents[0].order;

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

.update({"e-mail":e-mail}, {$set: {"order": order}});


og

json = order;
}
pr

}
do

res.setHeader("Content-type",
to

"application/json; charset=UTF-8");
e
.d

res.end(JSON.stringify(json));
w

});
w

});
w

Para eliminar el producto, se realizan las siguientes operaciones:

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


e. Reinicialización de la cesta de la compra
La reinicialización de la cesta de la compra, se hace a través de la plantilla del componente
cartDisplayComponent, que muestra la cesta de la compra.

CartReset() {
this.cart
.cartReset(this.email)
.subscribe(res => this.router.navigate(['/cart',
{outlets:{'cartDisplay': ['display']}}]));

A continuación se muestra el método del servicio CartService:

cartReset(e-mail: string): Observable<any> {


let url: string =

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

let email = req.params.email;


db.collection("Carts")
to

.update({"email":email}, {$set: {order: []}});


e
.d

res.setHeader("Content-type",
w

"application/json; charset=UTF-8");
w

res.end("Cart reset done");


w

});

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:32 p. m.]


La librería NgRx y los stores
La implementación de la librería NgRx permite centralizar todos los datos de la aplicación que
definen un estado de este, en un objeto global. Por ejemplo, en el marco de nuestra aplicación El hilo
rojo, estos datos se corresponden con el perfil del usuario, su última búsqueda sobre los productos
puestos a la venta, y el contenido de su cesta de la compra.

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

Con la implementación de un store, las transferencias de datos entre la aplicación Angular y el


 Node.js, se gestionan directamente por este. Los servicios Angular pueden continuar a ser
servidor
pr

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

del pattern Flux, implementado por Facebook para su librería React.


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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:44 p. m.]


Conocimientos adquiridos en este capítulo
Este capítulo ha estudiado la interfaz de los componentes Angular con Node.js. Esta interfaz se basa
en los servicios que son las clases inyectables que los componentes pueden compartirse, la
instanciación de estas clases se gestiona automáticamente por Angular a través de una inyección de
servicios. De esta manera, hemos visto cómo utilizar un servicio, declarando una variable como

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

datos con el servidor Node.js.


og

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

través del método HTTP GET.


.d
w

Después también hemos detallado el envío de datos en una querystring.


w
w

Posteriormente hemos ilustrado la conexión de la aplicación Angular con un servidor Node.js, a


través de la aplicación El hilo rojo de e-commerce.

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:

Partiendo del componente que necesita intercambiar datos con el servidor.


Que, para esto, utiliza uno de los dos servicios creados con anterioridad (búsqueda de
productos o gestión de la cesta de la compra), que ellos mismos utilizan el servicio HTTP de
Angular.
Que, él, invoca al servicio web de Node.js. 

De esta manera, hemos presentado todas las funcionalidades implementadas en la aplicación El hilo

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:57 p. m.]


rojo.

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:52:57 p. m.]


Principio general del enrutado
1. ¿Por qué establecer un enrutado?
Angular permite crear aplicaciones mono página. Estas aplicaciones están formadas por diferentes
códigos JavaScript y completamente descargadas antes de que el internauta realice la más mínima

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

internauta realizada a través de la plantilla de un primer componente, desencadena un procesamiento


to

operado por un segundo componente y activa su vista (es decir, su plantilla).


e
.d

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).

Para gestionar estas transacciones, hay dos posibilidades:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


A partir de la vista de un primer componente, se llama a un método de otro componente, lo que
constituye la creación de un enlace directo (y sin duda incómodo) entre los dos componentes.
Además de la poca flexibilidad que aporta esta solución, si la plantilla del segundo componente
solo debe aparecer en un momento dado, es necesario que su visualización se condicione por
una directiva estructural, lo que puede ser pesado de establecer.
O a partir de la vista de un primer componente, se envía un mensaje a un controlador (o router)
que como respuesta, invoca el componente que debe reaccionar a la acción (la vista de este
componente destino se activa).

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

propia tabla de enrutado.


to

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

servidor Node.js, la gestión de las rutas se organiza en dos niveles diferentes:


w
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.

2. Las rutas, el router, las tablas de enrutado

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


Una ruta es la expresión de una acción particular (por ejemplo, el internauta quiere conocer todos los
teléfonos en venta en el sitio). Si esta acción se realiza seleccionando un elemento HTML que se
puede pulsar en una plantilla, la directiva routerLink se puede utilizar para seleccionar la ruta.

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

ng new --routing <nombreDelProyecto>


og
pr

Esta opción permite crear el módulo app-routing.module.ts, que acompaña al root module y que
do

especifica la gestión del enrutado.


e to

Una tabla de enrutado se presenta como una lista de objetos (una colección), donde las rutas se
.d

asocian a los componentes que se deben invocar.


w
w

A continuación se muestra el esquema de programación más simple:


w

const routes Routes = [


{
path: <ruta>,
component: <componente a invocar>
},
{
path: <ruta>,
component: <componente a invocar>
},
...
];

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


La sintaxis de los objetos presentada anteriormente, se puede enriquecer por medio de otras
propiedades que vamos a describir, en las secciones siguientes.

También es posible que una ruta invoque, no un componente, sino un módulo:

const routes Routes = [


{
path: <routa primaria>,
loadChildren: <módulo a invocar>
},
...
];

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

3. Las vistas activadas por las rutas


do
e to

Acabamos de ver el proceso que conduce a la invocación de un componente destino a partir de la


.d

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.

La vista se muestra en la ubicación de una etiqueta <router-outlet>. Puede existir una o


varias directivas <router-outlet> en la composición de las plantillas que forman la interfaz
de su aplicación.

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


ubicación de todas las vistas de sus componentes enrutados.

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

aplicación Angular que comprende, a diferencia del componente principal (AppComponent),


do

otros dos componentes llamados Component1Component y Component2Component.


to

La plantilla del componente principal comprende:


e
.d

La vista de Component1Component a través de su selector (por lo tanto, esta vista se


w
w

muestra constantemente en la página web).


w

Un enlace que selecciona una ruta para invocar Component2Component.

 Lance los comandos Angular CLI que crean la aplicación y los dos componentes:

ng new --routing ENRUTADO


ng g c component1
ng g c component2

A continuación se muestra los archivos importantes, generados por estos comandos:

ENRUTADO/src/index.html: la página HTML de más alto nivel.


ENRUTADO/src/app/app.module.ts: el root module.
ENRUTADO/src/app/app.component.ts: el componente de más alto nivel AppComponent.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


ENRUTADO/src/app/app.component.html: la plantilla del componente AppComponent.
ENRUTADO/src/app/app-routing.module.ts: la tabla de enrutado.
ENRUTADO/src/app/component1/component1.component.ts: el componente
Component1Component.
ENRUTADO/src/app/component1/component1.component.html: la plantilla del componente
Component1Component.
ENRUTADO/src/app/component2/component2.component.ts: el componente
Component2Component.
ENRUTADO/src/app/component2/component2.component.html: la plantilla del componente
Component2Component.

El archivo app-routing.module.ts contiene el código de un módulo que implementa el enrutado (su

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

<a routerLink="/forthecomponent2"> Llamada del component2 </a>


e
.d
w

La variable title es un atributo del componente AppComponent, inicializada con la cadena


w

«app works!».
w

La directiva <router-outlet> define la ubicación de la vista de


Component2Component, que se invoca a través de una ruta.
Se crea un ancla para seleccionar esta ruta (la selección de las rutas a través de la directiva
routerLink se explica en detalle un poco más adelante):

La plantilla de Component1Component (archivo component1.component.html) permanece sin


cambios:

<p> component1 works! </p>

La plantilla de Component2Component (archivo component2.component.html) permanece sin

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


cambios:

<p> component2 works! </p>

 Configure la tabla de enrutado que el router utiliza:

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';
import { Component2Component } from
'./component2/component2.component';

const routes Routes = [


{
path: 'forthecomponent2',
component: Component2Component

rg
}

o
n.
];

io
@NgModule({
ac
m
imports: [RouterModule.forRoot(routes)],
ra

exports: [RouterModule],
og

providers: []
})
pr

export class AppRoutingModule { }


do
to

 Construya la aplicación con el comando de Angular CLI adecuado:


e
.d
w

ng serve -o
w
w

La aplicación se abre en una nueva pestaña de su navegador.

 Si este no es el caso, escriba la siguiente URL: localhost:4200.

La vista de Component1Component se muestra directamente en la vista del componente


principal:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


Cuando se selecciona el ancla, se activa la vista de Component2Component:

org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w

5. Definición de un árbol de vistas


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 la navegación a establecer:

route /: solo mostrar la vista del componente principal.


route /component1: mostrar la vista del componente principal que integra la del componente 1.
route /component1/component3: mostrar la vista del componente principal que integra la del
componente 1, que integra la del componente 3.
route /component1/component4: mostrar la vista del componente principal que integra la del

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


componente 1, que integra la del componente 4.
route /component2: mostrar la vista del componente principal que integra la del componente 2.

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 componente principal < vista del component1

vista del componente principal < vista del component1 < vista del component3

vista del componente principal < vista del component1 < vista del component4

vista del componente principal < vista del component2

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

ENRUTADO2/src/app/app.component.html: plantilla del componente AppComponent.


og
pr

ENRUTADO2/src/app/app.component.css: hoja de estilos de la plantilla de


do

AppComponent.
to

ENRUTADO2/src/app/component1/component1.component.ts / html: code y plantilla de


e
.d

Component1Component.
w

ENRUTADO2/src/app/component1/component2.component.ts / html: code y plantilla de


w

Component2Component.
w

ENRUTADO2/src/app/component1/component3.component.ts / html: code y plantilla de


Component3Component.
ENRUTADO2/src/app/component1/component4.component.ts / html: code y plantilla de
Component4Component.
ENRUTADO2/src/app/app-routing.module.ts: la tabla de enrutado.

A continuación se muestra el código del módulo de enrutado src/app/app-routing.ts que representa el


ejemplo:

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';
import { Component1Component } from

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


'./component1/component1.component';
import { Component2Component } from
'./component2/component2.component';
import { Component3Component } from
'./component3/component3.component';
import { Component4Component } from
'./component4/component4.component';

const routes Routes = [


{
path: 'component1',
component: Component1Component,
children: [
{
path: 'component3',

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 { }

A continuación se muestra el código de la plantilla del componente principal (AppComponent).

La ubicación de las vistas (las plantillas) de los componentes activados por el router, que se define
por la etiqueta <router-outlet>:

<a [routerLink]="['component1']"> component1 </a> <br/>


<a [routerLink]="['component1', 'component3']"> component1/

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


component3 </a> <br/>
<a [routerLink]="['component1', 'component4']"> component1/
component4 </a> <br/>
<a [routerLink]="['component2']"> component2 </a>
<br/><br/>
<router-outlet></router-outlet>

A continuación se muestran las plantillas de los otros cuatro componentes:

Plantilla del componente 1 (Component1Component):

<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

Plantilla del componente 2 (Component2Component):


e
.d

<p> Inside component2 </p>


w
w
w

Plantilla del componente 3 (Component3Component)

<p> Inside component3 </p>

Plantilla del componente 4 (Component4Component):

<p> Inside component4 </p>

A continuación se muestra la vista del componente principal sobre la URL localhost:4200:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


La vista del componente 1 se inserta en la ubicación de la etiqueta <router-outlet> sobre la
selección del ancla /component1:

org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w

La vista del componente 3 se integra en la del componente 1, sobre la selección de la ruta


w

/component1/component3:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


6. Utilización de los outlets con nombre

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

outlet en la configuración de la tabla de enrutado:


pr
do

const routes Routes = [


{
to

path: <ruta>,
e
.d

component: <componente a invocar>,


w

outlet: <nombre del outlet>


w

}
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:

en la ruta forthecomponent2 para que aparezca su vista en la parte inferior de la página


web.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


en la ruta fortheVIPcomponent2 para que aparezca su vista en la parte superior de la
página.

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:

ENRUTADO3/src/app/app.component.ts: componente de más alto nivel AppComponent.


ENRUTADO3/src/app/app.component.html: plantilla del componente AppComponent.
ENRUTADO3/src/app/app.component.css: hoja de estilos de la plantilla de
AppComponent.
ENRUTADO3/src/app/component1/component1.component.ts / html: code y plantilla de

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

A continuación se muestra la nueva versión de la plantilla del componente principal (archivo


og

app.component.html) para:
pr
do

crear un outlet con nombre en la parte superior de la página;


to

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>

<a routerLink="/forthecomponent2"> Llamada del component2 </a>


<br/>
<a [routerLink]
="[{'outlets':{'specialPlace': ['fortheVIPcomponent2']}}]">
Llamada VIP del component2
</a>

Esta plantilla se asocia a la siguiente hoja de estilos, que crea un borde alrededor de la ubicación VIP:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


div { width: 200px;
border-width: 1px;
border-style: solid;
}

Los componentes Component1Component y Component2Component no modifican sus


plantillas:

<p> Inside component1 </p>

y:

<p> Inside component2 </p>

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

import { Routes, RouterModule } from '@angular/router';


og

import { Component2Component } from


pr

'./component2/component2.component';
do

const routes Routes = [


to

{
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 { }

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


La vista inicial presenta las dos anclas para seleccionar la ubicación de la vista del segundo
componente:

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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]


https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:20 p. m.]
La sintaxis de las rutas
Esta sección pinta un escenario más completo para las rutas. La directiva routerlink asociada a
las anclas, se introducirá en primer lugar en los ejemplos de selección de rutas y después se
introducirá el método navigate(), que también permite realizar selecciones.

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 ubicación por defecto: la primary outlet.


to

La sintaxis de la selección de las rutas queda de esta manera condicionada respecto a varios factores:
e
.d

La ruta se define en JavaScript por una cadena o una lista.


w
w

La ruta puede ser absoluta o relativa.


w

La ruta puede tener argumentos en su ruta.


La ruta se puede asociar a un auxiliary outlet.
La ruta se puede tener en cuenta por varias tablas de enrutado, en el caso en el que la
aplicación esté formada por varios módulos.

En adelante, vamos a introducir el conjunto de elementos que influyen sobre la sintaxis de las rutas.

1. Las dos sintaxis de una ruta: cadena o link parameters array



Si la ruta solo está formada por un único segmento y si el objeto que especifica los auxiliary outlets
está ausente, la ruta se puede definir directamenteuna cadena de caracteres.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:36 p. m.]


A continuación se muestra un ejemplo con una ruta compuesta por el único segmente
forthecomponent2:

<a routerLink="/forthecomponent2"> Llamada al component2 </a>

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

Esta lista se llamada link parameters array.

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

tener una semántica tan clara como sea posible.


do

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.

Por ejemplo, la ruta product/id=58e643742ba15f90d13cb87d/add, indica la adición del producto de


identificador 58e643742ba15f90d13cb87d (el componente invocado sobre esta ruta «sabrá» a «qué»
añadir este producto...).

2. Las rutas absolutas y las rutas relativas


Las rutas se especifican de manera absoluta, prefijadas por una barra oblicua (un slash: /) o de manera

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:36 p. m.]


relativa.

Cuando se especifican de manera absoluta, la raíz se corresponde con en el directorio src/app y se


soportan por la tabla de enrutado asociada al módulo principal de la aplicación.

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

carácter dos puntos.


do

Por ejemplo, si la ruta seleccionada en la barra de URL del navegador es esta (solicita buscar y
to

mostrar todos los teléfonos): /research/display/phone/*/*/*/*


e
.d

y la ruta declarada en la tabla de enrutado, esta:


w
w

/research/display/:type/:brand/:minprice/:maxprice/:minpopularity
w

se realizan las siguientes correspondencias:

El argumento type tiene como valor phone;


Los argumentos brand, minprice, maxprice y minpopularity tienen el valor
* (que en la aplicación, es un joker que indica que no se fija ninguna restricción sobre estos
criterios de búsqueda).

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:36 p. m.]


4. Asociación de una ruta a un auxiliary outlet
La activación de un componente se puede corresponder con una ubicación determinada por un
auxiliary outlet.

A continuación se muestran dos ejemplos equivalentes de implementación de un outlet con nombre,


que definen la ubicación specialPlace:

<a [routerLink] = "[{'outlets':


{'specialPlace': ['fortheVIPcomponent2']}}]">
Llamada VIP del component2
</a>

<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

Llamada VIP del component2


do

</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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:36 p. m.]


Selección de las rutas
Hay dos posibilidades para seleccionar una ruta (esta selección se hace generalmente directa o
indirectamente a partir de una plantilla):

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

pulsables, como las anclas.


.d
w

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]="[...]"> ... </a>

A continuación se muestra un ejemplo:

<a [routerLink]="['/research',
{outlets: {'display': ['display',

this.params.type,
Índice
this.params.brand]}}]">

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:48 p. m.]


Selección de los productos del tipo y
de la marca elegidos </a>

Si la ruta es un segmento representado por una simple cadena de caracteres, entonces la directiva
routerLink se puede utilizar:

<a routerLink="/cart/validation"> Validación </a>

La directiva routerLink entonces puede estar acompañada por las siguientes directivas:

La directiva [queryParams] que acompaña a la ruta de una Query string (cadena de


consulta).
La directiva [routerLinkActive] permite especificar las clases CSS, que se aplican

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

de un objeto. A continuación se muestra el esquema de programación:


pr

<a [routerLink]="<link parameters array>"


do

[queryParams]="{<propiedad>: <valor>, ...}"> ...


to

</a>
e
.d
w

La directiva [routerLinkActive] especifica la o las clases CSS, aplicadas durante la


w

activación de la ruta. Normalmente se acompaña por la directiva


w

[routerLinkActiveOptions], que limita la aplicación de las clases CSS a la ruta estricta


(y no a las rutas derivadas), cuando tiene por valor {exact: true}.

A continuación se muestra un ejemplo:

<a [routerLink]="['/home]"
[routerLinkActive]="routeactive"
[routerLinkActiveOptions]="{ exact: true }">
Inicio
</a>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:48 p. m.]


2. El método navigate()
El método navigate() del servicio Router (paquete @angular/router), permite seleccionar
una ruta con la misma sintaxis que la admitida por la directiva routerLink.

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:

import { Router } from '@angular/router';

@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 (click)="cartValidation()"> Validación de la cesta de la compra


pr

</button>
do
to

Y la función llamada durante la selección del botón:


e
.d
w

cartValidation() {
w

this.router.navigate(['/cart', 'validation']);
w

El método navigateByUrl() permite navegar respecto a una URL, por ejemplo:

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:48 p. m.]


los teléfonos (los otros criterios de selección se especifican como indiferentes por el uso del
asterisco): research/display/phone/*/*/*/*

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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:53:48 p. m.]


Gestión de las rutas del controlador hacia
el componente destino
En una aplicación Angular, es posible crear módulos específicos, dedicados en el enrutado que
acompaña al root module y los feature modules (no es una obligación, ya que las configuraciones de
enrutado también se pueden especificar directamente en el código de los módulos, pero esta

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

con el componente a invocar, durante la selección de este:

import { Routes, RouterModule } from '@angular/router';


... // importaciones de los componentes invocados por las rutas

const routesRoutes = [
{
path: <ruta>,
component: <componente a invocar>
},
{

path: <ruta>,
component: <componente a invocar>
},

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


...
];

La tabla de enrutado se proporciona en el router por el método forRoot() si afecta al root


module, o por el método forChild() si afecta a una feature module.

A continuación se muestra el esquema de programación del módulo que especifica la tabla de


enrutado:

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';
... // importaciones de los componentes invocados por las rutas

const routes Routes = [

rg
...

o
];

n.
io
@NgModule({ ac
imports: [RouterModule.forRoot(routes)],
m
// o RouterModule.forChild(routes)],
ra

exports: [RouterModule],
og

providers: []
pr

})
do

export class AppRoutingModule { }


e to

El RouterModule se exporta para que los componentes del módulo que


.d

importa AppRoutingModule, puedan utilizar las directivas routerLink y router-


w
w

outlet.
w

2. Las propiedades de una ruta


Por el momento, hemos visto las cuatro propiedades siguientes de configuración de una ruta:

La propiedad path, que define la ruta.


La propiedad component, que define el componente a invocar durante la selección de la
ruta.
La propiedad children, que permite definir un árbol de vistas.
La propiedad outlet, que permite apuntar la vista del componente a un outlet con nombre
(una ubicación específica).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


A continuación se muestra un ejemplo de una declaración de ruta (que tiene cinco argumentos). Su
selección provoca la invocación del componente ProductDisplayComponent, cuya vista es
la que se muestra en el outlet con nombre display:

const routesRoutes = [
{ path: 'display/:type/:brand/:minprice/:maxprice/:minpopularity',
component: ProductDisplayComponent,
outlet: 'display'
}
];

A estas cuatro propiedades se añaden las dos propiedades siguientes:

La propiedad redirect, que permite seleccionar una nueva ruta.

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

indispensable para evitar que otras rutas no se seleccionen.


og

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

const routes Routes = [


e
.d

{ path: '', pathMatch: 'full', redirectTo: '/home' },


w

{ path: 'home', component: AppComponent },


w

{ path: 'Products', component: ProductDisplayComponent },


w

{ path: '**', component: PageNotFoundComponent }


];

También es posible pasar datos adicionales a transmitir al componente que se invoca. Para esto, se
puede utilizar la propiedad data.

A continuación se muestra un ejemplo:

const routes Routes = [


{ path: 'test', component: TestComponent,
data: {'p1': 'v1',
'p2': [1,2]
}
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


];

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.

3. Soporte de una ruta por varios módulos/tablas de enrutado


Una aplicación Angular generalmente está formada por diferentes módulos: un módulo principal (el
root module) y módulos que implementan diferentes grandes funcionalidades (los feature modules).
Una tabla de enrutado se asocia a cada módulo, para garantizar la reutilización de los feature
modules. Dicho esto, las diferentes tablas de enrutado, no se gestionan sobre una base de igualdad por
el router (importación del módulo @angular/router): la del root module se le debe asociar por

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

import { NgModule } from '@angular/core';


pr

import { Routes, RouterModule } from '@angular/router';


do

// importaciones de los componentes o de los feature modules invocados


por las rutas
e to
.d

const routes Routes = [


w

<tabla de enrutado asociada al root module>


w

];
w

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }

A continuación se muestra ahora el esquema de programación:

que define la tabla de enrutado de un feature module;


y la asocia al router.

import { NgModule } from '@angular/core';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


import { Routes, RouterModule } from '@angular/router';
// importaciones de los componentes invocados por las rutas

const routes Routes = [


<tabla de enrutado asociada al feature module>
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: []
})
export class <nombreDelFeatureModule>Routing { }

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

identificación del cliente.


e
.d

El módulo research (un primer feature module), que implementa la funcionalidad de


w

selección y visualización de los productos.


w
w

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.

Si la tabla de enrutado del root module se define de la siguiente manera:

const routes Routes = [


{ path: 'research',
loadChildren: './research/research.module#ResearchModule'
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


];

y la tabla de enrutado del módulo research de la siguiente manera:

const routes Routes = [


{ path: 'display/:type/:brand/:minprice/:maxprice/:minpopularity',
component: ProductDisplayComponent
}
];

entonces 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 al feature module research.

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

acciones quizás no apropiadas del internauta, es posible establecer un control de la legalidad de la


pr

ruta: si un control devuelve falso, se detiene la navegación al componente.


do

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

autorización de la ruta guardián, antes de permitir la invocación de un componente.

A continuación se muestra un ejemplo de la implementación de una autenticación. En esta, el


internauta se identifica una primera vez gracias al componente AuthComponent, que actualiza el
servicio AuthService con el resultado de esta autenticación. Posteriormente, este servicio se
utiliza por el guardián para autorizar o no la invocación de los componentes.

La vista de la aplicación presenta un enlace para invocar el componente TestComponent.

Si el internauta no se ha identificado, la vista del componente AuthComponent aparece en la


parte superior de la página y le solicita que se identifique.

Inicialmente, la aplicación presenta únicamente el enlace hacia el componente a invocar:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


rg
El internauta selecciona el enlace. Como no está identificado, aparece la vista del componente de

o
autenticación:

n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w

Se identifica, puede invocar el componente de prueba:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


rg
Este ejemplo agrupa los siguientes códigos:

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

GUARD/src/app/auth/auth.component.ts: el componente que permite la autenticación.


pr

GUARD/src/app/auth/auth.component.html: la plantilla del componente de autenticación.


do

GUARD/src/app/auth-guard.service.ts: el guardián que controla el acceso al componente.


to

GUARD/src/app/auth.service.ts: el servicio que comparte el booleano de identificación y que


e
.d

consulta el servidor.
w

GUARD/src/app/app-routing.module.ts: el módulo de enrutado.


w
w

GUARD/src/app/app.module.ts: el módulo que especifica todos los actores de este ejemplo.

Por otro lado, se crea un servidor Node.js HTTPS para preguntar a una colección llamada Users de
la base de datos MongoDB.

Este se crea en el modelo descrito en la sección Securización de un servidor Node.js (protocolo


HTTPS), del capítulo La plataforma Node.js.

const https = require('https');


const fs = require('fs');
const express = require('express');
const app = express();
const assert = require("assert");

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


const opciones = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt'),
ca: fs.readFileSync('./ca.crt'),
requestCert: true,
rejectUnauthorized: false
};

https.createServer(opciones, app).listen(8443);

let MongoClient = require("mongodb").MongoClient;


let ObjectId = require("mongodb").ObjectId;
let url = "mongodb://localhost:27017";

MongoClient.connect(url, {useNewUrlParser: true},(err, cliente) => {

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

let login = req.params.login;


og

let password = req.params.password;


pr

res.setHeader("Content-type",
do

"text/plain; charset=UTF-8");
to

db.collection("Users")
e

.find({"email":login, "password":password})
.d

.toArray((err, doc) => {


w
w

if ( doc !== undefined


w

&& doc.length == 1)
res.end("1");else res.end("0");
});
});
});

A continuación se muestra el componente principal AppComponent (archivo app.component.ts):

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


})
export class AppComponent {}

y su plantilla (archivo app.component.html):

<router-outlet name="login"></router-outlet>
<br/><br/>
<a [routerLink]="['/test']" > test </a>
<br/><br/>
<router-outlet></router-outlet>

A continuación se muestra el componente TestComponent, cuya invocación se controla (archivo


test/test.component.ts):

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

A continuación se muestra el componente de autenticación AuthComponent (archivo


pr

auth/auth.component.ts). La petición de autenticación pasa por el método authentication del


do

servicio AuthService.
e to
.d

import { Component } from '@angular/core';


w

import { BrowserModule } from '@angular/platform-browser';


w

import { FormsModule } from '@angular/forms';


w

import { AuthService } from '../auth.service';

@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css']
})
export class AuthComponent {
private login: string;
private password: string;
private nombreYApellido: string[] = [];

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


constructor(public auth: AuthService) {}

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

A continuación se muestra su plantilla (archivo auth/auth.component.html). Si el internauta se ha


pr

autenticado ya, el formulario de autenticación no se muestra. El booleano isLoggedIn que se


do

mantiene por el servicio AuthService, tiene como valor true.


e to

{{ mensaje }}
.d

<form *ngIf="!authService.isLoggedIn" (ngSubmit)="onSubmit()"


w

#loginForm="ngForm">
w

<div class="form-group">
w

<label for="login"> Login </label>


<input type="text"
class="form-control"
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"

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


[(ngModel)]="password" name="pass" />
</div>
<button type="submit"> Submit </button>
</form>
<button (click)="logout()" *ngIf="authService.isLoggedIn"> Logout
</button>

A continuación se muestra el guardián que controla el acceso al componente


AuthGuardService (archivo auth-guard.service.ts). El método canActivate() se
especifica en la configuración de la ruta, en la tabla de enrutado que se presenta un poco más
adelante.

import { Injectable } from '@angular/core';


import { Router, CanActivate } from '@angular/router';

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

if (this.authService.isLoggedIn) { return true; }


to

this.router.navigate([{outlets: {'login': ['login']}}]);


e

return false;
.d

}
w

}
w
w

A continuación se muestra el servicio AuthService (archivo auth.service.ts) que gestiona el


método que pregunta al servidor y comparte el booleano de identificación:

import { Injectable } from '@angular/core';


import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/';

@Injectable()

export class AuthService {


isLoggedIn: boolean = false;

constructor(private http: HttpClient) {}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


autenticación(login, password): Observable<any> {
console.log("En autenticación con "+login+" "+password);
let url: string = "https://localhost:8443/auth/login="+login
+"/password="+password;
return this.http.get(URL);
}

A continuación se muestra el módulo de enrutado AppRoutingModule (archivo app-


routing.module.ts):

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';
import { AuthGuardService } from './auth-guard.service';

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):

import { BrowserModule } from '@angular/platform-browser';


import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


import { AppComponent } from './app.component';
import { TestComponent } from './test/test.component';
import { AuthComponent } from './auth/auth.component';
import { AuthService } from './auth.service';
import { AuthGuardService } from './auth-guard.service';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
declarations: [
AppComponent,
TestComponent,
AuthComponent
],
imports: [

rg
BrowserModule,

o
FormsModule,

n.
io
HttpClientModule,
AppRoutingModule ac
],
m
providers: [AuthService, AuthGuardService],
ra

bootstrap: [AppComponent]
og

})
pr

export class AppModule { }


do
e to
.d

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.

La interfaz ActivatedRoute permite definir un objeto de manera convencional llamado


route, a través de una inyección de dependencias.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


A continuación se muestra el esquema de programación que crea este objeto, que contiene la
información asociada a la ruta que ha provocado la invocación de la plantilla:

import { ActivatedRoute, Params } from '@angular/router';

@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

A continuación se muestra una ilustración de este mecanismo, extraído de El hilo rojo.


do
to

El componente ProductDisplayComponent se invoca a través de una ruta, que especifica


e

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

método subscribe(), permite llamar al método getProducts() de un servicio que accede


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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


});
}

Los datos de la URL son accesibles siguiendo el mismo esquema, pero utilizando el observable url:

ngOnInit(): void {
this.route.url.subscribe(data => { ... });
}

6. Captura de una ruta durante la invocación de un componente


Cuando los componentes se invocan un número determinado de veces, los observables Params y

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

la ruta /test/paramValue1, y después una segunda vez en la ruta /test/paramValue1.


do

Este ejemplo agrupa el siguiente código:


e to

SNAPSHOT/src/app/app.component.ts: el componente principal.


.d
w

SNAPSHOT/src/app/app.component.html: la plantilla del componente principal.


w

SNAPSHOT/src/app/app-routing.module.ts: el módulo de enrutado.


w

SNAPSHOT/src/app/test/test.component.ts: el componente (y su plantilla) activada por la ruta.


SNAPSHOT/src/app/app.module.ts (no presentado): el módulo que especifica todos los
actores de este ejemplo.

El componente principal (archivo SNAPSHOT/src/app/app.component.ts) se implementa de la


siguiente manera (está «vacío»):

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


})
export class AppComponent {}

A continuación se muestra su plantilla (archivo SNAPSHOT/src/app/app.component.ts). Se crean dos


enlaces, cuya selección activa el componente asociado en la tabla de enrutado a la ruta /test/:param.
El primary outlet definido por la etiqueta router-outlet define la ubicación en la que aparece la vista
del componente activado.

<a [routerLink]="['/test', 'paramValue1']" >


test con paramValue1 </a> <br/>
<a [routerLink]="['/test', 'paramValue2']" >
test con paramValue2 </a>
<br/>
<router-outlet></router-outlet>

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

import { NgModule } from '@angular/core';


og

import { Routes, RouterModule } from '@angular/router';


pr

import { TestComponent } from './test/test.component';


do
to

const routes Routes = [


e
.d

{
w

path: 'test/:param',
w

component: TestComponent,
w

data: {'p1': 'v1', 'p2': [1,2]}


}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }

El componente invocado (archivo test/test.component.ts):

import { Component, OnInit } from '@angular/core';

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


import { ActivatedRoute, Params } from '@angular/router';

@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

En la primera invocación del componente sobre la ruta /test/paramValue1, la consola muestra:


og
pr

paramValue1
do

v1
1
to

Argumento = paramValue1
e
.d
w

En la segunda invocación del componente sobre la ruta /test/paramValue2, la consola muestra


w
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.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:01 p. m.]


Gestión de rutas en El hilo rojo
En el marco de la aplicación El hilo rojo de e-commerce, se han desarrollado dos funcionalidades en
dos feature modules:

La selección y la visualización de los productos a partir de diferentes modos de búsqueda


(módulo research).

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 de los feature modules.


definidas
pr
do

A nivel del root module, la tabla de enrutado del módulo AppRoutingModule (archivo
to

src/app/app-routing.module.ts) permite enrutar las rutas primarias:


e
.d

research hacia los componentes del módulo research.


w
w

cart hacia los componentes del módulo cart.


w

A nivel de los feature modules, las tablas de enrutado administran las rutas secundarias:

La tabla de enrutado del módulo research se especifica en el módulo


ResearchRoutingModule (archivo research-routing.module.ts).
La tabla de enrutado del módulo cart se especifica en el módulo
CartRoutingModule (archivo cart-routing.module.ts).

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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:15 p. m.]


Material para descargar
ONLINESALES/src/app/app-routing.module.ts: tabla de enrutado del root module.
ONLINESALES/src/app/research/research-routing.module.ts: tabla de enrutado del feature
module, que gestiona la selección y la visualización de los productos.
ONLINESALES/src/app/cart/cart-routing.module.ts: tabla de enrutado del feature module que
gestiona la cesta de la compra.

1. El módulo de enrutado asociado al root module


A continuación se muestra el código del módulo AppRoutingModule (archivo app-
routing.module.ts) que acompaña al root module y que define la tabla principal 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

import { ResearchComponent } from './research//research.component';


og

import { CartComponent } from './cart/cart.component';


pr

import { ResearchModule } from './research/research.module';


do

import { CartModule } from './cart/cart.module';


to

import { AuthGuardService } from './auth-guard.service';


e
.d

const routes Routes = [


w

{
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: []

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:15 p. m.]


})
export class AppRoutingModule { }

2. El módulo de enrutado asociado al feature module research


A continuación se muestra el código del módulo ResearchRoutingModule (archivo
research-routing.module.ts), que acompaña al feature module research. El componente que
muestra los productos seleccionados con los criterios de búsqueda predefinidos o respecto a las
palabras clave e, se activa en el mismo auxiliary outlet.

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';

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/*/*/*/*

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:15 p. m.]


los componentes del módulo research se cargan y el router utiliza la tabla de enrutado asociada a
este módulo, para invocar al componente destino.

3. El módulo de enrutado asociado al feature module cart


A continuación se muestra el código del módulo CartRoutingModule (archivo cart/cart-
routing.module.ts), que acompaña al feature module cart. Tres componentes para modificar,
mostrar o validar la cesta de la compra, se activan en los auxiliary outlets.

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';

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

const routes Routes = [


{ path: 'management/:action/:id/:numAction',
pr

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 { }

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:15 p. m.]


Conocimientos adquiridos en este capítulo
El router de Angular permite activar un componente (y por lo tanto, hacer que aparezca su vista),
después de que se haya seleccionado una ruta (como consecuencia de una acción, por ejemplo la
selección de un elemento pulsable, en el que se puede hacer clic, de una plantilla). Esta visualización
es temporal, al contrario de lo que sucede con la visualización de un componente, cuya etiqueta es

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

particulares se validan correctamente, antes de activar un componente.


og

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

sus elementos constitutivos (esta lista se llama link parameters array).

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:26 p. m.]


Material para descargar
fundamentales path y componente, que asocian una ruta al componente a invocar, se han
introducido inicialmente. La potente propiedad children, que permite definir un árbol de vistas,
así como la propiedad outlet, que asocia a la ruta una vista particular (definida por un outlet con
nombre), también se han abordado, así como la propiedad redirect, que permite la reescritura de
una ruta.

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:

La creación de gráficas (de charts), complementadas por flujos de datos asíncronos.

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

de realizar un análisis multidimensional.


do

La cobertura mediática de un mapa geográfico, se realizará por medio del uso del API Google Map a
to

través de un componente de la librería de componentes gráficos PrimeNG. Sobrecargaremos este


e
.d

Google Map de información gráfica.


w
w

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.

Este ejemplo introducirá la implementación de un componente gráfico (GUI component) de la librería


PrimeNG, así como otro componente de la misma librería: el componente Calendar, que permite
seleccionar una fecha.

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).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:39 p. m.]


Creación de charts con D3.js y dc.js
La librería D3.js (Data-Driven Documents) permite crear interfaces gráficas vectoriales en
JavaScript. El código creado en D3 genera etiquetas del lenguaje SVG (Scalable Vector Graphics)
(de hecho, insertan en el DOM elementos que se corresponden con estas etiquetas y sus atributos). El
aspecto original de D3, es generar los elementos gráficos en SVG, a partir de colecciones de

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

(después de haber sido descargada, lo que es recomendable):


.d
w

<script src="d3.min.js" type="text/javascript" />


w
w

En Angular, D3.js, se instala como módulo a través de npm:

npm install --save d3


npm install --save-dev @tipos/d3

Seguidamente, es suficiente con importar este módulo:

import * as d3 from 'd3';

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.

Por ejemplo, un rectángulo se puede crear con la etiqueta <rect>:

<rect x="<abscisa de la esquina superior izquierda>"


y="<ordenada de la esquina superior izquierda>"
width="<longitud>"
height="<altura>"
stroke="<color del borde>"
fill="<color de relleno>" />

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

fill="<color de relleno>" />


og
pr

Un segmento recto con la etiqueta <line>:


do
to

<line x1="<abscisa del punto de inicio>"


e

y1="<ordenada del punto de llegada>"


.d

x2="<abscisa del punto de inicio>"


w

y2="<ordenada del punto de llegada>"


w
w

stroke="<color del borde>"


fill="<color de relleno>" />

Una sucesión de segmentos rectos con la etiqueta <polyline>:

<polyline puntos="<lista de coordenadas>"


stroke="<color del borde>"
fill="<color de relleno>" />

Un trazo arbitrario con la etiqueta <path>:

<path d="<lista de coordenadas y elementos de control


de la visualización>"

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


stroke="<color del borde>"
fill="<color de relleno>" />

A continuación se muestra un ejemplo de etiqueta <path>:

<path d="M108,62 L90,10 70,45 50,10 32,62"


style="stroke:black; fill:none" />

en el que:

M significa MoveTo («salto» al punto de coordenadas absolutas que sigue).


L significa LineTo (las coordenadas siguientes se rellenan con segmentos de recta).

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

La función scale() permite un cambio de escala. También permite realizar operaciones de


w

mirroring.
w
w

La función translate() permite realizar rotaciones.


La función rotate() permite realizar translaciones.

A continuación se muestra un ejemplo clásico de código SVG:

<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">

<circle cx="140" cy="190" r="100" style="stroke:black;fill:none"/>


<circle cx="110" cy="160" r="10" style="stroke:black;fill:black"/>
<circle cx="170" cy="160" r="10" style="stroke:black;fill:black"/>
<circle cx="140" cy="230" r="15" style="stroke:black;fill:none"/>
<rect x="135" y="185" width="10" height="10"
style="stroke:black;fill:black"/>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


<g id="a">
<path d="M150,190 L270,170 M150,190 L270,190 M150,190 270,210
M150,190 270,230"
style="stroke:black;"/>
</g>
<use xlink:href="#vibrisses"
transform="scale(-1 1) translate(-280 0)" />
<polyline puntos="216 124, 150 20, 140 90, 130 20, 64 124"
style="stroke:black; fill:none" />
</svg>

El atributo xmlns de la etiqueta <svg> define el espacio de nombres (namespace) de SVG.

A continuación se muestra el resultado:

org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w

3. Generación de elementos gráficos asociados a los objetos de


una colección
El principio fundamental de D3, es generar objetos gráficos SVG (por ejemplo, círculos), respecto a
una colección de objetos JavaScript precisando todas o parte de sus propiedades (por ejemplo, los
objetos JavaScript que especifican las coordenadas de círculos).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


Esta asociación se hace en cuatro etapas:

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

etapa se implementa con los métodos:


og
pr

enter().append(): para los nuevos datos a tener en cuenta, creación de nuevos


do

elementos gráficos en el DOM.


e to

exit().remove(): eliminación de los antiguos elementos gráficos y construcción de


.d

nuevos elementos asociados a los datos del dataset de los mismos rangos.
w
w
w

4. Selección y modificación de elementos del DOM


La selección de elementos del DOM se puede realizar con los métodos select() y
selectAll(), que se aplican al objeto d3 de más alto nivel (también se podría hacer a través de
jQuery).

El método select() permite seleccionar un elemento del DOM. A continuación se muestran


diferentes ejemplos:

let body = d3.select("body");


svg.select("#miId") // selección del elemento de id "miId"
d3.select(this) // selección del elemento actual

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


El método selectAll() permite seleccionar un conjunto de elementos del DOM:

d3.selectAll("h3") // selección de los elementos correspondientes


// a la etiqueta "h3"
d3.selectAll(".miClase") // selección de los elementos asociados
// a la clase CSS "miClase"
d3.selectAll() // devuelve una lista vacía

a. Adición de elementos gráficos


En el siguiente ejemplo, no se selecciona ningún elemento gráfico pre-existente: hay tantos círculos
que se crean, como objetos en el dataset.

(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

x: function(currentObject) { return currentObject.x; },


e

y: function(currentObject) { return currentObject.y; }


.d

});
w
w
w

Se crean tres círculos.

A continuación se muestra otro ejemplo con la creación de textos (etiquetas <h3>) en dos
momentos:

let data = ["texto1", "texto2", "texto3"];

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


.text( (d) => { return d; });

data = ["texto4", "texto5", "texto6", "texto7", "texto8", "texto9"];

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

Este ejemplo produce la siguiente visualización:

org
texto1

n.
io
texto2 ac
m
texto3
ra

texto7
og
pr

texto8
do

texto9
e to
.d

b. Sustitución de elementos gráficos


w

Una fortaleza de D3 es poder sustituir «a voluntad», los elementos gráficos respecto a la llegada de
w
w

nuevos datos.

A continuación se muestra un ejemplo:

let data = ["texto1", "texto2", "texto3"];


d3.select("body")
.selectAll()
.data(data)
.enter()
.append("h3")
.text((d) => {return d; });

data = ["texto4", "texto5", "texto6"];

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


let selección = d3
.select("body")
.selectAll("h3") // selección de los tres elementos h3
.data(data); // asociación en el dataset
// ["texto4", "texto5", "texto6"]
selection.exit() // devuelve los data ya asociados,
// es decir ["texto1", "texto2", "texto3"]
.remove(); // elimina la asociación con los data asociados
// una nueva asociación se hace
// sobre ["texto4", "texto5", "texto6"]
selection.text((d) => {return d; });

Este ejemplo provoca la siguiente visualización:

texto4

org
texto5

n.
io
texto6
ac
m

5. Implementación de listeners de eventos


ra
og
pr

El método on() permite adjuntar listeners de eventos a uno o varios elementos del DOM.
do

A continuación se muestran algunos esquemas de programación utilizando este método:


e to

<elemento(s) del DOM> .on("click", function() { ... })


.d

.on("mouseover", function() { ... })


w
w

.on("mouseout", function() { ... });


w

A continuación se muestra el ejemplo de sustitución de datos visto anteriormente, pero ahora


desencadenado por un clic en uno de los textos:

let data = ["texto1", "texto2", "texto3"];

function update() {
selección = d3.select("body")
.selectAll("h3")
.data(data);
selection.exit().remove();
selection.text((d) => {return d; });
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


d3.select("body")
.selectAll("h3")
.data(data)
.enter()
.append("h3")
.text( function(d) {return d;})
.on("click",() => { update(); });

data = ["texto4", "texto5", "texto6"];

6. Integración de D3.js en Angular


Examinamos ahora cómo exhibir los gráficos generados por D3.js, dentro de una aplicación Angular.

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

CHARTwithD3/src/app/app.component.html: plantilla del componente host


(AppComponent) que integra el componente ChartComponent gracias a la inserción
pr
do

de la etiqueta <app-chart ...></app-chart>.


to

CHARTwithD3/src/app/app.component.ts: clase del componente host que configura el


e

componente.
.d
w

CHARTwithD3/src/app/chart/chart.component.ts: componente que crea el histograma con


w

D3.js.
w

CHARTwithD3/src/app/app.module.ts: módulo del proyecto (no presentado).

a. Un servidor virtual de datos comerciales


Simule el acceso a los datos comerciales, a través de un servidor Node.js de prueba. Esto devuelve:

en la ruta /places: una lista de lugares.


en la ruta /place=<nombre del lugar>: una lista de sub-listas que contiene el nombre y la
cantidad de cada producto pedido a partir del lugar especificado:

[{"name": "product1", "sales": 50},


{"name": "product2", "sales": 20},
{"name": "product3", "sales": 35},

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


...
]

A continuación se muestra el servidor de prueba (archivo CHARTwithD3/dataServer.js):

"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

"sales": [{"name": "product1", "sales": 82},


do

{"name": "product2", "sales": 18},


to

{"name": "product3", "sales": 27},


e
.d

{"name": "product4", "sales": 65}


w

]
w

}
w

];

app.get("/places", function(req, res) {


let places = []
for (let datum of data) {
places.push(datum.place);
}
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(places));
});

app.get("/place=:place", function(req, res) {


let place = req.params.place;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


let sales = [];
for (let datum of data)
if (datum.place == place) {
sales = datum.sales;
break;
}
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(sales));
})

b. El servicio que accede a los datos del servidor


Para utilizar los servicios REST del servidor Node.js, cree un servicio Angular cuyo código se

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

export class DataService {


e to

constructor(private http: HttpClient) {}


.d
w

getPlaces(): Observable<any> {
w

let url: string = "http://localhost:8889/places";


w

return this.http.get(url);
}

getSalesDataOfaPlace(place: string): Observable<any> {


let url: string = "http://localhost:8889/place="+place;
return this.http.get(url);
}
}

c. La plantilla del componente que muestra un histograma


La vista del componente exhibe una lista desplegable que contiene los diferentes lugares desde donde
se hacen los pedidos de productos. A continuación se muestra el código de la plantilla del

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


componente ChartComponent (archivo chart.component.html):

<select (change)="displaySalesOfAPlace($event)">
<opción *ngFor="let place of places">
{{place}}
</option>
</select>
<br/><br/>
<div id="divchart"></div>

d. Implementación del componente que muestra un histograma


El componente ChartComponent muestra, para un lugar dado, un histograma (un barchart) en el
que las ventas de cada producto se representan por una barra.

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

componentes de D3 y de su servicio) y los atributos de la clase:


og
pr

import { Component, OnInit } from '@angular/core';


do

import * as d3 from 'd3';


to

import { DataService } from '../data.service';


e
.d

@Component({
w

selector: 'app-chart',
w

templateUrl: 'chart.component.html',
w

styleUrls: ['chart.component.css']
})
export class ChartComponent implements OnInit {

private chart: any;


private data: Array<any> = [];
private svg: any;
private places: Array<string>;

private barWidth: number = 30;


private margin: number = 20;
private xAxisWidth: number;
private yAxisHeight: number;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


private xScale: any;
private yScale: any;
private colors: Array<string>
= ['red', 'blue', 'yellow', 'green'];

// Métodos de la clase: se presentan a continuación

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

constructor(private dataService: DataService) {}


.d
w

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

El método displaySalesOfAPlace() recupera el nombre del lugar seleccionado por el


usuario en la lista desplegable y llama a la función getSalesDataOfaPlace() del servicio.
Los datos devueltos por el servicio se almacenan en la lista data. A continuación, se llaman a los

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


métodos createAxis() y createBars(), para crear el histograma.

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

productos) y el eje de ordenadas (los productos).


w

Para crear un eje, es necesario inicialmente definir:

El domain, es decir los valores que se muestran a lo largo del eje.


El range, es decir el intervalo de valores sobre el que se muestran los valores anteriores.

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).

createAxis(nbProducts: number, salesMax: number) {


this.xAxisWidth = (this.barWidth + this.margin)*nbProducts;
this.yAxisHeight = salesMax + this.margin;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


let xDomain = this.data.map(d => d[0]);
this.xScale = d3.scaleBand()
.domain(xDomain).range([0, this.xAxisWidth]);
let yDomain = [0, salesMax];
this.yScale = d3.scaleLinear().domain(yDomain)
.range([this.yAxisHeight, 0]);

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

.attr('x', (d, i) => this.margin*1.5


e

+ i*(this.margin+this.barWidth))
.d

.attr('y', d => this.yAxisHeight - d[1] )


w

.attr('width', this.barWidth)
w

.attr('height', d => d[1])


w

.style('fill', (d, i) => this.colors[i%this.colors.length]);


}

Y a continuación se muestra el resultado después de la selección de place1, en la lista desplegable:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


org
n.
io
ac
7. Las librerías dc.js y Crossfilter
m
ra
og

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

función barchart() que crea los histogramas).


.d
w

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.

A continuación se muestra la organización del código principal de esta aplicación:

CHARTwithDC/dataServer.js: servidor de datos Node.js (no presentado).


CHARTwithDC/src/app/app.service.ts: servicio Angular para acceder a los servicios REST
gestionados por Node.js (no presentado).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


CHARTwithDC/src/app/app.component.html: plantilla del componente host que alberga el
componente <app-chart ...></app-chart> (no presentado).
CHARTwithDC/src/app/app.component.ts: clase del componente principal (no presentado).
CHARTwithDC/src/app/chart/chart.component.html: plantilla del componente que crea el
histograma (no presentado).
CHARTwithDC/src/app/chart/chart.component.ts: componente que crea el histograma con
dc.js y Crossfilter.
CHARTwithDC/src/app/app.module.ts: módulo del proyecto (no presentado).

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

La instalación de dc.js implica automáticamente la instalación de una versión compatible de D3.js y


pr

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

b. Implementación del componente que muestra el histograma


w
w

A continuación se muestra la implementación del componente que genera el histograma. Observe la


w

gran simplicidad del código comparativamente con el producido con D3.js.

import * as d3 from 'd3';


import crossfilter from 'crossfilter2';
import { ChartService } from '../chart.service';

@Component({
selector: 'app-chart',
templateUrl: 'chart.component.html',
styleUrls: ['chart.component.css']
})
export class ChartComponent implements OnInit {
private places: Array<string>;
private chart: any;

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


constructor(private chartService: ChartService) {}

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

let productDimension = ndx.dimension(function(d) {


og

return d.name;});
pr

let sumGroup = productDimension.group()


do

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


ndx.dimension() crea una «vista» sobre los datos a partir del nombre de los productos (dicho
esto, el modelo de datos es simplista, esta operación no representa completamente la potencia de
Crossfilter).

<nombreDelaDimension.group().reduceSum() genera los datos cuantitativos a


explotar, a partir de la dimensión elegida (mismo observación que antes).

chart.barPadding() define el espacio entre las barras en función de cada barra.

Chart.render() genera el gráfico a partir de la configuración elegida.


A continuación se muestra el resultado después de la selección de place1 en la lista desplegable:

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:54:53 p. m.]


Integración de mapas Google Map en un
proyecto Angular
Veamos cómo utilizar el componente presentado en la sección anterior, seleccionando los nombres de
los lugares a partir de los que los productos se han solicitado, no más en una lista desplegable, sino
directamente en una Google Map. Vamos a explicar cómo hacer la interfaz entre un mapa Google

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

(un layer) situada encima del mapa subyacente.


e to
.d

1. Instalación de los requisitos previos técnicos


w
w
w

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).

a. Instalación de los tipos TypeScript


La integración de Google Maps en un proyecto Angular requiere la instalación de tipos
complementarios para TypeScript.

A continuación se muestra el procedimiento a seguir:



 Si todavía no se ha hecho, instale la aplicación typings que gestiona el tipado de TypeScript:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


npm install typings --global

 Utilice la aplicación typings para instalar los tipos relativos para la utilización de Google Maps:

typings install dt~google.maps --global --save

Esto crea el archivo typings.json:

{
"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

b. Instalación de la librería PrimeNG


w
w

PrimeNG es una librería de componentes Angular de visualización de datos. En el momento en que


w

este libro se escribe, incluye más de 70 componentes open source.

Para utilizar esta librería, instale los paquetes que se corresponden con npm:

npm install --save primeng @angular/cdk font-awesome

 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

&callback=initMap" async defer></script>


og

<script type="text/javascript">
pr

function initMap() {
do

var latlng = new google.maps.LatLng(40.4167047, -3.7035825);


to

var myOptions = { zoom: 15,


e
.d

center: latlng,
w

mapTypeId:
w

google.maps.MapTypeId.ROADMAP
w

};
var map
= new google.maps.Map(documento
.getElementById("map_canvas"),
myOptions);

var marker = new google.maps


.Marker({ position: latlng,
map: map,
title: "Madrid",
draggable: true
});
}
</script>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


</head>
<body>
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>

El código es bastante claro. Solo se usan tres métodos del API:

El método google.maps.Map() crea un mapa asociado a un <div> HTML.


El método google.maps.LatLng() crea un objeto que especifica una
coordenada geográfica (latitud y longitud).
El método google.maps.Marker() crea un marcador.

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

3. Un componente PrimeNG para gestionar un mapa Google


Map
Va a modificar el servidor Node.js de prueba añadiendo información sobre la latitud y la longitud de
los lugares en los que se producen los pedidos.

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.

A continuación se muestra la estructura del código de la aplicación de prueba:

GOOGLEMAP/dataServerForGMap.js: servidor Node.js.


GOOGLEMAP/src/app/app.service.ts: servicio Angular para acceder a los servicios REST

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


gestionados por Node.js.
GOOGLEMAP/src/app/app.component.html: plantilla del componente host que alberga el
componente gmap de PrimeNG.
GOOGLEMAP/src/app/app.component.ts: clase del componente host que configura el
componente gmap de PrimeNG.
GOOGLEMAP/src/app/app.module.ts: root module del proyecto (no presentado).

A continuación se muestra el código de prueba de su servidor (archivo dataServerForGMap.js).

(Por supuesto, este servidor debería recuperar sus datos a partir de una base de datos.)

"use strict";

let express = require("express");

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

"sales": [{"name": "product1",


"nb": 50},
to

...
e
.d

]
w

},
w

...
w

];

app.get("/placeInfos", function(req, res){


let placeInfos = []
for (let datum of data) {
let nbProducts = 0;
for (let sales of datum.sales) { nbProducts += sales.nb; }
placeInfos.push([datum.place, datum.lat,
datum.lng, nbProducts]);
}
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(placeInfos));
});

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


Este servidor se lanza con el siguiente comando:

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):

import { Injectable } from '@angular/core';


import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/';

@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

A continuación se muestra el código de la plantilla del componente AppComponent host (archivo


to

app.component.html).
e
.d

El componente gmap de PrimeNG admite como argumentos el posicionamiento en mapa y los


w
w

overlays de anotaciones.
w

<p-gmap *ngIf="options !== undefined"


[options]="options"
[overlays]="overlays"
[style]="{'width':'100%','height':'500px'}"
(onMapClick)="handleMapClick($event)"
(onOverlayClick)="handleOverlayClick($event)">
</p-gmap>

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

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


handleMapClick() que reacciona a un clic en el mapa, no se utiliza en este ejemplo).

import { Component } from '@angular/core';


import { DataService } from './data.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public overlays: any[];
public options: any;

constructor(private dataService: DataService) {}

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

for (let infos of res) {


pr

let latLng = new google.maps


do

.LatLng(infos[1], infos[2]);
to

this.overlays
e

.push(new google.maps
.d

.Marker({title: infos[0], position: latLng}));


w

this.overlays
w
w

.push(new google.maps.Circle({name: infos[0],


center: latLng,
fillColor:'#1976D2',
fillOpacity: 0.35,
radius: infos[3]*10}));
}
});
}

handleMapClick(event) {}

handleOverlayClick(event) {
console.log("Clic sur "+event.overlay.name);
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


A continuación se muestra el mapa Google Map decorado con los overlays que representan la
importancia de las ventas.

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

presentan aquí las diferencias de código.


pr
do

A continuación se muestra la organización del código principal de la aplicación de prueba:


to

GOOGLEMAP_CHART/dataServerForGMapAndChart.js: servidor Node.js.


e
.d

GOOGLEMAP_CHART/src/app/app.service.ts: servicio Angular para acceder a los servicios


w

REST gestionados por Node.js.


w
w

GOOGLEMAP_CHART/src/app/app.component.html: plantilla del componente host que


alberga al componente gmap de PrimeNG.
GOOGLEMAP_CHART/src/app/app.component.ts: clase del componente host que configura
al componente gmap de PrimeNG.
GOOGLEMAP_CHART/src/app/chart/chart.component.ts: componente que crea el
histograma con D3.js.
GOOGLEMAP_CHART/src/app/app.module.ts: root module del proyecto (no presentado).

Su servidor (archivo dataServerForGMap.js) contiene los dos servicios REST siguientes:

En la ruta /place=:place, el servicio que devuelve los datos de las ventas relativas a un lugar
dado como argumento.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


En la ruta /placeInfos, el servicio que devuelve las ventas acumuladas para cada lugar.

A continuación se muestra su código:

"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

let place = req.params.place;


og

let sales = [];


pr

for (let datum of data) {


do

if (datum.place == place) {
sales = datum.sales;
e to

break;
.d

}
w

}
w

res.setHeader('Access-Control-Allow-Origin', '*');
w

res.setHeader('Content-Type', 'application/json; charset=utf-8');


res.end(JSON.stringify(sales));
});

app.get("/placeInfos", function(req, res){


let placeInfos = []
for (let datum of data) {
let nbProducts = 0;
for (let sales of datum.sales) {
nbProducts += sales.nb;
}
placeInfos.push([datum.place, datum.lat, datum.lng,

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


nbProducts]);
}
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(placeInfos));
});

Este servidor se lanza con el siguiente comando:

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

constructor(private http: HttpClient) {}


pr
do

getSalesDataOfaPlace(place: string): Observable<any> {


to

let url: string = "http://localhost:8889/place="+place;


e

return this.http.get(url);
.d

}
w
w

getPlaceInfos(): Observable<any> {
w

let url: string = "http://localhost:8889/placeInfos";


return this.http.get(url);
}
}

A continuación se muestra el código de la plantilla del componente AppComponent host. El


componente gmap de PrimeNG admite como argumentos el posicionamiento del mapa y los
overlays de anotaciones. El componente gráfico recibe como argumento el atributo place.

<p-gmap *ngIf="options !== undefined"


[options]="options"
[overlays]="overlays"
[style]="{'width':'100%','height':'500px'}"

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


(onMapClick)="handleMapClick($event)"
(onOverlayClick)="handleOverlayClick($event)">
</p-gmap>

<app-chart [place]="place"></app-chart>

A continuación se muestra el código del componente AppComponent host. Respecto al código


anterior, se crea una variable place e instancia con el nombre del overlay seleccionado en el
Google Map.

import { Component } from '@angular/core';


import { DataService } from './data.service';

@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

public options: any;


og

public place: string;


pr
do

constructor(private dataService: DataService) {}


to

...
e
.d

handleOverlayClick(event) {
w

this.place = event.overlay.name;
w
w

}
}

Para terminar, examinemos las evoluciones relativas al código del componente


ChartComponent que muestra el histograma.

El método displaySalesOfAPlace() ya no se llama durante la selección de uno de los


lugares presentados en una lista desplegable, sino cuando cambia el valor del atributo place.

...
@Input() place: string;

constructor(private chartService: ChartService) {}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


ngOnInit() {
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%").attr("height", 500);
this.chart = this.svg.append("g");
}
ngOnChanges(changes: SimpleChanges}) {
this.displaySalesOfAPlace(changes['place'].currentValue);
}
...

El internauta pulsa en el círculo que representa el volumen de pedidos realizados en la ciudad de


Madrid. El histograma que representa las estadísticas de venta, aparece:

org
n.
io
ac
m
ra
og
pr
do
e to
.d
w
w
w

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:08 p. m.]


Implementación de componentes gráficos
1. Otro ejemplo de componente PrimeNG (Calendar)
En la librería de componentes gráficos PrimeNG que hemos descubierto en la sección anterior, figura
el componente Calendar, que permite seleccionar una fecha a través de un calendario.

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

<p-nombreDelComponente ...></p- nombreDelComponente>


do
to

(La clase que implementa el componente host, puede por supuesto especificar los valores de las
e

directivas de atributos necesarias para la configuración del componente.)


.d
w

A continuación se muestra la lista del código principal de la aplicación de prueba:


w
w

CALENDAR/src/app/app.component.html: plantilla del componente host que integra el


componente Calendar de PrimeNG.
CALENDAR/src/app/app.component.ts: clase del componente host que permite recuperar la
fecha seleccionada.
CALENDAR/src/app/app.module.ts: root module del proyecto (no presentado).

A continuación se muestra el código de la plantilla del componente host AppComponent (archivo


app.component.html). La variable date contiene el valor de la fecha seleccionada a través del
calendario. Este valor se pasa a un método de la clase del componente host, para mostrarla en la
consola del navegador.

<p-calendar [(ngModel)]="date"

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


[showIcon]="true"><
(onSelect)= "onChange(date)">
</p-calendar>
<h3> A continuación se muestra la date seleccionada {{date}} </h3>

A continuación se muestra el código del componente host AppComponent (archivo


app.component.ts), que solo importa el módulo necesario para la gestión del calendario, definir las
hojas de estilo necesarias para PrimeNG y un método que recibe la nueva fecha seleccionada:

import { Component } from '@angular/core';


import {CalendarModule} from 'primeng/primeng';

@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

export class AppComponent {


pr

public onChange(date: string) {


do

console.log("Cambio de la fecha: "+date);


to

}
e

}
.d
w
w
w

2. La librería de componentes Material

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


La librería de componentes gráficos Material, es una librería que ofrece componentes de utilidades
(GUI components) para los frameworks JavaScript. Se ha creado una variante para Angular (así como
una variante llamada Material2).

Esta librería recubre las siguientes temáticas:

Elementos de formularios (controles).


Botones e indicadores.
Componentes de estructuración (barras de navegación, menús, tablas, etc.).
Pop-ups (alertas, elementos de notificaciones, etc.).

Vamos a descubrirla implementando el componente Datepicker, que permite, como en el


ejemplo anterior, seleccionar una fecha a través de un calendario.

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

DATEPICKER/src/app/app.component.html: plantilla del componente host que integra el


pr

componente datepicker de Material.


do

DATEPICKER/src/app/app.component.ts: clase del componente host que permite recuperar la


to

fecha seleccionada.
e
.d

DATEPICKER/src/app/app.module.ts: root module del proyecto (no presentado).


w
w

A continuación se muestra el código de la plantilla del componente host AppComponent (archivo


w

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

Por lo tanto, el datepicker se integra gracias a la etiqueta <mat-datepicker>:

<mat-form-field>
<input matInput [matDatepicker]="picker"
placeholder="Elija una fecha"
onSelect)="onChange($event.value)">
<mat-datepicker-toggle matSuffix [for]="picker">
</mat-datepicker-toggle>

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


<mat-datepicker #picker></mat-datepicker>
</mat-form-field>

La etiqueta <mat-datepicker> se corresponde con el calendario. La selección de una fecha


automáticamente va a volver a copiar esta fecha (en forma de cadena de caracteres), en la zona de
texto (<input>), que va a enviarla a un método de la clase con el atributo
(onSelect)="onChange($event.value)".

A continuación se muestra el código del componente host AppComponent (archivo


app.component.ts), que solo importa los módulos necesarios para la gestión del datepicker y definir el
método que recibe la nueva fecha selecciona:

import { Component } from '@angular/core';

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

export class AppComponent {


e to

public onChange(date: string) {


.d

console.log("Nuevo valor de la fecha: "+date);


w
w

}
w

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


org
n.
3. La librería ngx-bootstrap io
ac
m
ra

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

Esta librería necesita la instalación del siguiente módulo:


w

npm install ngx-bootstrap --save

Nuestra aplicación de prueba va a permitir presentar varios enlaces que invocan a diferentes
componentes a través del router.

A continuación se muestra la lista de su código principal:

NAVBAR/src/app/app.component.html: plantilla del componente de más alto nivel que integra


el componente Menu, que crea la barra de navegación.
NAVBAR/src/app/app.component.ts: clase del componente de más alto nivel.
NAVBAR/src/app/menu/menu.component.html: plantilla del componente que gestiona la
barra de navegación.

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


NAVBAR/src/app/menu/menu.component.ts: clase del componente que gestiona la barra de
navegación.
NAVBAR/src/app/app.module.ts: especificación del root module del proyecto.
NAVBAR/src/app/app-routing.module.ts: especificación de la tabla de enrutado.
El código de los dos componentes que se pueden invocar por el router,
NAVBAR/src/app/component1/component1.component.ts y
NAVBAR/src/app/component1/component1.component.ts, no se presentarán aquí (muestran
solo un mensaje).

A continuación se muestra el código de la plantilla del componente de más alto nivel


AppComponent (archivo app.component.html). Observe que:

La integración del componente MenuComponent, a través de su selector.

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

import { Component } from '@angular/core';


@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {}

A continuación se muestra el código de la plantilla del componente MenuComponent, que


gestiona la barra de navegación (archivo menu/menu.component.html). Los enlaces se podrían
localizar en diferentes listas (etiquetas, <ul>) y de esta manera, condicionar a una acción anterior

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


(como se sugiere en los comentarios).

<nav class="navbar navbar-dark bg-dark navbar-fixed-top navbar-expand">


<div class="container-fluid">
<ul class="navbar-nav"> <!-- *ngIf="..." -->
<li class="nav-item">
<a class="nav-link active" routerLink="/members/connection">
Log in </a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/component1">
Component1
</a>
</li>
<li class="nav-item">

rg
<a class="nav-link" routerLink="/component2">

o
Component2

n.
</a>

io
</li> ac
</ul>
m
ra

<!-- ul *ngIf="..." class="navbar-nav">


og

...
pr

</ul-->
do
to

</div>
e

</nav>
.d
w
w

A continuación se muestra el código del componente MenuComponent, que gestiona la barra de


w

navegación (archivo menu/menu.component.ts):

import { Component, OnInit } from '@angular/core';


import { Router } from '@angular/router';

@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.css']
})
export class MenuComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


A continuación se muestra el código de la especificación del root module del proyecto (archivo
app.module.ts).

import { BrowserModule } from '@angular/platform-browser';


import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { Component1Component } from './component1/component1.component'


import { Component2Component } from './component2/component2.component'
import { MenuComponent } from './menu/menu.component'

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

export class AppModule {}

A continuación se muestra el código de la especificación de la tabla de enrutado (archivo app-


routing.module.ts):

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';
import { Component1Component } from './component1/component1.component';
import { Component2Component } from './component2/component2.component';

const routes Routes = [


{ path: 'component1',
component: Component1Component

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


},
{ path: 'component2',
component: Component2Component
}
];

@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.

La selección del enlace "Component1", provoca la invocación del componente:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]


https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:22 p. m.]
Conocimientos adquiridos en este capítulo
La visualización de información es un dominio en el que una aplicación Angular incluida en una
arquitectura MEAN, tiene todo el sentido. Angular se adapta particularmente bien para realizar
cuadros de mando (los dashboards), que permiten visualizar a través de gráficas, la evolución de flujo
de datos asíncronos. Los datos geolocalizados, también se pueden publicitar por medio de la

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,
quees 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).

A través de un segundo proyecto llamado GOOGLEMAP_CHART, hemos combinado la

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:35 p. m.]


visualización de un Google Map, con la visualización de histogramas: la selección de un overlay que
representa el número de pedidos de una ciudad que invoca el componente que muestra el histograma.
Angular ofrece la posibilidad de utilizar numerosos componentes gráficos, principalmente con las
librerías PrimeNG y Material. Se ha ilustrado la generalización de su uso, implementando dos
componentes que muestran un calendario que permite seleccionar una fecha (proyectos CALENDAR
y DATEPICKER), así como la utilización de ngx-bootstrap para crear una barra de navegación
(proyecto NAVBAR).

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:35 p. m.]


Test
La fase de pruebas de una aplicación, es un ejercicio completo adicional, que hace necesario la
movilización generalmente de informáticos especializados en este dominio de competencia. En esta
sección solo abordaremos algunos conceptos básicos.

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

El framework Jasmine permite especificar las pruebas unitarias.


pr
do

La herramienta Karma permite ejecutar las pruebas unitarias especificadas con Jasmine.
to

La herramienta Protractor simula el comportamiento de un usuario, mientras que la aplicación


e

Angular realmente se ejecuta (la carpeta e2e se destina a la utilización de esta herramienta, que
.d

no se abordará en este libro).


w
w

A continuación se muestra el esquema de programación de una prueba unitaria «minimalista»


w

especificada con Jasmine:

it('<Descripción de la prueba>', async(() => {


const fixture = TestBed.createComponent(<nombre del componente>);
...
const app = fixture.debugElement.componentInstance;
const compiled = fixture.debugElement.nativeElement;
...
expect(<criterio a probar>).<método de prueba>;
}));

El método TestBed.createComponent() crea una instancia específica del componente a


probar y se devuelve un objeto que permite acce esta instancia (componentInstance), así

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:48 p. m.]


como al elemento del DOM que ha podido crear (nativeElement).

A continuación, es suficiente con especificar el atributo del componente o el sub-elemento del DOM
a probar, así como el valor esperado.

El método de prueba se puede elegir entre numerosos «matchers». A continuación se muestran


algunos:

toBeFalse(): el valor debe ser un booleano de valor false.


toBeTrue(): el valor debe ser un booleano de valor true.
toBeFalsy(): el valor debe ser indefinido.
toBeTruthy(): el valor debe ser definido.
toBeLessThan(<valor de referencia>): el valor debe ser inferior al valor

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

toContain(<valor de referencia>): el valor debe contener el valor de


pr

referencia.
do

Durante la creación de una nueva aplicación con el comando ng


to

new<nombreDelProyecto>, Angular CLI crea automáticamente el archivo


e
.d

app.component.spec.ts siguiente (acompaña al archivo app.component.ts que codifica el componente


w

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):

import { TestBed, async } from '@angular/core/testing';


import { AppComponent } from './app.component';

describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:48 p. m.]


// pruebas unitarias
...
});

A continuación se muestra una primera prueba unitaria sobre la creación del componente:

it('should create the app',


async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));

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

const app = fixture.debugElement.componentInstance;


og

expect(app.title).toEqual('INTRODUCTION');
pr

}));
do
to

A continuación se muestra una tercera prueba unitaria sobre el contenido de la plantilla:


e
.d

it('should render title in a h1 tag',


w
w

async(() => {
w

const fixture = TestBed.createComponent(AppComponent);


fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent)
.toContain('INTRODUCTION');
}));

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:

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:48 p. m.]


https://dogramcode.com/programacion
https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:55:48 p. m.]
Despliegue
Ha creado una aplicación Angular con Angular CLI, que está lista para la producción, y ahora quiere
desplegarla.

(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

especificar la nueva raíz de su aplicación.


og

Enejemplos de despliegue en Apache, Node.js y http-server que siguen, tomamos como ejemplo
los
pr

la puesta en producción de la aplicación de e-commerce y nos situamos de un servidor Apache,


do

ejecutado en un servidor Linux/Ubuntu.


e to

Para simplificar los ejemplos que siguen, consideremos que el servidor físico de desarrollo es el
.d

mismo que el de producción (pero por supuesto, deberían ser diferentes).


w
w
w

1. Despliegue con Apache


Quiere albergar la aplicación de e-commerce en una carpeta llamada OnLineSales, gestionada por su
servidor Apache.

 Construya la versión de producción de su aplicación. El servidor Apache puede servir a varias


aplicaciones, asocie la raíz de la aplicación a una carpeta llamada OnLineSales, a través de la
opción --bh:

ng build --prod --bh /OnLineSales/

 Renombre la carpeta dist por OnLineSales y vuelva a copiarla en el DocumentRoot de

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:56:01 p. m.]


Apache:

mv dist OnLineSales
sudo cp -R OnLineSales/ /var/www/html

 Y ahora, invoque su aplicación usando su navegador preferido:

<direccionIP:port>/OnLineSales/index.html

2. Despliegue con Node.js


También puede implementar su aplicación gracias a un servidor Node.js.

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

Construya la versión de producción de su aplicación.


og


pr

ng build --prod
do
to

 Renombre la carpeta dist por OnLineSales, y vuelva a copiarla en la carpeta de


e
.d

despliegue:
w
w

mv dist OnLineSales
w

cp -R OnLineSales/ <carpeta de despliegue>

 Codifique su servidor (<carpeta de despliegue>/OnLineSalesServer.js):

var express = require('express');


var app = express();

app.use(express.static(__dirname + '/OnLineSales'));
pp.listen(8880);

 Ejecute el servidor:

node <carpeta de despliegue>/OnLineSalesServer.js

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:56:01 p. m.]


 Y ahora, invoque a su aplicación gracias a su navegador preferido:

<direcciónIP:8880>

3. Despliegue con http-server (acceso directo a Node.js)


http-server es un script JavaScript ejecutado por Node.js, que permite lanzar un servidor HTTP. Una
única línea de comandos permite la ejecución de este script (con numerosas opciones).

Este servidor se instala con npm:

npm install http-server -g

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

--robots: responde negativamente a los robots.


pr

 Construya la versión de producción de su aplicación.


do
to

ng build --prod
e
.d
w

 Renombre la carpeta dist por OnLineSales, y vuelva a copiarla en la carpeta de


w

despliegue:
w

mv dist OnLineSales
cp -R OnLineSales/ <carpeta de despliegue>

 Ejecute el servidor en la carpeta que contiene su aplicación:

http-server <carpeta de despliegue>/OnLineSales

 E invoque a su aplicación de la siguiente manera (por defecto, http-server ocupa el puerto 8080):

<direcciónIP:port>/index.html

https://www.eni-training.com/portal/client/mediabook/home[9/05/2022 02:56:01 p. m.]


Para ir más lejos
Gracias a este libro, esperamos haberle convencido de la pertinencia de una arquitectura MEAN, que
asocia Angular con Node.js y MongoDB para crear aplicaciones mono página eficaces. Angular le
permite crear código perfectamente modular y le garantiza por lo tanto, costes de desarrollo
optimizados. Node.js funcionando como una pareja con MongoDB, garantiza una reactividad

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

construcción de aplicaciones Angular.


e to

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

datos dentro de su navegador, gracias a las técnicas de Map/Reduce.


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.

El amplio mundo de Angular no para de sorprendernos.

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

Material para los amantes de la


Programación Java,
C/C++/C#,Visual.Net, SQL,
Python, Javascript, Oracle,
Algoritmos, CSS, Desarrollo
Web, Joomla, jquery, Ajax y
Mucho Mas…

https://dogramcode.com/programacion

También podría gustarte