Перейти к содержанию

Store

NgRx Store (или просто хранилище) хранит в себе глобальное состояние Angular приложения в виде одного большого объекта.

В приложении может быть только одно хранилище.

Хранилище в NgRx представлено сервисом Store и выполняет следующие функции:

  • хранение глобального состояния приложения;
  • обновляет состояние в ответ на действие, принимаемое через метод dispatch();
  • предоставление доступа к состоянию.

Формирование глобального состояния в NgRx Store происходит путем объединения более мелких состояний, которые возвращают зарегистрированные в приложении редюсеры. Делается это с использованием ActionReducerMap<State>.

users.reducer.ts

1
2
3
4
5
6
7
8
9
export interface State {
    /* ... */
}
export function usersReducer(
    state: State = initialState,
    action: UsersUnion
) {
    /* ... */
}

articles.reducer.ts

1
2
3
4
5
6
7
8
9
export interface State {
    /*...*/
}
export function articlesReducer(
    state: State = initialState,
    action: ArticlesUnion
) {
    /*...*/
}

index.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import * as Users from './reducers/users.reducer';
import * as Articles from './reducers/articles.reducer';

export interface State {
    users: Users.State;
    articles: Articles.State;
}

export const reducers: ActionReducerMap<State> = {
    users: Users.usersReducer,
    articles: Articles.articlesReducer,
};

app.module.ts

1
2
3
4
5
6
import { reducers } from './store/reducers/index';

@NgModule({
    imports: [StoreModule.forRoot(reducers)],
})
export class AppModule {}

Ключи верхнего уровня иерархии глобального объекта состояния задаются разработчиком самостоятельно.

В последнем примере состояние определяется в корневом модуле. Но также NgRx Store может формироваться из состояний, определенных для второстепенных модулей.

users.module.ts

1
2
3
4
5
6
7
8
9
import { usersReducer } from './reducers/users.reducer';

@NgModule({
    imports: [
        StoreModule.forFeature('users', usersReducer),
        UsersModule,
    ],
})
export class UsersModule {}

app.module.ts

1
2
3
4
5
@NgModule({
    imports: [StoreModule.forRoot({}), UsersModule],
    // ...
})
export class AppModule {}

Для регистрации редюсеров на уровне второстепенных модулей используется метод forFeature() модуля StoreModule.forFeature(). При этом корневой модуль может вообще не иметь собственных редюсеров.

В случае если второстепенный модуль загружается асинхронно, то определенное для него состояние динамически добавится в глобальный объект после его полной загрузки. Если модуль не будет загружен вообще, то и в глобальном хранилище ничего связанного с ним тоже не будет.

Доступ к глобальному состоянию осуществляется через экземпляр сервиса Store, прямое обращение к которому возвращает объект Observable.

articles.actions.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Action } from '@ngrx/store';

export enum ArticlesActions {
    LoadArticle = '[Articles Page] LoadArticle',
    PublishArticle = '[Articles Page] PublishArticle',
}

export interface Article {
    id: number;
    title: string;
    published: boolean;
}

export class LoadArticle implements Action {
    readonly type = ArticlesActions.LoadArticle;

    constructor(public payload: { article: Article }) {}
}

export class PublishArticle implements Action {
    readonly type = ArticlesActions.PublishArticle;

    constructor(public payload: { id: number }) {}
}

export type ArticlesUnion = LoadArticle | PublishArticle;

articles.reducer.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export interface State {
    articles: {[id: number]: Article},
    count: number;
}

const initialState: State = {
    articles: {},
    count: 0
};

export function articlesReducer(state: State = initialState, action: ArticlesUnion) {
    switch(action.type){
    case ArticlesActions.LoadArticle:
        return {
            ...state,
            articles: {...state.articles, [action.payload.article.id]: action.payload.article}
        };
    case ArticlesActions.PublishArticle:
        return {
            ...state,
            articles: {...{published: true, ...state.article[action.payload.id]}, ...state.articles}
        };
    default:
        return state;
    }
}

app.component.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss'],
})
export class AppComponent {
    constructor(private store: Store) {
        this.store.subscribe((state) => console.log(state));

        this.store.dispatch(
            new LoadArticle({
                article: {
                    id: 1,
                    title: 'Learn NgRx',
                    publish: false,
                },
            })
        );

        this.store.dispatch(new PublishArticle({ id: 1 }));
    }
}

Значение NgRx Store передается обработчику непосредственно в момент вызова метода subscribe() и далее при любом изменении состояния.

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

Комментарии