Incremental Complexity
1. Start with simple state
StateAdapt stores can be as simple as RxJS BehaviorSubject
s:
typescript
export class NameComponent {
nameStore = adapt('Bob');
}
angular-html
<h1>Hello {{ nameStore.state$ | async }}!</h1>
<button (click)="nameStore.set('Bilbo')">Change Name</button>
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>
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>
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>
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>
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>
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>