lom_atom
State management with error handling and reactive cache done right.
Alternative standalone implementation of eigenmethod mol_atom.
- About 11kb minified
- Memory-efficient
- Simpler, less core concept than mobx
- Loading status / error handling features
Usage examples with reactive-di: example source, demo, todomvc benchmark
Install npm install --save lom_atom
Observable property
import {mem} from 'lom_atom'
class Todo {
@mem title = ''
}
const todo = new Todo()
todo.title = '123'
Observable get/set
import {mem} from 'lom_atom'
class Todo {
@mem set title(next: string) {
}
@mem get title(): string {
return 'default'
}
}
const todo = new Todo()
todo.title = '123'
Force mode cache management
Killer feature, grabbed from mol_atom. We can reset cache on get or obviously set cache value, using magic force property.
On set value: force mode talk lom to pass value through set handler. On get value: invoke handler with undefined, true
and reset cache.
import {force, mem} from 'lom_atom'
class TodoList {
@force force: TodoList
@mem set todos(todos: Todo | Error) {
console.log('set handler')
}
@mem get todos() {
console.log('get handler')
return [someTodo]
}
}
const list = new TodoList()
list.todos = [someTodo]
list.todos = [someTodo]
list.force.todos = [someTodo]
list.todos
list.todos
list.force.todos
Method-style properties
In this form we can change value on set. Less magic, than regular properties.
import {action, mem} from 'lom_atom'
class Some {
@force $: Some
@mem name(next?: string, force?: boolean): string {
if (next !== undefined) return next
return 'default value'
}
}
const some = new Some()
some.name() === 'default value'
some.name('new value')
some.name() === 'new value'
some.name('val', true)
some.name(undefined, true) === 'default value'
some.force.name(val)
alias of some.name(val, true)
And some.force.name()
alias of some.name(undefined, true)
Computed values
One decorator for all cases.
class TodoList {
@mem todos = []
@mem get unfinishedTodos() {
return this.todos.filter((todo) => !todo.finished)
}
}
Like mobx, unfinishedTodos is updated automatically when a todo changed.
Side effects
Like mobx reaction produces a side effect for making network requests, etc. But side effects in lom are simpler.
Listener.listen throws errors on todo list store property access, if todo list loading finished with erorr or loading in progress.
class TodoList {
@mem set todos(todos: Todo | Error) {}
@mem get todos() {
fetch('/todos')
.then((todos) => {
this.todos = todos
})
.catch(e => this.todos = e)
throw new mem.Wait()
}
@mem get unfinishedTodos() {
return this.todos.filter((todo) => !todo.finished)
}
}
class Listener {
_list = new TodoList()
@mem listen() {
try {
console.log('total todos:', this._list.todos.length)
console.log('unfinished todos:', this._list.unfinishedTodos.length)
} catch (e) {
if (e instanceof mem.Wait) {
console.log('loading...')
} else {
console.error('error', e.message)
}
}
}
}
const listener = new Listener()
listener.listen()
Key-value
Basic dictionary support. First argument is an key of any type. See eigenmethod mol_mem.
class TodoList {
@mem.key todo(id: string, next?: Todo, force?: boolean): Todo {
if (next === undefined) {
return {}
}
return next
}
}
const list = new TodoList()
list.todo('1', {id: 1, title: 'Todo 1'})
list.todo('1')
Actions
State updates are asynchronous, but sometime we need to do transactional synced updates via action helper:
import {action, mem} from 'lom_atom'
class Some {
@mem name = ''
@mem id = ''
@action set(id: string, name: string) {
this.id = id
this.name = name
}
}
const some = new Some()
action(() => {
some.name = 'test'
some.id = '123'
})
some.set('123', 'test')