Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Bobril - VI - BobX Application Store Management

5.00/5 (3 votes)
8 Jan 2020CPOL4 min read 13.1K  
Explanation of BobX application store management for bobril

Introduction

In the second article, we have created a simple ToDo application using bobflux framework. In this article, we will learn how to create such application in an easier way using the new framework BobX.

Background

BobX is MobX like library for managing application stores created by Boris Letocha (Quadient). It is written in TypeScript and fits the bobril application needs. It uses the observer pattern where stores are observable subjects and bobril components are observers.

Image 1

Let's Start

We will create a simple TODO application again. At first, we need to have prepared bobril-build on computer. Follow the steps in the first article to perform bobril-build installation.

Now you can start a new project again or use a predefined skeleton simpleApp from bobril-build github repository.

The following example will use it. To get the final code including all needed components, download the full sample.

BobX uses the experimental feature of TypeScript - decorators. To allow the usage of decorators, add the following parameter to the bobril/compilerOptions section in package.json:

JavaScript
"bobril": {
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

Add bobx to Application

Run the following commands in the root of application folder:

BAT
npm i
npm i bobx --save
bb

Store

At first, we will create a simple bobx store containing the same data as the bobflux variant. This store will be in file store.ts:

JavaScript
import { observable } from 'bobx';

class TodoStore {
    @observable todoName: string = '';
    @observable private _todos: string[] = [];

    get todos(): string[] {
        return this._todos;
    }

    addTodo(): void {
        if (this.todoName.trim().length === 0)
            return;
        this._todos.push(this.todoName.trim());
        this.todoName = '';
    }
}

export const todoStore = new TodoStore();

In the code above, you can see used an @observable decorator on fields todoName and _todos. This decorator creates getters/setters with the tracking functionality on these fields. It will cause tracking of these fields. When such field is used in any rendering function in node of bobril component, then function b.invalidate(ctx) will be called automatically with particular context on every change of this field.

Composing the Page with BobX

Now, we have everything prepared to be used on the page of the todo application. The src/mainPage.ts will look like this:

JavaScript
import * as b from 'bobril';
import { button } from './components/button';
import { textbox } from './components/textbox';
import { p } from './components/paragraph';
import { h1 } from './components/header';
import { todoStore } from './store';

export const mainPage = b.createComponent({
    render(_ctx: b.IBobrilCtx, me: b.IBobrilNode): void {
        me.children = [
            h1({}, 'TODO'),
            p({}, [
                textbox({ value: todoStore.todoName, 
                          onChange: newValue => todoStore.todoName = newValue }),
                button({ title: 'ADD', onClick: () => todoStore.addTodo() })
            ]),
            todoStore.todos.map(item => p({}, item)),
            p({}, `Count: ${todoStore.todos.length}`)
        ];
    }
});

The components definition is not the subject of this article, so you can use definitions in the attached source code.

You can see that a page imports the store directly from store module.

The textbox and button components use the actions defined on store in their onChange and onClick callbacks so the user interactions from view initiate the action calls. And finally, in the end of the render function is a mapped array of todos to 'p' tags with todo names.

Now, we are able to open the application in a browser on http://localhost:8080 and see how it works.

The Global store is one, but not the only one way how to define and use stores.
The Page store can be instantiated directly in the init method of component and given to its sub-components through data.
The Context store can be created as the component context for the component's inner data which you want to track.

Example of Context store:

JavaScript
class CtxStore extends b.BobrilCtx<IData> {
    @observable someProperty: string = '';

    constructor(data: IData, me: b.IBobrilCacheNode) {
        super(data, me);
        ...
    }
}

export const myComponent = b.createComponent<IData>({
    ctxClass: CtxStore,
    render(ctx: CtxStore) {
        ...
    }
})

Store Optimalizations

There is not only the pure observable function to define observable properties. Sometimes, you don’t want to track all properties of objects so let’s take a look at other possible ways:

  • observable.deep - The default observable way. It decorates all defined properties of given objects to be observable (tracked) recursively. The recursion stops when the property contains an object with defined prototype.
  • observable.ref - Only the reference of object is tracked. No change of inner properties will trigger rendering.
  • observable.shallow - This variant will track the reference of given object, its properties but nothing more. So for example, the array will be tracked for its reference, its content, but not for the content of its items.
  • observable.map - You can use this function to create a dynamic keyed observable map.
  • computed - You can use this decorator on any getter of a class property to declaratively created computed properties. Computed values are values that can be derived from the existing store or other computed values.

To get more information, see the project github pages:

or MobX documentation pages:

History

  • 2017-08-12: Article created for bobx@0.11.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)