The main idea of this library is remove useless code from class.
Usually state services violate DRY pattern
This library will help to create state in 3 lines.
Visit wiki page to get more useful information.
npm install ngx-base-state --save
OPTIONAL: If you want to use Devtools to explore your state via Chrome Extension:
import { NgxBaseStateDevtoolsModule } from 'ngx-base-state';
import { environment } from 'src/environments/environment';
@NgModule({
imports: [
NgxBaseStateDevtoolsModule.forRoot({ isEnabled: !environment.production })
]
})
export class AppModule {}
This tool allows you to see data in your states based on ngx-base-state.
- Install
ngx-base-state
extension from Chrome WebStore; - Open tab with your Application using ngx-base-state;
- Press F12 to open Devtools;
- Choose ngx-base-state panel in devtools;
Main page will contain list of all your states. Click to some state and will opened "details page" with state changes history.
List of States | List of Actions | State details |
---|---|---|
Base class for all kinds of states. You can create your abstract class
based on BaseState
to store the necessary custom data.
Name | Type | Description |
---|---|---|
data$ | Observable<T | null> | state data stream |
data | T | state data |
set | value: T (generic type) | set new value for state |
clear | clear value in the state | |
restoreInitialData | restore initial data from constructor. |
Extend your class from ObjectState
to store the object
.
Contains all fields and methods like at BaseState
, and also:
Name | Arguments | Description |
---|---|---|
updateWithPartial | value: Partial<T> | update state by merging current state with new partial object |
Extend your class from RecordState
to store the object
with Record
interface.
Store data in key -> value
format.
Contains all fields and methods like at BaseState
, and also:
Name | Arguments | Description |
---|---|---|
keys$ | stream with all keys of your Record object | |
keys | all keys of your Record object | |
values | all values of your Record object | |
values$ | stream with all values of your Record object | |
setItem | key: TKey, value: TValue | set item by key into state's object |
removeItem | key: TKey | remove item by key from state's object |
removeAllItems | remove all items from state's object |
Extend your class from ArrayState
to store the array
.
Contains all fields and methods like at BaseState
, and also:
Name | Arguments | Description |
---|---|---|
getItemId | item: T | protected method might be overridden, it used for comparing items in array |
set | value: T[] | set new array for state |
pushItem | item: T | push new item to array |
unshiftItem | item: T | unshift item to array |
shift | shift array | |
pop | pop array | |
insertItemByIndex | index: number, item: T | insert item in array by index. |
updateItem | itemToUpdate: T | update item in array |
updateItemByIndex | item: T, index: number | update item in array by index |
concatWith | array: T[] | concat current state with another array |
removeItem | item: T | remove item from array |
removeItemById | itemId: unknown | remove item from array by id (define id by overriding getItemId method) |
removeItemByIndex | index: number | remove item from array by index |
Extend your class from PrimitiveState
to store the: number
, string
, boolean
, enum
, type
etc...
Contains all fields and methods like at BaseState
and currently nothing else.
user.state.ts
import { ObjectState, NgxState } from 'ngx-base-state';
// So easy to create new State :)
@NgxState()
@Injectable({
providedIn: 'root'
})
class UserState extends ObjectState<User> {}
user.service.ts
import { User } from '../interfaces';
import { UserApi } from '../api';
import { UserState } from '../states';
// IMPORTANT: Work with states only via "Service" layer.
@Injectable({
providedIn: 'root'
})
class UserService {
// Share data for components.
public readonly data$ = this.userState.data$;
constructor(
private readonly userApi: UserApi,
private readonly userState: UserState
) {}
// Make your methods with business logic, which might affect states.
// Return Observable. Components can process result by subscribing (complete/next/error).
public update(): Observable<User> {
return this.userApi.getCurrent()
.pipe(
tap((user) => this.userState.set(user))
);
}
}
user.component.ts
import { ToastService } from '@my-library';
import { UserService } from '@features/user';
// IMPORTANT: Don't inject States directly to components!
// Only services with business logic should know how to affect your states.
@Component({
selector: 'smart-user',
template: '{{ user$ | async | json }}'
})
class UserComponent implements OnInit {
// Here is data from our state.
public readonly user$ = this.userService.data$;
constructor(
private readonly userService: UserService,
private readonly toastService: ToastService
) {}
public ngOnInit(): void {
this.updateUser();
}
// Run some services business logic from the smart component
private updateUser(): void {
this.userService.update()
.pipe(
catchError(() => this.showErrorToastAboutUserUpdatingError())
)
.subscribe();
}
// This is task of specific smart components to show UI staff, like: toasts, dialogs, bottomSheets etc...
private showErrorToastAboutUserUpdatingError(): Observable<unknown> {
return this.toastService.createError(`Can't update user!`);
}
}
users.state.ts
import { ArrayState, NgxState } from 'ngx-base-state';
import { UserFilters } from '../interfaces';
@NgxState()
@Injectable({
providedIn: 'root'
})
class UsersState extends ArrayState<User> {
constructor() {
super([]); // Here you can set initial data.
}
// Example of "custom action"
public filter(filters: UserFilters): void {
const newUsers = this.data!.filter((user) => user.name.includes(filters.searchString));
this.set(newUsers);
}
// ArrayState have base methods to work with array, like: removeItem, updateItem
// and these methods might compare items in array using some unique value.
// You can override method `getItemId` if you want operate with items via specific unique value like `id`.
protected override getItemId(user: User): number {
return user.id;
}
}
users.service.ts
import { UsersState } from './users.state';
// This service demonstrates examples of work with methods of ArrayState.
@Injectable({
providedIn: 'root'
})
export class UsersService implements OnInit {
// Async data for components.
public readonly data$ = this.usersState.data$;
// Sync data for components.
public get data(): User {
return this.usersState.data;
}
constructor(
private readonly usersState: UsersState
) {
this.usersState.data$
.subscribe(console.log);
this.setUserArray(); // [{ name: 'Nillcon', id: 248 }, { name: 'noname', id: 1 }]
this.updateUser() // [{ name: 'New name', id: 248 }, { name: 'noname', id: 1 }]
this.removeUser(); // [{ name: 'New name', id: 248 }]
this.addUser(); // [{ name: 'New name', id: 248 }, { name: 'John Doe', id: 2 }]
}
private setUserArray(): void {
this.usersState.set([
{
name: 'Nillcon',
id: 248
},
{
name: 'noname',
id: 1
}
]);
}
private updateUser(): void {
let user = this.usersState.data[0]; // { name: 'Nillcon', id: 248 }
user.name = 'New name';
// ngx-base-state will create new instance of user to avoid possible object mutations
this.usersState.updateItem(user);
}
private removeUser(): void {
const user = this.usersState.data[1]; // { name: 'noname', id: 1 }
this.usersState.removeItem(user);
}
private addUser(): void {
this.usersState.pushItem({
name: 'John Doe',
id: 2
});
}
}