2
votes

I have @ngrx entities based on following models:

export interface Product {
  id: number;
  code: string;
  text: string;
  brand: any;
  quantity_available: number;
  rate: number;
  // ... other variables
}

export interface BasketProduct {
  id: number; /*Product ID*/
  quantity: number;
  rate: number;
  value: number;
}

I store the user's profile in the Settings state, and the selected filters (coming from UI) in the products state:

export interface State extends EntityState<Setting> {
  // additional properties here
  user: {
    currency: {
      code: string,
      name: string,
    }
  };
}

export interface State extends EntityState<Product> {
  // additional properties here
  filters: {
    just_arrived: boolean;
    newly_launched: boolean;
    discounted: boolean;
    celebrity: boolean;
    niche: boolean;
    wish_list: boolean;
    search_text: string;
    brands;
    product_types;
    parent_companies;
  };
}

Excerpt of my reducer goes like this:

export const selectAllProducts = createSelector(
  selectProductState,
  fromProduct.selectAllProducts
);

export const selectBasketProductEntities = createSelector(
  selectBasketState,
  fromBasket.selectBasketProductEntities
);

export const selectUserProfile = createSelector(
  selectSettingState,
  fromSetting.selectUserProfile
);

export const selectAllFilters = createSelector(
  selectProductState,
  fromProduct.selectAllFilters
);

I then created a selector that merges data from the selectors above:

// Returns the List of Products after applying Filters
    export const selectFilteredProducts = createSelector(
      selectAllProducts,
      selectAllFilters,
      selectBasketProductEntities,
      selectUserProfile,
      (products, filters, basket, userProfile) => {
        // Step 1: Filter the Products based on the Parameters
        const filtered = products.filter(
          product =>
            ((filters.just_arrived === false) ||
            (product.is_just_arrived === 1 && filters.just_arrived === true)) &&
            // ..... Additional Filters
        );

        // Step 2: Map the Quantity Selected / Currency Information
        return filtered.map(product => Object.assign({}, product, {
          quantity_selected: basket[product.id],
          currency: userProfile.currency.code
        }));
      }
    );

The solution works well, but whenever a product is added to the Basket, my entire products list gets re-initialized (probably because I'm re-assigning the product using map). This is likely to create performance issues as the list of product grows. I want to limit the change detection to only the product that has changes.

Is my implementation of map / filter in the createSelector correct? How can I improve the performance?

1

1 Answers

0
votes

Sorry, but I don't see an other evident solution. If it's a basket, normally it won't contain thousand of items. No? If yes, maybe you should consider filtering with backend.

However, performance could be impacted when during Angular change detection. So you can improve by providing a trackBy function.

<li *ngFor="let item of collection;trackBy: trackByFn">{{item.id}}</li>

See this post.

On an other point, for better readability, you can write :

return filtered.map(product => {
      ...product,
      quantity_selected: basket[product.id],
      currency: userProfile.currency.code
    }));