Observable Types

Observable Objects

If a plain JavaScript object is passed to observable, all properties inside will be copied into a clone and made observable. (A plain object is an object that wasn't created using a constructor function / but has Object as its prototype, or no prototype at all.) observable is by default applied recursively, so if one of the encountered values is an object or array, that value will be passed through observable as well.

import {observable, autorun, action} from "mobx";

var person = observable({
    // observable properties:
    name: "John",
    age: 42,
    showAge: false,

    // computed property:
    get labelText() {
        return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
    },

    // action:
    setAge: action(function(age) {
        this.age = age;
    })
});

// object properties don't expose an 'observe' method,
// but don't worry, 'mobx.autorun' is even more powerful
autorun(() => console.log(person.labelText));

person.name = "Dave";
// prints: 'Dave'

person.setAge(21);
// etc

Some things to keep in mind when making objects observable:

  • When passing objects through observable, only the properties that exist at the time of making the object observable will be observable. Properties that are added to the object at a later time won't become observable, unless extendObservable is used.
  • Only plain objects will be made observable. For non-plain objects, it is considered the responsibility of the constructor to initialize the observable properties. Either use the @observable annotation or the extendObservable function.
  • Property getters will be automatically turned into derived properties, just like @computed would do.
  • observable is applied recursively to a whole object graph automatically. Both on instantiation and to any new values that will be assigned to observable properties in the future. Observable will not recurse into non-plain objects.
  • These defaults are fine in 95% of the cases, but fore more fine-grained control on how and which properties should be made observable, use the modifiers.

observable.object(props) & observable.shallowObject(props)

observable(object) is just a shorthand for observable.object(props). All properties are by default made deep observable. Modifiers can be used to override this behavior for individual properties. shallowObject(props) can be used to make the properties only shallow observables. That is, the reference to the value is observable, but the value itself won't be made observable automatically.

Name Argument

Both observable.object and observable.shallowObject take a second parameter which is sued as debug name in, for example, spy or the MobX dev tools.

Observable Arrays

Similar to objects, arrays can be made observable using observable.array(values?) or by passing an array to observable. This works recursively as well, so all (future) values of the array will also be observable.

import {observable, autorun} from "mobx";

var todos = observable([
    { title: "Spoil tea", completed: true },
    { title: "Make coffee", completed: false }
]);

autorun(() => {
    console.log("Remaining:", todos
        .filter(todo => !todo.completed)
        .map(todo => todo.title)
        .join(", ")
    );
});
// Prints: 'Remaining: Make coffee'

todos[0].completed = false;
// Prints: 'Remaining: Spoil tea, Make coffee'

todos[2] = { title: 'Take a nap', completed: false };
// Prints: 'Remaining: Spoil tea, Make coffee, Take a nap'

todos.shift();
// Prints: 'Remaining: Make coffee, Take a nap'

Due to limitations of native arrays in ES5, observable.array will create a faux-array (array-like object) instead of a real array. In practice, these arrays work just as fine as native arrays and all native methods are supported, including index assignments, up-to and including the length of the array.

Bear in mind, however, that Array.isArray(observable([])) will yield false, so whenever you need to pass an observable array to an external library, it is a good idea to create a shallow copy before passing it to other libraries or built-in functions (which is good practice anyway) by using array.slice(). In other words, Array.isArray(observable([]).slice()) will yield true.

Unlike the built-in implementation of the functions sort and reverse, observableArray.sort and reverse will not change the array in-place, but only will return a sorted / reversed copy.

Besides all built-in functions, the following goodies are available as well on observable arrays:

  • intercept(interceptor): Can be used to intercept any change before it is applied to the array.
  • observe(listener, fireImmediately? = false): Listen to changes in this array. This callback will receive arguments that express an array splice or array change, conforming to ES7 proposal. It returns a dispoer function to stop the listener.
  • clear(): Remove all current entries from the array.
  • replace(newItems): Replace all existing entries in the array with new ones.
  • find(predicate: (item, index, array) => boolean, thisArg?, fromIndex?): Basically the same as the ES7 Array.find proposal, except for the additional fromIndex parameter.
  • findIndex(predicate: (item, index, array) => boolean, thisArg?, fromIndex?): Basically the same as the ES7 Array.findIndex proposal, except for the additional fromIndex parameter.
  • remove(value): Remove a single item by value from the array. Returns true if the item was found and removed.
  • peek(): Returns an array with all the values which can safely be passed to other libraries, similar to slice().

In contrast to slice, peek doesn't create a defensive copy. Use this in performance-critical applications if you know for sure that you use the array in a read-only manner. In performance-critical sections, it is recommended to use a flat observable array as well.

observable.shallowArray(values)

Any values assigned to an observable array will be default passed through observable to make them observable. Create a shallow array to disable this behavior and store values as-is.

Name Argument

Both observable.array and observable.shallowArray take a second parameter, which is used as debug name in, for example, spy or the MobX dev tools.

Observable Maps

observable.map(values)

observable.map(values?) creates a dynamic keyed observable map. Observable maps are very useful if you don't want to react just to the change of a specific entry, but also to the additional or removal of entries. Optionally takes an object, entries array or string keyed ES6 map with initial values. Unlike ES6 maps, only strings are accepted as keys.

Using ES6 Map constructor, you can initialize observable map using observable(new Map()) or for class properties using the decorator @observable map = new Map().

The following methods are exposed according to the ES6 map spec:

  • has(key): Returns whether this map has an entry with the provided key. Note: The presence of a key is an observable fact in itself.
  • set(key, value): Sets the given key to value. The provided key will be added to the map if it didn't exist yet.
  • delete(key): Deletes the given key and its value from the map.
  • get(key): Returns the value at the given key (or undefined).
  • keys(): Returns all keys present in this map. Insertion order is preserved.
  • values(): Returns all values present in this map. Insertion order is preserved.
  • entries(): Returns an (insertion ordered) array that, for each key/value pair in the map, contains an array [key, value].
  • forEach(callback: (value, key, map) => void, thisArg?): Invokes the given callback for each key/value pair in the map.
  • clear(): Removes all entries from this map.
  • size: Returns the amount of entries in this map.

The following functions are not in the ES6 spec but are available in MobX:

  • toJS(): Returns a shallow plain object representation of this map. (For a deep copy use mobx.toJS(map)).
  • intercept(interceptor): Registers an interceptor that will be triggered before any changes are applied to the map.
  • observe(listener, fireImmediately?): Registers a listener that fires upon each change in this map, similarly to the events that are emitted for Object.observe.
  • merge(values): Copies all entries from the provided object in this map. values can be a plain object, array of entries or string-keyed ES6 Map.
  • replace(values): Replaces the entire contents of this map with the provided values. Short hand for .clear().merge(values).

observable.shallowMap(values)

Any values assigned to an observable map will by default be passed through observable to make them observable. Create a shallow map to disable this behavior and store all values as-is.

Name Argument

Both observable.map and observable.shallowMap take a second parameter which is used as debug name, for example, spy or the MobX dev tools.

Primitive Values and References

All primitive values in JavaScript are immutable and hence per definition not observable. Usually, that is fine, as MobX usually can just make the property that contains the value observable. In rare cases, it can be convenient to have an observable "primitive" that is not owned by an object. For these cases, it is possible to create an observable box that manages such a primitive.

observable.box(value)

So observable.box(value) accepts any value and stores it inside a box. The current value can be accessed through .get() and updating using .set(newValue).

Furthermore, you can register a callback using its .observe method to listen to changes on the stored value. But since MobX tracks changes to boxes automatically, in most cases it is better to use a reaction like mobx.autorun instead.

So the signature of object returned by observable.box(scalar) is:

  • .get(): Returns the current value.
  • .set(value): Replaces the currently stored value. Notifies all observers.
  • intercept(interceptor): Can be used to intercept changes before they are applied.
  • .observe(callback: (change) => void, fireImmediately = false): disposerFunction: Registers an observer function that will fire each time the stored value is replaced. Returns a function to cancel the observer. The change parameter is an object containing both the newValue and oldValue of the observable.

observable.shallowBox(value)

shallowBox creates a box based on the ref modifier. This means that any (future) value of box wouldn't be converted into an observable automatically.

observable(primitiveValue)

When using the generic observable(value) method, MobX will create an observable box for any value that could not be turned into an observable automatically.

Example

import {observable} from "mobx";

const cityName = observable("Vienna");

console.log(cityName.get());
// prints 'Vienna'

cityName.observe(function(change) {
    console.log(change.oldValue, "->", change.newValue);
});

cityName.set("Amsterdam");
// prints 'Vienna -> Amsterdam'
import {observable} from "mobx";

const myArray = ["Vienna"];
const cityName = observable(myArray);

console.log(cityName[0]);
// prints 'Vienna'

cityName.observe(function(observedArray) {
    if (observedArray.type === "update") {
        console.log(observedArray.oldValue + "->" + observedArray.newValue);
    } else if (observedArray.type === "splice") {
        if (observedArray.addedCount > 0) {
            console.log(observedArray.added + " added");
        }
        if (observedArray.removedCount > 0) {
            console.log(observedArray.removed + " removed");
        }
    }
});

cityName[0] = "Amsterdam";
// prints 'Vienna -> Amsterdam'

cityName[1] = "Cleveland";
// prints 'Cleveland added'

cityName.splice(0, 1);
// prints 'Amsterdam removed'

Name Argument

Both observable.box and observable.shallowBox take a second parameter which is used as debug name in, for example, spy or the MobX dev tools.

results matching ""

    No results matching ""