Services
A service contain methods, it can be simple methods or event hooks
type State = { count: number };
export class CounterService extends Service<State> {
state: State = { count: 0 };
async decrement() {
if (this.state.count !== 0) {
this.state.count--;
}
}
}
The methods of a service can:
Access to the state
You can access the local state by reading and writing directly on its properties. Thanks to valtio, the state is automatically reactive.
In this example the counter is decremented only if it is not 0
type State = { count: number };
export class CounterService extends Service<State> {
state: State = { count: 0 };
async decrement() {
if (this.state.count !== 0) {
this.state.count--;
}
}
}
Access to the other services
For exemple we have a todoList form, it should append the form values to a todoList, then the form resets.
The submit
method of TodoFormService
calls the method append
of TodoListService
type Form = { name: string };
type TodoFormState = Form;
export class TodoFormService extends Service<TodoFormState> {
state: TodoFormState = { name: "" };
submit() {
const todoListService = this.getService("todoList");
todoListService.append(this.state);
this.state.name = "";
}
}
This way each service can have a single responsibility
init()
You can write an init method that will be executed right after the core is instantiated It is the perfect place to initialize listeners or setup dependencies
type State = { messages: string[] };
export class MessageService extends Service<State> {
state: State = { messages: [] };
init() {
this.dependencies.notifications.onReceive(this.onReceive);
}
onReceive = (receivedNotification: string) => {
this.state.messages.push(receivedNotification);
}
}
Subscribe to state changes
Use this.subscribe()
to listen to state changes within a service:
export class UserService extends Service<{ name: string }> {
state = { name: "" };
init() {
this.subscribe((state) => {
console.log("User name changed:", state.name);
});
}
setName(name: string) {
this.state.name = name; // Will trigger the subscription
}
}
The dependencies
If your app is in clean architecture and uses the port-adapter pattern, you can access any of the dependencies in any service method
interface ShoesApiGateway {
get(): Promise<Shoe[]>;
}
type Dependencies = {
shoesApi: ShoesApiGateway;
};
type State = Shoe[];
export class ShoesService extends Service<State> {
state: State = [];
async loadShoes() {
const shoes = await this.dependencies.shoesApi.get();
this.state.push(...shoes);
}
}
The dependencies can then be injected when creating the core
class RealShoesApiAdapter implements ShoesApiGateway {
async get() {
return axios.get<Shoe[]>("https://my-api/shoes/get");
}
}
const core = createCore({ shoesApi: new RealShoesApiAdapter() });