State management in Angular

State management

Store

· usersList

· selectedUserID

Kacper Walczak · 10-08-2023

State management

Learn how to manage state in Angular.

How to manage data in Angular?

What is state management?

State management is a way to manage data in your application. It's a way to store data in one place and share it between components.

It's a way to keep your data in understandable manor and to be easy to access and modify.

Data in components

It is most often used to manage data in a single component.

It is a good solution where the data is not shared between components.

@Component({
  selector: 'app-users-list',
  templateUrl: './users-list.component.html',
  styleUrls: ['./users-list.component.scss']
})
export class UsersListComponent implements OnInit {
  usersList: User[] = [];
 
  constructor(private usersService: UsersService) {}
 
  ngOnInit(): void {
    this.usersService.getUsers().subscribe((users) => {
      this.usersList = users;
    });
  }
}

Or declarative way:

@Component({
  selector: 'app-users-list',
  styleUrls: ['./users-list.component.scss'],
  template: `
    <ul>
      <li *ngFor="let user of usersList$ | async">
        {{ user.name }}
      </li>
    </ul>
  `
})
export class UsersListComponent {
  usersList$: Observable<User[]>;
 
  constructor(private usersService: UsersService) {
    this.usersList$ = this.usersService.getUsers();
  }
}

Data in services

Store data in service when you need to share it between components. It will be very readable and easy to use.

It is a good solution where the data is shared between components.

@Injectable({
  providedIn: 'root'
})
export class UsersService {
  usersList$: Observable<User[]>;
  selectedUserID$: Observable<number | null>;
 
  constructor(private http: HttpClient) {
      this.usersList$ = this.http.get<User[]>('https://jsonplaceholder.typicode.com/users');
      this.selectedUserID$ = new BehaviorSubject<number | null>(null);
  }
 
  selectUser(id: number): void {
      this.selectedUserID$.next(id);
  }
}

It can be used in components like this:

@Component({
  selector: 'app-users-list',
  styleUrls: ['./users-list.component.scss'],
  template: `
    <ul>
      <li *ngFor="let user of usersService.usersList$ | async">
        <button (click)="usersService.selectUser(user.id)">{{ user.name }}</button>
      </li>
    </ul>
    <p *ngIf="usersService.selectedUserID$ | async as selectedUserID">
      Selected user ID: {{ selectedUserID }}
    </p>
  `
})
export class UsersListComponent {
  constructor(public usersService: UsersService) {}
}

Data in store

Store data in store when you need to share it between components. Just focus on the data, not on the implementation. And encapsulate the implementation in the store.

It is a good solution where the data is shared and listened between multiple components.

@Injectable({
  providedIn: 'root'
})
export class UsersStore {
  private usersListSubject = new BehaviorSubject<User[]>([]);
  private selectedUserIDSubject = new BehaviorSubject<number | null>(null);
 
  usersList$: Observable<User[]> = this.usersListSubject.asObservable();
  selectedUserID$: Observable<number | null> = this.selectedUserIDSubject.asObservable();
 
  constructor(private http: HttpClient) {
    this.http.get<User[]>('https://jsonplaceholder.typicode.com/users').subscribe((users) => {
      this.usersListSubject.next(users);
    });
  }
 
  selectUser(id: number): void {
    this.selectedUserIDSubject.next(id);
  }
}

Or maybe add Actions to the store, you can listen to them and react to them in any place of your application:

@Injectable({
  providedIn: 'root'
})
export class UsersStore {
  private usersListSubject = new BehaviorSubject<User[]>([]);
  private selectedUserIDSubject = new BehaviorSubject<number | null>(null);
  private actions$ = new Subject<Action>();
 
  usersList$: Observable<User[]> = this.usersListSubject.asObservable();
  selectedUserID$: Observable<number | null> = this.selectedUserIDSubject.asObservable();
 
  constructor() {
      this.listen('SELECT_USER').pipe(
          tap(action => this.selectedUserIDSubject.next(action.payload.id))
      ).subscribe();
  }
 
  dispatch(action: Action): void {
    this.actions$.next(action);
  }
 
  listen(actionType: string): Observable<Action> {
    return this.actions$.pipe(ofType(actionType));
  }
}
 
function ofType(type: string) {
  return function<Action>(source: Observable<Action>) {
    return source.pipe(filter(action => action.type === type));
  }
}
 
class Action {
  constructor(public type: string, public payload?: any) {}
}

It can be used in components like this:

@Component({
  selector: 'app-users-list',
  styleUrls: ['./users-list.component.scss'],
  template: `
    <ul>
      <li *ngFor="let user of usersStore.usersList$ | async">
        <button (click)="usersStore.dispatch({ type: 'SELECT_USER', payload: { id: user.id } })">
          {{ user.name }}
        </button>
      </li>
    </ul>
    <p *ngIf="usersStore.selectedUserID$ | async as selectedUserID">
      Selected user ID: {{ selectedUserID }}
    </p>
  `
})
export class UsersListComponent {
  constructor(public usersStore: UsersStore) {}
}

Use library

You can use library to manage your state. There are many libraries to choose from.

  1. NgRx (opens in a new tab)
  2. Redux (opens in a new tab)
  3. NGXS (opens in a new tab)
  4. QStore

Qstore

Install

Navigate to Qstore library for more info about installation.

Use
class UsersStore extends Store<{users: User[], selectedUserID: number}> {
    usersList$ = this.select(state => state.users);
    selectedUserID$ = this.select(state => state.selectedUserID);
 
    constructor() {
        super({
            users: [],
            selectedUserID: null
        });
    }
 
    selectUser(id: number): void {
        this.set({ selectedUserID: id });
    }
}

Or use actions:

class UsersStore extends Store<{users: User[], selectedUserID: number}> {
    usersList$ = this.select(state => state.users);
    selectedUserID$ = this.select(state => state.selectedUserID);
 
    actions = {
        selectUser: ActionWith<number>(),
        fetchUsers: Action()
    }
 
    constructor() {
        super({
            users: [],
            selectedUserID: null
        });
        this.actions.fetchUsers.listen().subscribe(async () => {
            this.set({ users: await this.http.get<User[]>('https://jsonplaceholder.typicode.com/users') });
        });
        this.actions.selectUser.listen().subscribe((id) => {
            this.set({ selectedUserID: id });
        });
    }
}

And use store in components:

@Component({
  selector: 'app-users-list',
  styleUrls: ['./users-list.component.scss'],
  template: `
    <ul>
      <li *ngFor="let user of usersStore.usersList$ | async">
        <button (click)="usersStore.actions.selectUser.execute(user.id)">
          {{ user.name }}
        </button>
      </li>
    </ul>
    <p *ngIf="usersStore.selectedUserID$ | async as selectedUserID">
      Selected user ID: {{ selectedUserID }}
    </p>
  `
})
export class UsersListComponent {
  constructor(public usersStore: UsersStore) {
      this.usersStore.actions.fetchUsers.execute();
  }
}

That's it! Now you can use this examples to manage your state in Angular.

READ

Latest readings

  • Readings are sites which will help you with detailed

  • information about given topic. Read latest ones from Learn.

AI

06-03-2026

Local Voice Assistant with Ollama
  • Build your own local voice assistant powered by Ollama.

AI

06-03-2026

AI YouTube Thumbnail Generator
  • Generate YouTube thumbnails with FastAPI and Ollama.

Architecture

05-09-2024

Graph DB usage comparison
  • Compare Neo4j and Tigergraph databases, which is easier to work with, etc.