Introducción
Contenido
- 1 Introducción
- 2 Código fuente
- 3 ¿Qué es ngrx?
- 4 ¿Qué es RxJS?
- 5 ¿Qué es Redux?
- 6 Conceptos básicos
- 7 Tienda
- 8 Reductor
- 9 Estado
- 10 Comportamiento
- 11 Ventajas de la tienda
- 12 ngrx / store: Detrás de escena
- 13 ngrx en la práctica
- 14 Depurar con Redux-DevTools
- 15 Conclusión
La gestión del estado es un término que siempre vendrá a la mente cuando se trate de una estructura de datos de aplicación.
El mayor problema en el desarrollo y mantenimiento de sistemas de software a gran escala es la complejidad: los sistemas grandes son difíciles de entender.
La programación reactiva es cuando reaccionamos a los datos que se nos transmiten con el tiempo.
En este artículo, vamos a cubrir los fundamentos de ngrx y sus aplicaciones en Angular.
Código fuente
El código fuente implementado en este artículo se puede encontrar en este repositorio de GitHub .
¿Qué es ngrx?
NGRX es un conjunto de RxJS bibliotecas de gestión de estado Accionado por angular , inspirado por Redux , un recipiente de gestión del estado popular y predecible para aplicaciones JavaScript. Fue desarrollado por Rob Wormald , un defensor de desarrolladores de Angular en 2013.
Estas son algunas de las ventajas que nos trae ngrx:
- ngrx tiene como objetivo traer extensiones reactivas a Angular.
- @ ngrx / store trae una tienda única similar a Redux para todos los estados de su aplicación a Angular.
ngrx / store es una implementación de Redux que se desarrolló con RxJS manteniendo los conceptos centrales y la API de Redux.
ngrx, inspirado por Redux, comparte los mismos principios con él y lo sobrecarga con RxJS.
Entraremos en el funcionamiento interno de @ ngrx / store en las secciones siguientes.
¿Qué es RxJS?
RxJS es una biblioteca de JavaScript para programación reactiva que le permite trabajar con flujos de datos asíncronos o basados en devolución de llamada.
Hablando de corrientes, una corriente es una secuencia de valores en el tiempo. Estos flujos de eventos y datos en tiempo real, que llamamos Observable
s, son una manera hermosa de manejar código asincrónico.
Usando RxJS, escribiríamos algo como esto:
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.subscribe(() => console.log('Clicked!'));
var arr = Rx.Observable.of(90, 80)
.subscribe((v) => console.log('Value:', v));
Verá, con RxJS podemos lograr mucho con muy poco código.
¿Qué es Redux?
Redux, como se indicó anteriormente, es una biblioteca de administración de estado para aplicaciones JavaScript. Aunque inicialmente fue desarrollado para la comunidad React, también se puede usar en JavaScript vanilla o con cualquier otro marco de JavaScript.
Redux es una biblioteca que implementa las ideas de Flux. Flux es un patrón de diseño que popularizó el flujo de datos unidireccional (flujo unidireccional), que fue presentado por primera vez por Facebook.
Los estados de una aplicación en Redux se guardan en la tienda. Los estados se actualizan mediante acciones transportadas a funciones puras llamadas reducers
. Los reductores toman el estado y la acción como parámetros y realizan una acción inmutable sobre el estado y devuelven un nuevo estado.
Conceptos básicos
Antes de sumergirnos en los aspectos prácticos de cómo / qué hace que @ ngrx / store funcione, echemos un vistazo a los conceptos básicos. Las aplicaciones desarrolladas con @ ngrx / store deben tratar con Store, Reducers, State y Actions.
Tienda
En pocas palabras, la tienda es la “base de datos” de nuestra aplicación. Se compone de diferentes estados definidos en nuestra aplicación. El estado, por tanto, es inmutable y sólo se altera mediante acciones.
La tienda combina todo el estado de la aplicación en una sola entidad, que actúa como una base de datos para la aplicación web. Al igual que una base de datos tradicional, representa el punto de registro de una aplicación, su tienda puede considerarse como una “fuente única de verdad” del lado del cliente.
Reductor
Si la tienda es la base de datos de la aplicación, los reductores son las tablas. Un reductor es una función pura que acepta dos parámetros: una acción y el estado anterior con un tipo y datos opcionales asociados con el evento.
Reductor de muestra
export function reducer(state = initialState, action: articles.Actions):State {
switch(action.type) {
case 'ADD_ARTICLE':
return {
articles: [...state.articles,action.payload]
}
default:
return state;
}
}
Estado
El estado es una estructura de datos única e inmutable. Los estados son los que componen la tienda. Como se indicó anteriormente, los reductores son como tablas y, por lo tanto, el estado son campos en la tabla.
Comportamiento
Store abarca el estado de nuestra aplicación y los reductores obtienen los segmentos o secciones del estado de la tienda, pero ¿cómo actualizamos la tienda cuando surge la necesidad? Ese es el papel de las acciones. Las acciones representan cargas útiles de información que se envían a la tienda desde la aplicación y, por lo general, se activan mediante la interacción del usuario.
// Action interface
export interface Action {
type: string,
payload?: any
}
// Action with payload
dispatch({type: 'ADD_ARTICLE', payload: {link: 'github.com/philipszdavido', points:90}})
// Action without payload
dispatch({type:'LOAD_LINKS'})
Cuando se envía una acción, el reductor la toma y aplica la carga útil, según el tipo de acción, y genera el nuevo estado.
Para recapitular algunos puntos: la tienda abarca todo el estado, los reductores devuelven fragmentos del estado y las acciones son eventos predefinidos activados por el usuario que comunican cómo debe cambiar un marco determinado del estado.
Ventajas de la tienda
Hemos visto lo efectivo y útil que es @ ngrx / store para administrar estados en nuestra aplicación. Pero antes de pasar a mostrar su aplicación en Angular, veremos sus ventajas.
La principal ventaja que tiene la tienda son Estado centralizado, Pruebas, Desempeño y DevTools.
- Estado centralizado : el estado de una tienda se mantiene en un directorio. Hace que sea más fácil predecir actualizaciones o cambios en la tienda y rastrear problemas
- Pruebas : es fácil escribir pruebas para funciones puras. Dado que la tienda está compuesta de reductores, que son funciones puras que usan solo sus entradas para producir sus salidas sin efectos secundarios. Simplemente ingrese y confirme la salida.
- Rendimiento : los cambios de estado del flujo de datos unidireccionales de su reactividad lo hacen muy rápido y eficiente.
- DevTools : Se han creado herramientas increíbles para ayudar a los desarrolladores. Un ejemplo es ngrx / store-devtool , que ayuda a los desarrolladores a “viajar en el tiempo” durante el desarrollo. También tiene algunas características interesantes que ayudan a proporcionar un historial de acciones y cambios de estado.
ngrx / store: Detrás de escena
@ ngrx / store se construyó con los principios de RxJS
. BehaviorSubject
, Subject
y Observable
son RxJS
los tipos principales que componen el motor de @ ngrx / store. Primero comprendamos estos conceptos, luego podremos usar la biblioteca de manera efectiva.
Para entender muy bien un concepto, hay que mirar el código fuente. @ ngrx / store fue, durante mucho tiempo, un misterio para mí hasta que vine a obtener la imagen cuando descargué el proyecto de su repositorio de Git y me sumergí en el código fuente. Pude ver cómo la biblioteca estaba brillantemente construida. Al hacerlo, realmente me familiaricé con el material.
Mirando en el código, podrás ver que @ NGRX / tienda tiene cuatro clases básicas Store
, State
, ActionsSubject
, y ReducerManager
que hace el trabajo principal de la biblioteca.
Store
es donde todo comienza, instancia otras clases. Extiende laObservable
clase para que podamos suscribirnos a ella para obtener el estado más reciente.ActionsSubject
maneja el envío de la acción alStore
.State
contiene el último valor de estado emitido.ReducerManager
contiene la función reductora y llama a la función reductora con el valor de estado deState
y la acción de laActionsSubject
clase.
ngrx en la práctica
Ahora es el momento de mostrar cómo usar @ ngrx / store en Angular. Para demostrar el poder de @ ngrx / store, crearemos una “tienda en línea” simple, que permitirá a los usuarios hacer lo siguiente:
- Ver una lista de productos
- Ver un producto en particular
- Los usuarios pueden agregar un producto a su carrito
- Los usuarios pueden eliminar un producto de su carrito
Aplicación de muestra
Lo usaremos angular/cli
para configurar nuestro proyecto, que puede instalar ejecutando el comando:
$ npm install angular/cli -g
Aquí, lo instalamos angular/cli
globalmente para que podamos usarlo desde cualquier directorio de nuestro sistema.
Preparar
Ahora estamos listos. Llamaremos a nuestra carpeta de proyectos, “tienda online”. Para aplicar scaffolding al proyecto, ejecute el comando:
$ ng new online-store --minimal
Observe el uso de la minimal
bandera, esto se usa para crear una aplicación Angular barebones. Genera archivos “spec”, HTML y CSS. Todo estará en línea (dentro del *.component.ts
archivo).
Ahora, nuestra estructura de directorio se verá así:
├── online-store
├── src
├── app
├── app.component.ts
└── app.module.ts
├── assets
└── .gitkeep
├── environment
├── environment.prod.ts
└── environment.ts
├── index.html
├── main.ts
├── polyfills.ts
├── style.css
├── tsconfig.app.json
└── typings.d.ts
├── .angular-cli.json
├── .gitignore
├── package.json
└── tsconfig.json
Ahora, instalaremos Bootstrap para que nuestra aplicación sea receptiva y atractiva:
$ npm install bootstrap -S
Remane “style.css” por “style.scss”, luego abre “style.scss” y agrega la siguiente línea:
@import "~bootstrap/scss/bootstrap.scss"
A continuación, extraemos la biblioteca @ ngrx / store:
$ npm install @ngrx/store @ngrx/core -S
Nuestra aplicación tendrá tres componentes:
products.component
: Esto mostrará listas de productos y sus precios.cart.component
: Este componente muestra todos los artículos que hemos agregado al carrito.product.component
: Este componente mostrará el nombre y los detalles de un producto seleccionado.
Para aplicar scaffolding a los componentes anteriores, ejecute los siguientes comandos:
$ ng g c products --inline-style=true --spec=false
$ ng g c cart --inline-style=true --spec=false
$ ng g c product --inline-style=true --spec=false
Observe las opciones --inline-style=true --spec=false
que le pasamos al ng g c
comando. La ng
utilidad tiene una gran cantidad de opciones que se pueden usar en Angular para satisfacer sus necesidades.
Aquí, pasar --inline-style=true
le dice a Angular que genere el estilo del componente dentro del ts
archivo. --spec=true
omite la generación del *.spec.ts
archivo de prueba .
A continuación, agregaremos enrutamiento a nuestra aplicación. Crearemos tres rutas:
- / productos : Esta será nuestra ruta de índice. Activará el
products.component
. - / cart : Esto activará
cart.component
para mostrar el carrito del usuario. - / product /: id : esta ruta tiene un
id
parámetro que se utilizará para mostrar un producto en particular.
Para habilitar el enrutamiento en nuestra aplicación, tenemos que importar el RouterModule
y Routes
desde @angular/router
en app.module.ts
:
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
...
import { Routes, RouterModule } from '@angular/router'
...
A continuación, definimos una variable routes
de tipo Routes
. Contendrá una serie de nuestras rutas:
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
...
import { Routes, RouterModule } from '@angular/router'
...
const routes: Routes = [
{
path: '',
redirectTo: '/products',
pathMatch: 'full'
},
{
path: 'products',
component: ProductsComponent
},
{
path: 'cart',
component: CartComponent
},
{
path: 'product/:id',
component: ProductComponent
},
{
path: '**',
redirectTo: '',
pathMatch: 'full'
}
];
...
Esto representa todos los posibles estados del enrutador en los que puede estar nuestra aplicación.
Como dijimos anteriormente, nuestra aplicación tiene tres rutas: “productos”, “productos /: id” y “carrito”. Agregamos alguna configuración adicional aquí:
path: ''
: Esto redirecciona a / products porque / products es nuestra página de índice. De hecho, podemos “” crear una página de índice simplemente agregando la propiedad del componente y asignándola al archivoproducts.component
. Tu decides.path:'**'
: Esto redirigirá a la/products
página si ninguna de las rutas coincide con la solicitud del usuario.
Ahora, para activar el sistema de enrutamiento en nuestra aplicación, llamamos al método RouterModule
‘s forRoot
en el imports
arreglo, pasando la routes
variable como parámetro.
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ProductsComponent } from './products/products.component';
import { CartComponent } from './cart/cart.component';
import { ProductComponent } from './product/product.component';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
redirectTo: '/products',
pathMatch: 'full'
},
{
path: 'products',
component: ProductsComponent
},
{
path: 'cart',
component: CartComponent
},
{
path: 'product/:id',
component: ProductComponent
},
{
path: '**',
redirectTo: '',
pathMatch: 'full'
}
];
@NgModule({
declarations: [
AppComponent,
ProductsComponent,
CartComponent,
ProductComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
});
export class AppModule { }
Finalmente, necesitamos decirle al enrutador angular dónde puede colocar la configuración de enrutamiento de nuestra aplicación en el DOM.
Agregaremos el <router-outlet></router-outlet>
elemento a AppComponent
la plantilla de.
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<p>
app works!
<router-outlet></router-outlet>
</p>
`,
styles: []
});
export class AppComponent {
title="app";
};
El <router-outlet></router-outlet>
elemento le dice al enrutador angular dónde insertar el componente correspondiente en el DOM.
Definir nuestro reductor
Ahora vamos a definir nuestra tienda y empezar a agregarle reductores. Creemos una carpeta central llamada “tienda” para todos nuestros archivos relacionados con la tienda:
$ mkdir src/app/store
Bien, ahora creemos nuestro archivo reductor “reducer.ts”:
$ touch src/app/store/reducer.ts
Este archivo contendrá nuestra función reductora, a la que llegaremos más adelante. Como se mencionó anteriormente, algunas de las características de esta aplicación es manejar “quitar un producto del carrito” y “agregar un producto al carrito”. Entonces, nuestro reductor se encargará de quitar y agregar productos al carrito.
Volviendo a lo que decíamos antes, el archivo “reducer.ts” contendrá una función reductora que acepta el estado anterior y la acción actualmente enviada como parámetros. Luego, necesitamos implementar un sistema de caja de interruptores para verificar la acción correcta y realizar un recálculo del estado.
// src/app/store/reducer.ts
import { CartActionTypes, CartActions } from "./actions";
export let initialState = []
export function reducer(state=initialState, action: CartActions) {
switch (action.type) {
case CartActionTypes.ADD_PRODUCT:
return [...state, action.payload]
case CartActionTypes.REMOVE_PRODUCT:
let product = action.payload
return state.filter((el)=>el.id != product.id)
default:
return state
}
}
Entonces aquí está nuestra función reductora. Tenga en cuenta que no queremos utilizar métodos de cambio de estado. La inmutabilidad es la clave aquí.
Estoy seguro de que se está preguntando acerca de algunos de los objetos del código anterior, como CartActionTypes
y CartActions
. No se preocupe, llegaremos a eso en la sección “Configuración de acciones”.
Inicializando la tienda
Actualmente solo tenemos un estado en esta aplicación, por lo tanto, solo un reductor ‘getter’. Puede ver el ejercicio aquí, cada elemento de estado tiene su propia función de reducción. En una aplicación grande y compleja, podemos tener muchos reductores, un reductor para cada elemento de estado en la aplicación. Estos reductores se combinarán en una sola función de reductor usando la combineReducers
función.
Ahora pongamos nuestro reductor en la tienda:
// app.module.ts
...
import { StoreModule } from "@ngrx/store";
import { reducer } from './store/reducer';
...
@NgModule({
declarations: [
...
],
imports: [
...
StoreModule.forRoot({cart: reducer})
],
providers: [],
bootstrap: [AppComponent]
});
export class AppModule { }
Aquí hemos puesto la tienda de nuestra aplicación a disposición de toda la aplicación. Podemos acceder a él desde cualquier lugar. Mirando el código, StoreModule
importamos el de @ ngrx / store y nuestra reducer
función del src/app/store/reducer.ts
archivo. Luego, llamamos al forRoot
método pasando {cart: reducer}
como parámetro en la imports
matriz.
Pasamos un objeto con la cart
propiedad establecida porque, dado que solo tenemos un estado, no necesitaremos considerar segmentos de estado. Entonces solo le decimos a ngrx que solo nos envíe el cart
estado.
Proyectando el Estado
Ahora que hemos hecho accesible nuestra tienda, podemos acceder a uno de los estados usando la select
función.
// app.componen.ts
import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
@Component({
selector: 'app-root',
template: `
<p>
app works !!
Cart: {{cart.length}}
<router-outlet></router-outlet>
</p>
`,
styles: []
})
export class AppComponent {
title="app";
cart: Array<any>
constructor(private store: Store<any>) {}
ngOnInit() {
// Called after the constructor, initializing input properties, and the first call to ngOnChanges.
// Add 'implements OnInit' to the class.
this.store.select('cart').subscribe((state => this.cart = state))
}
}
La select
función extrae el estado deseado de la aplicación y devuelve un Observable
, para que podamos suscribirnos a los cambios realizados en ese estado.
Configurar acciones
Las acciones son las consultas a la tienda de la aplicación. Ellos “despachan” acciones que se realizarán en la tienda.
Primero, necesitaremos un modelo. La base de nuestro estado es una variedad de productos. El producto dentro de la matriz representa un artículo que un usuario puede comprar. El usuario puede agregarlo cart
o eliminarlo.
Entonces, sabemos cómo nuestro carrito contiene productos / artículos, y un producto puede tener lo siguiente:
- nombre
- precio
- etiqueta
- carné de identidad
- descripción
Vamos a seleccionar solo name
, id
, price
para representar un producto aquí.
A continuación, crearemos un modelo para nuestro producto:
// src/app/store/product.model.ts
export class Product {
id: number
name: string
price: number
}
A continuación, definimos nuestras acciones como acciones personalizadas que implementan la clase @ ngrx / store Action
.
En lugar de enviar acciones como esta:
store.dispatch({type: '', payload: ''})
Creamos acciones como una nueva instancia de clase:
this.store.dispatch(new Cart.AddProduct(product))
Expresar acciones como clases permite la verificación de tipos en funciones reductoras. Para crear nuestras clases de acciones, primero creamos un enum
que contendrá nuestros tipos de acciones. Recuerde, lo único que hace esta aplicación es “Agregar al carrito” y “Eliminar del carrito”. Entonces nuestra enumeración se verá así:
// src/app/store/actions.ts
export enum CartActionTypes {
ADD_PRODUCT = 'ADD_PRODUCT',
REMOVE_PRODUCT = 'REMOVE_PRODUCT'
}
Nota : Usted tendrá que crear el actions
archivo en primer lugar: touch src/app/store/actions.ts
.
Ahora, defina las acciones implementando la Action
interfaz:
// src/app/store/actions.ts
import { Action } from '@ngrx/store'
...
export class AddProduct implements Action {
readonly type = CartActionTypes.ADD_PRODUCT
constructor(public payload: any){}
}
export class RemoveProduct implements Action {
readonly type = CartActionTypes.REMOVE_PRODUCT
constructor(public payload: any){}
}
Las acciones AddProduct
, RemoveProduct
implementa la Action
interfaz, y porque vamos a necesitar un producto para añadir o eliminar hemos añadido un constructor para tomar el payload
parámetro. Para que podamos pasar el producto al crear una instancia del objeto usando la new
palabra clave:
new Cart.AddProduct(product)
Por último, definiremos un type
alias para todas las acciones definidas anteriormente para que se use en nuestra función reductora:
// src/app/store/actions.ts
...
export type CartActions = AddProduct | RemoveProduct
Aquí está el código de acciones completo:
// src/app/store/actions.ts
import { Action } from '@ngrx/store'
export enum CartActionTypes {
ADD_PRODUCT = 'ADD_PRODUCT',
REMOVE_PRODUCT = 'REMOVE_PRODUCT'
}
export class AddProduct implements Action {
readonly type = CartActionTypes.ADD_PRODUCT
constructor(public payload: any){}
}
export class RemoveProduct implements Action {
readonly type = CartActionTypes.REMOVE_PRODUCT
constructor(public payload: any){}
}
export type CartActions = AddProduct | RemoveProduct
Configurar componentes
¡Creo que ya hemos terminado con la instalación de nuestra tienda! Ahora, veamos cómo utilizarlos en nuestros componentes.
Ya hemos creado todos los componentes que necesitaremos en nuestra aplicación.
products.component.ts
Esto mostrará una lista de productos. Para este artículo, vamos a codificar nuestra lista de productos. Puede expandir esta aplicación para cargar los productos desde un recurso, pero eso se lo dejaremos a usted.
Para codificar nuestra lista de productos, crearemos un archivo que contenga nuestra lista de productos. Crearemos un market.ts
archivo:
$ touch src/app/store/market.ts
A continuación, inicializaremos una variable PRODUCTS
de matriz de tipo Product
:
// src/app/store/market.ts
import { Product } from "./product.model";
export const PRODUCTS: Product[] = [
{
id: 0,
name: "HP Inspirion",
price: 700
},
{
id: 1,
name: "MacBook Pro 2018",
price: 15000
},
{
id: 2,
name: "Dell 5500",
price: 3000
}
]
Ahora podemos importar el PRODUCTS
archivo en cualquier lugar que lo necesitemos.
Para mostrar la lista de productos en el products.component
archivo que vamos a importar PRODUCTS
.
// src/app/products/products.component.ts
import { PRODUCTS } from "./../store/market";
...
A continuación, lo asignaremos a una products
variable:
// src/app/products/products.component.ts
...
export class ProductsComponent implements OnInit {
products = PRODUCTS
constructor() { }
ngOnInit() { }
}
Crearemos un buen HTML para mostrar nuestra lista de productos:
// src/app/products/products.component.ts
...
@Component({
selector: 'app-products',
template: `
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12" *ngFor="let product of products">
<div class="my-list">
<img src="https://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" />
<h2>{{product.name}}</h2>
<span>$</span>
<span class="pull-right">{{product.price}}</span>
<div class="offer">Extra 5% Off. Cart value $ {{0.5 * product.price}}</div>
<div class="detail">
<p>{{product.name}} </p>
<img src="https://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" />
<a [routerLink]="['/product',product.id]" class="btn btn-info">View</a>
</div>
</div>
`,
styles: [ ]
})
...
Usamos la *ngFor
directiva para iterar a través de la products
matriz y mostrarla en HTML usando el enlace de expresión.
Reuniéndolo todo, el src/app/products/products.componenet.ts
, se verá así:
// src/app/products/products.componenet.ts
import { Component, OnInit } from '@angular/core';
import { PRODUCTS } from "./../store/market";
@Component({
selector: 'app-products',
template: `
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12" *ngFor="let product of products">
<div class="my-list">
<img src="https://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" />
<h2>{{product.name}}</h2>
<span>$</span>
<span class="pull-right">{{product.price}}</span>
<div class="offer">Extra 5% Off. Cart value $ {{0.5 * product.price}}</div>
<div class="detail">
<p>{{product.name}} </p>
<img src="https://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" />
<a [routerLink]="['/product',product.id]" class="btn btn-info">View</a>
</div>
</div>
`,
styles: [ ]
})
export class ProductsComponent implements OnInit {
products = PRODUCTS
constructor() { }
ngOnInit() { }
}
A continuación, abra “src / app / styles.scss” y agregue el siguiente código SCSS:
// src/app/styles.scss
...
img {
max-width: 100%;
}
img {
transition: all .5s ease;
-moz-transition: all .5s ease;
-webkit-transition: all .5s ease
}
.my-list {
width: 100%;
padding: 10px;
border: 1px solid #f5efef;
float: left;
margin: 15px 0;
border-radius: 5px;
box-shadow: 2px 3px 0px #e4d8d8;
position: relative;
overflow: hidden;
}
.my-list h2 {
text-align: left;
font-size: 14px;
font-weight: 500;
line-height: 21px;
margin: 0px;
padding: 0px;
border-bottom: 1px solid #ccc4c4;
margin-bottom: 5px;
padding-bottom: 5px;
}
.my-list span {
float: left;
font-weight: bold;
}
.my-list span:last-child {
float: right;
}
.my-list .offer {
width: 100%;
float: left;
margin: 5px 0;
border-top: 1px solid #ccc4c4;
margin-top: 5px;
padding-top: 5px;
color: #afadad;
}
.detail {
position: absolute;
top: -100%;
left: 0;
text-align: center;
background: #fff;
height: 100%;
width: 100%;
}
.my-list:hover .detail {
top: 0;
}
Hacer esto hará que el código SCSS esté disponible para todos nuestros componentes.
product.component.ts
Aquí podemos ver un producto, ver su precio, nombre y luego agregarlo al carrito si nos atrae.
// src/app/product/product.component.ts
import { Component, OnInit } from '@angular/core';
import { PRODUCTS } from "./../store/market";
import { Product } from "./../store/product.model"
import { ActivatedRoute } from "@angular/router";
import { Store } from "@ngrx/store";
import * as Cart from "./../store/actions";
@Component({
selector: 'app-product',
template:
`
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="my-list">
<img src="https://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" />
<h2>{{product.name}}</h2>
<span>$</span>
<span class="pull-right">{{product.price}}</span>
<div class="offer">
Extra 5% Off. Cart value $ {{0.5 * product.price}}
</div>
<div class="offer">
<a (click)="addToCart(product)" class="btn btn-info">Add To Cart</a>
</div>
</div>
</div>
`,
styles: [ ]
})
export class ProductComponent implements OnInit {
product:Product
constructor(private route: ActivatedRoute, private store: Store<any>) { }
ngOnInit() {
this.route.params.subscribe((p)=>{
let id = p['id']
let result = Array.prototype.filter.call(PRODUCTS,(v)=>v.id == id)
if (result.length > 0) {
this.product = result[0]
}
})
}
addToCart(product) {
this.store.dispatch(new Cart.AddProduct(product))
}
}
Aquí hemos importado ActivatedRoute
para obtener los id
parámetros. Se suscribe al flujo de eventos de la ruta y luego filter
pasa por la PRODUCTS
matriz para obtener el parámetro correspondiente id
en la matriz.
Importamos la acción Store
para enviar ADD_PRODUCT
cuando addToCart
se ejecuta el método.
cart.component.ts
Esto muestra los productos en nuestro carrito. Aquí está el código:
// src/app/cart/cart.component.ts
import { Component, OnInit } from '@angular/core';
import { Store, select } from "@ngrx/store";
import { Observable } from "rxjs/Observable";
import * as Cart from "./../store/actions";
@Component({
selector: 'app-cart',
template:
`
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" *ngFor="let product of cart | async">
<div class="my-list">
<img src="https://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" />
<h2>{{product.name}}</h2>
<span>$</span>
<span class="pull-right">{{product.price}}</span>
<div class="offer">
Extra 5% Off. Cart value $ {{0.5 * product.price}}
<a (click)="removeFromCart(product)" class="btn btn-info">Remove From Cart</a>
</div>
</div>
</div>
`,
styles: []
})
export class CartComponent implements OnInit {
cart: Observable<Array<any>>
constructor(private store:Store<any>) {
this.cart = this.store.select('cart')
}
ngOnInit() { }
removeFromCart(product) {
this.store.dispatch(new Cart.RemoveProduct(product))
}
}
Declaramos una cart
variable de tipo Observable
. Luego, select
extraemos el cart
estado de la tienda. El método select devuelve un Observable
que asignamos a la cart
variable declarada previamente . El valor del carrito se suscribe y se recibe mediante AsyncPipe |
. Existe el removeFromCart
método que dispatch
es la REMOVE_PRODUCT
acción para la tienda.
app.component.ts
Este es nuestro componente final:
// src/app/app.component.ts
import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'app-root',
template: `
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1 class="text-center">Online Store</h1>
<h6 class="text-center"><a [routerLink]="['/cart']">Cart: {{(cart | async).length}}</a></h6>
<hr />
</div>
</div>
<router-outlet></router-outlet>
</div>
`,
styles: []
})
export class AppComponent {
title="app";
constructor(private store: Store<any>) {}
cart: Observable<Array<any>>
ngOnInit() {
// Called after the constructor, initializing input properties, and the first call to ngOnChanges.
// Add 'implements OnInit' to the class.
this.cart = this.store.select('cart')
}
}
Verá que hemos modificado la plantilla para incluir el nombre de nuestra aplicación “Tienda en línea”. Nos suscribimos a la tienda de carritos para obtener la cantidad de productos, luego se muestra usando la {{(cart | async).length}}
expresión. Se actualiza en tiempo real cuando agregamos o eliminamos un producto de la cart
tienda.
Puede ver el poder de RxJs
en juego aquí en el flujo de datos unidireccional.
Para ver todo lo que hemos hecho, asegúrese de guardar cada archivo y ejecutar el siguiente comando en su terminal:
$ ng serve
Voila !!! Ahora puedes jugar con la aplicación.
Utilizando AsyncPipe
AsyncPipe es una tubería integrada que podemos usar dentro de nuestras plantillas para desenvolver datos de Promise
s o Observable
s.
La tubería asíncrona, cuando se usa en un componente, lo marca para verificar si hay cambios.
Mirando el componente donde hicimos esto:
// src/app/app.component.ts
...
@Component({
selector: 'app-root',
template: `
...
<h6 class="text-center"><a [routerLink]="['/cart']">Cart: {{(cart | async).length}}</a></h6>
...
`,
...
})
...
Podríamos haber utilizado el subscribe
método para lograr el mismo resultado:
// src/app/app.component.ts
...
@Component({
selector: 'app-root',
template: `
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1 class="text-center">Online Store</h1>
<h6 class="text-center"><a [routerLink]="['/cart']">Cart: {{cart.length}}</a></h6>
<hr />
</div>
</div>
<router-outlet></router-outlet>
</div>
`,
styles: []
})
export class AppComponent {
title="app";
constructor(private store: Store<any>) {}
cart: Array<any>
ngOnInit() {
// Called after the constructor, initializing input properties, and the first call to ngOnChanges.
// Add 'implements OnInit' to the class.
this.cart = this.store.select('cart')
.subscribe(state => this.cart = state)
}
}
Puede ver que nuestro código se hizo un poco más largo, ¡pero aún funcionó!
Entonces, la “tubería asíncrona” realiza la suscripción por nosotros y agrega el valor a nuestra plantilla. Tanto trabajo en solo unas pocas líneas de código. ¡Perfecto!
Depurar con Redux-DevTools
Redux-Devtools es una herramienta de depuración de “viaje en el tiempo” para probar los estados de la interfaz de usuario. Hace el desarrollo de aplicaciones y aumenta la productividad de su desarrollo.
Con estas herramientas, puede pasar literalmente al futuro o al estado pasado de su aplicación (de ahí la descripción de “viaje en el tiempo”).
Las funciones de “bloqueo” y “pausa” de Redux-DevTools permiten eliminar las acciones pasadas del historial o deshabilitarlas.
Podemos usar Redux-Devtools en una aplicación Angular, pero el equipo de ngrx desarrolló su propia herramienta de desarrollo para usar en cualquier aplicación con tecnología ngrx.
Se puede instalar ejecutando el siguiente comando:
$ npm i @ngrx/store-devtools -S
Aquí está el enlace para descargar la extensión Redux-DevTools .
Importa StoreDevtoolsModule.instrumentOnlyWithExtension()
en tu app.module.ts
:
import { StoreDevtoolsModule } from '@ngrx/store-devtools'
@NgModule({
imports: [
StoreDevtoolsModule.instrumentOnlyWithExtension({
maxAge: 6
})
]
})
export AppModule() {}
No entraremos en detalles técnicos sobre cómo utilizar Redux-DevTools. En su lugar, puede tomarlo como una tarea para mejorar al agregarlo a este proyecto. ¡Me gustaría saber de usted! ¡No dudes en enviarme un PR!
Conclusión
Lo sé, violamos varias prácticas recomendadas, pero lo más importante es que ha visto cómo usar @ ngrx / store para crear aplicaciones Angular.
Si se perdió o necesita una referencia al código, puede bifurcarlo o clonarlo desde mi repositorio de GitHub . Puedes expandir la aplicación para hacer cosas en las que no pensaba. Siéntete libre, juega con él.