- Установка Angular CLI
- Запуск Development Backend Server
- Запуск UI Server
- Добавляем ngRx
- Добавляем
feature
- Обновляем
state store
Action
Reducer
- Как прочитать данные из
store
(напрямую из store) Selector
- оптимальный вариант получения данных изstore
- Получаем state конкретной
feature
- Effects
- Dev Tools for NgRx
При помощи команды ниже angular-cli будет установлена глобально на вашей машине:
npm install -g @angular/cli
Мы можем запустить backend приложение при помощи следующей команды:
npm run server
Это небольшой написанный на Node REST API сервер.
Чтобы запустить frontend часть нашего кода, мы будем использовать Angular CLI:
npm start
Приложение доступно на порту 4200: http://localhost:4200
-
ng add @ngrx/store@latest
-
ng add @ngrx/store-devtools@latest
-
Обновится
app.module.ts
-
Можем использовать Redux DevTools Extension в браузере
Добавим store
в Auth.module
:
ng g store auth/Auth --module auth.module.ts
- Тем самым мы создадим:
ngrx-course\src\app\auth\reducers\index.ts
- зарегистрируем в
Auth.module
reducer:
StoreModule.forFeature(fromAuth.authFeatureKey, authReducer),
Чтобы обновить state store
:
- мы в компоненте inject-им
store
(Observable
) - вызываем на нем
dispatch
метод пробрасывая в него соотв-йaction
:
this.store.dispatch(login({user}));
(login.component.ts
)
В квадратных скобках указываем место, откуда срабатывает action
, далее идет описание action
export const login = createAction(
'[Login Page] User Login',
props<{user: User}>()
)
Создадим reducer authReducer
, который будет обрабатывать наши action
и обновлять state
,
зарегистрируем его в Auth.module
:
StoreModule.forFeature(fromAuth.authFeatureKey, authReducer),
Инжектим в конструкторе:
(private store: Store<AppState>)
store
- это Observable
, на который мы можем подписаться, чтобы получать актуальный state
:
this.store.subscribe((state) => {
console.log('state:', state)
});
this.isLoggedIn$ = this.store.pipe(map((state) => !!state['auth'].user));
this.isLoggedOut$ = this.store.pipe(map((state) => !state['auth'].user));
(app.component.ts
)
Selectors позволяют получать значение лишь после того, как оно изменило свое значение т.е. сохранять предыдущее значение в памяти (в отличие от подписки выше)
// src/app/auth/selectors/auth.selectors.ts
export const isLoggedIn = createSelector(
state => state['auth'],
(auth) => !!auth.user
);
// app.component.ts
export const isLoggedOut = createSelector(
isLoggedIn, // используем isLoggedIn селектор, чтобы сразу получить нужное значение и преобразовать его
loggedIn => !loggedIn
);
Функции в createSelector
похожи на обычную map
ф-ю, но сохраняет значение в памяти.
Используем селектор:
this.isLoggedIn$ = this.store.pipe(select(isLoggedIn));
this.isLoggedOut$ = this.store.pipe(select(isLoggedOut));
Получаем state конкретной feature
:
export const selectAuthState = createFeatureSelector<AuthState>('auth'); // берем нашу feature
export const isLoggedIn = createSelector(
selectAuthState,
(auth) => !!auth.user
);
Эффекты позволяют реализовать side-effect, например, если action
вызывает запрос к б.д.
или сохранение данных в localstorage
В app.module.ts
добавим EffectsModule.forRoot
:
imports: [
EffectsModule.forRoot([])
]
в auth.module
добавим для EffectsModule.forFeature
:
imports: [
EffectsModule.forFeature([AuthEffect])
]
login$ = createEffect(() => {
return this.actions$.pipe(
ofType(login),
tap(action => {
localStorage.setItem('user', JSON.stringify(action.user));
})
)
},
{dispatch: false}) // этот эффект не диспатчит побочные action, превентим infinite loop
(src/app/auth/effects/auth.effect.ts
)
Зарегистрируем reducer на роутинг (actions, router info - state etc):
В основном модуле добавим StoreRouterConnectingModule
(app.module.ts
):
StoreRouterConnectingModule.forRoot({
stateKey: 'router',
routerState: RouterState.Minimal
})
Также зарегистрируем reducer router в основном reducer:
export const reducers: ActionReducerMap<AppState> = {
router: routerReducer
};
- Проверяем, что
state
вreducer
иммутабельный (т.е. проверяем, чтоstate
в reducer меняется корректно). *1 - Проверяем, что
actions
иммутабельный *2 - Проверяем, что actions Serializability *3 (обьект распарсится в строку)
- Проверяем, что state Serializability *4
Добавляем в StoreModule
:
StoreModule.forRoot(reducers, {
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
strictActionSerializability: true,
strictStateSerializability: true
}
}),
metareducer
- это reducer, который вызывается до выполнения стандартного reducer.
см. logger