How to use actions in NgRx
NgRx actions represent unique events in your Angular application that trigger state changes through reducers and side effects. As the creator of CoreUI with 12 years of Angular development experience, I’ve built enterprise NgRx applications where well-structured actions provide clear audit trails of all state mutations.
The most maintainable approach uses createAction with strongly-typed props for type-safe action creators.
Create Basic Actions
Create src/app/store/user/user.actions.ts:
import { createAction, props } from '@ngrx/store'
export const loadUsers = createAction('[User List] Load Users')
export const loadUsersSuccess = createAction(
'[User API] Load Users Success',
props<{ users: User[] }>()
)
export const loadUsersFailure = createAction(
'[User API] Load Users Failure',
props<{ error: string }>()
)
Dispatch Actions from Component
import { Component, OnInit } from '@angular/core'
import { Store } from '@ngrx/store'
import { loadUsers } from './store/user/user.actions'
@Component({
selector: 'app-user-list',
template: `
<button (click)="onLoadUsers()">Load Users</button>
<div *ngFor="let user of users$ | async">
{{ user.name }}
</div>
`
})
export class UserListComponent implements OnInit {
users$ = this.store.select(selectAllUsers)
constructor(private store: Store) {}
ngOnInit() {
this.store.dispatch(loadUsers())
}
onLoadUsers() {
this.store.dispatch(loadUsers())
}
}
Actions with Payload
import { createAction, props } from '@ngrx/store'
export const addUser = createAction(
'[User Form] Add User',
props<{ user: User }>()
)
export const updateUser = createAction(
'[User Form] Update User',
props<{ id: string; changes: Partial<User> }>()
)
export const deleteUser = createAction(
'[User List] Delete User',
props<{ id: string }>()
)
export const selectUser = createAction(
'[User List] Select User',
props<{ userId: string }>()
)
Action Naming Convention
// Format: [Source] Event
// Source: Where action originates
// Event: What happened
export const loginActions = {
// From login form component
login: createAction(
'[Login Form] Login',
props<{ username: string; password: string }>()
),
// From auth API effect
loginSuccess: createAction(
'[Auth API] Login Success',
props<{ user: User; token: string }>()
),
loginFailure: createAction(
'[Auth API] Login Failure',
props<{ error: string }>()
),
// From logout button
logout: createAction('[Header] Logout')
}
Action Groups
import { createActionGroup, props, emptyProps } from '@ngrx/store'
export const UserActions = createActionGroup({
source: 'User',
events: {
'Load Users': emptyProps(),
'Load Users Success': props<{ users: User[] }>(),
'Load Users Failure': props<{ error: string }>(),
'Add User': props<{ user: User }>(),
'Update User': props<{ id: string; changes: Partial<User> }>(),
'Delete User': props<{ id: string }>()
}
})
// Usage
this.store.dispatch(UserActions.loadUsers())
this.store.dispatch(UserActions.addUser({ user }))
Actions with Effects
Create src/app/store/user/user.effects.ts:
import { Injectable } from '@angular/core'
import { Actions, createEffect, ofType } from '@ngrx/effects'
import { of } from 'rxjs'
import { map, catchError, switchMap } from 'rxjs/operators'
import { UserService } from '@/services/user.service'
import * as UserActions from './user.actions'
@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUsers),
switchMap(() =>
this.userService.getUsers().pipe(
map(users => UserActions.loadUsersSuccess({ users })),
catchError(error =>
of(UserActions.loadUsersFailure({ error: error.message }))
)
)
)
)
)
addUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.addUser),
switchMap(({ user }) =>
this.userService.createUser(user).pipe(
map(newUser => UserActions.addUserSuccess({ user: newUser })),
catchError(error =>
of(UserActions.addUserFailure({ error: error.message }))
)
)
)
)
)
constructor(
private actions$: Actions,
private userService: UserService
) {}
}
Actions in Reducers
Create src/app/store/user/user.reducer.ts:
import { createReducer, on } from '@ngrx/store'
import * as UserActions from './user.actions'
export interface UserState {
users: User[]
loading: boolean
error: string | null
selectedUserId: string | null
}
const initialState: UserState = {
users: [],
loading: false,
error: null,
selectedUserId: null
}
export const userReducer = createReducer(
initialState,
on(UserActions.loadUsers, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false
})),
on(UserActions.loadUsersFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
on(UserActions.addUser, (state, { user }) => ({
...state,
users: [...state.users, user]
})),
on(UserActions.updateUser, (state, { id, changes }) => ({
...state,
users: state.users.map(user =>
user.id === id ? { ...user, ...changes } : user
)
})),
on(UserActions.deleteUser, (state, { id }) => ({
...state,
users: state.users.filter(user => user.id !== id)
})),
on(UserActions.selectUser, (state, { userId }) => ({
...state,
selectedUserId: userId
}))
)
Action Type Guards
import { Action } from '@ngrx/store'
export function isLoadUsersAction(action: Action): action is ReturnType<typeof loadUsers> {
return action.type === loadUsers.type
}
// Usage in middleware or testing
if (isLoadUsersAction(action)) {
console.log('Loading users...', action)
}
Conditional Actions
import { Component } from '@angular/core'
import { Store } from '@ngrx/store'
import { map, take } from 'rxjs/operators'
import { selectUserRole } from './store/selectors'
import { deleteUser } from './store/actions'
@Component({
selector: 'app-user-item',
template: `
<button (click)="onDelete()" *ngIf="canDelete$ | async">
Delete
</button>
`
})
export class UserItemComponent {
canDelete$ = this.store.select(selectUserRole).pipe(
map(role => role === 'admin')
)
constructor(private store: Store) {}
onDelete() {
this.canDelete$.pipe(take(1)).subscribe(canDelete => {
if (canDelete) {
this.store.dispatch(deleteUser({ id: this.user.id }))
}
})
}
}
Action Testing
import { TestBed } from '@angular/core/testing'
import { provideMockStore, MockStore } from '@ngrx/store/testing'
import { UserListComponent } from './user-list.component'
import { loadUsers } from './store/user/user.actions'
describe('UserListComponent', () => {
let store: MockStore
let component: UserListComponent
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UserListComponent],
providers: [provideMockStore()]
})
store = TestBed.inject(MockStore)
component = TestBed.createComponent(UserListComponent).componentInstance
})
it('should dispatch loadUsers on init', () => {
spyOn(store, 'dispatch')
component.ngOnInit()
expect(store.dispatch).toHaveBeenCalledWith(loadUsers())
})
})
Best Practice Note
This is the same NgRx action pattern we use in CoreUI’s Angular admin templates. Actions provide a clear contract between components and state management, making the application easier to debug and test. Always use descriptive action names with source context and strongly-typed props for compile-time safety.
For production applications, consider using CoreUI’s Angular Admin Template which includes pre-configured NgRx patterns with comprehensive action organization.
Related Articles
For complete state management implementation, you might also want to learn how to use selectors in NgRx and how to use effects in NgRx.



