Introduction

Edit This Page

makeReactive(value, scope?, name?)

makeReactive is to Mobservable as $ is to jQuery. Making data reactive starts with this function. If data is reactive, views that depend on that data will update automatically. In the very same way as Excel formulas update when cells on which they depend change.

makeReactive provides overloads for different kinds of data. Probably you will only use the version that takes objects. But anyway, these are the available variations.

Primitive values and references

For all type of values, with the exception of plain objects, arrays and functions without arguments this overload is run. makeReactive accepts a value and returns a getter / setter function that holds this value. The returned function can be invoked without arguments to get the currently stored value, or it can be invoked with one argument to update the currently stored value.

Furthermore you can register a callback using its .observe method to listen to changes on the stored value. But usually it is more convenient to use mobservable.observe instead.

So the signature of the return value of makeReactive is:

  • func() Returns the current value.
  • func(value) Replaces the currently stored value. Notifies all observers.
  • func.observe(callback: (newValue, previousValue) => 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.

Example:

  1. const cityName = makeReactive("Vienna");
  2. console.log(cityName());
  3. // prints 'Vienna'
  4. cityName.observe(function(newCity, oldCity) {
  5. console.log(oldCity, "->", newCity);
  6. });
  7. cityName("Amsterdam");
  8. // prints 'city: Vienna -> Amsterdam'

View functions

If an argumentless function is passed to makeReactive, Mobservable will make sure that that function is run each time that any of the values used by the function is changed. A new function is returned which has the same signature as the function returned for primitives, except that it is not allowed to assign a new value manually.

Example:

  1. var name = makeReactive("John");
  2. var age = makeReactive(42);
  3. var showAge = makeReactive(false);
  4. var labelText = makeReactive(() =>
  5. showAge() ? `${name()} (age: ${age()})` : name();
  6. );
  7. var disposer = labelText.observe(newLabel => console.log(newLabel));
  8. name("Dave");
  9. // prints: 'Dave'
  10. age(21);
  11. // doesn't print
  12. showAge(true);
  13. // prints: 'Dave (age: 21)'
  14. age(42);
  15. // prints: 'Dave (age: 42)'
  16. // cancel the observer
  17. disposer();
  18. name("Matthew");
  19. // doesn't print anymore...
  20. // ... but the value can still be inspected if needed.
  21. console.log(labelText());

Note how the function now automatically reacts to data changes, but only if they occurred in data that was actually used to produced the output. Hence the first change to age didn't result in a re-evaluation of the labelText function. Mobservable will automatically determine whether the function should run eagerly or lazily based on how the views are used throughout your application, so make sure your code doesn't rely on any side effects in those functions.


These two forms of makeReactive, one for primitives and references, and one for functions, form the core of Mobservable. The rest of the api is just syntactic sugar around these two core operations. Nonetheless, you will rarely use these forms; using objects is just a tat more convenient.

Objects

If a plain javascript object is passed to makeReactive (that is, an object that wasn't created using a constructor function), Mobservable will recursively pass all its values through makeReactive. This way the complete object (tree) is in-place instrumented to make it observable.

So we can rewrite the previous example as:

  1. var person = makeReactive({
  2. name: "John",
  3. age: 42,
  4. showAge: false,
  5. labelText: function() {
  6. return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
  7. }
  8. };
  9. // object properties don't expose an 'observe' method,
  10. // but don't worry, 'mobservable.observe' is even more powerful
  11. mobservable.observe(() => console.log(person.labelText));
  12. person.name = "Dave";
  13. // prints: 'Dave'
  14. person.age = 21;
  15. // etc

Some things to keep in mind when making objects reactive:

  • When passing objects through makeReactive, only the properties that exist at the time of making the object reactive will be reactive. Properties that are added to the object at a later time won't become reactive, unless extendReactive is used.
  • Only plain objects will be made reactive. For non-plain objects it is considered the responsibility of the constructor to initialize the reactive properties. Either use the @observable annotation or the extendReactive function.
  • Argumentless functions will be automatically turned into views. For view this will be automatically bound to the object it is defined on. However, if a function expression (ES6 / typescript) is used, this will be bound to undefined, so you probably want to either refer to the object directly, or use a classic function.
  • makeReactive is applied recursively, both on instantiation and to any new values that will be assigned to reactive properties in the future.
  • These defaults are fine in 95% of the cases, but for more fine-grained on how and which properties should be made reactive, see the modifiers section.

Arrays

Similar to objects, arrays can be made reactive using makeReactive. This works recursively as well, so all (future) values of the array will be reactive as well.

  1. var todos = makeReactive([
  2. { title: "Spoil tea", completed: true },
  3. { title: "Make coffee", completed: false }
  4. ]);
  5. mobservable.observe(() => {
  6. console.log("Remaining:", todos
  7. .filter(todo => !todo.completed)
  8. .map(todo => todo.title)
  9. .join(", ")
  10. );
  11. });
  12. // Prints: 'Remaining: Make coffee'
  13. todos[0].completed = false;
  14. // Prints: 'Remaining: Spoil tea, Make coffee'
  15. todos[2] = { title: 'Take a nap', completed: false };
  16. // Prints: 'Remaining: Spoil tea, Make coffee, Take a nap'
  17. todos.shift();
  18. // Prints: 'Remaining: Make coffee, Take a nap'

Due to limitations of native arrays in ES5 (array.observe is only available in ES7, and arrays cannot be extend), makeReactive will instrument a clone of the provided array instead of the original one. 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 that Array.isArray(makeReactive([])) will yield false, so whenever you need to pass an reactive 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). So Array.isArray(makeReactive([]).slice()) will yield true.

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

  • observe(listener, fireImmediately? = false) Listen to changes in this array. The callback will receive arguments that express an array splice or array change, conforming to ES7 proposal. It returns a disposer function to stop the listener.
  • clear() Remove all current entries from the array.
  • replace(newItems) Replaces 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.
  • remove(value) Remove a single item by value from the array. Returns true if the item was found and removed.