Guía para principiantes de ngrx y Angular

G

Introducció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 Observables, 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, Subjecty Observableson RxJSlos 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 ReducerManagerque hace el trabajo principal de la biblioteca.

  • Storees donde todo comienza, instancia otras clases. Extiende la Observableclase para que podamos suscribirnos a ella para obtener el estado más reciente.
  • ActionsSubjectmaneja el envío de la acción al Store.
  • State contiene el último valor de estado emitido.
  • ReducerManagercontiene la función reductora y llama a la función reductora con el valor de estado de Statey la acción de la ActionsSubjectclase.

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/clipara configurar nuestro proyecto, que puede instalar ejecutando el comando:

$ npm install angular/cli -g

Aquí, lo instalamos angular/cliglobalmente 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 minimalbandera, esto se usa para crear una aplicación Angular barebones. Genera archivos “spec”, HTML y CSS. Todo estará en línea (dentro del *.component.tsarchivo).

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=falseque le pasamos al ng g ccomando. La ngutilidad tiene una gran cantidad de opciones que se pueden usar en Angular para satisfacer sus necesidades.

Aquí, pasar --inline-style=truele dice a Angular que genere el estilo del componente dentro del tsarchivo. --spec=trueomite la generación del *.spec.tsarchivo 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.componentpara mostrar el carrito del usuario.
  • / product /: id : esta ruta tiene un idparámetro que se utilizará para mostrar un producto en particular.

Para habilitar el enrutamiento en nuestra aplicación, tenemos que importar el RouterModuley Routesdesde @angular/routeren 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 routesde 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 archivo products.component. Tu decides.
  • path:'**': Esto redirigirá a la /productspá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 forRooten el importsarreglo, pasando la routesvariable 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 AppComponentla 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 CartActionTypesy 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 combineReducersfunció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, StoreModuleimportamos el de @ ngrx / store y nuestra reducerfunción del src/app/store/reducer.tsarchivo. Luego, llamamos al forRootmétodo pasando {cart: reducer}como parámetro en la importsmatriz.

Pasamos un objeto con la cartpropiedad 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 cartestado.

Proyectando el Estado

Ahora que hemos hecho accesible nuestra tienda, podemos acceder a uno de los estados usando la selectfunció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 selectfunció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 carto 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, pricepara 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 enumque 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 actionsarchivo en primer lugar: touch src/app/store/actions.ts.

Ahora, defina las acciones implementando la Actioninterfaz:

// 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, RemoveProductimplementa la Actioninterfaz, y porque vamos a necesitar un producto para añadir o eliminar hemos añadido un constructor para tomar el payloadparámetro. Para que podamos pasar el producto al crear una instancia del objeto usando la newpalabra clave:

new Cart.AddProduct(product)

Por último, definiremos un typealias 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.tsarchivo:

$ touch src/app/store/market.ts

A continuación, inicializaremos una variable PRODUCTSde 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 PRODUCTSarchivo en cualquier lugar que lo necesitemos.

Para mostrar la lista de productos en el products.componentarchivo que vamos a importar PRODUCTS.

// src/app/products/products.component.ts

import { PRODUCTS } from "./../store/market";
...

A continuación, lo asignaremos a una productsvariable:

// 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 *ngFordirectiva para iterar a través de la productsmatriz 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 ActivatedRoutepara obtener los idparámetros. Se suscribe al flujo de eventos de la ruta y luego filterpasa por la PRODUCTSmatriz para obtener el parámetro correspondiente iden la matriz.

Importamos la acción Storepara enviar ADD_PRODUCTcuando addToCartse 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 cartvariable de tipo Observable. Luego, selectextraemos el cartestado de la tienda. El método select devuelve un Observableque asignamos a la cartvariable declarada previamente . El valor del carrito se suscribe y se recibe mediante AsyncPipe |. Existe el removeFromCartmétodo que dispatches la REMOVE_PRODUCTacció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 carttienda.

Puede ver el poder de RxJsen 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 Promises o Observables.

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

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad