Sie sind auf Seite 1von 100

Capítulo 5 ■ JavaScript y mecanografiado: parte 1

Tabla 5-4. Útil Número-a-String Métodos

Método Descripción

Encadenar() Este método devuelve una cadena que representa un número en base 10.

toString (2) toString (8) toString Este método devuelve una cadena que representa un número en binario, octal
(16) o hexadecimal.

toFixed (n) Este método devuelve una cadena que representa un número real con el
norte dígitos después del punto decimal.

toExponential (n) Este método devuelve una cadena que representa un número usando la notación
exponencial con un dígito antes del punto decimal y
norte dígitos después.

toPrecision (n) Este método devuelve una cadena que representa un número con norte
cifras significativas, usando la notación exponencial, si es necesario.

La conversión de cadenas en números

La técnica complementaria es la de convertir cadenas en números para que pueda realizar la suma en lugar de la concatenación. Puede hacer esto
con la Número función, como se muestra en el Listado 5-26 .

Listado 5-26. La conversión de cadenas a números en el archivo main.ts en la carpeta src

dejar que firstVal = "5"; dejar


que secondVal = "5";

dejar resultado = Número (firstVal) + Número (secondVal); console.log (


"Resultado:" + resultado);

La salida de este script es el siguiente:

Resultado: 10

los Número función es estricta en el modo en que analiza los valores de cadena, pero hay otras dos funciones que puede utilizar que son
más flexibles y no hará caso de fuga caracteres no numéricos. Estas funciones son parseInt
y parseFloat. He descrito los tres métodos de la Tabla 5-5 .

Tabla 5-5. Cadena Útil para Métodos Número

Método Descripción

Número (str) Este método analiza la cadena especificada para crear un valor entero o real.

parseInt (str) Este método analiza la cadena especificada para crear un valor entero.

parseFloat (str) Este método analiza la cadena especificada para crear un valor entero o real.

80
Capítulo 5 ■ JavaScript y mecanografiado: parte 1

Trabajar con matrices


matrices de JavaScript funcionan como matrices en la mayoría de los otros lenguajes de programación. Listado 5-27 muestra cómo se puede crear y rellenar una

matriz.

Listado 5-27. Crear y llenar una matriz en el archivo main.ts en la carpeta src

dejar que myArray = new Array ();


myArray [0] = 100; myArray [1] =
"Adam"; myArray [2] = true;

He creado una nueva matriz llamando new Array (). Esto crea una matriz vacía, lo que le asigno a la variable miMatriz. En los
estados posteriores, I asignar valores a varias posiciones de índice en el array. (No hay salida de la consola de este listado.)

Hay un par de cosas para observar en este ejemplo. En primer lugar, yo no tenía necesidad de declarar el número de elementos en la matriz cuando lo creé.

matrices de JavaScript cambiarán de tamaño sí mismos para contener cualquier número de elementos. El segundo punto es que yo no tenía que declarar los tipos de datos

que contendrá la matriz. Cualquier matriz de JavaScript puede contener cualquier combinación de tipos de datos. En el ejemplo, he asignado tres elementos a la matriz:

una número, una cuerda, y una

booleano.

El uso de un literal de matriz

El estilo literal de Array le permite crear y rellenar una matriz en un solo estado, como se muestra en el Listado 5-28 .

Listado 5-28. Utilizando el literal de matriz estilo en el main.ts archivo en la carpeta src

dejar que myArray = [100, "Adán", verdadero];

En este ejemplo, he especificado que la miMatriz variable debe ser asignado una nueva matriz mediante la especificación de los elementos
que quería en la matriz entre corchetes ([y]). (No hay salida de la consola de este listado.)

Leer y modificar el contenido de una matriz


Se lee el valor de un índice determinado, utilizando corchetes ([y]), colocando el índice necesita entre las llaves, como se muestra en el Listado 5-29
.

Listado 5-29. La lectura de los datos de un índice de matriz en los main.ts archivo en la carpeta src

dejar que myArray = [100, "Adán", verdadero]; console.log


( "Índice de 0:" + myArray [0]);

Puede modificar los datos contenidos en cualquier posición en una matriz de JavaScript simplemente mediante la asignación de un nuevo valor para el índice.
Al igual que con las variables regulares, puede cambiar el tipo de datos en un índice sin ningún problema. La salida de la lista es la siguiente:

El índice 0: 100

81
Capítulo 5 ■ JavaScript y mecanografiado: parte 1

Listado 5-30 demuestra modificar el contenido de una matriz.

Listado 5-30. Modificación del contenido de una matriz en el archivo main.ts en la carpeta src

dejar que myArray = [100, "Adán", verdadero];


myArray [0] = "Martes";
console.log ( "Índice de 0:" + myArray [0]);

En este ejemplo, he asignado una cuerda posicionar 0 en la matriz, una posición que se llevó a cabo anteriormente por una número y produce
esta salida:

El índice 0: Martes

Enumerando el contenido de una matriz


Enumerar el contenido de una matriz mediante una para bucle o el uso de la para cada método, que recibe una función que se llama para
procesar cada elemento de la matriz. Ambos enfoques se muestran en el Listado 5-31 .

Listado 5-31. Enumerando el contenido de una matriz en el archivo main.ts en la carpeta src

dejar que myArray = [100, "Adán", verdadero];

para (dejo i = 0; i <myArray.length; i ++) {


console.log ( "Índice" + i + ":" + myArray [i]); }

console.log ( "---");

myArray.forEach ((valor, índice) => console.log ( "Índice" + Índice + ":" + valor));

el JavaScript para bucle funciona de la misma manera que los bucles en muchos otros idiomas. A determinar cuántos elementos hay en la
matriz mediante el uso de la longitud propiedad.
La función pasa a la para cada método se da dos argumentos: el valor del elemento actual sea procesada y la posición de
ese elemento de la matriz. En este listado, he utilizado una función de la flecha como el argumento de la para cada método, que es
el tipo de uso para el que se destacan (y verá utilizado a lo largo de este libro). La salida de la lista es la siguiente:

El índice 0: 100
Índice 1: Índice de
Adam 2: true

El índice 0: 100
Índice 1: Índice de
Adam 2: true

82
Capítulo 5 ■ JavaScript y mecanografiado: parte 1

Uso del Spread


El operador propagación se utiliza para expandir una matriz de modo que sus contenidos pueden ser utilizados como argumentos de la función o en combinación con

otras matrices. en el Listado 5-32 , He usado el operador de difusión para ampliar una matriz de modo que sus elementos se pueden combinar en otra matriz.

Listado 5-32. Uso del spread en las main.ts archivo en la carpeta src

dejar que myArray = [100, "Adán", verdadero];


dejar que otherArray = [... miMatriz, 200, "Bob", falsa];

para (dejar que i = 0; i <otherArray.length; i ++) {console.log ( `elemento


del array $ {i}: $ {otherArray [i]} '); }

El operador propagación es una elipsis (una secuencia de tres periodos), y hace que la matriz para ser desempaquetado.

...
dejar que otherArray = [... miMatriz, 200, "Bob", false];
...

Usando el operador de difusión, soy capaz de especificar miMatriz como un elemento cuando defino otherArray, con el resultado de que el contenido
de la primera matriz se descomprime y se añaden como los artículos a la segunda matriz. Este ejemplo produce los siguientes resultados:

elemento de la matriz 0: 100

elemento de la matriz 1: Adam

elemento de la matriz 2: verdadero

elemento de la matriz 3: 200

elemento de la matriz 4: Bob

elemento de la matriz 5: false

Uso de los métodos de matriz incorporados

el JavaScript Formación objeto define una serie de métodos que se pueden utilizar para trabajar con matrices, los más útiles de los cuales se

describen en la Tabla 5-6 .

83
Capítulo 5 ■ JavaScript y mecanografiado: parte 1

Tabla 5-6. Métodos de matriz útiles

Método Descripción

concat (otherArray) Este método devuelve una nueva matriz que concatena la matriz en la que se le ha llamado con la
matriz especificada como argumento. Varias matrices se pueden especificar.

join (separador) Este método se une a todos los elementos de la matriz para formar una cadena. El argumento
especifica el carácter utilizado para delimitar los elementos.

popular() Este método elimina y devuelve el último elemento de la matriz.

cambio() Este método elimina y devuelve el primer elemento de la matriz.

push (elemento) Este método añade el elemento especificado al final de la matriz.

unshift (punto) Este método inserta un nuevo punto al comienzo de la matriz.

marcha atrás() Este método devuelve una nueva matriz que contiene los artículos en orden inverso.

rebanada (inicio, final) Este método devuelve una sección de la matriz.

ordenar() Este método clasifica la matriz. Una función de comparación opcional se puede utilizar para realizar
comparaciones personalizados.

empalme (índice, recuento) Este método elimina contar elementos de la matriz, a partir de la especificada
índice. Los elementos retirados se devuelven como el resultado del método.

unshift (punto) Este método inserta un nuevo punto al comienzo de la matriz.

cada (prueba) Este método llama al prueba función para cada elemento de la matriz y vuelve
cierto Si la función devuelve cierto para todos ellos, y en caso contrario.

algunos (de prueba) Este método devuelve cierto si llama la prueba función para cada elemento de los retornos de matriz cierto al
menos una vez.

filtro (test) Este método devuelve una nueva matriz que contiene los artículos para los que el prueba
devuelve la función cierto.

encontrar (prueba) Este método devuelve el primer elemento de la matriz para la cual el prueba
devuelve la función cierto.

FindIndex (test) Este método devuelve el índice del primer elemento de la matriz para la cual el
prueba devuelve la función cierto.

foreach (devolución de llamada) Este método invoca el llamar de vuelta función para cada elemento de la matriz, como se describe en la
sección anterior.

incluye (valor) Este método devuelve cierto Si la matriz contiene el valor especificado.

Mapa (devolución de llamada) Este método devuelve una nueva matriz que contiene el resultado de la invocación de la

llamar de vuelta la función de cada elemento de la matriz.

reducir (devolución de llamada) Este método devuelve el valor acumulado producido mediante la invocación de la función de devolución de

llamada para cada elemento de la matriz.

84
Capítulo 5 ■ JavaScript y mecanografiado: parte 1

Dado que muchos de los métodos de la Tabla 5-6 devolver una nueva matriz, estos métodos se pueden encadenar juntos para procesar una matriz de datos de

filtrado, como se muestra en el Listado 5-33 .

Listado 5-33. Procesamiento de una matriz de datos en el archivo main.ts en la carpeta src

deje productos = [
{Name: "sombrero", precio: 24,5, de almacén: 10}, {nombre: "Kayak",
precio: 289.99, existencia: 1}, {nombre: "Balón de fútbol", precio: 10,
existencia: 0}, { nombre: "zapatillas deportivas", precio: 116.50, Stock: 20}];

dejar que totalValue = Productos


. filtro (item => item.stock> 0)
. reducir ((prev, elemento) => prev + (item.price * item.stock), 0);

console.log ( "Valor total: $" + totalValue.toFixed (2));

Yo uso el filtrar Método para seleccionar los elementos de la matriz cuyos valores valor es mayor que cero y el uso de la reducir método para
determinar el valor total de esos artículos, produciendo el resultado siguiente:

Valor total: $ 2,864.99

Resumen
En este capítulo, he proporcionado un breve panorama sobre el código JavaScript, centrándose en la funcionalidad básica que le ayudará a comenzar con el
lenguaje. Algunas de las características que he descrito en este capítulo son las últimas adiciones a la especificación JavaScript y requieren el compilador de
transcripción de la convierte en el código que se puede ejecutar en los navegadores antiguos. Sigo este tema en el siguiente capítulo y presento algunas de las
características más avanzadas de JavaScript que se utilizan en el desarrollo angular.

85
CAPÍTULO 6

JavaScript y mecanografiado: Parte 2

En este capítulo, se describen algunas de las características más avanzadas de JavaScript que son útiles para el desarrollo angular. Explico cómo
ofertas de JavaScript con objetos, incluido el apoyo para las clases, y explico como la funcionalidad de JavaScript se envasa en módulos de
JavaScript. También les presento algunas de las características que ofrece mecanografiado que no forman parte de la especificación JavaScript y
que me baso en algunos de los ejemplos más adelante en el libro. Mesa 6-1 resume el capítulo.

Tabla 6-1. Resumen del capítulo

Problema Solución Listado

Crear un objeto especificando propiedades y valores Utilizar el nuevo Palabra clave o utilizar un objeto 1-3
literal

Crear un objeto utilizando una plantilla Definir una clase 4, 5

Heredar el comportamiento de otra clase Utilizar el se extiende palabra clave 6

El paquete cuenta con JavaScript juntos Crear un módulo de JavaScript 7

Declarar una dependencia de un módulo Utilizar el importar palabra clave 8-12

Declarar los tipos utilizados por las propiedades, parámetros y variables Uso mecanografiado anotaciones de tipo 13-18

Especificar varios tipos tipos de uso comunitario 19-21

Crear grupos ad hoc de tipos Use tuplas 22

Grupo valora en conjunto con la tecla Utilice los tipos de indexables 23

Control de acceso a los métodos y propiedades de una clase de uso de los modificadores de control de acceso 24

Preparación del Ejemplo de proyecto


Para este capítulo, continúo usando el JavaScriptPrimer proyecto del capítulo 5 . No se requieren cambios para prepararse para este capítulo,
y ejecutando el siguiente comando en el JavaScriptPrimer carpeta comenzará el compilador mecanografiado y el servidor HTTP de desarrollo:

ng servir --port 3000 --open

© 2018 Adam Freeman 87


A. Freeman, Pro angular 6, https://doi.org/10.1007/978-1-4842-3649-9_6
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Una nueva ventana del navegador se abrirá, pero será vacío porque me quita el contenido marcador de posición en el capítulo anterior. Los
ejemplos de este capítulo se basan en la consola JavaScript del navegador para mostrar los mensajes. Si nos fijamos en la consola, verá el siguiente
resultado:

Valor total: $ 2,864.99

Trabajo con objetos


Hay varias maneras de crear objetos en JavaScript. Listado 6-1 da un ejemplo simple para empezar.

■ Nota Algunos de los ejemplos de este capítulo que el compilador mecanografiado para informar errores. los ejemplos siguen trabajando, y se puede

ignorar estos mensajes, que surgen a causa mecanografiado ofrece algunas características adicionales que no describo hasta más adelante en este

capítulo.

Listado 6-1. Creación de un objeto en el Archivo main.ts en la carpeta src

dejar que myData = new Object ();


myData.name = "Adam";
myData.weather = "soleado";

console.log ( "Hola" + myData.name + ""); console.log ( "Hoy en día


es" + myData.weather + "");

Creo un objeto llamando new Object (), y asignar el resultado (el objeto recién creado) a una variable llamada mis datos. Una vez que se crea el
objeto, puedo definir las propiedades del objeto, simplemente mediante la asignación de valores, como este:

...
mis datos. name = " Adán";
...

Antes de esta declaración, mi objeto no tiene una propiedad llamada nombre. Cuando la declaración ha ejecutado, la propiedad
existe, y se le ha asignado el valor Adán. Puede leer el valor de una propiedad mediante la combinación del nombre de la variable y el
nombre de la propiedad con un punto, como esto:

...
console.log ( "Hola" + myData.name +) "";
...

El resultado de la lista es la siguiente:

Hola Adam. Hoy está


soleado.

88
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

El uso de objetos literales


Se puede definir un objeto y sus propiedades en un solo paso mediante el objeto literal formato, como se muestra en el Listado 6-2 .

Listado 6-2. Uso del objeto Formato literal en el main.ts archivo en la carpeta src

dejar que myData = {

nombre: "Adán",
tiempo: "soleado"};

console.log ( "Hola" + myData.name + ""); console.log ( "Hoy en día


es" + myData.weather + "");

Cada propiedad que desea definir se separa de su valor con dos puntos (:), y las propiedades se separan mediante una
coma (,). El efecto es el mismo que en el ejemplo anterior, y el resultado de la lista es la siguiente:

Hola Adam. Hoy está


soleado.

Uso de funciones como Métodos


Una de las características que más acerca de JavaScript me gusta es la forma en que puede agregar funciones a los objetos. Una función definida en un objeto
se llama una método. Listado 6-3 muestra cómo se puede añadir métodos de esta manera.

Listado 6-3. Adición de métodos para un objeto en el main.ts archivo en la carpeta src

dejar que myData = {

nombre: "Adán", tiempo:


"soleado",
printMessages: function () {
console.log ( "Hola" + this.name + ""); console.log ( "Hoy en
día es" + this.weather + ""); }

};
myData.printMessages ();

En este ejemplo, he utilizado una función para crear un método llamado printMessages. Observe que para referirse a las propiedades definidas
por el objeto, tengo que utilizar el esta palabra clave. Cuando una función se utiliza como método, la función se pasa implícitamente el objeto sobre el
cual el método ha sido llamado como un argumento a través de la variable especial esta. La salida de la lista es la siguiente:

Hola Adam. Hoy está


soleado.

89
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Definición de clases
Las clases son plantillas que se utilizan para crear objetos que tienen una funcionalidad idéntica. El apoyo a las clases es una adición reciente a
la especificación JavaScript destinada a facilitar el trabajo con JavaScript más coherente con otros lenguajes de programación convencionales, y
las clases se utiliza en todo el desarrollo angular. Listado 6-4 muestra cómo la funcionalidad definida por el objeto en la sección anterior se puede
expresar utilizando una clase.

Listado 6-4. La definición de una clase en el archivo main.ts en la carpeta src

Class MiClase {

constructor (nombre, tiempo) {


this.name = nombre;
this.weather = tiempo; }

printMessages () {
console.log ( "Hola" + this.name + ""); console.log ( "Hoy en día
es" + this.weather + ""); }}

dejar que myData = new MiClase ( "Adam", "sol");


myData.printMessages ();

clases de JavaScript será familiar si ha usado otro lenguaje corriente, como Java o C #. los clase palabra clave se utiliza para
declarar una clase, seguido del nombre de la clase, que es Mi clase en este caso.
los constructor función se invoca cuando se crea un nuevo objeto utilizando la clase, y ofrece la oportunidad de recibir los valores de
datos y realice una configuración inicial de que la clase requiere. En el ejemplo, el constructor define nombre y clima parámetros que se utilizan
para crear variables con los mismos nombres. Las variables definidas como esto se conocen como propiedades.

Las clases pueden tener métodos, que definen como funciones, aunque sin necesidad de utilizar la palabra clave función. Hay un
método en el ejemplo, se llama printMessages, y utiliza los valores de la name and
weather propiedades para escribir mensajes a la consola JavaScript del navegador.

■ Propina Las clases también pueden tener métodos estáticos, designados por el estático palabra clave. Los métodos estáticos pertenecen a la clase en

lugar de los objetos que crean. he incluido un ejemplo de un método estático en el Listado 6-14 .

los nuevo palabra clave se utiliza para crear un objeto de una clase, de esta manera:

...
dejar que myData = nuevo MiClase ( "Adam", "sol");
...

Esta sentencia crea un nuevo objeto con el Mi clase clase como su plantilla. Mi clase se utiliza como una función en esta situación, y los
argumentos que se le pasan serán recibidos por el constructor función definida por la clase. El resultado de esta expresión es un nuevo objeto
que se asigna a una variable llamada mis datos.
Una vez que haya creado un objeto, puede acceder a sus propiedades y métodos a través de la variable a la que se le ha asignado,
así:

90
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

...
myData. printMessages ();
...

Este ejemplo produce los siguientes resultados en la consola JavaScript del navegador:

Hola Adam. Hoy está


soleado.

CLASES VS PROTOTYPES JAVASCRIPT

el rasgo de clase no cambia la forma subyacente que maneja JavaScript tipos. En su lugar, simplemente proporciona una manera de utilizar
los que es más familiar para la mayoría de los programadores. Detrás de las escenas, JavaScript sigue utilizando su sistema de tipo
tradicional, que se basa en prototipos. como un ejemplo, el código del listado 6-4 También se puede escribir así:

var = MiClase función MiClase (nombre, tiempo) {


this.name = nombre;
this.weather = tiempo; }

MyClass.prototype.printMessages = function () {
console.log ( "Hola" + this.name + ""); console.log ( "Hoy en día
es" + this.weather + ""); };

var myData = new MiClase ( "Adam", "sol");


myData.printMessages ();

desarrollo angular es más fácil cuando el uso de clases, que es el enfoque que he tomado a lo largo de este libro. muchas de las

características introducidas en ES6 se clasifican como azúcar sintáctico, lo que significa que hacen aspectos de JavaScript más fáciles de

entender y utilizar. el termino azúcar sintáctica puede parecer peyorativo, pero JavaScript tiene algunas peculiaridades impares, y muchas de

estas características ayudará a los desarrolladores a evitar errores comunes.

La definición de clase Getter y Setter Propiedades

clases JavaScript pueden definir las propiedades en su constructor, lo que resulta en una variable que se puede leer y modificar otras partes de la
aplicación. Captadores y definidores aparecen propiedades como regulares fuera de la clase, pero permiten la introducción de una lógica adicional, que
es útil para la validación o la transformación de nuevos valores o generar valores de programación, como se muestra en el Listado 6-5 .

91
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Listado 6-5. El uso de captadores y definidores en los main.ts archivo en la carpeta src

Class MiClase {
constructor (nombre, tiempo) {
this.name = nombre;
this._weather = tiempo;
}

establecer el tiempo (valor) {

this._weather = valor; }

información del clima () {

retorno `Hoy es $ {}` this._weather; }

printMessages () {
console.log ( "Hola" + this.name + "");
console.log (this.weather);
}}

dejar que myData = new MiClase ( "Adam", "sol");


myData.printMessages ();

El getter y setter se implementan como funciones precedidas por el obtener o conjunto palabra clave. No existe la noción de control de acceso en
las clases de JavaScript, y la convención es como prefijo para los nombres de las propiedades internas con un guión bajo (_ el personaje). En el perfil,
la clima la propiedad se implementa con un colocador que actualiza una propiedad llamada _ clima y un captador que incorpora el _ clima valor en una
cadena de plantilla. Este ejemplo produce los siguientes resultados en la consola JavaScript del navegador:

Hola Adam. Hoy está


soleado

El uso de la clase de herencia

Las clases pueden heredar el comportamiento de otras clases utilizando el se extiende palabra clave, como se muestra en el Listado 6-6 .

Listado 6-6. Usando Clase herencia en el main.ts archivo en la carpeta src

Class MiClase {
constructor (nombre, tiempo) {
this.name = nombre;
this._weather = tiempo; }

establecer el tiempo (valor) {


this._weather = valor; }

92
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

información del clima () {

retorno `Hoy es $ {}` this._weather; }

printMessages () {
console.log ( "Hola" + this.name + ""); console.log
(this.weather); }}

clase MySubClass extiende MyClass {

constructor (nombre, el clima, la ciudad) {


super (nombre, tiempo);
this.city = ciudad; }

printMessages () {
super.printMessages ();
console.log ( `Usted está en $ {}` this.city); }}

dejar que myData = new MySubClass ( "Adam", "soleado", "Londres");


myData.printMessages ();

los se extiende palabra clave se utiliza para declarar la clase que será heredado de, conocido como el superclase
o clase base. En el perfil, la MySubClass hereda de Mi clase. los súper palabra clave se utiliza para invocar el constructor y los métodos de la
superclase. los MySubClass se basa en la Mi clase funcionalidad para añadir soporte para una ciudad, produciendo los siguientes resultados en la
consola JavaScript del navegador:

Hola Adam. Hoy en día es


soleado Usted está en
Londres

Trabajando con Módulos de JavaScript


módulos de JavaScript se utilizan para gestionar las dependencias en una aplicación web, lo que significa que no es necesario para gestionar un gran
conjunto de archivos de código individuales para asegurar que el navegador descarga todo el código de la aplicación. En cambio, durante el proceso de
compilación, todos los archivos JavaScript que requiera la aplicación se combinan en un archivo más grande, conocido como haz, y esto es lo que se
descarga por el navegador.

■ Nota Las versiones anteriores de angular se basó en un cargador de módulos, lo que enviaría solicitudes HTTP independiente para los archivos

JavaScript requeridos por una aplicación. Los cambios en las herramientas de desarrollo han simplificado este proceso y cambiar al uso de los paquetes

creados durante el proceso de construcción.

93
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Creación y uso de módulos


Cada archivo de transcripción o JavaScript que se agrega a un proyecto es tratado como un módulo. Para demostrarlo, he creado una carpeta llamada módulos en el src
carpeta, se añade a un archivo llamado NameAndWeather.ts, y se añade el código que se muestra en el Listado 6-7 .

Listado 6-7. El contenido de la NameAndWeather.ts archivo en la carpeta src / modules

Nombre de clase de exportación {

constructor (primero, segundo) {


this.first = primero;
this.second = segundos; }

obtener nameMessage () {
retorno `Hola $ {} $ this.first {}` this.second; }}

WeatherLocation clase de exportación {


constructor (si el tiempo, de la ciudad) {
this.weather = tiempo; this.city =
ciudad; }

obtener weatherMessage () {
volver `Es $ {} this.weather en $ {}` this.city; }}

Las clases, funciones y variables definidas en un fichero de transcripción JavaScript o sólo se puede acceder dentro de ese archivo por defecto.
los exportar palabra clave se utiliza para realizar funciones accesibles fuera del archivo para que puedan ser utilizados por otras partes de la aplicación.
En el ejemplo, he aplicado la exportar palabra clave para la
Nombre y WeatherLocation clases, lo que significa que están disponibles para ser utilizados fuera del módulo.

■ Propina he definido dos clases en el NameAndWeather.ts archivo, que tiene el efecto de crear un módulo que contiene dos clases. la
convención en aplicaciones angulares es poner cada clase en su propio archivo, lo que significa que cada clase se define en su propio

módulo y que verá el exportar palabra clave es los anuncios a lo largo de este libro.

los importar palabra clave se utiliza para declarar una dependencia de las características que ofrece un módulo. en el Listado 6-8 , He utilizado Nombre
y WeatherLocation clases en el main.ts Archivo y eso significa que tengo que usar el importar palabra clave para declarar una dependencia de ellos y el
módulo del que proceden.

Listado 6-8. Importación de tipos específicos en el archivo main.ts en la carpeta src

{Nombre importación, WeatherLocation} de "./modules/NameAndWeather";

dejar que name = Nombre nuevo ( "Adam", "Freeman");


dejar que loc = new WeatherLocation ( "llover", "Londres");

94
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

console.log (name.nameMessage);
console.log (loc.weatherMessage);

Esta es la forma en que utilizo el importar palabra clave en la mayoría de los ejemplos de este libro. La palabra clave es seguido por llaves que
contienen una lista separada por comas de las características que el código en los archivos actuales depende, seguido por el desde palabra clave, seguido
por el nombre del módulo. En este caso, he importado el
Nombre y WeatherLocation clases de la NameAndWeather en el módulo módulos carpeta. Observe que la extensión del archivo no se incluye
cuando se especifica el módulo.
Cuando los cambios en el main.ts archivos se guardan, las herramientas de desarrollo angular construir el proyecto y ver que el código en el main.ts
de archivo depende del código de la NameAndWeather.ts expediente. Esta dependencia asegura que la Nombre y WeatherLocation las clases se
incluyen en el archivo de paquete de JavaScript, y verá la siguiente salida en la consola de JavaScript del navegador, lo que demuestra que el código
en el módulo se utilizó para producir el resultado:

Hola Adam Freeman Está


lloviendo en Londres

Tenga en cuenta que yo no tenía para incluir el NaneAndWeather.ts archivo en una lista de archivos que se envía al navegador. Sólo mediante el importar
palabra clave es suficiente para declarar la dependencia y asegurar que el código requerido por la aplicación se incluye en el archivo JavaScript enviado al
navegador.
(Podrá ver los errores que le advierte de que las propiedades no se han definido haga caso de las advertencias por ahora;.. Me explico cómo
se resuelven más adelante en el capítulo)

RESOLUCIÓN módulo de comprensión del

verá dos maneras diferentes de la especificación de los módulos en el importar declaraciones de este libro. el primero es un módulo
relativo, en el que el nombre del módulo se prefija con ./, como en este ejemplo de la lista 6-8 :

...
{Nombre importación, WeatherLocation} de" ./ módulos / NameAndWeather ";
...

esta declaración especifica un módulo situado en relación con el archivo que contiene la importar declaración. en este caso, el NameAndWeather.ts archivo

está en el módulos directorio, que se encuentra en el mismo directorio que el

main.ts expediente. El otro tipo de importación es no relativa. aquí es un ejemplo de una importación que no es pariente del capítulo 2 y uno

que se pueden ver a lo largo de este libro:

...
{} la importación de componentes de "@ angular / núcleo ";
...

el módulo en este importar declaración no se inicia con ./, y las herramientas de construcción resolver la dependencia mediante la búsqueda de

un paquete en el node_modules carpeta. en este caso, la dependencia está en una característica proporcionada por el @ angular / núcleo paquete,

que se añade al proyecto cuando se crea por la ng nueva mando.

95
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Cambiar el nombre de las importaciones

En proyectos complejos que tienen un montón de dependencias, es posible que usted tendrá que utilizar dos clases con el mismo nombre de diferentes
módulos. Para volver a crear esta situación, he creado un archivo llamado DuplicateName.ts
en el src / modules carpeta y se define la clase de muestra en el listado 6-9 .

Listado 6-9. El contenido de la DuplicateName.ts archivo en la carpeta src / modules

Nombre de clase de exportación {

conocer el mensaje () {

volver "otro nombre"; }}

Esta clase no hace nada útil, pero se llama Nombre, lo que significa que la importación se aplique el método del inmueble 6-8 causará un
conflicto porque el compilador no será capaz de diferenciar entre las dos clases con ese nombre. La solución es utilizar la como palabra clave,
que permite un alias a ser creado para una clase cuando se importa desde un módulo, como se muestra en el listado 6-10 .

Listado 6-10. El uso de un alias de módulo en el main.ts archivo en la carpeta src

{Nombre importación, WeatherLocation} de "./modules/NameAndWeather";


importación {Nombre} otherName como de "./modules/DuplicateName";

dejar que name = Nombre nuevo ( "Adam", "Freeman");


dejar que loc = new WeatherLocation ( "llover", "Londres");
dejar que otra nueva otherName = ();

console.log (name.nameMessage);
console.log (loc.weatherMessage);
console.log (other.message);

los Nombre clase en el DupliateName módulo se importa como Otro nombre, lo que permite que sea utilizado sin entrar en conflicto con el Nombre
clase en el NameAndWeather módulo. En este ejemplo se produce el siguiente resultado:

Hola Adam Freeman Está


lloviendo en Londres Otro Nombre

La importación de todos los tipos en un módulo

Un enfoque alternativo es importar el módulo como un objeto que tiene propiedades para cada uno de los tipos que contiene, como se muestra en el
Listado 6-11 .

Listado 6-11. La importación de un módulo como un objeto en el archivo main.ts en la carpeta src

* importación como NameAndWeatherLocation de "./modules/NameAndWeather";


importación {Nombre} otherName como de "./modules/DuplicateName";

96
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

dejar que name = nueva NameAndWeatherLocation.Name ( "Adam", "Freeman"); dejar que loc = new
NameAndWeatherLocation.WeatherLocation ( "llover", "Londres");
dejar que otra nueva otherName = ();

console.log (name.nameMessage);
console.log (loc.weatherMessage);
console.log (other.message);

los importar declaración en este ejemplo se importa el contenido de la NameAndWeather módulo y crea un objeto llamado NameAndWeatherLoca
Este objeto tiene Nombre y Clima propiedades que corresponden a las clases definidas en el módulo. Este ejemplo produce la misma
salida que Listado 6-10 .

Características mecanografiado útiles


Letra de imprenta es un superconjunto de JavaScript, proporcionando las características del lenguaje que se basan en los que son proporcionados por la
especificación JavaScript. En las secciones que siguen, demuestro las características mecanografiado más útiles para el desarrollo angular, muchos de los cuales he
utilizado en los ejemplos de este libro.

■ Propina mecanografiado soporta más características que describo en este capítulo. Me presento algunas características adicionales como los utilizo en

capítulos posteriores, pero para una referencia completa, consulte la página mecanografiada a casa a las www.typescriptlang.org.

Utilizando el tipo de Anotaciones

La característica titular mecanografiado es el soporte para anotaciones de tipos, que pueden ayudar a reducir los errores comunes de JavaScript mediante la
aplicación de comprobación de tipos al compilar el código, de una manera que es una reminiscencia de lenguajes como C # o Java. Si ha tenido problemas para
ponerse de acuerdo con el sistema de tipos JavaScript (o incluso no darse cuenta de que había uno), a continuación, escriba anotaciones puede recorrer un largo
camino para la prevención de los errores más comunes. (Por otro lado, si te gusta la libertad de los tipos de JavaScript regulares, es posible que el tipo
mecanografiado advertencias restrictivas y molesto.)

Para mostrar el tipo de problema que resuelven las anotaciones tipo, he creado un archivo llamado tempConverter.ts en el
JavaScriptPrimer carpeta y añade el código del listado 6-12 .

Listado 6-12. El contenido de la tempConverter.ts en la carpeta src

TempConverter clase de exportación {

estática convertFtoC (temp) {


retorno ((parseFloat (temp.toPrecision (2)) - 32) / 1,8) .toFixed (1); }}

los TempConverter clase contiene un método estático simple llamado convertFtoC que acepta un valor de la temperatura expresada en
grados Fahrenheit y devuelve la misma temperatura expresada en grados Celsius.

97
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Hay supuestos en este código no explícitos. los convertFtoC método espera para recibir una número valor, en la que el toPrecision método
se llama para establecer el número de dígitos de coma flotante. El método devuelve un cuerda, aunque eso es difícil de decir sin
inspeccionar el código cuidadosamente (el resultado de la toFixed método es una cuerda).

Estas suposiciones implícitas conducen a problemas, especialmente cuando un desarrollador está utilizando código JavaScript escrito por otro.
en el Listado 6-13 , He creado deliberadamente un error al pasar la temperatura como una
cuerda valor, en lugar de la número que el método espera.

Listado 6-13. Utilizando el tipo incorrecto de los main.ts archivo en la carpeta src

{Nombre importación, WeatherLocation} de "./modules/NameAndWeather"; importación


{Nombre} otherName como de "./modules/DuplicateName";
{} la importación TempConverter de "./tempConverter";

dejar que name = Nombre nuevo ( "Adam", "Freeman");


dejar que loc = new WeatherLocation ( "llover", "Londres"); dejar que otra
nueva otherName = ();

dejar que cTemp = TempConverter.convertFtoC ( "38");

console.log (name.nameMessage); console.log


(loc.weatherMessage); console.log ( `La temperatura es
$ {} cTemp C ');

Cuando el código es ejecutado por el navegador, aparecerá el siguiente mensaje en la consola de JavaScript del navegador (el
funcionamiento exacto puede variar en función del navegador que esté utilizando):

temp.toPrecision no es una función

Este tipo de problema se puede solucionar sin necesidad de utilizar mecanografiado, por supuesto, pero sí significa que una cantidad sustancial del
código JavaScript en cualquier aplicación está dedicada a la comprobación de los tipos que se están utilizando. La solución mecanografiado es hacer que la
aplicación de tipo el trabajo del compilador, usando anotaciones de tipos que se agregan al código JavaScript. en el Listado 6-14 , He añadido anotaciones de
tipo a la TempConverter clase.

Listado 6-14. La adición de tipo Anotaciones en el Archivo tempConverter.ts en la carpeta src

TempConverter clase de exportación {

convertFtoC estática (temp: Number): String {


retorno ((parseFloat (temp.toPrecision (2)) - 32) / 1,8) .toFixed (1); }}

anotaciones de tipo se expresan utilizando dos puntos (el: carácter), seguido por el tipo. Hay dos anotaciones en el ejemplo.
El primero especifica que el parámetro a la convertFtoC método debe ser una
número.

...
estática convertFtoC (temp: número): cuerda {
...

98
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

La otra anotación especifica que el resultado del método es una cadena.

...
estática convertFtoC (temp: número): cuerda {
...

Al guardar los cambios en el archivo, el compilador mecanografiado se ejecutará. Entre los errores que se denuncian será la siguiente:

Argumento de tipo 'cadena' no es asignable al parámetro de tipo 'número'.

El compilador mecanografiado ha examinado que el tipo del valor pasa a la convertFtoC método en el main.ts archivo no coincide con el
tipo de anotación y se ha informado de un error. Este es el núcleo del sistema de tipo Letra de imprenta; esto significa que usted no tiene que
escribir código adicional en sus clases para comprobar que ha recibido los tipos esperados, y también hace que sea fácil de determinar el tipo
de resultado método. Para resolver el error reportado para el compilador, de venta 6-15 actualiza la declaración que invoca el convertFtoC

método para que utilice una número.

Listado 6-15. El uso de un argumento número en el main.ts archivo en la carpeta src

{Nombre importación, WeatherLocation} de "./modules/NameAndWeather"; importación


{Nombre} otherName como de "./modules/DuplicateName"; {} la importación TempConverter de
"./tempConverter";

dejar que name = Nombre nuevo ( "Adam", "Freeman"); dejar que loc = new
WeatherLocation ( "llover", "Londres"); dejar que otra nueva otherName = ();

dejar que cTemp = TempConverter.convertFtoC (38);

console.log (name.nameMessage);
console.log (loc.weatherMessage);
console.log (other.message);
console.log ( `La temperatura es $ {} cTemp C ');

Al guardar los cambios, verá los siguientes mensajes que aparecen en la consola de JavaScript del navegador:

Hola Adam Freeman Está


lloviendo en Londres Otro Nombre

La temperatura es 3.3C

99
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Propiedades de tipo y variables Anotaciones


anotaciones de tipos también se pueden aplicar a las propiedades y variables, lo que garantiza que todos los tipos utilizados en una aplicación puede ser
verificado por el compilador. en el Listado 6-16 , He añadido anotaciones de tipo a las clases en el NameAndWeather módulo.

Listado 6-16. Adición de anotaciones en el NameAndWeather.ts archivo en la carpeta src / modules

Nombre de clase de exportación {

primera cuerda;
segunda cuerda;

constructor (primero: cuerda, segundo: string) {


this.first = primero;
this.second = segundos; }

obtener nameMessage (): String {


retorno `Hola $ {} $ this.first {}` this.second; }}

WeatherLocation clase de exportación {


Tiempo: string;
ciudad: string;

constructor (si el tiempo: cadena, ciudad: cadena) {


this.weather = tiempo; this.city =
ciudad; }

obtener weatherMessage (): String {


volver `Es $ {} this.weather en $ {}` this.city; }}

Las propiedades se declaran con una anotación de tipo, siguiendo el mismo patrón como para los parámetros y resultados anotaciones. Los
cambios en el Listado 6-17 resolver los errores restantes reportados por el compilador mecanografiado, que se quejaba porque no sabía lo que eran los
tipos de las propiedades creadas en los constructores.
El patrón de recibir parámetros del constructor y la asignación de sus valores a las variables es tan común que mecanografiado incluye una
optimización, como se muestra en el Listado 6-17 .

Listado 6-17. Utilización de parámetros en los NameAndWeather.ts archivo en la carpeta src / modules

Nombre de clase de exportación {

constructor (privado en primer lugar: cadena, privada segundos: string) {}

obtener nameMessage (): String {


retorno `Hola $ {} $ this.first {}` this.second; }}

100
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

WeatherLocation clase de exportación {

constructor (si el tiempo privado: cuerda, ciudad privada: string) {}

obtener weatherMessage (): String {


volver `Es $ {} this.weather en $ {}` this.city; }}

la palabra clave privado es un ejemplo de un modificador de control de acceso, que describo en la sección “Uso de Modificadores de acceso”. La
aplicación de la palabra clave para el parámetro constructor tiene el efecto de definir automáticamente la propiedad de clase y asignarle el valor del
parámetro. El código del Listado 6-17 es una versión más concisa del Listado 6-16 .

Especificación de varios tipos o cualquier tipo

Mecanografiado permite que varios tipos que se especificarán, separada por medio de una barra (el carácter |). Esto puede ser útil cuando un método
puede aceptar o devolver varios tipos o cuando una variable se puede valores de diferentes tipos asignado. Listado 6-18 modifica la convertFtoC método
por lo que aceptará número o cuerda valores.

Listado 6-18. Aceptar varios valores en los tempConverter.ts archivo en la carpeta src

TempConverter clase de exportación {

convertFtoC estática (temp: número | String): String {


dejar que el valor: número = (<número> temp) .toPrecision
? <Número> temp: parseFloat (<cadena> temp);
retorno ((parseFloat (value.toPrecision (2)) - 32) / 1,8) .toFixed (1);
}}

los tipo Declaración para el temperatura parámetro tiene cambios en número | cuerda, lo que significa que el método puede aceptar cualquiera de
los valores. Esto se llama una tipo de unión. Dentro del método, una afirmación de tipo se utiliza para averiguar qué tipo ha sido recibido. Este es un
proceso algo torpe, pero el valor del parámetro es echada a un valor de número para comprobar si hay una toPrecision método definido en el resultado,
como este:

. . . (< número> temp) .toPrecision

...

Los corchetes angulares (los caracteres <y>) son para declarar una afirmación de tipo, que se tratará de convertir un objeto al tipo especificado. También
puede obtener el mismo resultado utilizando el como palabra clave, como se muestra en el Listado 6-19 .

Listado 6-19. El uso de la palabra clave en el que tempConverter.ts archivo en la carpeta src

TempConverter clase de exportación {

convertFtoC estática (temp: número | String): String {


dejar que el valor: número = (temp como número) .toPrecision
? temp como el número: parseFloat (<cadena> temp);

101
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

retorno ((parseFloat (value.toPrecision (2)) - 32) / 1,8) .toFixed (1); }}

En lugar de especificar un tipo de unión es el uso de la alguna palabra clave, que permite a cualquier tipo que se asigna a una variable, utilizado
como un argumento, o volvió de un método. Listado 6-20 sustituye al tipo de unión en el
convertFtoC método con el alguna palabra clave.

■ Propina el compilador mecanografiado implícitamente aplicar el alguna palabra clave cuando se omite una anotación de tipo.

Listado 6-20. Especificación de cualquier tipo en el archivo tempConverter.ts en la carpeta src

TempConverter clase de exportación {

convertFtoC estática (temp: los hay): String {


dejar que el valor: número;

if ((temp como número) .toPrecision) {


value = temp;
} Else if ((temp como cadena) .indexOf) {
value = parseFloat (<cadena> temp); } Else {

valor = 0; }

retorno ((parseFloat (value.toPrecision (2)) - 32) / 1,8) .toFixed (1); }}

El uso de tuplas
Las tuplas son matrices de longitud fija, donde cada elemento de la matriz es de un tipo especificado. Esta es una descripción de sonido vago porque tuplas
son tan flexibles. A modo de ejemplo, el listado 6-21 utiliza una tupla para representar una ciudad y su clima y la temperatura actual.

Listado 6-21. Usando una tupla de la main.ts archivo en la carpeta src

{Nombre importación, WeatherLocation} de "./modules/NameAndWeather"; importación


{Nombre} otherName como de "./modules/DuplicateName"; {} la importación TempConverter de
"./tempConverter";

dejar que name = Nombre nuevo ( "Adam", "Freeman");


dejar que loc = new WeatherLocation ( "llover", "Londres"); dejar que otra
nueva otherName = ();

dejar que cTemp = TempConverter.convertFtoC ( "38");

dejar que tupla: [cadena, cadena, cadena];


tupla = [ "Londres", "lloviendo", TempConverter.convertFtoC ( "38")]

console.log ( `Es $ {tupla [2]} grados C y $ {tupla [1]} en $ {tupla [0]}`);

102
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Las tuplas se definen como una matriz de tipos, y se accede a los elementos individuales usando indizadores de matriz. Este ejemplo produce el
siguiente mensaje en la consola de JavaScript del navegador:

Que es de 3,3 grados C y lloviendo en Londres

Uso de tipos de indexables


tipos indexables asocia una clave con un valor, la creación de una colección de mapas similares que se pueden utilizar para reunir los elementos de datos relacionados
entre sí. en el Listado 6-22 , He utilizado un tipo indexable para recoger información sobre juntas varias ciudades.

Listado 6-22. Uso de tipos de indexables en los main.ts archivo en la carpeta src

{Nombre importación, WeatherLocation} de "./modules/NameAndWeather"; importación


{Nombre} otherName como de "./modules/DuplicateName"; {} la importación TempConverter de
"./tempConverter";

dejar que las ciudades: {[índice: string]: [cadena, cadena]} = {};

ciudades [ "Londres"] = [ "lloviendo", TempConverter.convertFtoC ( "38")]; ciudades [


"París"] = [ "soleado", TempConverter.convertFtoC ( "52")]; ciudades [ "Berlin"] = [ "nieva",
TempConverter.convertFtoC ( "23")];

(clave en las ciudades let) {


console.log ( `$ {key}: $ {colonias [clave] [0]}, {$ ciudades [clave] [1]} '); }

los ciudades variable se define como un tipo indexable, con la tecla como una cadena y el valor de datos como un [ cadena, cadena] tupla. Los
valores se asignan y se leen utilizando indizadores de estilo de matriz, tales como
ciudades [ "Londres"]. La colección de teclas en un tipo orientable se puede acceder mediante una for ... in bucle, como se muestra en el
ejemplo, que produce la siguiente salida en la consola JavaScript del navegador:

Londres: la lluvia, 3.3 Paris:


soleado, 11,1 Berlín: nieva,
-5,0

Solamente número y cuerda Los valores se pueden utilizar como las teclas de tipos indexables, pero esto es una característica muy útil que utilizo en los ejemplos

en los capítulos posteriores.

El uso de modificadores de acceso

JavaScript no es compatible con la protección de acceso, lo que significa que las clases, sus propiedades y sus métodos pueden ser accedidos desde
cualquier parte de la aplicación. Hay una convención de prefijar el nombre de los miembros de implementación con un guión bajo (_ el personaje), pero
esto es sólo una advertencia a otros desarrolladores y no se cumple.

Mecanografiado proporciona tres palabras clave que se utilizan para gestionar el acceso y que son impuestas por el compilador. Mesa 6-2 describe

las palabras clave.

103
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

■ Precaución durante el desarrollo, estas palabras clave tienen un efecto limitado en aplicaciones angulares debido a que una gran cantidad de

funcionalidad se entrega a través de propiedades y métodos que se especifican en fragmentos de código incrustados en las expresiones de enlace de

datos. estas expresiones se evalúan en tiempo de ejecución en el navegador, donde no hay una aplicación de características mecanografiado. que se

vuelven más importantes cuando se llega a implementar la aplicación, y es importante asegurarse de que cualquier propiedad o método que se accede

en una expresión de enlace de datos se marca como público o no tiene ningún modificador de acceso (que tiene el mismo efecto que el uso de la público palabra

clave).

Tabla 6-2. La transcripción de acceso Modificador Palabras clave

Palabra clave Descripción

público Esta palabra clave se utiliza para referirse a una propiedad o un método que se puede acceder en cualquier lugar. Se trata de la

protección de acceso por defecto si no se utiliza la palabra clave.

privado Esta palabra clave se utiliza para denotar una propiedad o método que se puede acceder sólo dentro de la clase que lo
define.

protegido Esta palabra clave se utiliza para denotar una propiedad o método que se puede acceder sólo dentro de la clase que lo define o
por las clases que se extienden esa clase.

Listado 6-23 añade una privado método para la TempConverter clase.

Listado 6-23. El uso de un modificador de acceso en los tempConverter.ts archivo en la carpeta src

TempConverter clase de exportación {

convertFtoC estática (temp: los hay): String {


dejar que el valor: número;

if ((temp como número) .toPrecision) {


value = temp;
} Else if ((temp como cadena) .indexOf) {
value = parseFloat (<cadena> temp); } Else {

valor = 0; }

volver TempConverter.performCalculation (valor) .toFixed (1);


}

performCalculation estática privada (value: Number): Number {


RETURN (parseFloat (value.toPrecision (2)) - 32) / 1,8; }

los performCalculation método está marcado como privado, lo que significa que el compilador mecanografiado informará de un código de error
si cualquier otra parte de la aplicación intenta invocar el método.

104
Capítulo 6 ■ JavaScript y mecanografiado: parte 2

Resumen
En este capítulo, he descrito la forma en que es compatible con JavaScript trabajar con objetos y clases, explicó cómo funciona módulos de JavaScript, y
presenté las características mecanografiado que son útiles para el desarrollo angular. En el siguiente capítulo, comienzo del proceso de creación de un proyecto
realista que ofrece una visión general de cómo las diferentes características angulares trabajan juntos para crear aplicaciones antes de excavar en los detalles
individuales en la Parte 2 de este libro.

105
CAPÍTULO 7

SportsStore: una aplicación real

en el capítulo 2 , He construido una aplicación angular rápido y sencillo. ejemplos pequeñas y enfocadas me permiten mostrar las características angular
específica, pero pueden carecer de contexto. Para ayudar a superar este problema, voy a crear una aplicación de comercio electrónico sencilla pero realista.

Mi aplicación, denominada SportsStore, seguirá el enfoque clásico tomada por las tiendas en línea de todo el mundo. Voy a crear un catálogo de
productos en línea que los clientes pueden navegar por categorías y la página, un carro de compras donde los usuarios pueden añadir y eliminar los
productos, y un check out donde los clientes pueden introducir sus detalles del envío y poner sus órdenes. También voy a crear una zona de administración
que incluye crear, leer, actualizar y eliminar instalaciones (CRUD) para la gestión del catálogo y lo protegerá de manera que sólo los administradores
registrados en pueden hacer cambios. Por último, os muestro cómo preparar y distribuir una aplicación angular.

Mi objetivo en este capítulo y las que siguen es para darle una idea de lo verdadero desarrollo angular es como mediante la creación de lo más
realista un ejemplo posible. Me quiero centrar en angular, por supuesto, y por lo que he simplificado la integración con sistemas externos, como el
almacén de datos, y otros omitido por completo, tales como el procesamiento de pagos.

El ejemplo SportsStore es uno que utilizo en algunos de mis libros, no menos importante, ya que demuestra la forma en que los
diferentes marcos, idiomas y estilos de desarrollo se pueden utilizar para conseguir el mismo resultado. No es necesario haber leído alguno de
mis otros libros seguir este capítulo, pero se puede encontrar los contrastes interesante si usted ya tiene mi Pro Core ASP.NET MVC 2 libro, por
ejemplo.
Las características angular que uso en la aplicación SportsStore están cubiertos en profundidad en capítulos posteriores. En lugar de duplicar
todo aquí, te digo lo suficiente para dar sentido a la aplicación de ejemplo y hacen referencia a otros capítulos para obtener información en
profundidad. Se pueden leer los capítulos SportsStore de extremo a extremo para tener una idea de cómo funciona o salto hacia y desde los
capítulos detalle angular para entrar en la profundidad. De cualquier manera, no esperes entender todo de inmediato-angular tiene una gran
cantidad de partes móviles, y la aplicación SportsStore está destinado a mostrar cómo encajan entre sí sin bucear demasiado en los detalles que me
paso el resto del libro que describe .

Preparación del Proyecto


Para crear el proyecto SportsStore, abra un símbolo del sistema, vaya a un lugar conveniente, y ejecutar el siguiente comando:

ng nuevo SportsStore

los angular-cli paquete creará un nuevo proyecto para el desarrollo angular, con los archivos de configuración, contenido marcador de posición, y
herramientas de desarrollo. El proceso de configuración del proyecto puede tomar algún tiempo, ya que hay muchos paquetes de NGP para descargar e instalar.

© 2018 Adam Freeman 107


A. Freeman, Pro angular 6, https://doi.org/10.1007/978-1-4842-3649-9_7
Capítulo 7 ■ SportSStore: una aplicación real

■ Propina Puede descargar el proyecto de ejemplo para este capítulo, y para todos los demás capítulos de este libro: de https://github.com/Apress/pro-angula
.

Instalación de los paquetes adicionales de la NGP

Se requieren paquetes adicionales para el proyecto SportsStore, además de los paquetes angular del núcleo y construir herramientas establecidas
por el ng nueva mando. Ejecute los siguientes comandos para navegar a la carpeta SportsStore y añadir los paquetes requeridos:

cd SportsStore
NPM instalar bootstrap@4.1.1 NPM instalar
font-awesome@4.7.0
NPM instalar --save-dev json-server@0.12.1 NPM instalar
--save-dev jsonwebtoken@8.1.1

Es importante utilizar los números de versión que aparecen en la lista. Es posible que vea advertencias acerca de las dependencias no satisfechas
por pares a medida que agrega los paquetes, pero se puede ignorar. Algunos de los paquetes se instalan mediante el - save-dev argumento, lo que indica
que se utilizan durante el desarrollo y no serán parte de la aplicación SportsStore.

La adición de las hojas de estilo del CSS para la Aplicación

Una vez que se han instalado los paquetes, añadir las declaraciones que aparecen en el listado 7-1 al angular.json presentar para incorporar los archivos
CSS desde el marco Bootstrap CSS y los paquetes de fuentes impresionantes en la aplicación. Voy a utilizar los estilos CSS Bootstrap para todo el
contenido HTML en la aplicación SportsStore, y utilizar los iconos del paquete impresionante fuente de presentar un resumen de un carrito de la compra
para el usuario.

Listado 7-1. La adición de CSS para el archivo en la carpeta angular.json SportsStore

...
"Arquitecto": {
"construir": {
"Builder": "@ angular DevKit / build-angular: el navegador", "opciones": {

"OutputPath": "dist / SportsStore", "índice": "src


/ index.html", "principales": "src / main.ts",

"polyfills": "src / polyfills.ts", "TSconfig": "src /


tsconfig.app.json", "activos": [

"Src / favicon.ico", "src /


activos"],

"estilos": [
"Src / styles.css",
"Node_modules / bootstrap / dist / css / bootstrap.min.css", "node_modules
/ font-awesome / css / font-awesome.min.css"
],

108
Capítulo 7 ■ SportSStore: una aplicación real

"guiones": [] },

...

Preparación del servicio Web REST


La aplicación SportsStore utilizará peticiones HTTP asíncronas para obtener datos de los modelos proporcionados por un servicio web REST. Como describo en el
Capítulo 24 , El resto es un enfoque para el diseño de servicios web que utilizan el método HTTP o un verbo para especificar una operación y la dirección URL para
seleccionar los objetos de datos que la operación se aplica a.
He añadido el JSON-servidor paquete para el proyecto en la sección anterior. Este es un excelente paquete para la creación de servicios web
a partir de datos JSON o código JavaScript. Añada la sentencia muestra en el listado 7-2 al guiones sección de la package.json archivo para que el JSON-servidor
el paquete se puede iniciar desde la línea de comandos.

Listado 7-2. Adición de un script en el archivo en la carpeta package.json SportsStore

...
"guiones": { "ng":
"NG",
"Inicio": "ng sirven", "construir":
"ng acumulación", "prueba":
"prueba ng", "pelusa": "ng
pelusa", "E2E": "ng E2E",

"Json": "data.js JSON-servidor -p 3500 authMiddleware.js -m"


},
...

Para proporcionar la JSON-servidor paquete con datos para trabajar, he añadido un archivo llamado data.js en el
Tienda de deportes carpeta y añade el código de muestra en el listado 7-3 , Que se asegurará de que el mismo se dispone de datos cada vez que el JSON-servidor
paquete se inició de modo que tenga un punto fijo de referencia durante el desarrollo.

■ Propina es importante prestar atención a los nombres de archivo al crear los archivos de configuración. Algunos tienen la

. JSON extensión, que significa que contienen datos estáticos con formato JSON. otros archivos tienen la extensión. js extensión, lo que significa que

contienen el código JavaScript. cada herramienta necesaria para el desarrollo angular tiene expectativas sobre su archivo de configuración.

Listado 7-3. El contenido de la data.js archivo en la carpeta SportsStore

module.exports = function () {
regreso {
los productos [
{Id: 1, nombre: "Kayak", categoría: "deportes acuáticos",
Descripción: "Un barco para una persona", precio: 275}, {id: 2, nombre:
"chaleco salvavidas", categoría: "deportes acuáticos",
Descripción: "Protección y de moda", precio: 48,95}, {id: 3, nombre: "Balón de
fútbol", categoría: "fútbol",
Descripción: "aprobado por la FIFA tamaño y peso", precio: 19,50}, {id: 4, nombre:
"banderas de la esquina", categoría: "fútbol",

109
Capítulo 7 ■ SportSStore: una aplicación real

Descripción: "Dé su campo de juego un toque profesional", precio: 34.95},

{Id: 5, nombre: "Estadio", categoría: "fútbol",


Descripción: "Plano-envasados ​estadio de 35.000 asientos", precio: 79500}, {id: 6, nombre:
"casquillo de pensamiento", categoría: "Ajedrez",
Descripción: "Mejorar la eficiencia del cerebro en un 75%", precio: 16}, {id: 7,
nombre: "Silla inestable", categoría: "Ajedrez",
Descripción: "En secreto darle a su oponente una desventaja", precio: 29,95},

{Id: 8, nombre: "tablero de ajedrez humano", categoría: "Ajedrez",


Descripción: "Un juego divertido para la familia", precio: 75}, {id: 9, nombre:
"Rey de Bling Bling", categoría: "Ajedrez",
Descripción: "chapado en oro, con diamantes Rey", precio: 1200}],

pedidos: [] } }

Este código define dos colecciones de datos que serán presentados por el servicio web REST. los productos
colección contiene los productos para la venta al cliente, mientras que el pedidos colección contendrá las órdenes que los clientes han depositado
(pero que está actualmente vacía).
Los datos almacenados por el servicio web REST necesita ser protegido de manera que los usuarios normales no pueden modificar los productos o
cambiar el estado de los pedidos. los JSON-servidor paquete no incluye ningún características de autenticación incorporados, así que creé un archivo
llamado authMiddleware.js en el Tienda de deportes carpeta y añade el código del listado 7-4 .

Listado 7-4. Los contenidos del archivo en la carpeta authMiddleware.js SportsStore

jwt const = requieren ( "jsonwebtoken");

const APP_SECRET = "myappsecret"; NOMBRE DE


USUARIO const = "admin"; CONTRASEÑA const =
"secreto";

module.exports = function (req, res, siguiente) {

if ((req.url == "/ api / login" || req.url == "/ login")


&& req.method == "POST") {
si (req.body! = null && req.body.name == NOMBRE DE USUARIO
&& req.body.password == contraseña) {
dejar que token = jwt.sign ({datos: NOMBRE DE USUARIO, expiresIn: "1h"}, APP_SECRET); res.json
({éxito: verdadero, símbolo: token}); } Else {

res.json ({éxito: false}); }

res.end ();
regreso;
} Else if ((((( "req.url.startsWith / API / productos")
|| req.url.startsWith ( "/") productos) ||
(req.url.startsWith ( "/ API / categorías")
|| req.url.startsWith ( "/" categorías))) && req.method! = "GET") || ((( "req.url.startsWith /
API / órdenes")

110
Capítulo 7 ■ SportSStore: una aplicación real

|| req.url.startsWith ( "/ órdenes")) && req.method = "POST")) {dejar token =


req.headers [ "autorización"]!; si (Token! = null && token.startsWith ( "Portador <")) {

token = token.substring (7, token.length - 1); tratar {

jwt.verify (token, APP_SECRET);


siguiente(); regreso; } Catch (err) {}}

res.statusCode = 401;
res.end (); regreso; }

siguiente(); }

Este código inspecciona las peticiones HTTP enviadas al servicio web REST y pone en práctica algunas de las características básicas de seguridad. Este es el
código del lado del servidor que no está directamente relacionado con el desarrollo angular, por lo que no se preocupe si su propósito no es inmediatamente evidente.
Explico el proceso de autenticación y autorización en el capítulo 9 , Incluyendo cómo autenticar a los usuarios angular.

■ Precaución No utilice el código del listado 7-4 excepto para la aplicación SportsStore. que contiene las contraseñas débiles que están cableados en el

código. esto está muy bien para el proyecto SportsStore porque el énfasis está en el lado del cliente con el desarrollo angular, pero esto no es adecuado

para proyectos reales.

Preparación del archivo HTML


Cada aplicación web angular se basa en un archivo HTML que está cargado por el navegador y que las cargas e inicia la aplicación. editar el index.html presentar
en el SportsStore / src carpeta para eliminar el contenido marcador de posición y añadir los elementos que se muestran en el Listado 7-5 .

El listado 7-5. Preparación del archivo index.html en la carpeta src

<! DOCTYPE html> <html


lang = "es"> <head>

<Charset meta = "UTF-8"> <title>


SportsStore </ title> <base href = "/">

<Meta name = "viewport" content = "width = dispositivo de ancho, inicial escala = 1"> <rel = "icono" type
= "image / x-icon" enlace href = "favicon.ico"> </ head >

<Clase cuerpo = "m-2">


<App> SportsStore volveremos aquí </ app> </ body>

</ Html>

111
Capítulo 7 ■ SportSStore: una aplicación real

El documento HTML incluye una aplicación elemento, que es el marcador de posición para la funcionalidad SportsStore. También hay una base elemento,
que es requerido por las características de enrutamiento de URL angular, que agrego al proyecto SportsStore en el capítulo 8 .

La creación de la estructura de carpetas

Una parte importante de la creación de cualquier aplicación angular es crear la estructura de carpetas. los ng nueva
comando establece un proyecto que pone todos los archivos de la aplicación en el src carpeta, con los archivos angular en el
src / app carpeta. Para añadir un poco de estructura para el proyecto, crear las carpetas adicionales que se muestran en la Tabla 7-1 .

Tabla 7-1. Las carpetas adicionales necesarios para el Proyecto SportsStore

Carpeta Descripción

SportsStore / src / app / modelo Esta carpeta contiene el código para el modelo de datos.

SportsStore / src / app / tienda Esta carpeta contiene la funcionalidad básica para ir de compras.

SportsStore / src / app / admin Esta carpeta contiene la funcionalidad para la administración.

Ejecutar el Ejemplo de Aplicación


Asegúrese de que todos los cambios se han guardado, y ejecutar el siguiente comando en la carpeta SportsStore:

ng servir --port 3000 --open

Este comando iniciará las herramientas de desarrollo establecidos por el ng nueva de comandos, lo que compilar y empaquetar los
archivos de código y contenidos en el automática src carpeta cada vez que se detecta un cambio. Una nueva ventana del navegador se abrirá y
mostrar el contenido se ilustra en la figura 7-1 .

La Figura 7-1. Ejecución de la aplicación de ejemplo

El servidor web de desarrollo se inicia en el puerto 3000, por lo que la dirección URL de la aplicación será http: // localhost: 3000. Usted
no tiene que incluir el nombre del documento HTML, porque index.html es el archivo predeterminado que el servidor responde con.

112
Capítulo 7 ■ SportSStore: una aplicación real

Inicio del servicio Web REST


Para iniciar el servicio web REST, abrir un nuevo símbolo del sistema, vaya a la Tienda de deportes carpeta y ejecute el siguiente comando:

NPM plazo JSON

El servicio web REST está configurado para ejecutarse en el puerto 3500. Para probar la solicitud de servicio web, utilizar el navegador para
solicitar la URL http: // localhost: 3500 / productos / 1. El navegador mostrará una representación JSON de uno de los productos definidos en el
Listado 7-3 , como sigue:

{
"Id": 1,
"Name": "Kayak",
"Categoría": "deportes acuáticos",
"Descripción": "Un barco para una persona", "precio":
275}

Preparación de las características del proyecto angular


Cada proyecto angular requiere un poco de preparación básica. En las secciones que siguen, sustituyo el contenido marcador de posición para construir
las bases para la aplicación SportsStore.

La actualización del componente de raíz

El componente de raíz es el bloque de construcción angular que gestionará el contenido de la aplicación elemento en el documento HTML de la lista 7-5 .
Una aplicación puede contener muchos componentes, pero siempre hay un componente de raíz que asume la responsabilidad por el contenido de primer
nivel se presenta al usuario. He editado el archivo llamado
app.component.ts en el SportsStore / src / app carpeta y se reemplaza el código existente con las declaraciones que aparecen en el listado 7-6 .

Listado 7-6. El contenido de la app.component.ts archivo en la carpeta src / app

importación {Componente} de "@ angular / núcleo";

@Component ({selector:
"aplicación",
Plantilla: `<div class = "bg-éxito p-2-texto centro de texto blanco">
Esta es SportsStore </
div> `
})
AppComponent clase de exportación {}

Los @ Componente decorador dice que el angular AppComponent clase es un componente y sus propiedades configurar cómo se aplica el
componente. El conjunto completo de propiedades de los componentes descritos en el Capítulo 17 , Pero las propiedades que aparecen en la lista
son los más básicos y de uso más frecuente.

113
Capítulo 7 ■ SportSStore: una aplicación real

propiedad define el contenido HTML del componente mostrará. Los componentes pueden definir plantillas en línea, como éste, o utilizar archivos
HTML externos, que pueden hacer la gestión de contenidos complejos más fácil.
No hay ningún código en el AppComponent clase porque existe el componente de raíz en un proyecto angular sólo para administrar el contenido se muestra al

usuario. Al principio, me las arreglaré el contenido mostrado por el componente de raíz de forma manual, pero en el capítulo 8 , Utilizo una función llamada enrutamiento de

URL para adaptar el contenido automáticamente en función de las acciones del usuario.

Actualización del módulo de raíz


Hay dos tipos de módulos: módulos de función angular y el módulo de raíz. Características módulos se utilizan para la funcionalidad de aplicaciones
relacionadas con el grupo para hacer la aplicación más fácil de manejar. Puedo crear módulos de características para cada área funcional importante de la
aplicación, incluyendo el modelo de datos, la interfaz de almacén presenta a los usuarios, y la interfaz de administración.

El módulo de raíz se utiliza para describir la aplicación de angular. La descripción incluye los cuales se requieren módulos de función para
ejecutar la aplicación, que las características de encargo deben cargarse, y el nombre del componente de raíz. El nombre convencional del
archivo de componente raíz es app.module.ts, que se crea en el SportsStore / src / app carpeta. No se requieren cambios a este archivo por el
momento, y su contenido inicial se muestra en el Listado 7-7 .

Listado 7-7. El contenido de la app.module.ts archivo en la carpeta src / app

{} la importación de BrowserModule '@ / plataforma de navegador angular'; importación


{NgModule} de '@ angular / núcleo'; {} la importación de AppComponent
'./app.component';

@NgModule ({
declaraciones: [AppComponent], las
importaciones: [BrowserModule], los proveedores
de: [],
bootstrap: [AppComponent]})

AppModule clase de exportación {}

Al igual que en el componente de raíz, no hay ningún código en la clase del módulo de raíz. Eso es porque el módulo de raíz en realidad sólo existe
para proporcionar la información a través de la @ NgModule decorador. los importaciones propiedad dice angular que debe cargar el BrowserModule módulo
de función, que contiene las características angulares básicas requeridas para una aplicación web.

los declaraciones propiedad dice angular que debe cargar el componente raíz, el proveedores
propiedad dice angular alrededor de los objetos compartidos utilizados por la aplicación y el oreja propiedad dice angular que el componente fundamental es
la AppModule clase. Voy a añadir información a las propiedades de este decorador como añado características para la aplicación SportsStore, pero esta
configuración básica es suficiente para iniciar la aplicación.

Inspeccionar el archivo de arranque


La siguiente pieza de fontanería es el archivo de arranque, que se inicia la aplicación. Este libro se centra en el uso angular para crear aplicaciones que
funcionan en los navegadores web, pero la plataforma angular puede ser portado a diferentes entornos. El archivo de arranque utiliza la plataforma del
navegador angular para cargar el módulo root e inicie la aplicación. No se requieren cambios de los contenidos de la main.ts archivo, que se encuentra en
el SportsStore / src
carpeta, como se muestra en el listado 7-8 .

El 114 selector propiedad dice angular cómo aplicar el componente en el documento HTML, y el modelo
Capítulo 7 ■ SportSStore: una aplicación real

Listado 7-8. El contenido de la main.ts archivo en la carpeta src

importación {enableProdMode} de '@ angular / núcleo';


{} la importación de platformBrowserDynamic '@ angular / plataforma de navegador-dinámico';

{} la importación de AppModule './app/app.module'; {} la importación entorno de


'./environments/environment';

si (environment.production)
{enableProdMode (); }

platformBrowserDynamic (). bootstrapModule (AppModule)


. captura (err => console.log (err));

Las herramientas de desarrollo detectan los cambios en el archivo del proyecto, compilar los archivos de código, y recargar automáticamente el navegador,

produciendo el contenido se muestra en la figura 7-2 .

Figura 7-2. Inicio de la aplicación SportsStore

A partir del modelo de datos


El mejor lugar para comenzar cualquier nuevo proyecto es el modelo de datos. Quiero llegar al punto donde se pueden ver algunas de las características angulares
en el trabajo, por lo que en lugar de definir el modelo de datos de extremo a extremo, voy a poner un poco de funcionalidad básica en su lugar con datos ficticios. Voy
a utilizar estos datos para crear funciones de cara al usuario y luego regresar al modelo de datos de unirlo al servicio web REST en el capítulo 8 .

La creación de las clases del modelo

Cada modelo de datos debe clases que describen los tipos de datos que se contienen en el modelo de datos. Para la aplicación SportsStore, esto
significa que describen las clases de los productos que se venden en la tienda y las órdenes que se reciben de los clientes.

Ser capaz de describir los productos será suficiente para empezar a trabajar con la aplicación SportsStore, y voy a crear otras clases del modelo para
apoyar características como las ponen en práctica. He creado un archivo llamado product.model.ts
en el SportsStore / src / app / modelo carpeta y añade el código del listado 7-9 .

115
Capítulo 7 ■ SportSStore: una aplicación real

Listado 7-9. El contenido de la product.model.ts archivo en la carpeta src / app / modelo

clase de exportación de productos {

constructor(
Identificación del público ?: número, cadena
de nombre público ?:, categoría de público ?:
cadena, descripción público ?: cadena, precio
público ?: número) {}}

los Producto clase define un constructor que acepta Identificación, nombre, categoría, descripción, y precio
propiedades, que corresponden a la estructura de los datos utilizados para poblar el servicio web REST en el Listado 7-3 . Los signos de interrogación (?
Los caracteres) que siguen los nombres de los parámetros indican que estos son parámetros opcionales que pueden ser omitidos cuando se crean nuevos
objetos utilizando la Producto clase, que puede ser útil cuando se escriben las aplicaciones en las propiedades de modelo de objetos se rellenarán
utilizando formularios HTML.

Crear el origen de datos simulada


Para prepararse para la transición del muerto a los datos reales, voy a alimentar a los datos de las aplicaciones que utilizan una fuente de datos. El resto de
la aplicación no sabrá donde los datos está viniendo, lo que hará que el interruptor para obtener datos mediante peticiones HTTP sin fisuras.

He añadido un archivo llamado static.datasource.ts al SportsStore / src / app / modelo carpeta y se define la clase de muestra en el listado 7-10 .

Listado 7-10. El contenido de la static.datasource.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación de productos de "./product.model"; importación
{observable, a partir de} "rxjs";

@Injectable ()
StaticDataSource clase de exportación {
productos privados de producto: [] = [
nuevo producto (1, "Producto 1", "Categoría 1", "Producto 1 (Categoría 1)", 100), el nuevo producto (2, "Producto 2", "Categoría 1", "Producto 2

(Categoría 1)" , 100), el nuevo producto (3, "Producto 3", "Categoría 1", "Producto 3 (Categoría 1)", 100), el nuevo producto (4, "Producto 4",

"Categoría 1", "Producto 4 ( Categoría 1)", 100), el nuevo producto (5, "producto 5", "Categoría 1", "Producto 5 (categoría 1)", 100), el nuevo

producto (6, "Producto 6", "Categoría 2", "Producto 6 (categoría 2)", 100), el nuevo producto (7, "Producto 7", "Categoría 2", "Producto 7

(categoría 2)", 100), el nuevo producto (8, "Producto 8", " Categoría 2" , "Producto 8 (categoría 2)", 100), el nuevo producto (9 "Producto 9" ,

"Categoría 2", "Producto 9 (categoría 2)", 100), el nuevo producto (10, "producto 10", "Categoría 2", "producto 10 (categoría 2)", 100), los

productos nuevos (11 "11 de producto", "Categoría 3", "Producto 11 (Categoría 3)", 100), el nuevo producto (12, "Producto 12", "categoría 3",

"Producto 12 (Categoría 3)", 100 ), los productos nuevos (13, "Producto 13", "categoría 3", "Producto 13 (Categoría 3)", 100), el nuevo producto

(14, "Producto 14", "categoría 3", "Producto 14 (Categoría 3 )", 100), el nuevo producto (15, "Producto 15", "Categoría 3", "Producto 15

(Categoría 3)", 100),];Categoría 2" , "Producto 10 (categoría 2)", 100), el nuevo producto (11, "Producto 11", "Categoría 3", "Producto 11

(Categoría 3)", 100), el nuevo producto (12, "Producto 12" , "categoría 3", "Producto 12 (Categoría 3)", 100), el nuevo producto (13, "Producto

13", "categoría 3", "Producto 13 (Categoría 3)", 100), el nuevo producto ( 14, "14 Producto", "categoría 3", "Producto 14 (Categoría 3)", 100), el

nuevo producto (15, "Producto 15", "categoría 3", "Producto 15 (Categoría 3)", 100) ,];Categoría 2" , "Producto 10 (categoría 2)", 100), el nuevo

producto (11, "Producto 11", "Categoría 3", "Producto 11 (Categoría 3)", 100), el nuevo producto (12, "Producto 12" , "categoría 3", "Producto

12 (Categoría 3)", 100), el nuevo producto (13, "Producto 13", "categoría 3", "Producto 13 (Categoría 3)", 100), el nuevo producto ( 14, "14

Producto", "categoría 3", "Producto 14 (Categoría 3)", 100), el nuevo producto (15, "Producto 15", "categoría 3", "Producto 15 (Categoría 3)", 100) ,];100), el nuevo producto (13, "Pro

116
Capítulo 7 ■ SportSStore: una aplicación real

getProducts (): observable <Producto []> {


volver a partir de ([this.products]); }}

los StaticDataSource clase define un método llamado getProducts, que devuelve los datos ficticios. El resultado de llamar al getProducts
método es una Observable <Producto []>, que es una Observable que produce matrices de Producto objetos.

los Observable clase es proporcionada por el paquete de extensiones reactivas, que es utilizado por angular para manejar los cambios de estado
en las aplicaciones. Describo el Observable clase en el capítulo 23 , Pero para este capítulo, es suficiente para saber que una Observable objeto
representa una tarea asíncrona que producirá un resultado en algún momento en el futuro. Angular expone el uso de Observable Objetos para algunas
funciones, como hacer peticiones HTTP, y esta es la razón por la getProducts método devuelve una Observable <Producto []> en lugar de simplemente
devolver los datos de forma sincrónica.

Los @ inyectable decorador ha sido aplicada a la StaticDataSource clase. Este decorador se utiliza contar angular que esta clase será utilizada
como un servicio, que permite a otras clases para acceder a su funcionalidad a través de una característica llamada inyección de dependencia, que se
describe en los capítulos 19 y 20 . Vas a ver cómo funcionan los servicios como la aplicación toma forma.

■ Propina cuenta de que tengo que importar inyectable desde el @ angular / núcleo módulo de JavaScript para que pueda aplicar el @ inyectable decorador.

No voy a poner de relieve las diferentes clases de todos los angulares que i de importación para el ejemplo SportsStore, pero se puede obtener

todos los detalles en los capítulos que describen las características que se relacionan con.

Creación del modelo de repositorio


La fuente de datos es responsable de proporcionar la aplicación con los datos que requiere, pero el acceso a esos datos normalmente está mediada por una repositorio,
que es responsable de la distribución de los datos a los bloques de construcción de aplicaciones individuales para que los detalles de cómo se ha obtenido los
datos se mantienen ocultos. He añadido un archivo llamado
product.repository.ts en el SportsStore / src / app / modelo carpeta y se define la clase de muestra en el listado 7-11 .

Listado 7-11. El contenido de la product.repository.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la importación de


productos de "./product.model"; {} la importación StaticDataSource de
"./static.datasource";

@Injectable ()
ProductRepository clase de exportación {
productos privados: Producto [] = []; privadas
categorías: string [] = [];

constructor (dataSource privada: StaticDataSource) {


dataSource.getProducts (). suscribirse (datos => {
this.products = datos;
this.categories = data.map (p => p.category)
. filtro ((c, índice, array) => Array.indexOf (c) == índice) .Sort (); }); }

117
Capítulo 7 ■ SportSStore: una aplicación real

getProducts (categoría: String = null): Producto [] {


this.products volver
. filtro (p => categoría == null || categoría == p.category); }

obtenerProducto (id: Number): Producto {


volver this.products.find (p => p.id == id); }

GetCategories (): String [] {


this.categories regresar; }}

Cuando angular tiene que crear una nueva instancia del repositorio, se inspeccionará la clase y ver que se necesita una StaticDataSource
oponerse a invocar la ProductRepository constructor y crear un nuevo objeto.
El constructor repositorio llama a la fuente de datos de getProducts método y luego utiliza el suscribir
método en el Observable objeto que se devuelve para recibir los datos de productos. Véase el capítulo 23 para más detalles de cómo Observable objetos de
trabajo.

Crear el módulo de funciones


Voy a definir un modelo de características angular que permitirá la funcionalidad modelo de datos para ser utilizado fácilmente en otras partes de la
aplicación. He añadido un archivo llamado model.module.ts en el SportsStore / src / app / modelo
carpeta y se define la clase de muestra en el listado 7-12 .

■ Propina No se preocupe si todos los nombres de los archivos parecen similares y confuso. Se acostumbrará a la forma en que las aplicaciones

angulares están estructurados a medida que trabaja a través de los otros capítulos en el libro, y que pronto será capaz de mirar a los archivos en un

proyecto angular y saben lo que están destinados a hacer.

Listado 7-12. El contenido de la model.module.ts archivo en la carpeta src / app / modelo

importación {NgModule} de "@ angular / núcleo";


{} la importación ProductRepository de "./product.repository"; {} la importación
StaticDataSource de "./static.datasource";

@NgModule ({
proveedores: [ProductRepository, StaticDataSource]})

ModelModule clase de exportación {}

Los @ NgModule decorador se utiliza para crear módulos de características, y sus propiedades diga angular de cómo se debe utilizar el módulo.
Sólo hay una propiedad en este módulo, proveedores, y se dice angular que las clases deben ser utilizados como servicios para la función de
inyección de dependencias, que se describe en los capítulos 19
y 20 . Características módulos-y el @ NgModule decorador-se describen en el Capítulo 21 .

118
Capítulo 7 ■ SportSStore: una aplicación real

Inicio de la tienda
Ahora que el modelo de datos está en su lugar, puedo empezar a construir la funcionalidad de la tienda, que le permitirá al usuario ver los productos para la
venta y hacer pedidos para ellos. La estructura básica de la tienda será un diseño de dos columnas, con botones de categoría que permiten que la lista de
productos que se va a filtrar y una tabla que contiene la lista de productos, como se ilustra en la figura 7-3 .

Figura 7-3. La estructura básica de la tienda

En las secciones que siguen, voy a usar rasgos angulosos y los datos en el modelo para crear el diseño mostrado en la figura.

Crear el almacén de componentes y Plantilla


A medida que se familiarice con angular, aprenderá que las características se pueden combinar para resolver el mismo problema de diferentes maneras. Trato de
introducir un poco de variedad en el proyecto SportsStore de mostrar algunas de las características angulares importantes, pero voy a mantener las cosas simples,
por el momento, en el interés de ser capaz de obtener el proyecto comenzó rápidamente.

Con esto en mente, el punto de partida para la funcionalidad de tienda será un componente nuevo, que es una clase que proporciona datos y la
lógica de una plantilla HTML, que contiene enlaces de datos que generan contenidos de forma dinámica. He creado un archivo llamado store.component.ts
en el SportsStore / src / app / tienda carpeta y se define la clase de muestra en el listado 7-13 .

Listado 7-13. El contenido de la store.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo"; {} la importación de productos de


"../model/product.model"; {} la importación ProductRepository de
"../model/product.repository";

@Componente({
Selector: "tienda",
templateUrl: "store.component.html"})

StoreComponent clase de exportación {

119
Capítulo 7 ■ SportSStore: una aplicación real

constructor (depósito privado: ProductRepository) {}

obtener productos (): Producto [] {


this.repository.getProducts retorno (); }

obtener categorías (): String [] {


this.repository.getCategories retorno (); }}

Los @ Componente decorador ha sido aplicada a la StoreComponent clase, que dice angular que es un componente. Las propiedades del
decorador dicen angular cómo aplicar el componente de contenido HTML (usando un elemento llamado almacenar) y cómo encontrar la plantilla del
componente (en un archivo llamado store.component.html).
los StoreComponent clase proporciona la lógica que apoyará el contenido de la plantilla. El constructor recibe una ProductRepository objeto
como un argumento, proporcionada a través de la función de inyección de dependencia se describe en los capítulos 20 y 21 . Los componentes
define productos y categorías propiedades que se van a utilizar para generar contenido HTML en la plantilla, utilizando los datos obtenidos desde el
repositorio. Para proporcionar el componente con su plantilla, he creado un archivo llamado store.component.html en el SportsStore / src / app /
tienda carpeta y añade el contenido HTML se muestra en el Listado 7-14 .

Listado 7-14. Los contenidos del archivo store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div>

<Div class = "fila de texto-blanco"> <div class =


"col-3-bg información P-2"> {{}} categories.length
Categorías </ div>

<Div class = "col-9-bg éxito P-2"> {{}}


products.length los productos </ div> </ div> </
div>

La plantilla es simple, sólo para empezar. La mayor parte de los elementos proporcionan la estructura para la distribución de la tienda y se
aplican algunas clases CSS Bootstrap. Sólo hay dos enlaces de datos en el momento angular, que están indicados por las {{y}} caracteres. Estos son interpolación
de cadenas encuadernaciones, y ellos dicen angular para evaluar la expresión de enlace e introduzca el resultado en el elemento. Las expresiones en
estos enlaces muestran el número de productos y categorías previstas por el componente de almacén.

Crear el módulo de funciones tienda


No hay tienda de funcionalidad tanto en el lugar en el momento, pero aun así, se requiere un trabajo adicional de unirlo al resto de la
aplicación. Para crear el módulo de función angular para la funcionalidad de la tienda, he creado un archivo llamado store.module.ts en el SportsStore
/ src / app / tienda carpeta y añade el código del listado 7-15 .

120
Capítulo 7 ■ SportSStore: una aplicación real

Listado 7-15. El contenido de la store.module.ts archivo en la carpeta src / app / tienda

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; importación
{FormsModule} de "@ angulares / formas"; {} la importación ModelModule de
"../model/model.module"; {} la importación StoreComponent de "./store.component";

@NgModule ({
importaciones: [ModelModule, BrowserModule, FormsModule], declaraciones:
[StoreComponent], las exportaciones: [StoreComponent]})

StoreModule clase de exportación {}

Los @ NgModule decorador configura el módulo, utilizando el importaciones propiedad para decirle angular que el módulo de tienda depende del
módulo de modelo, así como BrowserModule y FormsModule, que contienen las características angulares estándar para aplicaciones web y trabajar con
elementos de formulario HTML. El decorador utiliza el declaraciones propiedad para decirle angular alrededor del StoreComponent clase, que la exportaciones
propiedad dice angular puede ser utilizado también en otras partes de la aplicación, lo cual es importante porque va a ser utilizado por el módulo de
raíz.

Actualización de la raíz y de componentes del módulo de raíz


La aplicación del modelo básico y funcionalidad Store requiere la actualización del módulo raíz de la aplicación para importar los dos módulos de
características y también requiere la actualización de la plantilla del módulo de raíz para añadir el elemento HTML a la que se aplicará el componente en
el módulo de tienda. Listado 7-16 muestra el cambio de la plantilla del componente raíz.

Listado 7-16. Adición de un elemento de la app.component.ts archivo en la carpeta src / app

importación {Componente} de "@ angular / núcleo";

@Componente({
Selector: "aplicación",
plantilla: "<store> </ tienda>"
})
AppComponent clase de exportación {}

los almacenar elemento reemplaza el contenido anterior de la plantilla del componente de la raíz y se corresponde con el valor de la selector propiedad
de la @ Componente decorador en el Listado 7-13 . Listado 7-17 muestra el cambio necesario para el módulo de raíz para que Angular carga el
módulo característica que contiene la funcionalidad de tienda.

Listado 7-17. Importación de módulos de características en los app.module.ts archivo en la carpeta src / app

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; {} la
importación AppComponent de "./app.component";
{} la importación StoreModule de "./store/store.module";

@NgModule ({

121
Capítulo 7 ■ SportSStore: una aplicación real

importaciones: [BrowserModule, StoreModule],


declaraciones: [AppComponent],
bootstrap: [AppComponent]})

AppModule clase de exportación {}

Al guardar los cambios en el módulo de raíz, angular tendrá todos los detalles que necesita para cargar la aplicación y mostrar el
contenido del módulo de tienda, como se muestra en la figura 7-4 . Todos los bloques de construcción creado en la sección anterior
trabajan juntos para mostrar la verdad es simple: el contenido, lo que demuestra la cantidad de productos que hay y cuántas categorías
encajan a.

Figura 7-4. Las características básicas de la aplicación SportsStore

Añadiendo tienda cuenta con los detalles del producto


La naturaleza del desarrollo angular comienza con un inicio lento como el fundamento del proyecto se pone en su lugar y los bloques de construcción básicos son
creados. Pero una vez hecho esto, las nuevas características se pueden crear con relativa facilidad. En las secciones que siguen, añado características a la tienda
para que el usuario pueda ver los productos en oferta.

Viendo los detalles del producto


El lugar obvio para comenzar es para mostrar los detalles de los productos para que el cliente puede ver lo que está en oferta. Listado 7-18 agrega elementos
HTML a la plantilla del componente de almacén con enlaces de datos que generan contenidos para cada producto proporcionado por el componente.

Listado 7-18. La adición de elementos en el archivo store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div>

<Div class = "fila">


<Div class = "col-3-bg información p-2-texto blanco">
{{}} Categories.length Categorías </ div>

122
Capítulo 7 ■ SportSStore: una aplicación real

<Div class = "col-9 P-2">


<Div * ngFor = "dejar que el producto de los productos" class = "tarjeta de m-1 P-1 BG-luz"> <h4>

{{nombre del producto}}

<Span class = "insignia-píldora de placa-primaria flotador-derecha">


{{product.price | la divisa: "USD": "símbolo": "2.2-2"}} </ span> </ h4>

<Div class = "tarjeta de texto bg-blanca p-1"> {{}} PRODUCT.DESCRIPTION </ div> </ div> </ div>

</ Div>

La mayor parte de los elementos de control del diseño y el aspecto del contenido. El cambio más importante es la adición de una expresión
de enlace de datos angulares.

. . . <Div * ngFor = "dejar que el producto de los productos" class = "tarjeta de m-1 P-1 bg-luz">

...

Este es un ejemplo de una directiva, que transforma el elemento HTML que se aplica. Esta directiva específica se llama ngFor, y
transforma la div elemento mediante la duplicación de que para cada objeto devuelto por el componente de productos propiedad. Angular
incluye una serie de directivas incorporadas que realizan las tareas más comúnmente requerida, tal como se describe en el capítulo 13 . Ya que
duplica la div elemento, el objeto actual se asigna a una variable llamada producto, que permite que se hace referencia en otros enlaces de
datos, tales como éste, que inserta el valor del producto actual de nombre Descripción del inmueble como el contenido de la div elemento:

...
<Div class = "tarjeta de texto p-1 BG-blanco"> {{ PRODUCT.DESCRIPTION}} </ div>
...

No todos los datos en el modelo de datos de una aplicación se pueden visualizar directamente al usuario. Angular incluye una característica llamada tubería,
que son clases que se utilizan para transformar o preparar un valor de datos para su uso en un enlace de datos. Hay varias tuberías integradas incluidas con
angular, incluyendo el moneda pipa, que da formato a valores numéricos como monedas, así:

...
{{Product.price | la divisa: "USD": "símbolo": "2.2-2"}}
...

La sintaxis para la aplicación de las tuberías puede ser un poco incómodo, pero la expresión en esta unión dice angular para formatear la precio
propiedad del producto actual con el moneda tubería, con las convenciones de moneda de los Estados Unidos. Guarde los cambios en la plantilla, y
verá una lista de los productos en el modelo de datos se muestra como una larga lista, como se ilustra en la figura 7-5 .

123
Capítulo 7 ■ SportSStore: una aplicación real

Figura 7-5. Visualización de información del producto

Adición de selección de categoría

Añadir soporte para el filtrado de la lista de productos por categoría requiere la preparación del componente de almacén de modo que mantiene un registro de qué
categoría el usuario quiere visualizar y requiere cambiar la forma en que los datos se recuperan de usar esa categoría, como se muestra en el Listado 7-19 .

Listado 7-19. Añadiendo una Categoría de filtrado en los store.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo"; {} la importación de


productos de "../model/product.model";
{} la importación ProductRepository de "../model/product.repository";

@Componente({
Selector: "tienda",
templateUrl: "store.component.html"})

StoreComponent clase de exportación {


selectedCategory pública = null;

constructor (depósito privado: ProductRepository) {}

obtener productos (): Producto [] {


this.repository.getProducts (this.selectedCategory) return;
}

obtener categorías (): String [] {


this.repository.getCategories retorno (); }

changeCategory (cadena newCategory ?:) {

124
Capítulo 7 ■ SportSStore: una aplicación real

this.selectedCategory = newCategory; }

Los cambios son simples, ya que construir sobre la base de que tomó tanto tiempo para crear al comienzo del capítulo. los selectedCategory
la propiedad se le asigna la elección del usuario de categoría (donde nulo significa todas las categorías) y se utiliza en el actualizar datos método
como un argumento a la getProducts método, delegar el filtrado a la fuente de datos. los changeCategory método trae estos dos miembros juntos
en un método que puede ser invocado cuando el usuario realiza una selección de categoría.

Listado 7-20 muestra los cambios correspondientes en la plantilla del componente para proporcionar al usuario con el conjunto de botones que
cambian la categoría seleccionada y mostrar que ha sido recogido categoría.

Listado 7-20. Añadiendo Categoría Botones en el Archivo store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div>

<Div class = "fila">


<Div class = "col-3 p-2">
<Clase de botón = "btn btn-bloque BTN-outline-primaria" (click) = "changeCategory ()"> Inicio </ botón>

<Botón * ngFor = "dejar que el gato de las categorías"

class = "btn btn-outline-primaria BTN-bloque"


[Class.active] = "gato == selectedCategory" (click) = "changeCategory (cat)"> {{cat}} </ button> </
div>

<Div class = "col-9 P-2">


<Div * ngFor = "dejar que el producto de los productos" class = "tarjeta de m-1 P-1 BG-luz"> <h4>

{{nombre del producto}}

<Span class = "insignia-píldora de placa-primaria flotador-derecha">


{{product.price | la divisa: "USD": "símbolo": "2.2-2"}} </ span> </ h4>

<Div class = "tarjeta de texto bg-blanca p-1"> {{}} PRODUCT.DESCRIPTION </ div> </ div> </ div> </
div> </ div>

Hay dos nuevos botón elementos en la plantilla. La primera es una Casa botón, y se ha de unión a un evento que invoca el componente de changeCateg
método cuando se hace clic en el botón. Ningún argumento se proporciona al método, que tiene el efecto de establecer la categoría a la nulo y la
selección de todos los productos.
los ngFor La unión se ha aplicado a la otra botón elemento, con una expresión que se repetirá el elemento para cada valor en la matriz
devuelta por el componente de categorías propiedad. los botón tiene un hacer clic de enlaces de sucesos cuya expresión llama al changeCategory
Método para seleccionar la categoría actual,

125
Capítulo 7 ■ SportSStore: una aplicación real

que filtrará los productos que se muestran al usuario. También hay una clase vinculante, que añade el elemento de botón a la activo clase
cuando la categoría asociada con el botón es la categoría seleccionada. Esto proporciona al usuario con información visual cuando se filtran
las categorías, como se muestra en la figura 7-6 .

Figura 7-6. La selección de categorías de productos

La adición de Producto paginación

Filtrado de los productos por categorías ha ayudado a hacer la lista de productos más manejables, pero un enfoque más típica es la de romper la lista en
secciones más pequeñas y presentar cada uno de ellos como una página, junto con los botones de navegación que se mueven entre las páginas. Listado 7-21
Aumenta el componente tienda para que no pierde de vista la página actual y el número de elementos de una página.

Listado 7-21. La adición de paginación apoyo en la store.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo"; {} la importación de


productos de "../model/product.model";
{} la importación ProductRepository de "../model/product.repository";

@Componente({
Selector: "tienda",
templateUrl: "store.component.html"})

StoreComponent clase de exportación {


selectedCategory pública = null;
productsPerPage pública = 4;
selectedPage pública = 1;

constructor (depósito privado: ProductRepository) {}

obtener productos (): Producto [] {


dejar que pageIndex = (this.selectedPage - 1) * this.productsPerPage
this.repository.getProducts de retorno (this.selectedCategory)
. rebanada (pageIndex, pageIndex + this.productsPerPage);
}

126
Capítulo 7 ■ SportSStore: una aplicación real

obtener categorías (): String [] {


this.repository.getCategories retorno (); }

changeCategory (cadena newCategory ?:) {


this.selectedCategory = newCategory; }

changePage (newPage: número) {


this.selectedPage = newPage; }

changePageSize (newSize: número) {


this.productsPerPage = Número (newSize);
this.changePage (1); }

obtener números de página (): número de [] {

volver Array (Math.ceil (this.repository


. getProducts (this.selectedCategory) .length / this.productsPerPage))
. llenar (0) .map ((x, i) => i + 1);
}
}

Hay dos nuevas características en este listado. El primero es la capacidad de conseguir una página de productos, y el segundo es para cambiar el tamaño de
las páginas, permitiendo que el número de productos que contiene cada página a ser alterado.
No es una rareza que el componente tiene que evitar. Hay una limitación en el built-in ngFor
directiva que Angular ofrece, que puede generar contenido sólo para los objetos en una matriz o una colección, en lugar de utilizar un contador. Desde que
necesita para generar botones de navegación de páginas numeradas, esto significa que necesito para crear una matriz que contiene los números que
necesito, así:

...
volver Array (Math.ceil (this.repository.getProducts (this.selectedCategory) .length
/ This.productsPerPage)). llenar( 0). mapa(( x, i) => i + 1);
...

Esta sentencia crea una nueva matriz, lo llena con el valor 0, y luego utiliza la mapa método para generar una nueva matriz con la secuencia de
números. Esto funciona bastante bien como para implementar la función de paginación, pero se siente incómodo, y que demuestran un mejor enfoque en
la siguiente sección. Listado 7-22 muestra los cambios en la plantilla del componente de almacén para implementar la función de paginación.

Listado 7-22. La adición de la paginación en el Archivo de store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div>

<Div class = "fila">


<Div class = "col-3 p-2">
<Clase de botón = "btn btn-bloque BTN-outline-primaria" (click) = "changeCategory ()">

127
Capítulo 7 ■ SportSStore: una aplicación real

Inicio </
botón>
<Botón * ngFor = "dejar que el gato de las categorías"
class = "btn btn-outline-primaria BTN-bloque"
[Class.active] = "gato == selectedCategory" (click) = "changeCategory (cat)"> {{cat}} </ button> </
div>

<Div class = "col-9 P-2">


<Div * ngFor = "dejar que el producto de los productos" class = "tarjeta de m-1 P-1 BG-luz"> <h4>

{{nombre del producto}}

<Span class = "insignia-píldora de placa-primaria flotador-derecha">


{{product.price | la divisa: "USD": "símbolo": "2.2-2"}} </ span> </ h4>

<Div class = "tarjeta de texto bg-blanca p-1"> {{}} PRODUCT.DESCRIPTION </ div> </ div>

<Div class = "forma-inline flotador-izquierda mr-1">


<Select class = "-control de formulario" [valor] = "productsPerPage"
(Modificar) = "changePageSize ($ event.target.value)"> <option value
=> 3 por página </ option> <valor "3" opción = "4"> 4 por página </ option>
<option value = "6"> 6 por página </ option> <option value = "8"> 8 por página </
option> </ select> </ div>

<Div class = "grupo flotador btn-derecha">


<Botón * ngFor = "dejar que la página de números de página" (click) = "changePage (página)"

class = "btn btn-outline-primaria" [class.active] =


"página == selectedPage"> {{página}} </ button> </ div>

</ Div> </


div> </ div>

Los nuevos elementos añaden una seleccionar elemento que permite que el tamaño de la página para cambiar y un conjunto de botones que navegan a través de

las páginas de productos. Los nuevos elementos tienen enlaces de datos para cablear hacia arriba a las propiedades y métodos proporcionados por el componente. El

resultado es un conjunto más manejable de productos, como se muestra en la figura 7-7 .

128
Capítulo 7 ■ SportSStore: una aplicación real

■ Propina la seleccionar elemento de la lista 7-22 se rellena con opción elementos que están definidas estáticamente, en lugar de crear utilizando los

datos desde el componente. Una de las consecuencias de esto es que cuando el valor seleccionado se pasa a la changePageSize método, que será

una cuerda valor, por lo que el argumento se analiza a una

número antes de ser utilizados para establecer el tamaño de página en la lista 7-21 . Se debe tener cuidado cuando se recibe valores de datos de los

elementos HTML para asegurar que son del tipo esperado. Tipo mecanografiado anotaciones no ayudan en esta situación porque la expresión de enlace

de datos se evalúa en tiempo de ejecución, mucho después de que el compilador mecanografiado ha generado el código JavaScript que no contiene la

información de tipo adicional.

Figura 7-7. Paginación para productos

129
Capítulo 7 ■ SportSStore: una aplicación real

Creación de una directiva personalizada

En esta sección, voy a crear una directiva personalizada para que yo no tengo que generar una amplia gama de números para crear los botones de
navegación. Angular ofrece una buena gama de directivas incorporadas, pero es un proceso simple para crear sus propias directivas para resolver
problemas que son específicos de su aplicación o para apoyar características que las directivas incorporadas no tienen. He añadido un archivo llamado counter.directive.ts
en el
src / app / tienda carpeta y lo utilizó para definir la clase de muestra en el listado 7-23 .

Listado 7-23. El contenido de la counter.directive.ts archivo en la carpeta src / app / tienda

{importación

Directiva, ViewContainerRef, TemplateRef, de entrada, Atributo, SimpleChanges} de "@ angular /


núcleo";

@Directiva({
Selector: "[counterOf]"})

CounterDirective clase de exportación {

constructor (privado de contenedores: ViewContainerRef,


plantilla privada: TemplateRef <objeto>) {}

@Input ( "counterOf")
contador: número;

ngOnChanges (cambios: SimpleChanges) {


this.container.clear ();
para (dejar que i = 0; i <this.counter; i ++) {
this.container.createEmbeddedView (this.template,
nuevo CounterDirectiveContext (i + 1)); }}}

CounterDirectiveContext clase {
constructor ($ pública implícita: los hay) {}}

Este es un ejemplo de una directiva estructural, que se describe en detalle en el capítulo dieciséis . esta directiva
se aplica a los elementos a través de una mostrador propiedad y se basa en las características especiales que ofrece angular para la creación de contenido en varias
ocasiones, al igual que el built-in ngFor directiva. En este caso, en lugar de producir cada objeto en una colección, la directiva personalizada produce una serie de
números que se pueden utilizar para crear los botones de navegación.

■ Propina esta directiva elimina todo el contenido que ha creado y comienza de nuevo cuando el número de páginas cambios. esto puede ser

un proceso costoso en las directivas más complejas, y explicar cómo mejorar el rendimiento en el capítulo dieciséis .

130
Capítulo 7 ■ SportSStore: una aplicación real

Para usar la directiva, debe ser añadido a la declaraciones propiedad de su módulo de función, como se muestra en el Listado 7-24 .

Listado 7-24. Registro de la Directiva personalizado en el store.module.ts archivo en la carpeta src / app / tienda

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; importación
{FormsModule} de "@ angulares / formas"; {} la importación ModelModule de
"../model/model.module"; {} la importación StoreComponent de "./store.component";

{} la importación CounterDirective de "./counter.directive";

@NgModule ({
importaciones: [ModelModule, BrowserModule, FormsModule],
declaraciones: [StoreComponent, CounterDirective],
exportaciones: [StoreComponent]})

StoreModule clase de exportación {}

Ahora que la directiva ha sido registrado, se puede utilizar en la plantilla del componente de almacén para reemplazar el ngFor Directiva, como se
muestra en el Listado 7-25 .

Listado 7-25. Sustitución de la Directiva incorporado en el archivo store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div>

<Div class = "fila">

<Div class = "col-3 p-2">


<Clase de botón = "btn btn-bloque BTN-outline-primaria" (click) = "changeCategory ()"> Inicio </ botón>

<Botón * ngFor = "dejar que el gato de las categorías"


class = "btn btn-outline-primaria BTN-bloque"
[Class.active] = "gato == selectedCategory" (click) = "changeCategory (cat)"> {{cat}} </ button> </
div>

<Div class = "col-9 P-2">


<Div * ngFor = "dejar que el producto de los productos" class = "tarjeta de m-1 P-1 BG-luz"> <h4>

{{nombre del producto}}

<Span class = "insignia-píldora de placa-primaria flotador-derecha">


{{product.price | la divisa: "USD": "símbolo": "2.2-2"}} </ span> </ h4>

<Div class = "tarjeta de texto bg-blanca p-1"> {{}} PRODUCT.DESCRIPTION </ div>

131
Capítulo 7 ■ SportSStore: una aplicación real

</ Div>

<Div class = "forma-inline flotador-izquierda mr-1">


<Select class = "-control de formulario" [valor] = "productsPerPage"
(Modificar) = "changePageSize ($ event.target.value)"> <option value
=> 3 por página </ option> <valor "3" opción = "4"> 4 por página </ option>
<option value = "6"> 6 por página </ option> <option value = "8"> 8 por página </
option> </ select> </ div>

<Div class = "grupo flotador btn-derecha">


<Botón * contador = "dejar que la página de pageCount" (click) = "changePage (página)"
class = "btn btn-outline-primaria" [class.active] = "página == selectedPage"> {{página}} </ button>
</ div>

</ Div> </


div> </ div>

Los nuevos datos de unión se basa en una propiedad llamada pageCount para configurar la directiva personalizada. en el Listado 7-26 , He sustituido
la matriz de números con un simple número que proporciona el valor de expresión.

Listado 7-26. El apoyo a la Directiva personalizado en el store.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo"; {} la importación de


productos de "../model/product.model";
{} la importación ProductRepository de "../model/product.repository";

@Componente({
Selector: "tienda",
templateUrl: "store.component.html"})

StoreComponent clase de exportación {


selectedCategory pública = null;
productsPerPage pública = 4; selectedPage
pública = 1;

constructor (depósito privado: ProductRepository) {}

obtener productos (): Producto [] {


dejar que pageIndex = (this.selectedPage - 1) * this.productsPerPage
this.repository.getProducts de retorno (this.selectedCategory)
. rebanada (pageIndex, pageIndex + this.productsPerPage); }

obtener categorías (): String [] {


this.repository.getCategories retorno (); }

132
Capítulo 7 ■ SportSStore: una aplicación real

changeCategory (cadena newCategory ?:) {


this.selectedCategory = newCategory; }

changePage (newPage: número) {


this.selectedPage = newPage; }

changePageSize (newSize: número) {


this.productsPerPage = Número (newSize);
this.changePage (1); }

obtener pageCount (): Número {


volver Math.ceil (this.repository
. getProducts (this.selectedCategory) .length / this.productsPerPage)}

// obtener números de página (): número de [] {//


retorno Array (Math.ceil (this.repository //
. getProducts (this.selectedCategory) .length / this.productsPerPage))
// . llenar (0) .map ((x, i) => i + 1);
//}
}

No hay ningún cambio visual para la aplicación SportsStore, pero esta sección ha demostrado esto, es posible complementar la funcionalidad
angular integrado con un código personalizado que se adapte a las necesidades de un proyecto específico.

Resumen
En este capítulo, empecé el proyecto SportsStore. La primera parte del capítulo se gastó la creación de las bases para el proyecto, incluyendo la creación de los
bloques de construcción de la raíz de la aplicación y empezar a trabajar en los módulos de características. Una vez que la base estaba en su lugar, yo era capaz de
añadir rápidamente características para mostrar los datos del modelo de maniquí para el usuario, agregar la paginación, y filtrar los productos por categoría. Terminé
el capítulo mediante la creación de una directiva personalizada para demostrar cómo las características incorporadas proporcionadas por angular pueden
complementarse con código personalizado. En el siguiente capítulo, continúo para construir la aplicación SportsStore.

133
CAPÍTULO 8

Spor tsStore: Órdenes y Pedido

En este capítulo, continúo la adición de características a la aplicación SportsStore que he creado en el Capítulo 7 . Agrego soporte para un carro
de compras y un proceso de pago y vuelva a colocar los datos ficticios con los datos del servicio web REST.

Preparación del Ejemplo de Aplicación


No se requiere ninguna preparación para este capítulo, que sigue utilizando el proyecto SportsStore del capítulo 7 . Para iniciar el servicio web
REST, abra un símbolo del sistema y ejecute el comando siguiente en el Tienda de deportes
carpeta:

NPM plazo JSON

Abrir una segunda línea de comandos y ejecute el comando siguiente en el Tienda de deportes carpeta para iniciar las herramientas de desarrollo
y servidor HTTP:

ng servir --port 3000 --open

■ Propina Puede descargar el proyecto de ejemplo para este capítulo, y para todos los demás capítulos de este libro: de https://github.com/Apress/pro
.

Creación de la Cesta
El usuario necesita un carro en el que los productos pueden ser colocados y utilizados para iniciar el proceso de pago. En las secciones que siguen, voy a añadir
un carrito de la aplicación e integrarla en la tienda de manera que el usuario puede seleccionar los productos que desean.

Creación de la cesta Modelo


El punto de partida para la función de la compra es una nueva clase de modelo que se utilizará para reunir a los productos que el usuario ha seleccionado.
He añadido un archivo llamado cart.model.ts en el src / app / modelo carpeta y lo utilizó para definir la clase de muestra en el listado 8-1 .

© 2018 Adam Freeman 135


A. Freeman, Pro angular 6, https://doi.org/10.1007/978-1-4842-3649-9_8
Capítulo 8 ■ Spor tSStore: órdenes y salida

Listado 8-1. El contenido de la cart.model.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación de productos de "./product.model";

@Injectable ()
clase de exportación de la compra {

líneas públicas: CartLine [] = [];


objetoCuenta pública: Number = 0;
cartPrice pública: Number = 0;

AddLine (producto: El producto, cantidad: número = 1) {


dejar que la línea = this.lines.find (línea => line.product.id == product.id); si (línea! = no
definido) {
line.quantity + = cantidad; } Else {

this.lines.push (nuevo CartLine (producto, cantidad)); }

this.recalculate (); }

updateQuantity (producto: El producto, cantidad: número) {


dejar que la línea = this.lines.find (línea => line.product.id == product.id); si (línea! = no
definido) {
line.quantity = Número (cantidad); }

this.recalculate (); }

removeLine (id: número) {


dejar que el índice = this.lines.findIndex (línea => line.product.id == id); this.lines.splice
(índice, 1); this.recalculate (); }

claro() {
this.lines = [];
this.itemCount = 0;
this.cartPrice = 0; }

Volver a calcular privada () {


this.itemCount = 0; this.cartPrice =
0; this.lines.forEach (l => {

this.itemCount + = l.quantity;
this.cartPrice + = (l.quantity * l.product.price); })}}

136
Capítulo 8 ■ Spor tSStore: órdenes y salida

CartLine clase de exportación {

constructor (producto pública: Producto,


Cantidad pública: número) {}

obtener LineTotal () {
volver this.quantity * this.product.price; }}

selecciones de productos individuales se representan como un conjunto de CartLine objetos, cada uno de los cuales contiene una Producto objeto
y una cantidad. los Carro clase no pierde de vista el número total de elementos que se han seleccionado y su coste total.

Debe haber una sola Carro objeto que se utiliza en toda la aplicación, lo que garantiza que cualquier parte de la aplicación puede acceder
a la selección de productos del usuario. Para lograr esto, voy a hacer el Carro un servicio, lo que significa que angular tendrá la responsabilidad
de crear una instancia de la Carro clase y lo utilizará cuando se necesita para crear un componente que tiene una Carro argumento del
constructor. Este es otro uso de la función de inyección de dependencia angular, que puede ser utilizado para compartir objetos a lo largo de
una aplicación y que se describe en detalle en los capítulos 19 y 20 . Los @ inyectable decorador, que ha sido aplicado a la Carro clase en el perfil,
indica que esta clase será utilizada como un servicio.

■ Nota Estrictamente hablando, el @ inyectable Se requiere decorador sólo cuando una clase tiene sus propios argumentos de constructor para resolver,

pero es una buena idea para aplicar de todos modos, ya que sirve como una señal de que la clase es para uso como un servicio.

Listado 8-2 registra el Carro clase como un servicio en el proveedores propiedad de la clase módulo de función del modelo.

Listado 8-2. El registro del carro, como un servicio en el archivo model.module.ts en la carpeta src / app / modelo

importación {NgModule} de "@ angular / núcleo";


{} la importación ProductRepository de "./product.repository"; {} la importación
StaticDataSource de "./static.datasource";
{} la importación de la compra de "./cart.model";

@NgModule ({
proveedores: [ProductRepository, StaticDataSource, Cesta]
})
ModelModule clase de exportación {}

La creación de los componentes del carro Resumen

Los componentes son los elementos esenciales para aplicaciones angular, ya que permiten unidades discretas de código y contenido a crearse
fácilmente. La aplicación SportsStore mostrará a los usuarios un resumen de sus selecciones de productos en el área de título de la página, lo que
voy a poner en práctica mediante la creación de un componente. He añadido un archivo llamado cartSummary.component.ts en el src / app / tienda carpeta
y lo utilizó para definir el componente de muestra en el listado 8-3 .

137
Capítulo 8 ■ Spor tSStore: órdenes y salida

El listado 8-3. El contenido de la cartSummary.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo"; {} la importación


de la compra de "../model/cart.model";

@Componente({
Selector: "cesta-Resumen",
templateUrl: "cartSummary.component.html"})

CartSummaryComponent clase de exportación {

constructor (compra pública: Cesta) {}}

Cuando angular necesita para crear una instancia de este componente, se tendrá que proporcionar una Carro objeto como un argumento del constructor,

utilizando el servicio que he configurado en la sección anterior, añadiendo el Carro la clase a la década de los módulos de funciones proveedores propiedad. El

comportamiento por defecto para los servicios significa que una sola Carro

objeto se crea y se comparte en toda la aplicación, aunque hay diferentes comportamientos de servicios disponibles (como se describe en el
capítulo 20 ).
Para proporcionar el componente con una plantilla, he creado un archivo HTML llamado cartSummary.component.html
en la misma carpeta que el archivo de clase de componente añadido y el marcado se muestra en el Listado 8-4 .

Listado 8-4. Los contenidos del cartSummary.component.html archivo en la carpeta src / app / tienda

<Div class = "float-derecha">


<small>
Su cesta:
<Span * ngIf = "cart.itemCount> 0"> elemento
{{}} cart.itemCount (s)
{{Cart.cartPrice | la divisa: "USD": "símbolo": "2.2-2"}} </ span>

<Span * ngIf = "cart.itemCount == 0"> (vacío) </


span> </ small>

<Clase de botón = "btn btn-sm bg-oscuro texto-blanco"


[Desactivado] = "cart.itemCount == 0"> <i class =
"fa fa-carro de la compra"> </ i> </ button> </ div>

Esta plantilla utiliza la Carro objeto proporcionado por su componente para visualizar el número de artículos en el carro y el costo total. También
hay un botón que se iniciará el proceso de pago cuando lo añado a la aplicación más adelante en el capítulo.

■ Propina el elemento de botón en el Listado 8-4 se labró el uso de clases definidas por Font impresionante, que es uno de los paquetes en el package.json

presentar en el capítulo 7 . este paquete de código abierto ofrece un excelente soporte para iconos de aplicaciones web, incluyendo el carrito de la

compra que necesito para la aplicación SportsStore. Ver http: // fontawesome.io para detalles.

138
Capítulo 8 ■ Spor tSStore: órdenes y salida

Listado 8-5 registra el nuevo componente con el módulo de función tienda, en preparación para su uso en la siguiente sección.

Listado 8-5. Registrar el componente en el archivo store.module.ts en la carpeta src / app / tienda

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular";
importación {FormsModule} de "@ angulares / formas"; {} la importación ModelModule
de "../model/model.module"; {} la importación StoreComponent de
"./store.component"; {} la importación CounterDirective de "./counter.directive";

{} la importación CartSummaryComponent de "./cartSummary.component";

@NgModule ({
importaciones: [ModelModule, BrowserModule, FormsModule],
declaraciones: [StoreComponent, CounterDirective, CartSummaryComponent],
exportaciones: [StoreComponent]})

StoreModule clase de exportación {}

La integración del carro en la tienda


El componente de tienda es la clave para integrar el carro y el widget de la compra en la aplicación. Listado 8-6
actualiza el componente de almacén para que su constructor tiene una Carro parámetro y define un método que va a añadir un producto a la cesta.

Listado 8-6. La adición de la compra Atención en el Archivo store.component.ts en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo"; {} la importación de productos de


"../model/product.model"; {} la importación ProductRepository de
"../model/product.repository";
{} la importación de la compra de "../model/cart.model";

@Componente({
Selector: "tienda",
templateUrl: "store.component.html"})

StoreComponent clase de exportación {


selectedCategory pública = null;
productsPerPage pública = 4; selectedPage
pública = 1;

constructor (depósito privado: ProductRepository,


compra privada: Cesta) {}

obtener productos (): Producto [] {


dejar que pageIndex = (this.selectedPage - 1) * this.productsPerPage
this.repository.getProducts de retorno (this.selectedCategory)
. rebanada (pageIndex, pageIndex + this.productsPerPage); }

139
Capítulo 8 ■ Spor tSStore: órdenes y salida

obtener categorías (): String [] {


this.repository.getCategories retorno (); }

changeCategory (cadena newCategory ?:) {


this.selectedCategory = newCategory; }

changePage (newPage: número) {


this.selectedPage = newPage; }

changePageSize (newSize: número) {


this.productsPerPage = Número (newSize);
this.changePage (1); }

obtener pageCount (): Número {


volver Math.ceil (this.repository
. getProducts (this.selectedCategory) .length / this.productsPerPage)}

addProductToCart (producto: Producto) {


this.cart.addLine (producto); }

Para completar la integración del carro en el componente de almacén, el Listado 8-7 agrega el elemento que se aplicará el componente
resumen del carrito de la plantilla del componente de almacén y añade un botón para cada descripción del producto con la unión que llama al
evento addProductToCart método.

Listado 8-7. La aplicación del componente en el archivo store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a DEPORTES
class="navbar-brand"> TIENDA </a>
<Cesta-summary> </ cesta-summary>
</ Div> </
div>
<Div class = "fila">

<Div class = "col-3 p-2">


<Clase de botón = "btn btn-bloque BTN-outline-primaria" (click) = "changeCategory ()"> Inicio </ botón>

<Botón * ngFor = "dejar que el gato de las categorías"


class = "btn btn-outline-primaria BTN-bloque"
[Class.active] = "gato == selectedCategory" (click) = "changeCategory (cat)"> {{cat}} </ button> </
div>

140
Capítulo 8 ■ Spor tSStore: órdenes y salida

<Div class = "col-9 P-2">


<Div * ngFor = "dejar que el producto de los productos" class = "tarjeta de m-1 P-1 BG-luz"> <h4>

{{nombre del producto}}

<Span class = "insignia-píldora de placa-primaria flotador-derecha">


{{product.price | la divisa: "USD": "símbolo": "2.2-2"}} </ span> </ h4>

<Div class = "tarjeta de texto bg-blanca p-1"> {{}}


PRODUCT.DESCRIPTION
<Clase de botón = "btn btn-éxito BTN-sm flotador-derecha"
(Click) = "addProductToCart (producto)"> Añadir a la
Cesta </ ​botón>

</ Div> </


div>

<Div class = "forma-inline flotador-izquierda mr-1">


<Select class = "-control de formulario" [valor] = "productsPerPage"
(Modificar) = "changePageSize ($ event.target.value)"> <option value
=> 3 por página </ option> <valor "3" opción = "4"> 4 por página </ option>
<option value = "6"> 6 por página </ option> <option value = "8"> 8 por página </
option> </ select> </ div>

<Div class = "grupo flotador btn-derecha">


<Botón * contador = "dejar que la página de pageCount" (click) = "changePage (página)"
class = "btn btn-outline-primaria" [class.active] = "página == selectedPage"> {{página}} </ button>
</ div>

</ Div> </


div> </ div>

El resultado es un botón para cada producto que lo añade al carro, como se muestra en la figura 8-1 . El carro lleno
proceso no está completo, pero se puede ver el efecto de cada adición en el resumen de la cesta en la parte superior de la página.

141
Capítulo 8 ■ Spor tSStore: órdenes y salida

Figura 8-1. Añadir soporte de la cesta de la aplicación SportsStore

Observe cómo hacer clic en uno de los botones Añadir al carro actualiza el contenido del componente Resumen de forma automática. Esto
sucede porque hay una sola Carro objeto que se comparte entre dos componentes y los cambios realizados por uno de los componentes se reflejan
cuando Angular evalúa las expresiones de unión en el otro componente de datos.

La adición de enrutamiento de URL


La mayoría de las aplicaciones tienen que mostrar un contenido diferente a los usuarios en diferentes momentos. En el caso de la aplicación SportsStore, cuando el
usuario hace clic en uno de los botones Añadir al carro, que se deben mostrar una vista detallada de sus productos seleccionados y se les da la oportunidad de
iniciar el proceso de pago.
Angular es compatible con una función llamada enrutamiento de URL, que utiliza la URL actual mostrada por el navegador para seleccionar los
componentes que se muestran al usuario. Este es un enfoque que hace que sea fácil para crear aplicaciones cuyos componentes son de estructura
flexible y fácil de cambiar sin necesidad de modificaciones correspondientes en otra parte de las aplicaciones. enrutamiento de URL también hace que sea
fácil cambiar el camino que sigue un usuario a través de una aplicación.

Para la aplicación SportsStore, voy a añadir soporte para tres direcciones URL diferentes, que se describen en la tabla 8-1 . Esta es una configuración
simple, pero el sistema de encaminamiento tiene un montón de características, que se describen en detalle en los capítulos 25 a 27 .

Tabla 8-1. Las direcciones URL soportadas por la aplicación SportsStore

URL Descripción

/almacenar Esta URL se mostrará la lista de productos.

/carro Esta URL se mostrará la compra del usuario en detalle.

/revisa Esta URL se mostrará el proceso de pago.

142
Capítulo 8 ■ Spor tSStore: órdenes y salida

En las secciones que siguen, se crea componentes de marcador de posición para las etapas carrito SportsStore y salida orden y luego integrarlos en
la aplicación mediante el enrutamiento de URL. Una vez que se implementan las direcciones URL, voy a volver a los componentes y añadir más funciones
útiles.

Crear el carro Detallada y Pedido Componentes


Antes de añadir el enrutamiento de URL de la aplicación, lo que necesito para crear los componentes que serán mostrados por el /
carro y / revisa URLs. Sólo necesito un poco de contenido básico marcador de posición para empezar, simplemente para que sea obvio qué componente se está
visualizando. Empecé mediante la adición de un archivo llamado cartDetail.component.ts en el src / app / tienda carpeta y se define el componente de muestra en
el listado 8-8 .

Listado 8-8. El contenido de la cartDetail.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo";

@Componente({
Plantilla: `<div> <h3 class = "bg-p-1 información de texto blanco"> carro Detallada de componentes </ h3> </ div>`})

CartDetailComponent clase de exportación {}

A continuación, he añadido un archivo llamado checkout.component.ts en el src / app / tienda carpeta y se define el componente de muestra en el
listado 8-9 .

Listado 8-9. El contenido de la checkout.component.ts archivo en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo";

@Componente({
Plantilla: `<div> <h3 class = "bg-p-1 información de texto blanco"> Pedido de componentes </ h3> </ div>`})

CheckoutComponent clase de exportación {}

Este componente sigue el mismo patrón que el componente de la cesta y muestra un mensaje de marcador de posición. Listado 8-10 registra los
componentes en el módulo de función tienda y los añade a la exportaciones propiedad, lo que significa que se pueden utilizar en otras partes de la
aplicación.

Listado 8-10. Registro de componentes en el store.module.ts archivo en la carpeta src / app / tienda

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; importación
{FormsModule} de "@ angulares / formas"; {} la importación ModelModule de
"../model/model.module"; {} la importación StoreComponent de "./store.component"; {} la
importación CounterDirective de "./counter.directive"; {} la importación
CartSummaryComponent de "./cartSummary.component";

{} la importación CartDetailComponent de "./cartDetail.component"; {} la importación


CheckoutComponent de "./checkout.component";

@NgModule ({
importaciones: [ModelModule, BrowserModule, FormsModule],
declaraciones: [StoreComponent, CounterDirective, CartSummaryComponent,
CartDetailComponent, CheckoutComponent],

143
Capítulo 8 ■ Spor tSStore: órdenes y salida

exportaciones: [StoreComponent, CartDetailComponent, CheckoutComponent]


})
StoreModule clase de exportación {}

Creación y aplicación de la configuración de enrutamiento


Ahora que tengo una amplia gama de componentes para mostrar, el siguiente paso es crear la configuración de encaminamiento que comunique angular cómo asignar

las direcciones URL en componentes. Cada asignación de un URL a un componente que se conoce como una

ruta URL o sólo una ruta. En la parte 3, donde puedo crear configuraciones de enrutamiento más complejas, defino las rutas en un archivo separado, pero
para este proyecto, voy a seguir un enfoque más sencillo y definir las rutas dentro de la
@NgModule decorador del módulo raíz de la aplicación, como se muestra en el Listado 8-11 .

■ Propina la función de enrutamiento angular requiere una base elemento en el documento HTML, que proporciona la URL de base contra la cual se

aplican las rutas. este elemento se añadió a la index.html presentar por el ng nueva comando cuando creé el proyecto SportsStore en el capítulo 7 . Si se

omite el elemento angular informará de un error y ser incapaces de aplicar las rutas.

Listado 8-11. Creación de la configuración de enrutamiento en los app.module.ts archivo en la carpeta src / app

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; {} la
importación AppComponent de "./app.component";
{} la importación StoreModule de "./store/store.module"; {} la importación StoreComponent de
"./store/store.component"; {} la importación CheckoutComponent de
"./store/checkout.component"; {} la importación CartDetailComponent de
"./store/cartDetail.component"; {} la importación RouterModule de "@ angular / enrutador";

@NgModule ({
importaciones: [BrowserModule, StoreModule,
RouterModule.forRoot ([
{Ruta: "tienda", componente: StoreComponent}, {ruta: "carrito",
componente: CartDetailComponent}, {ruta: "checkout",
componente: CheckoutComponent}, {ruta: "**", RedirectTo: "/
tienda" }])],

declaraciones: [AppComponent],
bootstrap: [AppComponent]})

AppModule clase de exportación {}

los RouterModule.forRoot método se aprobó un conjunto de rutas, cada una de las cuales se asigna una dirección URL a un componente. Las tres
primeras rutas en el listado coinciden con las direcciones URL de la tabla 8-1 . La ruta final es un comodín que redirige cualquier otra URL a / almacenar, que
mostrará StoreComponent.
Cuando se utiliza la función de enrutamiento, angular busca la enrutador de salida elemento, que define la ubicación en la que
el componente que corresponde a la URL actual se debe mostrar. Listado 8-12
sustituye a la almacenar elemento de la plantilla de la raíz con el componente enrutador de salida elemento.

144
Capítulo 8 ■ Spor tSStore: órdenes y salida

Listado 8-12. Definir el enrutamiento de destino en los app.component.ts archivo en la carpeta src / app

importación {Componente} de "@ angular / núcleo";

@Componente({
Selector: "aplicación",
plantilla: "<enrutador de salida> </ enrutador de salida>"
})
AppComponent clase de exportación {}

Angular se aplicará la configuración de enrutamiento al guardar los cambios y el navegador vuelve a cargar el documento HTML. El contenido que
se muestra en la ventana del navegador no ha cambiado, pero si se examina la barra de direcciones del navegador, usted será capaz de ver que la
configuración de enrutamiento se ha aplicado, como se muestra en la figura 8-2 .

Figura 8-2. El efecto de enrutamiento de URL

Navegación a través de la Aplicación


Con la configuración de enrutamiento en su lugar, es el momento de añadir soporte para navegar entre los componentes cambiando la URL del navegador. La
función de enrutamiento de URL se basa en una API de JavaScript que proporciona el navegador, lo que significa que el usuario no puede simplemente escriba la
dirección URL de destino en la barra de direcciones del navegador. En cambio, la navegación tiene que ser realizada por la aplicación, ya sea mediante el uso de
código JavaScript en un bloque de componentes u otro edificio o añadiendo atributos a elementos HTML de la plantilla.

Cuando el usuario hace clic en uno de los botones Añadir al carro, el componente de la cesta detalle debe ser mostrado, lo que significa que la
aplicación debe navegar a la / carro URL. Listado 8-13 añade navegación para el método de componentes que se invoca cuando el usuario hace clic en el
botón.

Listado 8-13. Navegando Uso de JavaScript en el archivo store.component.ts en la aplicación / src / carpeta de almacén

importación {Componente} de "@ angular / núcleo"; {} la importación de productos de


"../model/product.model"; {} la importación ProductRepository de "../model/product.repository";
{} la importación de la compra de "../model/cart.model";

{} la importación router de "@ angular / enrutador";

145
Capítulo 8 ■ Spor tSStore: órdenes y salida

@Componente({
Selector: "tienda",
templateUrl: "store.component.html"})

StoreComponent clase de exportación {


selectedCategory pública = null;
productsPerPage pública = 4; selectedPage
pública = 1;

constructor (depósito privado: ProductRepository,


compra privada: la compra,
enrutador privada: Router) {}

obtener productos (): Producto [] {


dejar que pageIndex = (this.selectedPage - 1) * this.productsPerPage
this.repository.getProducts de retorno (this.selectedCategory)
. rebanada (pageIndex, pageIndex + this.productsPerPage); }

obtener categorías (): String [] {


this.repository.getCategories retorno (); }

changeCategory (cadena newCategory ?:) {


this.selectedCategory = newCategory; }

changePage (newPage: número) {


this.selectedPage = newPage; }

changePageSize (newSize: número) {


this.productsPerPage = Número (newSize);
this.changePage (1); }

obtener pageCount (): Número {


volver Math.ceil (this.repository
. getProducts (this.selectedCategory) .length / this.productsPerPage)}

addProductToCart (producto: Producto) {


this.cart.addLine (producto);
this.router.navigateByUrl ( "/ carro");
}}

El constructor tiene una Router parámetro, que es proporcionada por angular a través de la función de inyección de dependencia cuando
se crea una nueva instancia del componente. En el addProductToCart método, el
Router.navigateByUrl método se utiliza para navegar a la / carro URL.

146
Capítulo 8 ■ Spor tSStore: órdenes y salida

La navegación también se puede hacer mediante la adición de la routerLink atribuir a los elementos de la plantilla. en el Listado 8-14 , la
routerLink atributo se ha aplicado al botón del carro en la plantilla de la compra de componentes de resumen.

Listado 8-14. Adición de una navegación en el archivo cartSummary.component.html en la carpeta src / app / tienda

<Div class = "float-derecha">


<small> Su cesta:

<Span * ngIf = "cart.itemCount> 0"> elemento


{{}} cart.itemCount (s)
{{Cart.cartPrice | la divisa: "USD": "símbolo": "2.2-2"}} </ span>

<Span * ngIf = "cart.itemCount == 0"> (vacío) </


span> </ small>

<Clase de botón = "btn btn-sm bg-oscuro texto-blanco"


[Desactivado] = "cart.itemCount == 0" routerLink = "/ carro">
<Clase i = "fa fa-carro de la compra"> </ i> </ button>
</ div>

El valor especificado por la routerLink atributo es la dirección URL que la aplicación se vaya a cuando el
botón se hace clic. Este botón concreto se desactiva cuando el carro está vacío, por lo que llevará a cabo la navegación sólo cuando el usuario ha
añadido un producto a la cesta.
Para añadir soporte para el routerLink atributo, el RouterModule módulo debe ser importada en el módulo de función, como se muestra
en el Listado 8-15 .

Listado 8-15. Importación del módulo de router en la store.module.ts archivo en la carpeta src / app / tienda

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; importación
{FormsModule} de "@ angulares / formas"; {} la importación ModelModule de
"../model/model.module"; {} la importación StoreComponent de "./store.component"; {} la
importación CounterDirective de "./counter.directive"; {} la importación
CartSummaryComponent de "./cartSummary.component"; {} la importación
CartDetailComponent de "./cartDetail.component"; {} la importación CheckoutComponent de
"./checkout.component";

{} la importación RouterModule de "@ angular / enrutador";

@NgModule ({
importaciones: [ModelModule, BrowserModule, FormsModule, RouterModule],
declaraciones: [StoreComponent, CounterDirective, CartSummaryComponent,
CartDetailComponent, CheckoutComponent],
exportaciones: [StoreComponent, CartDetailComponent, CheckoutComponent]})

StoreModule clase de exportación {}

Para ver el efecto de la navegación, guardar los cambios de los archivos y, una vez que el navegador ha vuelto a cargar el documento HTML, haga
clic en uno de los botones Añadir al carro. El navegador se vaya a la / carro URL, como se muestra en la figura 8-3 .

147
Capítulo 8 ■ Spor tSStore: órdenes y salida

Figura 8-3. Mediante la dirección de URL

Guardando las Rutas


Recuerde que la navegación puede ser realizado solamente por la aplicación. Si cambia la dirección URL directamente en la barra de direcciones del
navegador, el navegador solicitará la dirección URL se introduce desde el servidor web. El servidor de desarrollo angular que está respondiendo a las
peticiones HTTP responderá a cualquier URL que no corresponde a un archivo mediante la devolución de los contenidos de index.html. Esto es
generalmente un comportamiento útil porque significa que no recibirá un error HTTP cuando se hace clic en el botón de recarga del navegador. Pero puede
causar problemas si la aplicación espera al usuario navegar a través de la aplicación siguiendo una ruta específica.

A modo de ejemplo, si hace clic en uno de los botones Añadir al carro y haga clic en el botón de recarga del navegador, el servidor HTTP
devolverá el contenido de la index.html archivo y angular saltará inmediatamente al componente de la cesta detalle, pasando por alto la parte de la
aplicación que permite al usuario seleccionar productos.
Para algunas aplicaciones, pudiendo comenzar a utilizar diferentes direcciones URL tiene sentido, pero si ese no es el caso, entonces soportes angulares guardias

de ruta, que se utilizan para gobernar el sistema de enrutamiento.


Para evitar que la aplicación a partir de la / carro o / orden URL, he añadido un archivo llamado
storeFirst.guard.ts en el SportsStore / src / app carpeta y se define la clase de muestra en el listado 8-16 .

Listado 8-16. El contenido de la storeFirst.guard.ts archivo en la carpeta src / app

importación {inyectable} de "@ angular / núcleo"; {importación

ActivatedRouteSnapshot, RouterStateSnapshot, Router

} De "@ angular / enrutador";


{} la importación StoreComponent de "./store/store.component";

148
Capítulo 8 ■ Spor tSStore: órdenes y salida

@Injectable ()
StoreFirstGuard clase de exportación {
firstNavigation privada = true;

constructor (enrutador privada: Router) {}

canActivate (ruta: ActivatedRouteSnapshot,


Estado: RouterStateSnapshot): boolean {if
(this.firstNavigation) {
this.firstNavigation = false; si (route.component! =
StoreComponent) {
this.router.navigateByUrl ( "/"); falso retorno;
}}

return true; }}

Hay diferentes maneras de protegerse rutas, tal como se describe en el capítulo 27 , Y esto es un ejemplo de un guardia
que impide una ruta de ser activado, que se implementa como una clase que define una canActivate
método. La implementación de este método utiliza los objetos de contexto que Angular proporciona que describen la ruta que está a punto de ser
navegado a y se comprueba para ver si el componente diana es una StoreComponent.
Si esta es la primera vez que el canActivate método ha sido llamado y un componente diferente está a punto de ser utilizado, a continuación, la Router.navigateByU
método se utiliza para navegar a la URL raíz.
Los @ inyectable decorador se ha aplicado en la lista porque los guardias de ruta son los servicios. Listado 8-17
registra la guardia como un servicio a través de los módulos de la raíz proveedores propiedad y guarda cada ruta mediante el
canActivate propiedad.

Listado 8-17. Que guardan en las rutas app.module.ts archivo en la carpeta src / app

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; {} la importación
AppComponent de "./app.component"; {} la importación StoreModule de "./store/store.module"; {}
la importación StoreComponent de "./store/store.component"; {} la importación
CheckoutComponent de "./store/checkout.component"; {} la importación CartDetailComponent de
"./store/cartDetail.component"; {} la importación RouterModule de "@ angular / enrutador";

{} la importación StoreFirstGuard de "./storeFirst.guard";

@NgModule ({
importaciones: [BrowserModule, StoreModule,
RouterModule.forRoot ([
{
ruta: "tienda", componente: StoreComponent,
canActivate: [StoreFirstGuard]
}, {

ruta: "carrito", componente: CartDetailComponent,


canActivate: [StoreFirstGuard]
},

149
Capítulo 8 ■ Spor tSStore: órdenes y salida

{
ruta: "caja", componente: CheckoutComponent,
canActivate: [StoreFirstGuard]
},
{Ruta: "**", RedirectTo: "/ tienda"}])],

proveedores: [StoreFirstGuard],
declaraciones: [AppComponent],
bootstrap: [AppComponent]})

AppModule clase de exportación {}

Si vuelve a cargar el navegador después de hacer clic en uno de los botones Añadir al carro ahora, entonces verá el navegador se dirige
automáticamente a la seguridad, como se muestra en la figura 8-4 .

Figura 8-4. guardando rutas

Completando la característica carro Detallada


Ahora que la aplicación tiene soporte de navegación, es el momento de completar la visión de que detalla el contenido de la compra del usuario.
Listado 8-18 elimina la plantilla en línea del componente de la cesta detalle, especifica una plantilla externa en el mismo directorio, y añade una Carro
parámetro al constructor, que será accesible en la plantilla a través de una propiedad llamada carro.

Listado 8-18. Cambio de la plantilla en el archivo cartDetail.component.ts en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo";


{} la importación de la compra de "../model/cart.model";

@Componente({

150
Capítulo 8 ■ Spor tSStore: órdenes y salida

templateUrl: "cartDetail.component.html"
})
CartDetailComponent clase de exportación {

constructor (compra pública: Cesta) {}


}

Para completar la función de la cesta detalle, he creado un archivo HTML llamado cartDetail.component.html en el
src / app / tienda carpeta y añade el contenido se muestra en el Listado 8-19 .

Listado 8-19. Los contenidos del cartDetail.component.html archivo en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div>

<Div class = "fila"> <div class =


"col mt-2">
<H2 class = "text-center"> Su pedido </ h2>
<Table class = "mesa de ping-confinado mesa de rayas p-2"> <thead> <tr>

<Th> Cantidad </ th> <th>


Producto </ th>
<Th class = "text-derecha"> Precio </ th> <th class =
"text-derecha"> Subtotal </ th> </ tr> </ culata en T> <tbody>

<Tr * ngIf = "cart.lines.length == 0"> <td colspan = "4"


class = "text-center"> Su cesta está vacía </ td> </
tr>

<Tr * ngFor = "dejar que la línea de cart.lines"> <td>

<Input type = "número" class = "forma-control-sm"


style = "width: 5em" [valor] =
"line.quantity"
(Modificar) = "cart.updateQuantity (line.product,
$ Event.target.value)"/>
</ Td>
<Td> {{line.product.name}} </ td> <td class
= "text-derecha">
{{Line.product.price | la divisa: "USD": true: "2.2-2"}} </ td>

<Td class = "text-derecha">


{{(Line.lineTotal) | la divisa: "USD": true: "2.2-2"}} </ td>

<Td class = "text-center">

151
Capítulo 8 ■ Spor tSStore: órdenes y salida

<Clase de botón = "btn btn btn-sm-peligro"


(Click) = "cart.removeLine (line.product.id)"> Eliminar </
button> </ td> </ tr> </ tbody> <tfoot> <tr>

<Td colspan = clase "3" = "text-derecha"> Total: </ td> <td class =
"text-derecha">
{{Cart.cartPrice | la divisa: "USD": "símbolo": "2.2-2"}} </ td> </ tr> </
tfoot> </ table> </ div> </ div>

<Div class = "fila"> <div


class = "col">
<Div class = "text-center">
<Clase de botón = "btn btn-primaria m-1" routerLink = "/ tienda">
Continuar comprando el
botón </>
<Clase de botón = "btn btn-secundaria m-1" routerLink = "/ checkout"
[Desactivado] = "cart.lines.length == 0"> Pedido </
button> </ div> </ div> </ div>

Esta plantilla muestra una tabla que muestra la selección de productos del usuario. Para cada producto, hay una entrada
elemento que se puede utilizar para cambiar el botón Quitar una que lo elimina de la compra cantidad y. También hay dos botones de navegación
que permiten al usuario volver a la lista de productos o continuar con el proceso de compra, como se muestra en la figura 8-5 . La combinación de
los enlaces de datos angular y la compartida Carro objeto significa que cualquier cambio realizado en el carro tienen efecto inmediato, volver a
calcular los precios; y si hace clic en el botón Continuar compra, los cambios se reflejan en el componente resumen del carrito mostrado en la lista
de productos.

152
Capítulo 8 ■ Spor tSStore: órdenes y salida

Figura 8-5. Completando la característica carro Detallada

Proceso de pedidos
Ser capaz de recibir órdenes de clientes es el aspecto más importante de una tienda en línea. En las secciones que siguen, no edificar sobre la
aplicación para añadir soporte para recibir los últimos detalles del usuario y la comprobación hacia fuera. Para mantener el proceso simple, voy a
evitar el trato con plataformas de pago y cumplimiento, que son generalmente los servicios de back-end que no son específicos de aplicaciones
angular.

Extender el modelo
Describir las compras de todos los usuarios, he añadido un archivo llamado order.model.ts en el src / app / modelo carpeta y definido el código que se muestra en
el Listado 8-20 .

153
Capítulo 8 ■ Spor tSStore: órdenes y salida

Listado 8-20. El contenido de la order.model.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la importación


de la compra de "./cart.model";

@Injectable ()
Orden de la clase {exportación

Identificación del público: número;


nombre público: string;
megafonía: string; público de la
ciudad: string; estado público:
string; postal pública: string; país
pública: string;

pública enviada: Boolean = false;

constructor (compra pública: Cesta) {}

claro() {
this.id = null;
this.name = this.address = this.city = null; this.state = this.zip =
this.country = null; this.shipped = false; this.cart.clear (); }}

los Orden clase será otro servicio, lo que significa que habrá una instancia compartida en toda la aplicación. Cuando angular
crea la Orden objeto, se detectará el Carro parámetro constructor y proporcionar la misma Carro objeto que se utiliza en otras partes
de la aplicación.

La actualización del repositorio y origen de datos

Para gestionar los pedidos en la aplicación, lo que necesito para extender el repositorio y la fuente de datos para que puedan recibir
Orden objetos. Listado 8-21 agrega un método a la fuente de datos que recibe una orden. Dado que este sigue siendo la fuente de datos ficticios, el
método simplemente produce una cadena JSON de la orden y lo escribe en la consola de JavaScript. Voy a hacer algo más útil con los objetos de la
sección siguiente cuando se crea un origen de datos que utiliza peticiones HTTP para comunicarse con el servicio web REST.

Listado 8-21. Manejo de pedidos de los static.datasource.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación de productos de "./product.model";
importación {observable, a partir de} "rxjs";
{} la importación de pedido de "./order.model";

@Injectable ()
StaticDataSource clase de exportación {
productos privados de producto: [] = [
nuevo producto (1, "Producto 1", "Categoría 1", "Producto 1 (Categoría 1)", 100), el nuevo producto (2,
"Producto 2", "Categoría 1", "Producto 2 (Categoría 1)" , 100), el nuevo producto (3, "Producto 3",
"Categoría 1", "Producto 3 (Categoría 1)", 100),

154
Capítulo 8 ■ Spor tSStore: órdenes y salida

nuevo producto (4, "Producto 4", "Categoría 1", "Producto 4 (Categoría 1)", 100), el nuevo producto (5,
"producto 5", "Categoría 1", "Producto 5 (categoría 1)" , 100), el nuevo producto (6, "Producto 6", "Categoría
2", "Producto 6 (categoría 2)", 100), el nuevo producto (7, "Producto 7", "Categoría 2", "Producto 7 ( categoría
2)", 100), el nuevo producto (8, "producto 8", "Categoría 2", "Producto 8 (categoría 2)", 100), el nuevo
producto (9, "Producto 9", "Categoría 2", "Producto 9 (categoría 2)", 100), el nuevo producto (10, "Producto
10", "Categoría 2", "Producto 10 (categoría 2)", 100), el nuevo producto (11, "Producto 11", " Categoría 3" ,
"Producto 11 (Categoría 3)", 100), el nuevo producto (12 "Producto 12" , "categoría 3", "producto 12
(Categoría 3)", 100), el nuevo producto (13, "producto 13", "categoría 3", "producto 13 (Categoría 3)", 100),
los productos nuevos (14 "14 del producto", "Categoría 3", "Producto 14 (Categoría 3)", 100), el nuevo
producto (15, "Producto 15", "categoría 3", "Producto 15 (Categoría 3)", 100 ),];

getProducts (): observable <Producto []> {


volver a partir de ([this.products]); }

saveOrder (orden: Orden): observable <Order> {


console.log (JSON.stringify (orden)); volver a
partir de ([orden]); }

Para gestionar los pedidos, he añadido un archivo llamado order.repository.ts al src / app / modelo carpeta y lo utilizó para definir la clase de
muestra en el listado 8-22 . Sólo hay un método en el repositorio fin por el momento, pero voy a añadir más funcionalidad en el capítulo 9 cuando
creo las funciones de administración.

■ Propina Usted no tiene que utilizar diferentes repositorios para cada tipo de modelo en la aplicación, pero a menudo lo hacen porque una sola

clase responsable de múltiples tipos de modelos puede llegar a ser complejos y difíciles de mantener.

Listado 8-22. El contenido de la order.repository.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación observable de "rxjs"; {} la importación de pedido
de "./order.model";
{} la importación StaticDataSource de "./static.datasource";

@Injectable ()
OrderRepository clase de exportación {
órdenes privadas: Solicitar [] = [];

constructor (dataSource privada: StaticDataSource) {}

getOrders (): Orden [] {


this.orders regresar; }

155
Capítulo 8 ■ Spor tSStore: órdenes y salida

saveOrder (orden: Orden): observable <Order> {


volver this.dataSource.saveOrder (orden); }}

Actualización del módulo de funciones

Listado 8-23 registra el Orden clase y el nuevo repositorio como servicios utilizando el proveedores propiedad del módulo de función del modelo.

Listado 8-23. Registro de Servicios en los model.module.ts archivo en la carpeta src / app / modelo

importación {NgModule} de "@ angular / núcleo";


{} la importación ProductRepository de "./product.repository"; {} la importación
StaticDataSource de "./static.datasource"; {} la importación de la compra de
"./cart.model";
{} la importación de pedido de "./order.model";
{} la importación OrderRepository de "./order.repository";

@NgModule ({
proveedores: [ProductRepository, StaticDataSource, Carro,
Orden, OrderRepository]
})
ModelModule clase de exportación {}

La recogida de los detalles del pedido

El siguiente paso es reunir los datos del usuario requerido para completar el pedido. Angular incluye directivas incorporadas para trabajar con
formularios HTML y validar su contenido. Listado 8-24 se prepara el componente de checkout, el cambio a una plantilla externa, la recepción de
la Orden objeto como un parámetro de constructor, y proporcionar un apoyo adicional para ayudar a la plantilla.

Listado 8-24. Preparación para un formulario en el archivo checkout.component.ts en la carpeta src / app / tienda

importación {Componente} de "@ angular / núcleo";


importación {NgForm} de "@ angulares / formas";
{} la importación OrderRepository de "../model/order.repository"; {} la importación de
pedido de "../model/order.model";

@Componente({
templateUrl: "checkout.component.html", styleUrls:
[ "checkout.component.css"]
})
CheckoutComponent clase de exportación {
orderSent: boolean = false;
presentado: boolean = false;

constructor (repositorio público: OrderRepository,


el orden público: Orden) {}

submitOrder (forma: NgForm) {

156
Capítulo 8 ■ Spor tSStore: órdenes y salida

this.submitted = true; si
(form.valid) {
this.repository.saveOrder (this.order) .subscribe (orden => {
this.order.clear ();
this.orderSent = true; this.submitted
= false; }); }}

los orden de envio método será invocado cuando el usuario envía un formulario, que está representado por una NgForm objeto.
Si los datos que contiene el formulario es válido, entonces el Orden objeto se pasa al repositorio de saveOrder método, y los datos en el
carro y el orden se restablecerá.
Los @ Componente decorador de styleUrls propiedad se utiliza para especificar una o más hojas de estilo CSS que se deben aplicar a los contenidos
en la plantilla del componente. Para proporcionar comentarios de validación de los valores que el usuario entra en los elementos de formulario HTML, he
creado un archivo llamado checkout.component.css en el
src / app / tienda carpeta y define los estilos que se muestran en el Listado 8-25 .

Listado 8-25. Los contenidos del archivo checkout.component.css en la carpeta src / app / tienda

input.ng-dirty.ng-inválida {border: 2px sólidos # ff0000} input.ng-dirty.ng-válida


{border: 2px # sólida 6bc502}

Angular agrega elementos a la ng-sucio, ng-válida, y ng-válido clases para indicar su estado de validación. El conjunto completo de clases de
validación se describe en el capítulo 14 , Pero el efecto de los estilos en venta 8-25 es añadir un borde verde alrededor entrada elementos que son
válidos y un borde rojo alrededor de los que no son válidos.
La pieza final del rompecabezas es la plantilla para el componente, que presenta al usuario con los campos de formulario necesarios para poblar las
propiedades de una Orden objeto, como se muestra en el listado 8-26 .

Listado 8-26. Los contenidos del archivo checkout.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> </ div> </ div> </ div>

<Div * = ngIf clase "orderSent" = "m-2 texto-center"> <h2> Gracias! </


H2>
<P> Gracias por realizar su pedido. </ P> <p> Vamos a enviar sus mercancías
tan pronto como sea posible. </ P>
<Clase de botón = "btn btn-primaria" routerLink = "/ tienda"> Vuelta a tienda botón </> </ div>

<Form * ngIf = "! OrderSent" # forma = "ngForm" novalidate


(NgSubmit) = "submitOrder (forma)" class = "m-2"> <div class =
"forma-grupo"> <label> Nombre </ label>

<Input class = "-control de formulario" # name = "ngModel" name = "nombre"


[(NgModel)] = "order.name" requerido />

157
Capítulo 8 ■ Spor tSStore: órdenes y salida

<Span * ngIf = "presentado && name.invalid" class = "text-peligro"> Por favor, introduzca su
nombre </ span> </ div>

<Div class = "forma-grupo"> <label>


Dirección </ label>
<Input class = "-control de formulario" # dirección = "ngModel" name = "dirección"
[(NgModel)] = "order.address" requerido />
<Span * ngIf = "presentado && address.invalid" class = "text-peligro"> Por favor, introduzca su
dirección </ span> </ div>

<Div class = "forma-grupo">


<label> City </ label>
<Input class = "-control de formulario" # ciudad = "ngModel" name = "ciudad"
[(NgModel)] = "order.city" requerido />
<Span * ngIf = "presentado && city.invalid" class = "text-peligro"> Por favor, introduzca su
ciudad </ span> </ div>

<Div class = "forma-grupo"> <label>


Estado </ label>
<Clase de entrada = "-control de formulario" # estado = nombre de "ngModel" = "estado"

[(NgModel)] = "order.state" requerido />


<Span * ngIf = "presentado && state.invalid" class = "text-peligro"> Por favor, introduzca su
estado </ span> </ div>

<Div class = "forma-grupo"> <label> Zip /


Código Postal </ label>
<Input class = "-control de formulario" # zip = nombre de "ngModel" = "zip"
[(NgModel)] = "order.zip" requerido />
<Span * ngIf = "presentado && zip.invalid" class = "text-peligro"> Por favor, introduzca su
código postal / código postal </ span> </ div>

<Div class = "forma-grupo">


<label> País </ label>
<Clase de entrada = "-control de formulario" # país = nombre de "ngModel" = "país"
[(NgModel)] = "order.country" requerido />
<Span * ngIf = "presentado && country.invalid" class = "text-peligro"> Por favor,
introduzca su país </ span> </ div>

<Div class = "text-center">


<Clase de botón = "btn btn-secundaria m-1" routerLink = "/ carro"> <clase de botón = "btn btn-primaria m-1"
type = "submit"> Volver botón </> finalizar la orden de botón </> </ div> </ form>

158
Capítulo 8 ■ Spor tSStore: órdenes y salida

los formar y entrada elementos en esta plantilla utilizan características angulares para asegurar que el usuario proporciona los valores para cada campo,
y proporcionan retroalimentación visual si el usuario hace clic en el botón Completar pedido sin completar el formulario. Parte de esta información proviene de
la aplicación de los estilos que se han definido en el Listado 8-25 Y parte proviene de lapso elementos que permanecen ocultos hasta que el usuario intenta
enviar una forma inválida.

■ Propina requiriendo valores es sólo una de las formas en que puede validar angular campos de formulario, y como he explicado en el capítulo 14 ,

Puede agregar fácilmente su propia validación personalizada también.

Para ver el proceso, comenzar con la lista de productos y haga clic en uno de los botones Añadir al carro para agregar un producto a la
cesta. Haga clic en el botón Checkout y verá el formulario HTML se muestra en la figura 8-6 . Haga clic en el botón Completar Orden sin introducir
texto en cualquiera de los entrada elementos, y verá los mensajes de información de validación. Rellene el formulario y haga clic en el botón
Completar Orden; verá el mensaje de confirmación que se muestra en la figura.

Figura 8-6. Completar un pedido

159
Capítulo 8 ■ Spor tSStore: órdenes y salida

Si nos fijamos en la consola JavaScript del navegador, verá una representación JSON de la orden de la siguiente manera:

{"carro":
{"líneas":[
{ "Producto": { "id": 1, "name": "Producto 1", "categoría": "Categoría 1", "Descripción": "Producto
1 (Categoría 1)", "precio": 100}, "cantidad": 1}], "objetoCuenta": 1, "cartPrice": 100}, "enviado":
false,

"Name": "Joe Smith", "dirección": "123 Main Street",


"País" "10036",:: "ciudad": "Smallville", "estado": "NY", "zip" "EE.UU."}

Uso del servicio web REST


Ahora que la funcionalidad básica SportsStore está en su lugar, es el momento de reemplazar la fuente de datos ficticios con uno que obtiene sus datos
del servicio web REST que se creó durante la configuración del proyecto en el Capítulo 7 . Para crear el origen de datos, he añadido un archivo
llamado rest.datasource.ts en el src / app / modelo carpeta y añade el código del listado 8-27 .

Listado 8-27. El contenido de la rest.datasource.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la importación


HttpClient de "@ angular / common / http"; {} la importación observable de
"rxjs"; {} la importación de productos de "./product.model"; {} la importación
de la compra de "./cart.model"; {} la importación de pedido de
"./order.model";

PROTOCOLO const = "http"; PORT


const = 3,500;

@Injectable ()
RestDataSource clase de exportación
{baseUrl: string;

constructor (http privada: HttpClient) {


this.baseUrl = `$ {} PROTOCOLO: // $ {} location.hostname: $ {PORT} /`; }

getProducts (): observable <Producto []> {


volver this.http.get <Producto []> (+ "productos" this.baseUrl); }

saveOrder (orden: Orden): observable <Order> {


volver this.http.post <Order> (+ "órdenes", this.baseUrl orden); }}

160
Capítulo 8 ■ Spor tSStore: órdenes y salida

Angular proporciona un servicio incorporado llamado HttpClient que se utiliza para realizar peticiones HTTP. los
RestDataSource constructor recibe el HttpClient servicio y utiliza el mundial ubicación objeto proporcionado por el navegador para determinar
la URL que las solicitudes serán enviados a, que es el puerto 3500 en el mismo host que la aplicación se ha cargado desde.

Los métodos definidos por la RestDataSource clase corresponden a los definidos por la fuente de datos estáticos pero se
implementan utilizando la HttpClient servicio, descrito en el capítulo 24 .

■ Propina Cuando la obtención de datos a través de HTTP, es posible que la congestión de red o carga del servidor retrasarán la solicitud y dejar que el

usuario busca en una aplicación que no tiene datos. en el capítulo 27 , Explico cómo configurar el sistema de enrutamiento para evitar este problema.

La aplicación de la Fuente de Datos

Para completar este capítulo, voy a aplicar la fuente de datos REST volviendo a configurar la aplicación para que el cambio de los datos
ficticios a los datos resto se hace con los cambios en un solo archivo. Listado 8-28
cambia el comportamiento del servicio de fuente de datos en el módulo de función del modelo.

Listado 8-28. Cambio de la configuración de servicio en las model.module.ts archivo en la carpeta src / app / modelo

importación {NgModule} de "@ angular / núcleo";


{} la importación ProductRepository de "./product.repository"; {} la importación
StaticDataSource de "./static.datasource"; {} la importación de la compra de
"./cart.model"; {} la importación de pedido de "./order.model";

{} la importación OrderRepository de "./order.repository"; {} la importación


RestDataSource de "./rest.datasource";
{} la importación HttpClientModule de "@ angular / common / http";

@NgModule ({
importaciones: [HttpClientModule],
proveedores: [ProductRepository, Carro, Orden, OrderRepository, {proporcionar:
StaticDataSource, useClass: RestDataSource}]
})
ModelModule clase de exportación {}

los importaciones la propiedad se utiliza para declarar una dependencia en el HttpClientModule módulo de función, que proporciona la HttpClient servicio
utilizado en el Listado 8-27 . El cambio en el proveedores propiedad dice angular que cuando se necesita para crear una instancia de una clase con una StaticDataSo
parámetro constructor, se debe utilizar una RestDataSource en lugar. Dado que ambos objetos definen los mismos métodos, el sistema de tipos
JavaScript dinámica significa que la sustitución es perfecta. Cuando todos los cambios se han guardado y el navegador vuelve a cargar la aplicación,
verá los datos ficticia ha sido sustituido por los datos obtenidos a través de HTTP, como se muestra en la figura 8-7 .

161
Capítulo 8 ■ Spor tSStore: órdenes y salida

Figura 8-7. Utilizando el servicio web REST

Si usted pasa por el proceso de selección de productos y la salida, se puede ver que la fuente de datos ha escrito la orden al servicio
web mediante la navegación a esta URL:

http: // localhost: 3500 / db

Esto mostrará todo el contenido de la base de datos, incluida la recogida de pedidos. Usted no será capaz de solicitar la / pedidos URL,
ya que requiere autenticación, que he creado en el siguiente capítulo.

■ Propina recordar que los datos proporcionados por el servicio web REST se restablece cuando se detiene el servidor y empezar de nuevo con el NPM

plazo JSON mando.

162
Capítulo 8 ■ Spor tSStore: órdenes y salida

Resumen
En este capítulo, continué la adición de características a la aplicación SportsStore, añadiendo soporte para un carro de compras en el que el usuario
puede colocar los productos y un proceso de pago que completa el proceso de compra. Para completar el capítulo, que sustituyó a la fuente de datos
ficticios con uno que envía peticiones HTTP al servicio web REST. En el siguiente capítulo, puedo crear las funciones de administración que permiten a
los datos SportsStore a ser gestionados.

163
CAPÍTULO 9

SportsStore: Administración

En este capítulo, continúo la construcción de la aplicación SportsStore mediante la adición de funciones de administración. Relativamente pocos usuarios
necesitan acceder a las funciones de administración, por lo que sería un desperdicio de forzar a los usuarios a descargar el código de la administración y el
contenido cuando es probable que se utilice. En su lugar, voy a poner las funciones de administración en un módulo separado que se cargarán sólo cuando se
requiere la administración.

Preparación del Ejemplo de Aplicación


No se requiere ninguna preparación para este capítulo, que sigue utilizando el proyecto SportsStore del capítulo 8 . Para iniciar el servicio
web REST, abra un símbolo del sistema y ejecute el comando siguiente en el
Tienda de deportes carpeta:

NPM plazo JSON

Abrir una segunda línea de comandos y ejecute el comando siguiente en el Tienda de deportes carpeta para iniciar las herramientas de desarrollo
y servidor HTTP:

ng servir --port 3000 --open

■ Propina Puede descargar el proyecto de ejemplo para este capítulo, y para todos los demás capítulos de este libro: de https://github.com/Apress/pro
.

Crear el módulo
El proceso para crear el módulo de función sigue el mismo patrón que ha visto en los capítulos anteriores. La diferencia clave es que es
importante que ninguna otra parte de la aplicación tiene dependencias en el módulo o las clases que contiene, lo que socavaría la carga
dinámica del módulo y hacer que el módulo de JavaScript para cargar el código de la administración, incluso si se trata no utilizado.

El punto de partida para las funciones de administración será de autenticación, que se asegurará de que sólo los usuarios autorizados son
capaces de administrar la aplicación. He creado un archivo llamado auth.component.ts en el
src / app / admin carpeta y lo utilizó para definir el componente de muestra en el listado 9-1 .

© 2018 Adam Freeman 165


A. Freeman, Pro angular 6, https://doi.org/10.1007/978-1-4842-3649-9_9
Capítulo 9 ■ SportSStore: Administración

Listado 9-1. El contenido de los auth.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo"; importación


{NgForm} de "@ angulares / formas"; {} la importación router de
"@ angular / enrutador";

@Componente({
templateUrl: "auth.component.html"})

AuthComponent clase de exportación {


nombre de usuario pública: string; clave
pública: string; errorMessage pública:
string;

constructor (enrutador privada: Router) {}

autenticar (forma: NgForm) {


si (form.valid) {
// realizar la autenticación
this.router.navigateByUrl ( "/ admin / main"); } Else {

this.errorMessage = "Formulario de datos no válido"; }}}

El componente define propiedades para el nombre de usuario y contraseña que se utilizan para autenticar el usuario, una mensaje de
error propiedad que se utiliza para mostrar mensajes cuando hay problemas, y un
autenticar método que va a realizar el proceso de autenticación (pero que no hace nada por el momento).
Para proporcionar el componente con una plantilla, he creado un archivo llamado auth.component.html en el src / app / admin carpeta y añade el
contenido se muestra en el Listado 9-2 .

Listado 9-2. El contenido del archivo auth.component.html en el directorio src / aplicación de carpeta / admin

<Div class = "bg-info p-2-centro de texto texto blanco"> <h3> SportsStore


Administrador </ h3> </ div>

<Div class = "bg-peligro mt-2 P-2-texto centro de texto-blanco"


* ngIf = "errorMessage! = null"> {{}}
errorMessage </ div>

<Div class = "P-2">


<Form novalidate # forma = "ngForm" (ngSubmit) = "autenticar (forma)"> <div class =
"forma-grupo"> <label> Nombre </ label>

<Input class = "-control de formulario" name = "nombre de usuario"

[(NgModel)] = "nombre de usuario" requerido /> </ div>

<Div class = "forma-grupo"> <label>


Contraseña </ label>
<Input class = "-control de formulario" type = "contraseña" name = "contraseña"
[(NgModel)] = "contraseña" requerido /> </ div>

166
Capítulo 9 ■ SportSStore: Administración

<Div class = "text-center">


<Clase de botón = "btn btn-secundaria m-1" routerLink = "/"> Regresar </ botón> <clase de botón = "btn
btn-primaria m-1" type = "submit"> Iniciar sesión </ botón> </ div> </ form> </ div>

La plantilla contiene un formulario HTML que utiliza expresiones de las propiedades del componente de enlace de datos bidireccionales. Hay un
botón que va a enviar el formulario, un botón que navega a la dirección URL de la raíz, y una
div elemento que es visible sólo cuando hay un mensaje de error para mostrar.
Para crear un marcador de posición para las funciones de administración, he añadido un archivo llamado admin.component.ts en el

src / app / admin carpeta y se define el componente de muestra en el listado 9-3 .

Listado 9-3. El contenido de la admin.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo";


@Componente({
templateUrl: "admin.component.html"})

AdminComponent clase de exportación {}

El componente no contiene ninguna funcionalidad en el momento. Para proporcionar una plantilla para el componente, he añadido un archivo
llamado admin.component.html al src / app / admin carpeta y el contenido marcador de posición se muestra en el Listado 9-4 .

Listado 9-4. Los contenidos del archivo admin.component.html en el src / aplicación de carpeta / admin

<Div class = "bg-info p-2-texto blanco"> <h3> marcador de posición para


funciones de administración </ h3> </ div>

Para definir el módulo de función, he añadido un archivo llamado admin.module.ts en el src / app / admin carpeta y añade el código del
listado 9-5 .

Listado 9-5. El contenido de la admin.module.ts archivo en la carpeta src / app / admin

importación {NgModule} de "@ angular / núcleo"; {} la importación


CommonModule de "@ angular / común"; importación {FormsModule} de "@
angulares / formas"; {} la importación RouterModule de "@ angular /
enrutador"; {} la importación AuthComponent de "./auth.component"; {} la
importación AdminComponent de "./admin.component";

dejar que el enrutamiento = RouterModule.forChild ([


{Ruta: "auth", componente: AuthComponent}, {path:
"principal", componente: AdminComponent}, {path: "**",
RedirectTo: "auth"}]);

167
Capítulo 9 ■ SportSStore: Administración

@NgModule ({
importaciones: [CommonModule, FormsModule, enrutamiento],
declaraciones: [AuthComponent, AdminComponent]})

AdminModule clase de exportación {}

los RouterModule.forChild método se utiliza para definir la configuración de enrutamiento para el módulo de función, que luego se incluye en la década de
los módulos importaciones propiedad.
Un módulo cargado dinámicamente debe ser autónomo y incluir toda la información que requiere angular, incluyendo los URL de enrutamiento
que están soportadas y los componentes que se vea. Si cualquier otra parte de la aplicación depende del módulo, a continuación, se incluye en el
paquete de JavaScript con el resto del código de la aplicación, lo que significa que todos los usuarios tendrán que descargar el código y recursos
para las funciones que no van a utilizar.

Sin embargo, se permite que un módulo cargado dinámicamente para declarar dependencias en la parte principal de la aplicación. Este módulo se basa en
la funcionalidad del módulo de modelo de datos, que se ha añadido a la década de los módulos importaciones por lo que los componentes pueden tener acceso a
las clases del modelo y los repositorios.

Configuración del sistema de enrutamiento de URL

módulos cargados dinámicamente son administrados a través de la configuración de enrutamiento, lo que desencadena el proceso de carga cuando la aplicación
se desplaza a un URL específico. Listado 9-6 se extiende la configuración de enrutamiento de la aplicación para que el / administración URL se carga el módulo de
función de administración.

Listado 9-6. Configuración de un módulo cargado dinámicamente en el Archivo app.module.ts en la carpeta src / app

importación {NgModule} de "@ angular / núcleo";


{} la importación BrowserModule de "@ / plataforma de navegador angular"; {} la importación
AppComponent de "./app.component"; {} la importación StoreModule de "./store/store.module"; {}
la importación StoreComponent de "./store/store.component"; {} la importación
CheckoutComponent de "./store/checkout.component"; {} la importación CartDetailComponent de
"./store/cartDetail.component"; {} la importación RouterModule de "@ angular / enrutador"; {} la
importación StoreFirstGuard de "./storeFirst.guard";

@NgModule ({
importaciones: [BrowserModule, StoreModule,
RouterModule.forRoot ([
{
ruta: "tienda", componente: StoreComponent, canActivate:
[StoreFirstGuard]}, {

ruta: "carrito", componente: CartDetailComponent, canActivate:


[StoreFirstGuard]}, {

ruta: "caja", componente: CheckoutComponent, canActivate:


[StoreFirstGuard]},

168
Capítulo 9 ■ SportSStore: Administración

{
ruta: "admin",
loadChildren: "./admin/admin.module#AdminModule",
canActivate: [StoreFirstGuard]},

{Ruta: "**", RedirectTo: "/ tienda"}])],

proveedores: [StoreFirstGuard],
declaraciones: [AppComponent], de
arranque: [AppComponent]})

AppModule clase de exportación {}

La nueva ruta angular dice que cuando la aplicación navega a la / administración URL, debe cargar un módulo de función definido por una clase
llamada AdminModule desde el admin / admin.module.ts archivo, cuyo camino se especifica en relación con el app.module.ts expediente. Cuando los
procesos angular del módulo de administración, incorporará la información de enrutamiento que contiene en el conjunto global de rutas y completar la
navegación.

Navegando a la URL de administración


La etapa preparatoria final es proporcionar al usuario la capacidad para navegar a la / administración URL de modo que el módulo de función de
administración será cargado y su componente de muestra al usuario. Listado 9-7 agrega un botón a la plantilla del componente de almacén que
realizará la navegación.

Listado 9-7. La adición de un botón de navegación en el archivo store.component.html en la carpeta src / app / tienda

<Div class = "contenedor de fluido"> <div


class = "fila">
<Div class = "col bg-oscuro texto blanco"> <a
class="navbar-brand"> de la tienda </a> <cesta-summary> </
cesta-summary> </ div> </ div>

<Div class = "fila">

<Div class = "col-3 p-2">


<Clase de botón = "btn btn-bloque BTN-outline-primaria" (click) = "changeCategory ()"> Inicio </ botón>

<Botón * ngFor = "dejar que el gato de las categorías"


class = "btn btn-outline-primaria BTN-bloque"
[Class.active] = "gato == selectedCategory" (click) = "changeCategory (cat)"> {{cat}} </ botón>

<Clase de botón = "btn btn-bloque-BTN peligro mt-3" routerLink = "/ admin"> Administrador </ botón>

</ Div>

169
Capítulo 9 ■ SportSStore: Administración

<Div class = "col-9 P-2">

<! - ... elementos omitidos por razones de brevedad ... ->

</ Div> </


div> </ div>

Para reflejar los cambios, se detiene las herramientas de desarrollo y reiniciarlos ejecutando el siguiente comando en el Tienda de deportes carpeta:

ng servir --port 3000

Utilizar el navegador para navegar hasta http: // localhost: 3000 y el uso de herramientas de desarrollo F12 de tu navegador para ver las peticiones de red
realizadas por el navegador cuando se carga la aplicación. Los archivos para el módulo de administración no se cargará hasta que haga clic en el botón de
administración, y en ese momento angular solicitará los archivos y mostrar la página de inicio de sesión se muestra en la figura 9-1 .

La Figura 9-1. Utilización de un módulo cargado dinámicamente

170
Capítulo 9 ■ SportSStore: Administración

Introducir el nombre y la contraseña en los campos del formulario y haga clic en el botón Iniciar sesión para ver el contenido marcador de posición, como se

muestra en la figura 9-2 . Si deja cualquiera de los campos de formulario vacío, se mostrará un mensaje de advertencia.

Figura 9-2. Las características de administración de marcador de posición

autenticación de Ejecución
El servicio web REST se ha configurado para que requiera autenticación para las peticiones que la función de administración requerirá. En
las secciones que siguen, agrego soporte para la autenticación del usuario mediante el envío de una petición HTTP al servicio web REST.

Comprender el sistema de autenticación


Cuando el servicio web REST autentica un usuario, se devolverá un token web JSON (JWT) que la aplicación debe incluir en las solicitudes
HTTP posteriores a mostrar que la autenticación se ha realizado con éxito. Puede leer la especificación en JWT https://tools.ietf.org/html/rfc7519
, pero a los efectos de la aplicación SportsStore, es suficiente para saber que la aplicación angular puede autenticar al usuario mediante el
envío de una solicitud POST al / iniciar sesión URL, incluyendo un objeto en formato JSON, en el cuerpo de la petición que contenga el nombre
y la contraseña de propiedades. Sólo hay un conjunto de credenciales válidas en el código de autenticación he añadido a la aplicación en el
Capítulo 7 , Que se muestra en la Tabla 9-1 .

Tabla 9-1. Las credenciales de autenticación soportados por el servicio web REST

Nombre de usuario Contraseña

administración secreto

Como he señalado en el capítulo 7 , No debe credenciales de código duro en proyectos reales, pero este es el nombre de usuario

y la contraseña que va a necesitar para la aplicación SportsStore.

171
Capítulo 9 ■ SportSStore: Administración

Si las credenciales correctas se envían al / iniciar sesión URL, entonces la respuesta del servicio web REST contendrá un objeto JSON
como esto:

{
"Éxito": true,
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiYWRtaW4iLCJleHBpcmVz
SW4iOiIxaCIsImlhdCI6MTQ3ODk1NjI1Mn0.lJaDDrSu-bHBtdWrz0312p_DG5tKypGv6cA NgOyzlg8"

los éxito propiedad describe el resultado de la operación de autenticación y el simbólico propiedad contiene el JWT, que debe ser
incluido en las solicitudes posteriores utilizando el Autorización cabecera HTTP en este formato:

Autorización: Portador <eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiYWRtaW4iLC


JleHBpcmVzSW4iOiIxaCIsImlhdCI6MTQ3ODk1NjI1Mn0.lJaDDrSu-bHBtd
Wrz0312p_DG5tKypGv6cANgOyzlg8>

He configurado los tokens JWT devueltos por el servidor para que expiran después de una hora.

Si las credenciales incorrectas se envían al servidor, entonces el objeto JSON devuelto en la respuesta sólo va a
contener una propiedad éxito se define como false, como este:

{
"Éxito": false}

La extensión de la Fuente de Datos

La fuente de datos REST va a hacer la mayor parte de la obra, ya que se encarga de enviar la solicitud de autenticación a la / iniciar sesión URL
e incluyendo el JWT en las solicitudes posteriores. Listado 9-8 añade autenticación a la RestDataSource clase y define una variable que
almacenará la JWT, una vez que se ha obtenido.

Listado 9-8. La adición de Autenticación en el rest.datasource.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la importación


HttpClient de "@ angular / common / http"; {} la importación observable de
"rxjs"; {} la importación de productos de "./product.model"; {} la importación
de la compra de "./cart.model"; {} la importación de pedido de
"./order.model";

{} la importación mapa de "rxjs / operadores";

PROTOCOLO const = "http"; PORT


const = 3,500;

172
Capítulo 9 ■ SportSStore: Administración

@Injectable ()
RestDataSource clase de exportación {
baseUrl: string;
auth_token: string;

constructor (http privada: HttpClient) {


this.baseUrl = `$ {} PROTOCOLO: // $ {} location.hostname: $ {PORT} /`; }

getProducts (): observable <Producto []> {


volver this.http.get <Producto []> (+ "productos" this.baseUrl); }

saveOrder (orden: Orden): observable <Order> {


volver this.http.post <Order> (+ "órdenes", this.baseUrl orden); }

autenticar (usuario: cadena, pasan: string): Observable <boolean> {


volver this.http.post <ninguna> "login" (this.baseUrl +, {
Nombre: usuario, contraseña. pase})
tubería (mapa (respuesta => {
this.auth_token = response.success? response.token: null; response.success
regresar; })); }

Creación del Servicio de autenticación


En lugar de exponer a la fuente de datos directamente al resto de la aplicación, voy a crear un servicio que puede ser utilizado para
realizar la autenticación y determinar si la solicitud ha sido autenticada. He añadido un archivo llamado auth.service.ts en el src / app /
modelo carpeta y añade el código del listado 9-9 .

Listado 9-9. El contenido de la auth.service.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación observable de "rxjs";
{} la importación RestDataSource de "./rest.datasource";

@Injectable ()
AuthService clase de exportación {

constructor (fuente de datos privados: RestDataSource) {}

autenticar (nombre de usuario: cadena, contraseña: string): observable <boolean> {


volver this.datasource.authenticate (nombre de usuario, contraseña); }

autenticarse (): Boolean {


volver this.datasource.auth_token! = null; }

173
Capítulo 9 ■ SportSStore: Administración

claro() {
this.datasource.auth_token = null; }}

los autenticar método recibe las credenciales del usuario y las transmite a la fuente de datos
autenticar método, devolver un Observable que producirá cierto si el proceso de autenticación ha tenido éxito y falso de otra manera. los autenticado
la propiedad es una propiedad de sólo captador que devuelve cierto Si la fuente de datos ha obtenido un token de autenticación. los claro método
elimina el token de la fuente de datos.

Listado 9-10 registra el nuevo servicio con el módulo de función del modelo. También agrega una proveedores entrada para el RestDataSource clase,
que sólo se ha utilizado como un sustituto de la StaticDataSource clase en los capítulos anteriores. Desde el AuthService clase tiene una RestDataSource parámetro
de constructor, que necesita su propia entrada en el módulo.

Listado 9-10. Configuración de los servicios en los model.module.ts archivo en la carpeta src / app / modelo

importación {NgModule} de "@ angular / núcleo";


{} la importación ProductRepository de "./product.repository"; {} la importación
StaticDataSource de "./static.datasource"; {} la importación de la compra de
"./cart.model"; {} la importación de pedido de "./order.model";

{} la importación OrderRepository de "./order.repository"; {} la importación


RestDataSource de "./rest.datasource"; {} la importación HttpClientModule de
"@ angular / common / http";
{} la importación AuthService de "./auth.service";

@NgModule ({
importaciones: [HttpClientModule],
proveedores: [ProductRepository, Carro, Orden, OrderRepository, {proporcionar:
StaticDataSource, useClass: RestDataSource}, RestDataSource, AuthService]

})
ModelModule clase de exportación {}

La autenticación que permite


El siguiente paso es conectar el componente que obtiene las credenciales del usuario de modo que se llevará a cabo la autenticación a través del nuevo
servicio, como se muestra en el Listado 9-11 .

Listado 9-11. Habilitación de la autenticación en los auth.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo"; importación


{NgForm} de "@ angulares / formas"; {} la importación router de
"@ angular / enrutador";
{} la importación AuthService de "../model/auth.service";

@Componente({
templateUrl: "auth.component.html"})

174
Capítulo 9 ■ SportSStore: Administración

AuthComponent clase de exportación {


nombre de usuario pública: string; clave
pública: string; errorMessage pública:
string;

constructor (enrutador privada: Router,


auth privada: AuthService) {}

autenticar (forma: NgForm) {


si (form.valid) {
this.auth.authenticate (this.username, this.password)
. suscribirse (respuesta => {
si (respuesta) {
this.router.navigateByUrl ( "/ admin / main"); }

this.errorMessage = "falla la autenticación"; })

} Else {
this.errorMessage = "Formulario de datos no válido"; }}}

Para evitar que la aplicación navegar directamente a las funciones de administración, lo que conducirá a las peticiones HTTP que se envían
sin una ficha, he añadido un archivo llamado auth.guard.ts en el src / app / admin carpeta y definido el guardia ruta mostrada en el Listado 9-12 .

Listado 9-12. El contenido de la auth.guard.ts archivo en la carpeta src / app / admin

importación {inyectable} de "@ angular / núcleo"; importación


{ActivatedRouteSnapshot, RouterStateSnapshot,
Router} de "@ angular / enrutador";
{} la importación AuthService de "../model/auth.service";

@Injectable ()
clase de exportación AuthGuard {

constructor (enrutador privada: Router,


auth privada: AuthService) {}

canActivate (ruta: ActivatedRouteSnapshot,


Estado: RouterStateSnapshot): boolean {

if (! this.auth.authenticated) {
this.router.navigateByUrl ( "/ admin / auth"); falso retorno;
}

return true; }}

175
Capítulo 9 ■ SportSStore: Administración

Listado 9-13 se aplica el protector de ruta a una de las rutas definidas por el módulo de función de administración.

El listado 9-13. Protección de una ruta en el archivo admin.module.ts en la carpeta src / app / admin

importación {NgModule} de "@ angular / núcleo"; {} la importación


CommonModule de "@ angular / común"; importación {FormsModule} de "@
angulares / formas"; {} la importación RouterModule de "@ angular /
enrutador"; {} la importación AuthComponent de "./auth.component"; {} la
importación AdminComponent de "./admin.component";

{} la importación AuthGuard de "./auth.guard";

dejar que el enrutamiento = RouterModule.forChild ([


{Ruta: "auth", componente: AuthComponent},
{Ruta: "principal", componente: AdminComponent, canActivate: [AuthGuard]},
{Ruta: "**", RedirectTo: "auth"}]);

@NgModule ({
importaciones: [CommonModule, FormsModule, enrutamiento],
proveedores de: [], AuthGuard
declaraciones: [AuthComponent, AdminComponent]})

AdminModule clase de exportación {}

Para probar el sistema de autenticación, haga clic en el botón de administrador, introduce algunas credenciales y haga clic en el botón Iniciar sesión.
Si las credenciales son los de la tabla 9-1 , A continuación, podrás ver el marcador de posición para las funciones de administración. Si introduce otras
credenciales, verá un mensaje de error. Figura 9-3 ilustra tanto los resultados.

Figura 9-3. Prueba de la función de autenticación

176
Capítulo 9 ■ SportSStore: Administración

■ Propina el token no se almacena persistentemente, lo que si puede, vuelva a cargar la aplicación en el navegador para empezar de nuevo y probar

un conjunto diferente de credenciales.

La extensión de la Fuente y repositorios de datos


Con el sistema de autenticación en su lugar, el siguiente paso es extender la fuente de datos para que se pueda enviar solicitudes autenticadas
y exponer esas características a través de las clases de pedidos y repositorio del producto. Listado 9-14 añade métodos a la fuente de datos que
incluye el token de autenticación.

Listado 9-14. La adición de nuevas operaciones en los rest.datasource.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la importación HttpClient


de "@ angular / common / http"; {} la importación observable de "rxjs"; {} la
importación de productos de "./product.model"; {} la importación de la
compra de "./cart.model"; {} la importación de pedido de "./order.model"; {}
la importación mapa de "rxjs / operadores";

{} la importación de HttpHeaders '@ angular / common / http';

PROTOCOLO const = "http"; PORT


const = 3,500;

@Injectable ()
RestDataSource clase de exportación {
baseUrl: string;
auth_token: string;

constructor (http privada: HttpClient) {


this.baseUrl = `$ {} PROTOCOLO: // $ {} location.hostname: $ {PORT} /`; }

getProducts (): observable <Producto []> {


volver this.http.get <Producto []> (+ "productos" this.baseUrl); }

saveOrder (orden: Orden): observable <Order> {


volver this.http.post <Order> (+ "órdenes", this.baseUrl orden); }

autenticar (usuario: cadena, pasan: string): Observable <boolean> {


volver this.http.post <ninguna> "login" (this.baseUrl +, {
Nombre: usuario, contraseña. pase})
tubería (mapa (respuesta => {
this.auth_token = response.success? response.token: null; response.success
regresar; })); }

177
Capítulo 9 ■ SportSStore: Administración

saveProduct (producto: Producto): observable <Producto> {


volver this.http.post <productos> (+ "productos" this.baseUrl,
productos, this.getOptions ()); }

updateProduct (producto): observable <Producto> {


volver this.http.put <productos> ( `$ {} this.baseUrl productos / $ {}` product.id,
productos, this.getOptions ()); }

deleteProduct (id: Number): observable <Producto> {


volver this.http.delete <productos> ( `$ {} this.baseUrl productos / $ {id}`,
this.getOptions ()); }

getOrders (): observable <Order []> {


volver this.http.get <Solicitar []> (+ "órdenes" this.baseUrl, this.getOptions ()); }

deleteOrder (id: Number): observable <Order> {


volver this.http.delete <Order> ( `$ {} this.baseUrl órdenes / $ {id}`,
this.getOptions ()); }

updateOrder (orden: Orden): observable <Order> {


volver this.http.put <Order> ( `$ {} this.baseUrl órdenes / $ {}` order.id,
this.getOptions ()); }

getOptions privadas () {
regreso {
encabezados: nuevos HttpHeaders ({

"Autorización": `portador <$ {} this.auth_token>`})}}

Listado 9-15 agrega nuevos métodos a la clase repositorio del producto que permiten que los productos pueden crear, actualizar o eliminar.
los saveProduct método es responsable de la creación y actualización de productos, que es un enfoque que funciona bien cuando se utiliza un
único objeto administrado por un componente, que usted verá demostrado más adelante en este capítulo. La lista también cambia el tipo del
argumento del constructor de
RestDataSource.

Listado 9-15. La adición de nuevas operaciones en los product.repository.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación de productos de "./product.model";
// importación {} StaticDataSource de "./static.datasource"; {} la importación
RestDataSource de "./rest.datasource";

178
Capítulo 9 ■ SportSStore: Administración

@Injectable ()
ProductRepository clase de exportación {
productos privados: Producto [] = []; privadas
categorías: string [] = [];

constructor (dataSource privada: RestDataSource) {


dataSource.getProducts (). suscribirse (datos => {
this.products = datos;
this.categories = data.map (p => p.category)
. filtro ((c, índice, array) => Array.indexOf (c) == índice) .Sort (); }); }

getProducts (categoría: String = null): Producto [] {


this.products volver
. filtro (p => categoría == null || categoría == p.category); }

obtenerProducto (id: Number): Producto {


volver this.products.find (p => p.id == id); }

GetCategories (): String [] {


this.categories regresar; }

saveProduct (producto: Producto) {


si (product.id == null || product.id == 0) {
this.dataSource.saveProduct (producto)
. suscribirse (p => this.products.push (p)); } Else {

this.dataSource.updateProduct (producto)
. suscribirse (p => {
this.products.splice (this.products.
FindIndex (p => p.id == product.id), 1, del producto); }); }}

deleteProduct (id: número) {


this.dataSource.deleteProduct (id) .subscribe (p => {
this.products.splice (this.products.
FindIndex (p => p.id == id), 1); })}

Listado 9-16 hace que los correspondientes cambios en el repositorio de orden, la adición de métodos que permiten órdenes a ser modificados
y borrados.

179
Capítulo 9 ■ SportSStore: Administración

Listado 9-16. La adición de nuevas operaciones en los order.repository.ts archivo en la carpeta src / app / modelo

importación {inyectable} de "@ angular / núcleo"; {} la


importación observable de "rxjs"; {} la importación de pedido
de "./order.model";
// importación {} StaticDataSource de "./static.datasource"; {} la importación
RestDataSource de "./rest.datasource";

@Injectable ()
OrderRepository clase de exportación {
órdenes privadas: Solicitar [] = [];
privado cargado: boolean = false;

constructor (dataSource privada: RestDataSource) {}

loadOrders () {
this.loaded = true;
this.dataSource.getOrders ()
. suscribirse (órdenes => = this.orders órdenes); }

getOrders (): Orden [] {


if (! this.loaded) {
this.loadOrders (); }

this.orders regresar; }

saveOrder (orden: Orden): observable <Order> {


volver this.dataSource.saveOrder (orden); }

updateOrder (orden: Orden) {


this.dataSource.updateOrder (orden) .subscribe (orden => {
this.orders.splice (this.orders.
FindIndex (o => o.id == order.id), 1, orden); }); }

deleteOrder (id: número) {


this.dataSource.deleteOrder (id) .subscribe (orden => {
this.orders.splice (this.orders.findIndex (o => Identificación del == o.id)); }); }

El repositorio orden define una loadOrders método que obtiene las órdenes del repositorio, y que se utiliza para asegurarse de
que la solicitud no se envía al servicio web REST hasta que la autenticación se ha realizado.

180
Capítulo 9 ■ SportSStore: Administración

Crear la estructura de funciones Administración


Ahora que el sistema de autenticación está en su lugar y los repositorios de proporcionar toda la gama de operaciones, puedo crear la estructura que
mostrará las funciones de administración, que he creado mediante la construcción de la configuración de enrutamiento de URL existente. Mesa 9-2 enumera
las direcciones URL que voy a apoyar y la funcionalidad que cada uno va a presentar al usuario.

Tabla 9-2. Las direcciones URL de las funciones de administración

Nombre Descripción

/ admin / / productos principales Navegando a esta URL mostrará todos los productos en una tabla, junto con los botones que
permiten a un producto existente para ser editado o borrado y un nuevo producto que se creará.

/ Admin / main / productos / crean Navegando a esta URL presentará al usuario con un editor de vacío para crear un nuevo
producto.

/ Admin / main / productos / editar / 1 Navegando a esta URL presentará al usuario con un editor poblada para la edición de un producto
existente.

/ admin / main / órdenes Navegando a esta URL presentará al usuario con todos los pedidos en una mesa, junto con los
botones para marcar una orden enviada, y para cancelar un pedido si la elimina.

Crear los componentes del marcador de posición

Me parece la forma más fácil de agregar características a un proyecto angular es definir los componentes que tienen contenido marcador de posición y
construyen la estructura de la aplicación que les rodea. Una vez que la estructura está en su lugar, y luego vuelvo a los componentes y poner en práctica las
características en detalle. Para las funciones de administración, empecé mediante la adición de un archivo llamado productTable.component.ts al src / app / admin carpeta
y se define el componente de muestra en el listado 9-17 . Este componente será responsable de mostrar una lista de productos, junto con los botones necesarios
para editar y eliminarlos o para crear un nuevo producto.

Listado 9-17. El contenido de la productTable.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo";

@Componente({
Plantilla: `<div class = "bg-info p-2-texto blanco">
<H3> Producto Tabla marcador de posición </ h3> </ div> `

})
ProductTableComponent clase de exportación {}

He añadido un archivo llamado productEditor.component.ts en el src / app / admin carpeta y lo utilizó para definir el componente de muestra en el listado 9-18
, El cual será utilizado para permitir que el usuario introduzca los datos necesarios para crear o editar un componente.

181
Capítulo 9 ■ SportSStore: Administración

Listado 9-18. El contenido de la productEditor.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo";

@Componente({
Plantilla: `<div class = "bg-advertencia p-2-texto blanco">
<H3> Producto Editor de marcador de posición </ h3> </ div> `

})
ProductEditorComponent clase de exportación {}

Para crear el componente que será responsable de la gestión de pedidos de los clientes, he añadido un archivo llamado
orderTable.component.ts al src / app / admin carpeta y añade el código del listado 9-19 .

Listado 9-19. El contenido de la orderTable.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo";

@Componente({
Plantilla: `<div class = "bg-primaria p-2-texto blanco">
<H3> orden de la tabla de marcador de posición </ h3> </

div> `

})
OrderTableComponent clase de exportación {}

Preparación del contenido común y el módulo de funciones


Los componentes creados en el apartado anterior serán responsables de funciones específicas. Para llevar esas características juntas y
permitir al usuario navegar entre ellos, tengo que modificar la plantilla del componente marcador de posición que he estado usando para
demostrar el resultado de un exitoso intento de autenticación. He sustituido el contenido marcador de posición con los elementos mostrados en
el Listado 9-20 .

Listado 9-20. Sustitución del contenido en el archivo admin.component.html en el directorio src / aplicación de carpeta / admin

<Div class = "contenedor de fluido">


<Div class = "fila">
<Div class = "col bg-oscuro texto blanco">
<a DEPORTES class="navbar-brand"> TIENDA </a> </ div> </
div>

<Div class = "fila mt-2">


<Div class = "col-3">
<Clase de botón = "btn btn-outline-info BTN-bloque"
routerLink = "/ admin / / productos principales"
routerLinkActive => Productos </ botón "activo">

<Clase de botón = "btn btn-outline-info BTN-bloque"


routerLink = "/ admin / main / órdenes"
routerLinkActive = "activos"> Órdenes </ botón>

182
Capítulo 9 ■ SportSStore: Administración

<Clase de botón = "btn btn-outline-peligro BTN-bloque" (click) = "Salir ()">


Salir </
button> </ div>

<Div class = "col-9">


<Enrutador de salida> </ enrutador de salida> </ div>
</ div> </ div>

Esta plantilla contiene una enrutador de salida elemento que se utilizará para mostrar los componentes de la sección anterior.
También hay botones que navegar por la aplicación a la / admin / / productos principales
y / admin / main / órdenes URL, que seleccionará los productos o pedidos características. Estos botones utilizan el
routerLinkActive atributo, que se utiliza para agregar el elemento a una clase CSS cuando la ruta especificada por el
routerLink atributo está activo.
La plantilla contiene también una Cerrar sesión botón que tiene un suceso de unión que se dirige a un método llamado
cerrar sesión. Listado 9-21 añade este método para el componente, que utiliza el servicio de autenticación para eliminar el token al portador y se
desplaza la aplicación a la URL predeterminada.

Listado 9-21. La implantación del método Salir en los admin.component.ts archivo en la carpeta src / app / admin

importación {Componente} de "@ angular / núcleo";


{} la importación router de "@ angular / enrutador"; {} la importación
AuthService de "../model/auth.service";

@Componente({
templateUrl: "admin.component.html"})

AdminComponent clase de exportación {

constructor (auth privada: AuthService,


enrutador privada: Router) {}

cerrar sesión() {

this.auth.clear ();
this.router.navigateByUrl ( "/"); }

Listado 9-22 permite a los componentes de marcador de posición que se utilizarán para cada función de administración y se extiende la configuración
URL de enrutamiento para implementar las URL de la tabla 9-2 .

Listado 9-22. Configuración del módulo de funciones en el archivo admin.module.ts en la carpeta src / app / admin

importación {NgModule} de "@ angular / núcleo"; {} la importación


CommonModule de "@ angular / común"; importación {FormsModule} de "@
angulares / formas"; {} la importación RouterModule de "@ angular /
enrutador"; {} la importación AuthComponent de "./auth.component"; {} la
importación AdminComponent de "./admin.component"; {} la importación
AuthGuard de "./auth.guard";

{} la importación ProductTableComponent de "./productTable.component";

183

Das könnte Ihnen auch gefallen