Skip to content

Latest commit

 

History

History
90 lines (72 loc) · 1.87 KB

change-control.md

File metadata and controls

90 lines (72 loc) · 1.87 KB

Change Control

Using the non-enumerable property control we can temporarily prevent changes

const MyClass = M({n: Number});
let myInstance = new MyClass();

myInstance.n = 3;
assert.equal(myInstance.n, 3);

myInstance.control.isChangeAllowed = false;

assert.throws(function() {myInstance.n = 4;});
assert.equal(myInstance.n, 3);

This feature is used to enforce no state mutations in getters

M({
  n: Number,
  get g() {
    // if we would try and assign `this.n = 4`
    // it would throw a run-time exception
    return this.n + 1;
  }
}).createInstance().g;

We can also use the control object to listen to changes

myInstance.control.on('change', 
  function (changeType, changePayload, opt) {
    // triggered on any direct or nested changes
  }
);

Additionally, we can declare an object methods that may introduce mutations

myInstance = M({
  n: Number,
  m1(x) {
    this.n += x;
  }
}).createInstance();

and those can be overridden by the control's onMutatorCall method

myInstance.control.onMutatorCall = 
  function (keyPath, args, mutator) {
    // apply the original methd (add args[0] to this.n)
    mutator.apply(this, args); 
    // add another 4 - just because we can
    return this.n += 4; 
  };
    
myInstance.m1(3);
assert.equal(myInstance.n, 7);

and be guarded by an idempotent function (ensured internally by control.isChangeAllowed)

Note that to order to pass the guard property, we now need to explicitly use M.Mutator class factory

myInstance = M({
    n: 5,
    m: M.Mutator({
      guard: function () {
        return this.n < 10;
      },
      fn: function (x) {
        this.n += x;
      }
    })
}).createInstance();

myInstance.m(6);
assert.equal(myInstance.n, 11);

assert.throws(function () {
    myInstance.m(1);
});
assert.equal(myInstance.n, 11);