Skip to content

Incremental Complexity

1. Start with simple state

StateAdapt stores can be as simple as RxJS BehaviorSubjects:

typescript
export class NameComponent {
  nameStore = adapt('Bob');
}
angular-html
<h1>Hello {{ nameStore.state$ | async }}!</h1>
<button (click)="nameStore.set('Bilbo')">Change Name</button>

StackBlitz

2. Add selectors for derived state

typescript
export class NameComponent {
  nameStore = adapt('Bob', {
    selectors: {
      yelledName: name => name.toUpperCase(), // Will be memoized
    },
  });
}
angular-html
<h1>Hello {{ nameStore.state$ | async }}!</h1>
<h1>Hello {{ nameStore.yelledName$ | async }}!</h1>
<button (click)="nameStore.set('Bilbo')">Change Name</button>

StackBlitz

3. Define state changes declaratively in stores

Maintain separation of concerns by keeping state logic together instead of scattered.

typescript
export class NameComponent {
  nameStore = adapt('Bob', {
    reverseName: name => name.split('').reverse().join(''), // [!code +]
    selectors: {
      yelledName: name => name.toUpperCase(), // Will be memoized
    },
  });
}
angular-html
 <h1>Hello {{ nameStore.yelledName$ | async }}!</h1>
 <button (click)="nameStore.set('Bilbo')">Change Name</button>
 <button (click)="nameStore.reverseName()">Reverse Name</button>

StackBlitz

4. Reuse state patterns with state adapters

If you need to reuse state logic, it's as simple as dragging it outside the adapt call into a createAdapter call.

typescript
export class NameComponent {
  nameStore = adapt('Bob', { 
  nameAdapter = createAdapter<string>()({ 
    reverseName: name => name.split('').reverse().join(''),
    selectors: {
      yelledName: name => name.toUpperCase(), // Will be memoized
    },
  });

  name1Store = adapt('Bob', this.nameAdapter); 
  name2Store = adapt('Bob', this.nameAdapter); 
}
angular-html
 <h1>Hello {{ name1Store.yelledName$ | async }}!</h1>
 <button (click)="name1Store.set('Bilbo')">Change Name</button>
 <button (click)="name1Store.reverseName()">Reverse Name</button>

 <h1>Hello {{ name2Store.yelledName$ | async }}!</h1>
 <button (click)="name2Store.set('Bilbo')">Change Name</button>
 <button (click)="name2Store.reverseName()">Reverse Name</button>

StackBlitz

5. React to observable data sources

Multiple stores might need to react to the same observable, so it needs independent annotation.

typescript
export class NameComponent {
  nameAdapter = createAdapter<string>()({
    reverseName: name => name.split('').reverse().join(''),
    concatName: (name, anotherName: string) => `${name} ${anotherName}`, 
    selectors: {
      yelledName: name => name.toUpperCase(), // Will be memoized
    },
  });

  nameFromServer$ = timer(3000).pipe(map(() => 'Joel')); 

  name1Store = adapt('Bob', this.nameAdapter); 
  name1Store = adapt('Bob', {
    adapter: this.nameAdapter, 
    sources: this.nameFromServer$, // Set state
  }); 
  name2Store = adapt('Bob', this.nameAdapter); 
  name2Store = adapt('Bob', {
    adapter: this.nameAdapter, 
    sources: {
      concatName: this.nameFromServer$, // Trigger a specific state reaction
    }, 
  }); 
}
angular-html
<h1>Hello {{ name1Store.yelledName$ | async }}!</h1>
<button (click)="name1Store.set('Bilbo')">Change Name</button>
<button (click)="name1Store.reverseName()">Reverse Name</button>

<h1>Hello {{ name2Store.yelledName$ | async }}!</h1>
<button (click)="name2Store.set('Bilbo')">Change Name</button>
<button (click)="name2Store.reverseName()">Reverse Name</button>

StackBlitz

6. Share DOM event sources with multiple stores

Don't write callback functions to imperatively change state in multiple stores. Instead, declare the DOM event as an independent source that multiple stores can react to.

typescript
export class NameComponent {
  nameAdapter = createAdapter<string>()({
    reverseName: name => name.split('').reverse().join(''),
    concatName: (name, anotherName: string) => `${name} ${anotherName}`,
    selectors: {
      yelledName: name => name.toUpperCase(), // Will be memoized
    },
  });

  resetBoth$ = source(); 

  nameFromServer$ = timer(3000).pipe(map(() => 'Joel'));

  name1Store = adapt('Bob', {
    adapter: this.nameAdapter,
    sources: this.nameFromServer$, // Set state
    sources: {
      set: this.nameFromServer$, // `set` is provided with all adapters
      reset: this.resetBoth$, // `reset` is provided with all adapters
    }, 
  });
  name2Store = adapt('Bob', {
    adapter: this.nameAdapter,
    sources: {
      concatName: this.nameFromServer$, // Trigger a specific state reaction
      reset: this.resetBoth$, // `reset` is provided with all adapters
    },
  });
}
angular-html
 <h1>Hello {{ name1Store.yelledName$ | async }}!</h1>
 <button (click)="name1Store.set('Bilbo')">Change Name</button>
 <button (click)="name1Store.reverseName()">Reverse Name</button>

 <h1>Hello {{ name2Store.yelledName$ | async }}!</h1>
 <button (click)="name2Store.set('Bilbo')">Change Name</button>
 <button (click)="name2Store.reverseName()">Reverse Name</button>

 <button (click)="resetBoth$.next()">Reset Both</button>

StackBlitz

7. Select state from multiple stores

typescript
export class NameComponent {
  nameAdapter = createAdapter<string>()({
    reverseName: name => name.split('').reverse().join(''),
    concatName: (name, anotherName: string) => `${name} ${anotherName}`,
    selectors: {
      yelledName: name => name.toUpperCase(), // Will be memoized
    },
  });

  resetBoth$ = source();

  nameFromServer$ = timer(3000).pipe(map(() => 'Joel'));

  name1Store = adapt('Bob', {
    adapter: this.nameAdapter,
    sources: {
      set: this.nameFromServer$, // `set` is provided with all adapters
      reset: this.resetBoth$, // `reset` is provided with all adapters
    },
  });
  name2Store = adapt('Bob', {
    adapter: this.nameAdapter,
    sources: {
      concatName: this.nameFromServer$, // Trigger a specific state reaction
      reset: this.resetBoth$, // `reset` is provided with all adapters
    },
  });

  bothBobs$ = joinStores({
    name1: this.name1Store, 
    name2: this.name2Store, 
  })({
    bothBobs: s => s.name1 === 'Bob' && s.name2 === 'Bob', 
  })().bothBobs$; 
}
angular-html
 <h1>Hello {{ name1Store.yelledName$ | async }}!</h1>
 <button (click)="name1Store.set('Bilbo')">Change Name</button>
 <button (click)="name1Store.reverseName()">Reverse Name</button>

 <h1>Hello {{ name2Store.yelledName$ | async }}!</h1>
 <button (click)="name2Store.set('Bilbo')">Change Name</button>
 <button (click)="name2Store.reverseName()">Reverse Name</button>

 <button (click)="resetBoth$.next()">Reset Both</button>

 <h2 *ngIf="bothBobs$ | async">Hello Bobs!</h2>

StackBlitz