Я испытываю странное поведение с моим приложением.Я подписался на свойство состояния с помощью селектора, и я вижу, что моя подписка вызывается независимо от того, какое свойство в состоянии изменяется.
Ниже приведена очищенная версия моего кода.Мое состояние имеет все виды свойств, некоторые объекты и некоторые плоские свойства.Селекторы для всех свойств работают как положено, кроме селекторов getImportStatus
и getImportProgress
. Подписка на эти селекторы активируется независимо от того, какое свойство в магазине меняется. Я просто схожу с ума.Кто-нибудь может подсказать, что я делаю не так?Кто-нибудь сталкивался с такой проблемой?Я знаю, что люди сталкиваются с подобными проблемами, когда они не отписываются от подписок.Но в моем случае, как вы можете видеть, я отписался, и событие вызывается для любого изменения свойства, которое меня озадачило.
Вот мой редуктор:
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;
}
}
Здесь 'мои действия:
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;
Вот мои эффекты:
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()
]))
);
})
);
}
Вот мои селекторы:
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
);
Вот мой компонент:
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;
};
}