angularrxjsngrxngrx-store

Ngrx Selector returns a partial object


I am trying to set up ngrx and I am nearly there, I just can't get the selector to return an Observable of the correct object type. It is currently returning Partial< Observable < Product > > rather than just Observable< Product >.

Is my selector code correct?

Model:

export class Product {

    id: string | undefined;
    product: string | undefined;
    price: number | undefined;

    constructor(init?: Partial<any>) {

        if (init) {
            if ('id' in init) {
                this.id = init.id;
            }
            if ('product' in init) {
                this.product = init.product;
            }
            if ('price' in init) {
                this.price = init.price;
            }
        }
    }
}

My App State:

I have several sub-states within my AppState.
I am trying to select one at a time.

export const initialState: AppState = {
    products: productsInitialState,
    customers: customersInitialState,
  }

Selector:

export const selectProductsState = (state: AppState) => state.products;

export const selectProducts = createSelector(
  selectProductsState,
  state => new Product({id: state.id, product: state.product, price: state.price})
);

Component:

products: Product[] = [];

// I am getting an error that res is of type Partial<Observable<Product>>

this.store.select(selectProducts).subscribe((res: Product[]) => {
      this.products = blah... do manipulation of the data here...
});

Solution

  • This is an example without using Partial as constructor.

    Finally, you as you can see I using the subscriber without partial format.

    The most important thing with the SELECTOR, should be the "NAME of the state" gave when provideStore(...)

    app.config.ts

    import { ApplicationConfig } from '@angular/core';
    import { provideRouter } from '@angular/router';
    
    import {
      createAction,
      createReducer,
      createSelector,
      on,
      props,
      provideStore,
    } from '@ngrx/store';
    
    import { routes } from './app.routes';
    
    export class Product {
      id?: string;
      product?: string;
      price?: number;
      constructor(id: string, product: string, price: number) {
        this.id = id;
        this.product = product;
        this.price = price;
      }
    }
    
    export type ProductState = {
      id?: string;
      product?: string;
      price?: number;
    };
    
    export type AppState = {
      products: ProductState[];
    };
    
    export const productsInitialState = [
      {
        id: '1',
        product: 'example',
        price: 0.0,
      },
    ];
    
    export const initialState: AppState = {
      products: productsInitialState,
    };
    
    export const selectProductsState = (state: AppState) => state.products;
    
    export const selectProducts = createSelector(selectProductsState, (state) => {
      return state;
    });
    
    /**
     * Boilerplate
     */
    export const action = createAction(
      '[Example consume SELECT]',
      props<Product>()
    );
    
    export const productReducer = createReducer(
      initialState,
      on(action, (state, { id, product, price }) => {
        return { ...state, products: [...state.products, { id, product, price }] };
      })
    );
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideStore({ products: productReducer }),
        provideRouter(routes),
      ],
    };
    

    app.component.ts

        import { Component } from '@angular/core';
        import { CommonModule, DecimalPipe } from '@angular/common';
        import { RouterOutlet } from '@angular/router';
        import { Store } from '@ngrx/store';
        import { AppState, Product, action, selectProducts } from './app.config';
        
        @Component({
          selector: 'app-root',
          standalone: true,
          imports: [CommonModule, DecimalPipe, RouterOutlet],
          template: `
            <h1>{{ title }}</h1>
            <ul>
              @for(product of products; track product.id) {
              <li>
                <span>
                  {{ product.id }}
                  {{ product.product }}
                  {{ product.price | number }}
                </span>
              </li>
              } @empty { 0 products. }
            </ul>
          `,
        })
        export class AppComponent {
          title = 'ngrx-selector-returns-a-partial-object';
          products?: Product[];
          constructor(private store: Store<AppState>) {
            //Add
            this.store.dispatch(action(new Product('2', 'my new product', 0.02)));
            //Example
            this.store.select(selectProducts).subscribe({
              next: (value: any) => {
                console.dir(value);
                this.products = value.products;
              },
            });
    }}
    

    package.json

    {
      "name": "ngrx-selector-returns-a-partial-object",
      "version": "0.0.0",
      "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "watch": "ng build --watch --configuration development",
        "test": "ng test"
      },
      "private": true,
      "dependencies": {
        "@angular/animations": "^17.0.0",
        "@angular/common": "^17.0.0",
        "@angular/compiler": "^17.0.0",
        "@angular/core": "^17.0.0",
        "@angular/forms": "^17.0.0",
        "@angular/platform-browser": "^17.0.0",
        "@angular/platform-browser-dynamic": "^17.0.0",
        "@angular/router": "^17.0.0",
        "@ngrx/store": "^16.3.0",
        "@ngrx/store-devtools": "^16.3.0",
        "rxjs": "~7.8.0",
        "tslib": "^2.3.0",
        "zone.js": "~0.14.2"
      },
      "devDependencies": {
        "@angular-devkit/build-angular": "^17.0.1",
        "@angular/cli": "^17.0.1",
        "@angular/compiler-cli": "^17.0.0",
        "@types/jasmine": "~5.1.0",
        "jasmine-core": "~5.1.0",
        "karma": "~6.4.0",
        "karma-chrome-launcher": "~3.2.0",
        "karma-coverage": "~2.2.0",
        "karma-jasmine": "~5.1.0",
        "karma-jasmine-html-reporter": "~2.1.0",
        "typescript": "~5.2.2"
      }
    }