我正在使用Ngrx管理应用程序的状态。我创建了一个在分派“ [Todo] Query”操作时触发的效果。该效果会通过TodosService.getTodos()从Firestore调用“ Todos”的集合,并在成功时使用Todos有效内容调度动作“ [Todo] Load success”,或使用以下命令触发动作“ [Todo] Load Error”错误时出错。
在Todo.component中,我将选择器“ getTodos”分配给变量“ todos $”,并通过模板中的异步管道进行订阅。但是,当SUCCESS reducer返回带有待办事项列表的新状态时,状态变化不会得到反映。
我使用的是Chrome扩展程序“ Ngrx Store Devtools”,它向我显示了状态更改,包括“ Todos”列表从空到有待办事项的时间。
这是应用程序在“查询”效果通过待办事项列表分派“成功”操作之前的状态:
这是“成功” reducer返回带有待办事项列表的新状态后应用程序的状态:
引起我注意的是该工具正在更新状态更改,但选择器没有更新
这是涉及的所有代码。也许某个地方有错误。
todos.actions.ts:
import { createAction, props } from '@ngrx/store';
import { Todo } from '../../models/domain/todo';
import { TodoId } from '../../models/domain/todo-id/todo-id.model';
export const CREATE = createAction('[Todo] Add Todo', props<{ todo: Todo }>());
export const QUERY = createAction('[Todo] Query');
export const SUCCESS = createAction('[Todo] Load Todos Success', props<{ todos: TodoId[] }>());
export const ERROR = createAction('[Todo] Load Todos Error', props<{error: any}>());
todos.reducer.ts:
import { Todo } from '../../models/domain/todo';
import * as actions from '../actions/todo.actions';
// import { EntityState, createEntityAdapter } from '@ngrx/entity';
import { createReducer, on, Action } from '@ngrx/store';
// export const todoAdapter = createEntityAdapter<Todo>();
export interface State {
todos: Todo[];
loadedSuccess: boolean;
loadError: any;
}
export const initialState: State = {
todos: [],
loadedSuccess: false,
loadError: false
};
const todoReducerFn = createReducer(
initialState,
on(actions.QUERY, (state) => state),
on(actions.SUCCESS, handleSuccess),
on(actions.ERROR, (state, action) => ({ ...state, loadError: action.error }))
);
export function reducer(state: State | undefined, action: Action): State {
return todoReducerFn(state, action);
}
function handleSuccess(state: State, action): State {
return Object.assign(
{},
state,
{
todos: action.todos.slice(),
loadedSuccess: true
});
}
export const getTodos = (state: State) => state.todos;
export const getSuccessTodos = (state: State) => state.loadedSuccess;
export const getLoadError = (state: State) => state.loadError;
todos.effects.ts:
import { Injectable } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { Observable, EMPTY, of } from 'rxjs';
import { Action } from '@ngrx/store';
import * as actions from '../actions/todo.actions';
import { mergeMap, map, catchError, retry, switchMap } from 'rxjs/operators';
import { TodosService } from '../../services/todos-service/todos.service';
@Injectable()
export class TodosEffects {
constructor(
private actions$: Actions,
private todosService: TodosService
) { }
query$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(actions.QUERY),
switchMap(() =>
this.todosService._getAll().pipe(
map((todos) => actions.SUCCESS({ todos: todos })),
catchError((error) => of(actions.ERROR({ error: error })))
)
)
));
}
todos.service.ts:
import { Injectable } from '@angular/core';
import { Md5 } from 'ts-md5';
import { Todo } from '../../models/domain/todo';
import * as fromStore from '../../store/reducers/index';
import * as todoActions from '../../store/actions/todo.actions';
import { Store } from '@ngrx/store';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { map } from 'rxjs/operators';
import { TodoId } from '../../models/domain/todo-id/todo-id.model';
@Injectable({
providedIn: 'root'
})
export class TodosService {
todosCollection: AngularFirestoreCollection<Todo>;
constructor(
private store: Store<fromStore.State>,
private readonly db: AngularFirestore
) {
}
_getAll() {
return this.db.collection<Todo>('todos').stateChanges().pipe(
map(actions =>
actions.map(a => {
const data = a.payload.doc.data() as Todo;
const id = a.payload.doc.id;
return { id, ...data };
})
)
);
}
addTodo(text: string): void {
if (text != '') {
let newTodo = this.newTodo(text);
const id = this.db.createId();
this.todosCollection.doc<Todo>(id).set({ ...newTodo });
this.store.dispatch(todoActions.CREATE({ todo: newTodo }));
}
}
}
todos.component.ts:
import { Component, OnInit } from '@angular/core';
import { Todo } from '../../models/domain/todo';
import { TodosService } from '../../services/todos-service/todos.service';
import { Subscription, Observable } from 'rxjs';
import { Store, select } from '@ngrx/store';
import * as fromStore from '../../store/reducers/index';
import * as todoActions from '../../store/actions/todo.actions';
import { TodoId } from 'src/app/models/domain/todo-id/todo-id.model';
import { tap } from 'rxjs/operators';
@Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
styleUrls: ['./todos.component.scss']
})
export class TodosComponent implements OnInit {
todos: Todo[];
todosSubscription: Subscription;
todos$: Observable<Todo[]> = this.store.select(fromStore.getTodos).pipe(
tap((todos) => console.log(todos))
);
loadedSucess$: Observable<boolean>;
constructor(
private todosService: TodosService,
private store: Store<fromStore.State>
) { }
ngOnInit() {
this.store.dispatch(todoActions.QUERY());
}
}
todos.component.html:
<ng-container *ngFor="let todo of todos$ | async">
<mat-list-item *ngIf="!todo.completed">
<div fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px">
<mat-checkbox (click)="completeTodo(todo)">
{{ todo.text }}
</mat-checkbox>
<button role="button"
type="button"
mat-icon-button
(click)="onClickRemoveTodo(todo)"
aria-label="Remover tarea de la lista">
<mat-icon>clear</mat-icon>
</button>
</div>
</mat-list-item>
</ng-container>
app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { TodosModule } from './todos/todos.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { SharedModule } from './shared/shared.module';
import { MaterialModule } from './shared/material/material.module';
import { FlexLayoutModule } from "@angular/flex-layout";
import { NavigationModule } from './navigation/navigation.module';
import { ReactiveFormsModule } from '@angular/forms';
import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { AppEffects } from '../store/effects/app.effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../../environments/environment';
import { AuthEffects } from '../store/effects/auth.effects';
import { HttpClientModule } from '@angular/common/http';
import { reducers, metaReducers } from '../store/reducers';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFireAuthModule,
AngularFirestoreModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
BrowserAnimationsModule,
SharedModule,
MaterialModule,
FlexLayoutModule,
NavigationModule,
ReactiveFormsModule,
StoreDevtoolsModule.instrument({
maxAge: 25
}),
StoreModule.forRoot(reducers, { metaReducers }),
TodosModule,
EffectsModule.forRoot([AppEffects, AuthEffects]),
!environment.production ? StoreDevtoolsModule.instrument() : [],
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
todos.module.ts:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TodosRoutingModule } from './todos-routing.module';
import { TodosComponent } from './todos.component';
import { MaterialModule } from '../shared/material/material.module';
import { FlexLayoutModule } from '@angular/flex-layout';
import * as fromTodos from '../../store/reducers/todos.reducer';
import { StoreModule } from '@ngrx/store';
import { TodosEffects } from '../../store/effects/todos.effects';
import { EffectsModule } from '@ngrx/effects';
@NgModule({
declarations: [
TodosComponent
],
imports: [
CommonModule,
TodosRoutingModule,
MaterialModule,
FlexLayoutModule,
StoreModule.forFeature('todos', fromTodos.reducer),
EffectsModule.forFeature([TodosEffects])
]
})
export class TodosModule { }
store / reducers / index.ts
import {
ActionReducerMap,
createFeatureSelector,
createSelector,
MetaReducer,
ActionReducer
} from '@ngrx/store';
import { environment } from '../../../environments/environment';
import * as fromAuth from './auth.reducer';
import * as fromTodos from './todos.reducer';
export interface State {
auth: fromAuth.State;
todos: fromTodos.State;
}
export const reducers: ActionReducerMap<State> = {
auth: fromAuth.reducer,
todos: fromTodos.reducer,
};
export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
return (state, action) => {
console.log('state', state);
console.log('action', action);
return reducer(state, action);
};
}
export const metaReducers: MetaReducer<State>[] = !environment.production ? [debug] : [];
export const selectAuthState = createFeatureSelector<fromAuth.State>('auth');
export const getUserName = createSelector(selectAuthState, fromAuth.getUserName);
export const getFriendlyName = createSelector(selectAuthState, fromAuth.getFriendlyName);
export const selectTodoState = createFeatureSelector<fromTodos.State>('todos');
export const getTodos = createSelector(selectTodoState, fromTodos.getTodos);
export const getSuccessTodos = createSelector(selectTodoState, fromTodos.getSuccessTodos);
export const getLoadError = createSelector(selectTodoState, fromTodos.getLoadError);
这是依赖项版本:
"@angular/animations": "~7.2.0",
"@angular/cdk": "~7.3.7",
"@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0",
"@angular/core": "~7.2.0",
"@angular/fire": "^5.2.1",
"@angular/flex-layout": "^7.0.0-beta.24",
"@angular/forms": "~7.2.0",
"@angular/material": "^7.3.7",
"@angular/platform-browser": "~7.2.0",
"@angular/platform-browser-dynamic": "~7.2.0",
"@angular/router": "~7.2.0",
"@angular/service-worker": "~7.2.0",
"@ngrx/effects": "^8.0.1",
"@ngrx/entity": "^8.0.1",
"@ngrx/router-store": "^8.0.1",
"@ngrx/store": "^8.0.1",
"@ngrx/store-devtools": "^8.0.1",
"core-js": "^2.5.4",
"firebase": "^6.2.4",
"hammerjs": "^2.0.8",
"rxjs": "~6.3.3",
"ts-md5": "^1.2.4",
"tslib": "^1.9.0",
"zone.js": "~0.8.26"