Почему @Selector () моего штата возвращает как предыдущие, так и текущие данные за один вызов? - PullRequest
0 голосов
/ 16 апреля 2020

Форма моего состояния выглядит следующим образом

@State<ProductDataModel>({
    name: 'productDataState',
    defaults:{
        categories: [],
        currentProduct: {
            productId : '',
            name : '',
            description : '',
            instructionKey : '',
            models : []
        }
    }
})

Я создал @Selector() для возврата свойства currentProduct, которое выглядит следующим образом.

@Selector()
    static fetchCurrentProduct(state:ProductDataModel){
        console.log(state.currentProduct);
        return state.currentProduct;
    }

The console.log() здесь возвращает 2 currentProduct объектов, когда загружается мой компонент, который внутри моего компонента использует @Select(state.data) VariableName$ способ вызова данных. Когда console.log() в переменной @Select() внутри компонента в хуке ngOnInit() количество возвращаемых им ответов увеличивается на один каждый экземпляр.

Я создал @Action() для установки currentProduct свойство путем проверки внутри состояния для продукта, если оно существует, оно подключает, оно исправляет свойство currentProduct. Если он не существует, он отправляет запрос API в мое приложение Nest JS, добавляет его в соответствующую категорию и устанавливает его в качестве текущего продукта.

export class FetchProduct{
    static readonly type='[Product Data] Fetch Product';
    constructor(public category: string, public product: string){console.log(product);}
}

@Action(FetchProduct)
    public fetchProduct(ctx:StateContext<ProductDataModel>, {category, product}: FetchProduct){

        const currentState = ctx.getState();//gets current state

        //fetches the category from the state
        const categoryMatch = currentState.categories.find(cat => cat.productCategoryId === category);

        //search to see if the product already exists
        const productMatch = categoryMatch.products.find(p => p.productId === product); 

        //if the product exists set it as the current product
        if(productMatch){ ctx.patchState({currentProduct: productMatch}); }


        //if the product doesn't exist fetch it from the backend app
        else{

            //fetchProductData() makes api request and returns an Observable
            return this.dataService.fetchProductData(category, product).pipe(tap(result =>{

                //empty variable to store mutated category data
                const newCategoryList: ProductCategoryModel[] = [];

                //clone of original category data
                const categoryList: ProductCategoryModel[] = [...currentState.categories];

                //creates an object out of the returned product
                const newProduct: ProductDataItem ={
                    productId: result.productId,
                    name: result.name,
                    description: result.description,
                    instructionKey: result.instructionKey,
                    models: result.models
                };

                //create updated category data
                categoryList.forEach(a=>{

                    //adds product to respective category and pushes to empty variable
                    if(a.productCategoryId === category){
                        const newItem: ProductCategoryModel = {
                            productCategoryId: a.productCategoryId,
                            name: a.name,
                            description: a.description,
                            products: [...a.products, newProduct]
                        };

                        newCategoryList.push(newItem);
                    }

                    //pushes categories to variable that don't need mutating 
                    else{ newCategoryList.push(a); }
                });

                //updates the state
                ctx.patchState({categories: newCategoryList, currentProduct: newProduct});
            }));
        }
    }

Я заметил, что когда я нажимаю на продукт, который уже был загружен в состояние, вызывающее этот фрагмент

if(productMatch){ ctx.patchState({currentProduct: productMatch}); }

Все работает точно так, как ожидалось.

Кажется, возникает проблема, когда запрос API добавляется в уравнение. Однако при прохождении каждой части моего действия все возвращается, как и ожидалось. Регистрация свойств внутри конструктора моего FetchProduct класса показывает только один вызов, инициируемый за клик. Функция в моем служебном файле, которая делает вызов API, выглядит следующим образом.

public fetchProductData(category: string, product: string):Observable<ProductDataItem> /*ProductDataItem*/{
        const path = `${this.URL}/get-product/find-product?cattegory=${category}&product=${product}`;
        const data: Observable<ProductDataItem> = this.httpClient.get(path) as Observable<ProductDataItem>;

        data.subscribe(a=> console.log(a));
        return data;
    }

console.log() здесь также возвращает только один ответ на клик. То, как я вызываю свойство currentProduct в моем компоненте, выглядит следующим образом.

@Select(ProductDataState.fetchCurrentProduct) Product$: Observable<ProductDataItem>;

Затем я делаю это внутри OnInit hook

this.Product$.subscribe(a=> console.log(a));

Как я уже говорил ранее это также показывает несколько экземпляров, за исключением того, что число возвращаемых значений увеличивается с каждым кликом.

Я дошел до того, что изменил свое действие следующим образом

//set the currentProduct back to an empty object
 ctx.patchState({currentProduct: {
                    productId: '',
                    name: '',
                    description: '',
                    instructionKey: '',
                    models: []
                }});


                //adds product to category
                ctx.patchState({categories: [...newCategoryList]});

                //sets currentProduct from the object now saved inside the state
                ctx.patchState({
                    currentProduct: ctx.getState().categories.find(a=>a.productCategoryId === category)
                    .products.find(a=> a.productId === product)
                });

Я пытался это увидеть Если бы что-то изменилось, обновив его так же, как это происходит, когда продукт уже существует, но я все равно получаю те же результаты. Кто-нибудь может увидеть, что я делаю неправильно, чтобы создать такое поведение, или кто-нибудь знает что-то под капотом с NGXS, что я мог бы путать какой-то тип пути?

Обновление

Я переместил код, который устанавливает свойство currentProduct, на все пустые значения, до первого, что делается в моем действии FetchProduct, и теперь я получаю правильные данные, отраженные в компоненте Product$ наблюдаемый, однако в моей консоли все еще отображается множество значений, ни один из моих других наблюдаемых не ведет себя так.

Ответы [ 3 ]

1 голос
/ 16 апреля 2020

у вас утечка памяти, вам нужно очистить подписки при уничтожении компонента:

private productSub: Subscription;

ngOnDestroy() { this.productSub.unsubscribe(); }

private createModelSummaryData(): ModelDataItemSummary[]{
    const modelSummaryList: ModelDataItemSummary[] = [];

    this.productSub = this.Product$.subscribe(a=>{

        a.models.forEach(b=>{
            const item: ModelDataItemSummary = {
                modelId: b.modelId,
                name: b.name,
                photo: b.photos.find(c=> c.cover === true).path
            };

            modelSummaryList.push(item);

        });
   });

}
1 голос
/ 17 апреля 2020

Если я правильно понимаю желаемое поведение, что-то вроде этого должно работать. Хук ngOnInit будет вызываться один раз для этого компонента, поэтому вы хотите подключить свою подписку здесь и позволить ей обновляться при каждом изменении - перестраивать массив ModelSummaryData каждый раз, когда происходит изменение. И, как отмечали другие ответы, очистите подписку после уничтожения компонента.

export class ProductDashboardComponent implements OnInit, OnDestroy {

  @Select(ProductDataState.fetchCurrentProduct) Product$: Observable<ProductDataItem>;

  ModelSummaryData: ModelDataItemSummary[] = [];

  private destroyed$ = new Subject<void>();

  constructor(private dataService: ProductPageService, private store: Store) { }

   ngOnInit(): void { 
     this.initModelSummaryData();
   }

   ngOnDestroy(): void {
       this.destroyed$.next();
       this.destroyed$.complete();
   }  

   private initModelSummaryData(): void {
       this.Product$.pipe(
            tap(a => {
               // clear the summary data, so it's rebuild with the latest update
               this.ModelSummaryData = [];

               a.models.forEach(b => {
                   const item: ModelDataItemSummary = {
                       modelId: b.modelId,
                       name: b.name,
                       photo: b.photos.find(c=> c.cover === true).path
                   };
                  ModelSummaryData.push(item);
                });
            }),                
            takeUntil(this.destroyed$), // Clean up the subscription 
       ).subscribe();
    }
}
1 голос
/ 16 апреля 2020

Это потому, что когда вы переключаетесь между продуктами, вы не отмените подписку на старую наблюдаемую. Просто отписаться от него. Пожалуйста, найдите код ниже:

private createModelSummaryData(): ModelDataItemSummary[]{
    const productSubscription:Subscription;
    const modelSummaryList: ModelDataItemSummary[] = [];

    if (productSubscription) {
      productSubscription.unsubscribe();
    }
    productSubscription = this.Product$.subscribe(a=>{

        a.models.forEach(b=>{
            const item: ModelDataItemSummary = {
                modelId: b.modelId,
                name: b.name,
                photo: b.photos.find(c=> c.cover === true).path
            };

            modelSummaryList.push(item);

        });
   });

}
...