我的应用程序遇到异常行为。我已经使用选择器订阅了一个state属性,而我看到的是,无论状态中的哪个属性发生变化,我的订阅都会被调用。
下面是我的代码的清理版本。我的状态具有各种属性,一些对象和一些平面属性。除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;
};
}
答案 0 :(得分:0)
我不知道发生了什么事。由于时间不多了,我不得不去找另一种方法。
this.store.pipe(select(getImportStatus), takeUntil(this.unsubscribe$), map((importStatus: ImportStatus) => importStatus),
filter((importStatus: ImportStatus) => !!importStatus))
.subscribe((importStatus: ImportStatus) => {
if (_.isEqual(this.importStatus, importStatus)) {
return;
}
this.importStatus = importStatus;
this.status = importStatus.status;
this.phase = importStatus.phase;
this.handleStatusChange();
});
我使用loadash库将新的商店属性与订阅主体内部的旧商店属性进行比较。这是不必要的,因为存储只应发出更改的值。现在,至少这会让我继续前进。