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...
});
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"
}
}