Gu铆a para principiantes de ngrx y Angular

    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 鈥嬧媏n 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.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *