0
votes

My user can add products to a list, and I want to generate an ID to each product that enters the list (id: 1, id: 2, id: 3....). Here is my code:

REDUCER:

export const productReducer = createReducer(
initialSate,
on(addProductToList, (state, action) => {
    console.log(state)
    return {
        ...state,
        productList: [...state.productList, action]
    }
}))

ACTION:

export const addProductToList = createAction(
"[Product] Add Product To List",
props<{ product: Product; }>()
);

SELECTOR:

export const selectFeature = (state: any) => state;

export const selectFeatureCount = createSelector(
selectFeature,
(state: any) => state.products.idCounter
);

ONCLICK funcion:

onSubmit(e) {
  e.preventDefault();

  if (this.addProductForm.valid)
  {
    this.store.pipe(select(selectFeatureCount)).subscribe(res => {
      this.addProductForm.value.id = res;
      this.addProductForm.value.isRecieved = false;
      this.store.dispatch(addProductToList(this.addProductForm.value));
      this.addProductForm.reset(this.addProductForm)
      this.router.navigate(['/products']);
    })
  }
}
1
What is the issue that you are facing? I can't see what exactly is the problem, are you asking how to add idCounter in the store?Християн Христов
Yes. What will be the best solution or best practice to do so...Shira

1 Answers

0
votes

After your comment, I think I understood what is the reasoning behind your question.

Here are the solutions that I can propose to you:

Simple

So if you want to just have a way to identify the items that are inside the productList you can use the indexes of the array, so if you have some simplistic use case where for example you use *ngFor to render your items on a single page once you can do the following

<div *ngFor="let product of products; let i = index">
  <div>{{product....}} </div>
  
  <button (click)="delete(i)">Delete</button>
  <button (click)="edit(i)">Edit</button>
</div>

So the thing that will happen (if you are using the default behaviour of ngFor, without the trackBy option) is that whenever you modify your products list the elements will re-rendered with the new value coming from the store and you will be able to edit/modify again the new (modified) products list again and again the same way.

Inside the reducer, you will have something like the following

export const productReducer = createReducer(
initialSate,
on(addProductToList, (state, action) => {
    return {
        ...state,
        productList: [...state.productList, action]
    }
}),
on(deleteProduct, (state, {idx}) => {
    return {
        ...state,
        productList: [...state.productList.slice(0,idx), ...state.productList.slice(idx+1)]
    }
}),
on(deleteProduct, (state, {idx, modifiedProduct}) => {
    return {
        ...state,
        productList: [
        ...state.productList.slice(0,idx), 
        modifiedProduct,
        ...state.productList.slice(idx+1)
        ]
    }
}),
)

Bit more complex

Let's say that you have some kind of more complex view where you display the same product element from the product list in multiple places and the dynamic relation between array idx and actual product element might mess up your code (it's dynamic because whenever you delete element from the array, the elements with a higher index than the deleted element, will decrement their index with 1 or in other words will move one position to the left inside the array)

In this case, the thing I will recommend is to create some kind of unique id for each new product, which for example might be a time-based id (you can use also Math.random or whatever you feel like), like in the snippet below.

{
  id: Date.now(),
  productName: 'Whatever the products is called',
  productPrice: 'Whatever the price is',
  ...
}

By doing so you will guarantee yourself that no funny business will happen as long as you are the only consumer of that product list (more about the issue that might occur later).

Inside the reducers, you can use the same approach but instead of having the index of the element, from the start, we will need to find it by ourselves, which is no biggie. (You can look at Array.findIndex())

The recommended solution

So with the previous approach as I mentioned, there are few problems that might occur, the issues are related to the uniqueness of the id-s, let's say if we used the Date.now() method for creating multiple products at the same time there is a chance for us to create multiple instances with the same id, which will mess up the things badly (Let's say you want to delete the element with id=x but there are two elements with that id, which one should be deleted?). The easy fix is to either create our id-s in a smarter way or to use some library to do that for us, and this solution will work in 99.9%+ of the cases.

The more realistic scenario for such duplication to happen however is a bit different, lets say someone intentionally generates a new product with already existing id and sends that product to your BE ... you can guess what will happen.

So the thing I will recommend you to do is whenever you are creating a new entity, send a request to the BE, let it handle the whole id generation stuff, and then use that unique id in the frontend however you like, by doing so you are guaranteeing that no duplication will ever happen.

For this flow to work, you will also have to look a bit into the @ngrx/effects

Conclusion

The best practice depends on the use case if you are doing some kind of small side project go for the second solution if you are doing something that is going to be on production and used by customers don't let anything on chance and go for the third solution.

** side-note: If you are interested in more advanced ngrx usage definitely check out @ngrx/entity