I am experiencing strange behavior with my app. I have subscribed to a 
state property using the selector and what I am seeing is, my subscription 
is getting called no matter what property in the state changes.

Below is a cleaned-up version of my code. My state has all kinds of 
properties, some objects, and some flat properties. Selectors for all 
properties work as expected except `getImportStatus` and 
`getImportProgress` selectors. **Subscription to these selectors is 
triggered no matter what property in the store changes.** I am just about 
to lose my mind. Can anyone suggest what I am doing wrong? Has anyone faced 
such issue? I know people run into similar issues when they do not 
unsubscribe the subscriptions. But, in my case, as you can see I am 
unsubscribing and the event is being triggered for any property change 
which has got me puzzled.

Here's my reducer:

    import {ImportConfigActions, ImportConfigActionTypes} from '../actions';
    import * as _ from 'lodash';
    import {ImportProgress} from '../../models/import-progress';
    import {ImportStatus} from '../../models/import-status';
    import {ActionReducerMap, createFeatureSelector} from '@ngrx/store';

    export interface ImportState {
      importConfig: fromImportConfig.ImportConfigState;
    }
    export const reducers: ActionReducerMap<ImportState> = {
      importConfig: fromImportConfig.reducer,
    };

    export const getImportState = 
createFeatureSelector<ImportState>('import');
    
    export interface ImportConfigState {
      spinner: boolean;
      importStatus: ImportStatus; // This is my custom model
      importProgress: ImportProgress; // This is my custom model
    }
    
    export const initialState: ImportConfigState = {
      spinner: false,
      importStatus: null,
      importProgress: null
    };
    
    export function reducer(state = initialState, action: 
ImportConfigActions): ImportConfigState {
      let newState;
    
      switch (action.type) {
        case ImportConfigActionTypes.ShowImportSpinner:
          newState = _.cloneDeep(state);
          newState.spinner = false;
          return newState;
      
    case ImportConfigActionTypes.HideImportSpinner:
          newState = _.cloneDeep(state);
          newState.spinner = false;
          return newState;
    
        case ImportConfigActionTypes.FetchImportStatusSuccess:
          newState = _.cloneDeep(state);
          newState.importStatus = action.importStatus;
          return newState;
      
        case ImportConfigActionTypes.FetchImportProgressSuccess:
          newState = _.cloneDeep(state);
          newState.importProgress = action.importProgress;
          return newState;
    
        default:
          return state;
      }
    }

Here're my actions:

    import {Action} from '@ngrx/store';
    import {ImportStatus} from '../../models/import-status';
    import {ImportProgress} from '../../models/import-progress';
    
    export enum ImportConfigActionTypes {
      ShowImportSpinner = '[Import Config] Show Import Spinner',
      HideImportSpinner = '[Import Config] Hide Import Spinner',
    
      FetchImportStatus = '[Import Config] Fetch Import Status',
      FetchImportStatusSuccess = '[ImportConfig] Fetch Import Status 
Success',
      FetchImportStatusFailure = '[Import Config] Fetch Import Status 
Failure',
      FetchImportProgress = '[Import Config] Fetch Import Progress',
      FetchImportProgressSuccess = '[ImportConfig] Fetch Import Progress 
Success',
      FetchImportProgressFailure = '[Import Config] Fetch Import Progress 
Failure'
    }
    
    export class ShowImportSpinner implements Action {
      readonly type = ImportConfigActionTypes.ShowImportSpinner;
    }
    export class HideImportSpinner implements Action {
      readonly type = ImportConfigActionTypes.HideImportSpinner;
    }
    
    export class FetchImportStatus implements Action {
      readonly type = ImportConfigActionTypes.FetchImportStatus;
      constructor(readonly projectId: number, readonly importId: number) {}
    }
    export class FetchImportStatusSuccess implements Action {
      readonly type = ImportConfigActionTypes.FetchImportStatusSuccess;
      constructor(readonly importStatus: ImportStatus) {}
    }
    export class FetchImportStatusFailure implements Action {
      readonly type = ImportConfigActionTypes.FetchImportStatusFailure;
    }
    export class FetchImportProgress implements Action {
      readonly type = ImportConfigActionTypes.FetchImportProgress;
      constructor(readonly projectId: number, readonly importId: number) {}
    }
    export class FetchImportProgressSuccess implements Action {
      readonly type = ImportConfigActionTypes.FetchImportProgressSuccess;
      constructor(readonly importProgress: ImportProgress) {}
    }
    export class FetchImportProgressFailure implements Action {
      readonly type = ImportConfigActionTypes.FetchImportProgressFailure;
    }
    
    
    export type ImportConfigActions =
      ShowImportSpinner | HideImportSpinner |
      FetchImportStatus | FetchImportStatusSuccess | 
FetchImportStatusFailure |
      FetchImportProgress | FetchImportProgressSuccess | 
FetchImportProgressFailure;

Here're my effects:

    import {Injectable} from '@angular/core';
    import {Actions, Effect, ofType} from '@ngrx/effects';
    import {ImportConfigService} from '../../services';
    import {from, Observable} from 'rxjs';
    import {Action} from '@ngrx/store';
    import {
      FetchImportProgress, FetchImportProgressFailure, 
FetchImportProgressSuccess,
      FetchImportStatus, FetchImportStatusFailure, FetchImportStatusSuccess,
      HideImportSpinner,
      ImportConfigActionTypes,
      StartImport
    } from '../actions';
    import {catchError, map, mergeMap, switchMap} from 'rxjs/operators';
    
    @Injectable()
    export class ImportConfigEffects {
    
      constructor(private actions$: Actions, private service: 
ImportConfigService, private errorService: ErrorService) {}
    
      @Effect()
      startImport: Observable<Action> = this.actions$.pipe(
        ofType<StartImport>(ImportConfigActionTypes.StartImport),
        switchMap((action) => {
          return this.service.startImport(action.payload.projectId, 
action.payload.importId, action.payload.importConfig)
            .pipe(
              mergeMap((res: any) => {
                if (res.status === 'Success') {
                  return [
                    new HideImportSpinner()
                  ];
                }
                return [];
              }),
              catchError(err => from([
                new HideImportSpinner()
              ]))
            );
        })
      );
    
      @Effect()
      fetchImportStatus: Observable<Action> = this.actions$.pipe(
        
ofType<FetchImportStatus>(ImportConfigActionTypes.FetchImportStatus),
        switchMap((action) => {
          return this.service.fetchImportStatus(action.projectId, 
action.importId)
            .pipe(
              mergeMap((res: any) => {
                  if (res.status === 'Success') {
                    return [
                      new FetchImportStatusSuccess(res.data)
                    ];
                  }
              }),
              catchError(err => from([
                new FetchImportStatusFailure()
              ]))
            );
        })
      );
    
      @Effect()
      fetchImportProgress: Observable<Action> = this.actions$.pipe(
        
ofType<FetchImportProgress>(ImportConfigActionTypes.FetchImportProgress),
        switchMap((action) => {
          return this.service.fetchImportProgress(action.projectId, 
action.importId)
            .pipe(
              mergeMap((res: any) => {
                if (res.status === 'Success') {
                  return [
                    new FetchImportProgressSuccess(res.data)
                  ];
                }
              }),
              catchError(err => from([
                new FetchImportProgressFailure()
              ]))
            );
        })
      );
    }

Here're my selectors:

    import {createSelector} from '@ngrx/store';
    import {ImportConfig} from '../../models/import-config';
    import {ImportConfigState} from '../reducers/import-config.reducer';
    import {getImportState, ImportState} from '../reducers';
    
    export const getImportConfigState = createSelector(
      getImportState,
      (importState: ImportState) => importState.importConfig
    );
    
    export const getImportConfig = createSelector(
      getImportConfigState,
      (importConfigState: ImportConfigState) => 
importConfigState.importConfig
    );
    
    export const isImportSpinnerShowing = createSelector(
      getImportConfigState,
      (importConfigState: ImportConfigState) => 
importConfigState.importSpinner
    );
    
    export const getImportStatus = createSelector(
      getImportConfigState,
      (importConfigState: ImportConfigState) => 
importConfigState.importStatus
    );
    export const getImportProgress = createSelector(
      getImportConfigState,
      (importConfigState: ImportConfigState) => 
importConfigState.importProgress
    );

Here's my component:

    import {Component, OnDestroy, OnInit, ViewEncapsulation} from 
'@angular/core';
    import {select, Store} from '@ngrx/store';
    import {ImportState} from '../../store/reducers';
    import {library} from '@fortawesome/fontawesome-svg-core';
    import {faAngleLeft, faAngleRight, faExchangeAlt,
      faFolder, faFolderOpen, faFileImport, faLink, faEquals, faCogs,
      faExclamationCircle, faFilter, faSearch, faHome} from 
'@fortawesome/free-solid-svg-icons';
    import {faFile} from '@fortawesome/free-regular-svg-icons';
    import {FetchImportProgress, FetchImportStatus} from 
'../../store/actions';
    import {ActivatedRoute} from '@angular/router';
    import {Subject} from 'rxjs';
    import {BsModalRef, BsModalService} from 'ngx-bootstrap';
    import {ImportProgressComponent} from 
'../import-progress/import-progress.component';
    import {getImportStatus} from '../../store/selectors';
    import {filter, map, takeUntil} from 'rxjs/operators';
    import {ImportStatus} from '../../models/import-status';
    
    @Component({
      selector: 'app-import',
      templateUrl: './import.component.html',
      styleUrls: ['./import.component.scss'],
      encapsulation: ViewEncapsulation.None
    })
    export class ImportComponent implements OnInit, OnDestroy {
    
      importId: string;
      projectId: string;
    
      status: number;
      phase: number;
    
      private importProgressModalRef: BsModalRef;
      private isProgressModalShowing = false;
    
      private unsubscribe$ = new Subject<void>();
    
      queryParamsSubscription: any;
    
      constructor(
        private store: Store<ImportState>,
        private route: ActivatedRoute,
        private modalService: BsModalService) {
    
        library.add(
          faHome,
          faFolder, faFolderOpen, faFile, faFileImport,
          faAngleRight, faAngleLeft,
          faFilter, faSearch,
          faExchangeAlt,
          faLink,
          faEquals,
          faCogs,
          faExclamationCircle);
    
        this.queryParamsSubscription = this.route.queryParams
          .subscribe(params => {
            this.importId = params['importId'];
            this.projectId = params['projectId'];
          });
      }
    
      ngOnInit(): void {
        this.store.dispatch(new FetchImportStatus(+this.projectId, 
+this.importId));
        this.store.dispatch(new FetchImportProgress(+this.projectId, 
+this.importId));
    
        this.store.pipe(select(getImportStatus), 
takeUntil(this.unsubscribe$), map((importStatus: ImportStatus) => 
importStatus),
          filter((importStatus: ImportStatus) => !!importStatus))
          .subscribe((importStatus: ImportStatus) => {
            this.status = importStatus.status; // This is getting triggered 
for all property changes
            this.phase = importStatus.phase;
            this.handleStatusChange();
          });
      }
    
      ngOnDestroy(): void {
        this.unsubscribe$.next();
        this.unsubscribe$.complete();
    
        this.queryParamsSubscription.unsubscribe();
      }
      
      handleStatusChange() {
        if (this.status !== 2 || (this.phase === 5)) {
          if (!this.isProgressModalShowing) {
            this.openImportProgressModal();
            this.isProgressModalShowing = true;
          }
        }
      }
    
      openImportProgressModal() {
        this.importProgressModalRef = 
this.modalService.show(ImportProgressComponent,
          Object.assign({}, { class: 'modal-md', ignoreBackdropClick: true 
}));
        this.importProgressModalRef.content.modalRef = 
this.importProgressModalRef;
        this.importProgressModalRef.content.onModalCloseCallBack = 
this.onImportProgressModalClose;
      }
    
      onImportProgressModalClose = () => {
        this.isProgressModalShowing = false;
      };
    }

-- 
You received this message because you are subscribed to the Google Groups 
"Angular and AngularJS discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to angular+unsubscr...@googlegroups.com.
To post to this group, send email to angular@googlegroups.com.
Visit this group at https://groups.google.com/group/angular.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/angular/b1483ddd-dd11-45a5-a1cf-cc45036226b1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to