519

Is it possible to have an event in JS that fires when the value of a certain variable changes? JQuery is accepted.

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
rashcroft22
  • 5,401
  • 4
  • 15
  • 6
  • @BenjaminGruenbaum probably you want to say MutableObserver (for DOM). Object is only for JS objects from what I remember. – HellBaby Apr 03 '15 at 10:31
  • 4
    @HellBaby this question is about variables - not the DOM. – Benjamin Gruenbaum Apr 03 '15 at 10:59
  • 10
    @BenjaminGruenbaum according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe Object.observe is obsolete or deprecated. The recommended replacement (per that same page) is the Proxy object. – stannius Mar 31 '16 at 21:02
  • 5
    The question only asking about `variable`, yet all of the answers here refer to `property`. I wonder if we can listen for `local variable` changes though. – Thariq Nugrohotomo Jul 29 '16 at 03:24

24 Answers24

131

Yes, this is now completely possible!

I know this is an old thread but now this effect is possible using accessors (getters and setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters

You can define an object like this, in which aInternal represents the field a:

x = {
  aInternal: 10,
  aListener: function(val) {},
  set a(val) {
    this.aInternal = val;
    this.aListener(val);
  },
  get a() {
    return this.aInternal;
  },
  registerListener: function(listener) {
    this.aListener = listener;
  }
}

Then you can register a listener using the following:

x.registerListener(function(val) {
  alert("Someone changed the value of x.a to " + val);
});

So whenever anything changes the value of x.a, the listener function will be fired. Running the following line will bring the alert popup:

x.a = 42;

See an example here: https://jsfiddle.net/5o1wf1bn/1/

You can also user an array of listeners instead of a single listener slot, but I wanted to give you the simplest possible example.

Akira
  • 3,605
  • 1
  • 13
  • 22
  • What about a way to detect when a new property has been added to an object, or one has been removed? – Michael Sep 15 '18 at 16:39
  • This is an old answer, but I wanted to add that this works well for array values as well as long as you set the value of the array instead of pushing to it. – TabsNotSpaces Dec 06 '18 at 23:22
  • @Akira Nice Solution! How would go about registering multiple listeners to the same variable? – Mike Jan 18 '19 at 20:27
  • 2
    You simply need to have an array of listeners instead of a single one and then instead of just calling `this.aListener(val)`, you would have to loop through all listener functions and call each one passing `val`. Typically, the method is called `addListener` instead of `registerListener`. – Akira Jan 20 '19 at 01:18
118

This question was originally posted in 2009 and most of the existing answers are either outdated, ineffective, or require the inclusion of large bloated libraries:

  • Object.watch and Object.observe are both deprecated and should not be used.
  • onPropertyChange is a DOM element event handler that only works in some versions of IE.
  • Object.defineProperty allows you to make an object property immutable, which would allow you to detect attempted changes, but it would also block any changes.
  • Defining setters and getters works, but it requires a lot of setup code and it does not work well when you need to delete or create new properties.

As of 2018, you can now use the Proxy object to monitor (and intercept) changes made to an object. It is purpose built for what the OP is trying to do. Here's a basic example:

var targetObj = {};
var targetProxy = new Proxy(targetObj, {
  set: function (target, key, value) {
      console.log(`${key} set to ${value}`);
      target[key] = value;
      return true;
  }
});

targetProxy.hello_world = "test"; // console: 'hello_world set to test'

The only drawbacks of the Proxy object are:

  1. The Proxy object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy functionality.
  2. Proxy objects do not always behave as expected with special objects (e.g., Date) -- the Proxy object is best paired with plain Objects or Arrays.

If you need to observe changes made to a nested object, then you need to use a specialized library such as Observable Slim (which I have published). It works like this:

var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
    console.log(JSON.stringify(changes));
});

p.testing.blah = 42; // console:  [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]

 
Elliot B.
  • 14,589
  • 9
  • 70
  • 99
  • 1
    I'll add also another drawback, you don't actually watch changes on the target object but only on proxy object. In some cases, you just want to know when a property change on the target object (i.e. `target.hello_world = "test"`) – Cristiano Jul 10 '19 at 21:18
  • 2
    `you don't actually watch changes on the target object but only on proxy object` -- that's not quite accurate. The `Proxy` object is not modified -- it doesn't have it's own copy of the target. `you just want to know when a property change on the target object` -- you can accomplish that with a `Proxy`, that's one of the primary use cases for proxies. – Elliot B. Jul 10 '19 at 21:27
  • so when i call `target.hello_world="test"` my set function defined inside `targetProxy` will be called? – Cristiano Jul 10 '19 at 21:31
  • 4
    No, because you're modifying the target directly. If you want to observe the modification to `target`, then you have to do it through a proxy. However `proxy.hello_world = "test"` does not mean that you are modifying the proxy, the proxy remains unchanged, `target` gets modified (if your set handler is configured to do so). It sounds like your point is that you cannot directly observe `target.hello_world = "test"`. That is true. Plain variable assignments do not emit any kind of event. That's why we have to use tools like those described in the answers to this question. – Elliot B. Jul 10 '19 at 21:35
  • 2
    Thank you Elliot B. `It sounds like your point is that you cannot directly observe target.hello_world = "test". That is true.` that's exactly my point. In my case I have an object created somewhere else and been updated by some other code... a proxy, in this case, is not useful since the changes will be done on the target. – Cristiano Jul 11 '19 at 10:33
  • 1
    @Cristiano I guess what Elliot is trying to say is that you can use the proxy *instead* of the actual object, meaning you can pass the proxy around like if it was your object and make the other parts of the app interact with your proxy. Changes on the proxy will be reflected on the actual object. – Zoltán Matók Sep 27 '19 at 09:26
  • yeah, the point is that I don't have control over the creation of the object that I want to observe. – Cristiano Dec 20 '19 at 13:47
  • Another drawback of the Proxy object is that it does not behave well with classes containing private members (with the hash, like `#member`), because Proxy does not see them. So at some point your proxified instance will throw an error. – Gabriel Hautclocq Nov 10 '20 at 12:47
  • `Object.defineProperty` works just perfectly. Look at my answer below. – yurin Mar 25 '21 at 09:48
36

No.

But, if it's really that important, you have 2 options (first is tested, second isn't):

First, use setters and getters, like so:

var myobj = {a : 1};

function create_gets_sets(obj) { // make this a framework/global function
    var proxy = {}
    for ( var i in obj ) {
        if (obj.hasOwnProperty(i)) {
            var k = i;
            proxy["set_"+i] = function (val) { this[k] = val; };
            proxy["get_"+i] = function ()    { return this[k]; };
        }
    }
    for (var i in proxy) {
        if (proxy.hasOwnProperty(i)) {
            obj[i] = proxy[i];
        }
    }
}

create_gets_sets(myobj);

then you can do something like:

function listen_to(obj, prop, handler) {
    var current_setter = obj["set_" + prop];
    var old_val = obj["get_" + prop]();
    obj["set_" + prop] = function(val) { current_setter.apply(obj, [old_val, val]); handler(val));
}

then set the listener like:

listen_to(myobj, "a", function(oldval, newval) {
    alert("old : " + oldval + " new : " + newval);
}

Second, you could put a watch on the value:

Given myobj above, with 'a' on it:

function watch(obj, prop, handler) { // make this a framework/global function
    var currval = obj[prop];
    function callback() {
        if (obj[prop] != currval) {
            var temp = currval;
            currval = obj[prop];
            handler(temp, currval);
        }
    }
    return callback;
}

var myhandler = function (oldval, newval) {
    //do something
};

var intervalH = setInterval(watch(myobj, "a", myhandler), 100);

myobj.set_a(2);
meetar
  • 6,683
  • 7
  • 39
  • 68
Luke Schafer
  • 8,977
  • 2
  • 26
  • 28
  • 15
    "watching" is not actually detecting a change. This is not event based, and would certainly slow down the entire app very very soon. These approaches IMHO should never be part of a real project. – Muhammad bin Yusrat Jan 17 '17 at 16:59
33

Using Prototype: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

// Console
function print(t) {
  var c = document.getElementById('console');
  c.innerHTML = c.innerHTML + '<br />' + t;
}

// Demo
var myVar = 123;

Object.defineProperty(this, 'varWatch', {
  get: function () { return myVar; },
  set: function (v) {
    myVar = v;
    print('Value changed! New value: ' + v);
  }
});

print(varWatch);
varWatch = 456;
print(varWatch);
<pre id="console">
</pre>

Other example

// Console
function print(t) {
  var c = document.getElementById('console');
  c.innerHTML = c.innerHTML + '<br />' + t;
}

// Demo
var varw = (function (context) {
  return function (varName, varValue) {
    var value = varValue;
  
    Object.defineProperty(context, varName, {
      get: function () { return value; },
      set: function (v) {
        value = v;
        print('Value changed! New value: ' + value);
      }
    });
  };
})(window);

varw('varWatch'); // Declare
print(varWatch);
varWatch = 456;
print(varWatch);

print('---');

varw('otherVarWatch', 123); // Declare with initial value
print(otherVarWatch);
otherVarWatch = 789;
print(otherVarWatch);
<pre id="console">
</pre>
Eduardo Cuomo
  • 13,985
  • 3
  • 93
  • 80
  • The second example is a bit misleading, `varw` requires 2 arguments but part of your example shows the function being called with just the value parameter. – Hlawuleka MAS May 28 '18 at 12:57
22

Sorry to bring up an old thread, but here is a little manual for those who (like me!) don't see how Eli Grey's example works:

var test = new Object();
test.watch("elem", function(prop,oldval,newval){
    //Your code
    return newval;
});

Hope this can help someone

Andrew
  • 6,630
  • 1
  • 24
  • 23
  • 10
    Watch is not supported on Chrome or Safari at the moment, only Firefox – Paul McClean Nov 13 '13 at 15:01
  • 5
    Per mozilla developer network, this is not recommended. Object.prototype.watch() was intended for testing only and should not be used in production code. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch – Dennis Bartlett Jul 27 '16 at 16:27
  • 4
    @PaulMcClean this answer was in Response to Eli Grey's answer which contained a Polyfill. https://gist.github.com/eligrey/384583 – Hobbyist Aug 29 '16 at 01:46
  • 5
    watch has been deprecated by Mozilla. This answer could be misleading. – Arnaud Apr 09 '18 at 15:53
9

As Luke Schafer's answer (note: this refers to his original post; but the whole point here remains valid after the edit), I would also suggest a pair of Get/Set methods to access your value.

However I would suggest some modifications (and that's why I'm posting...).

A problem with that code is that the field a of the object myobj is directly accessible, so it's possible to access it / change its value without triggering the listeners:

var myobj = { a : 5, get_a : function() { return this.a;}, set_a : function(val) { this.a = val; }}
/* add listeners ... */
myobj.a = 10; // no listeners called!

Encapsulation

So, to guarantee that the listeners are actually called, we would have to prohibit that direct access to the field a. How to do so? Use a closure!

var myobj = (function() { // Anonymous function to create scope.

    var a = 5;            // 'a' is local to this function
                          // and cannot be directly accessed from outside
                          // this anonymous function's scope

    return {
        get_a : function() { return a; },   // These functions are closures:
        set_a : function(val) { a = val; }  // they keep reference to
                                            // something ('a') that was on scope
                                            // where they were defined
    };
})();

Now you can use the same method to create and add the listeners as Luke proposed, but you can rest assured that there's no possible way to read from or write to a going unnoticed!

Adding encapsulated fields programmatically

Still on Luke's track, I propose now a simple way to add encapsulated fields and the respective getters/setters to objects by the means of a simple function call.

Note that this will only work properly with value types. For this to work with reference types, some kind of deep copy would have to be implemented (see this one, for instance).

function addProperty(obj, name, initial) {
    var field = initial;
    obj["get_" + name] = function() { return field; }
    obj["set_" + name] = function(val) { field = val; }
}

This works the same as before: we create a local variable on a function, and then we create a closure.

How to use it? Simple:

var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);
Community
  • 1
  • 1
Bruno Reis
  • 34,789
  • 11
  • 109
  • 148
  • 2
    +1 for encapsulation. That was my first thought, but I wanted the ability to add the create_gets_sets method eventually, and since it is indiscriminate, hiding the values isn't cool :) we can take it a step further and write some things to hide the values, but I think the code i've posted is confusing enough for most people... maybe if there's call for it... – Luke Schafer Nov 19 '09 at 00:36
7

If you're using jQuery {UI} (which everyone should be using :-) ), you can use .change() with a hidden <input/> element.

Chuck Han
  • 568
  • 1
  • 5
  • 11
  • 7
    I don't quite understand. How can you attach a variable to a hidden `` element? – Peter Lee Dec 05 '12 at 23:44
  • 4
    I think Chuck is suggesting that you can set the value of the input using jquery and then and a .change() event listener. If you update the input's value with .val() then the .change() event callback will fire. – jarederaj Mar 24 '14 at 21:20
  • 2
    `` and with jQuery `$("#thisOne").change(function() { do stuff here });` and `$("#thisOne").val(myVariableWhichIsNew);` and then the `.change()` will fire. – khaverim Jun 28 '14 at 13:28
  • The keypress + mousedown events will also work if you're using an input field – JVE999 Oct 24 '14 at 00:53
  • Was just thinking this could work and was about to add an answer like this, glad to see someone else has already tested it. – sage88 Mar 03 '15 at 19:09
  • 2
    This is the best solution in my books. Simple, easy. Instead of changing the variable in your code, e.g. `var1 = 'new value';`, you'll instead set the value of this hidden input, then add a listener to change the variable. `$("#val1").on('change', function(){ val1 = this.val(); ... do the stuff that you wanted to do when val1 changed... }); $("#val1").val('new value');` – Tom Walker Apr 15 '16 at 21:32
  • Sorry for the double comment... ran out of the 5 min limit for edits. If you wanted to have a pure javascript solution, you could add this input to the DOM using JS: `$(document).ready(function() { $('body').append(''); });` You would, of course, have to run the "change" listener after this, and this might not work if you're relying on the change listener working before the DOM loads (that warning probably goes for this whole solution). – Tom Walker Apr 15 '16 at 21:41
  • 1
    A getter and setter would be way easier and probably faster. – Aloso Jun 03 '16 at 02:32
  • 2
    For anyone who is having the same problem as me,if the change event doesnt trigger try $("#thisOne").val(myVariableWhichIsNew).trigger('change') .Hope this helps – Alator Mar 10 '18 at 14:08
6

AngularJS (I know this is not JQuery, but that might help. [Pure JS is good in theory only]):

$scope.$watch('data', function(newValue) { ..

where "data" is name of your variable in the scope.

There is a link to doc.

ses
  • 12,339
  • 25
  • 106
  • 203
5

For those tuning in a couple years later:

A solution for most browsers (and IE6+) is available that uses the onpropertychange event and the newer spec defineProperty. The slight catch is that you'll need to make your variable a dom object.

Full details:

http://johndyer.name/native-browser-get-set-properties-in-javascript/

MandoMando
  • 4,793
  • 3
  • 25
  • 32
4

Easiest way I have found, starting from this answer:

// variable holding your data
const state = {
  count: null,
  update() {
    console.log(`this gets called and your value is ${this.pageNumber}`);
  },
  get pageNumber() {
    return this.count;
  },
  set pageNumber(pageNumber) {
    this.count = pageNumber;
    // here you call the code you need
    this.update(this.count);
  }
};

And then:

state.pageNumber = 0;
// watch the console

state.pageNumber = 15;
// watch the console
Giorgio Tempesta
  • 928
  • 12
  • 21
3

Not directly: you need a pair getter/setter with an "addListener/removeListener" interface of some sort... or an NPAPI plugin (but that's another story altogether).

jldupont
  • 82,560
  • 49
  • 190
  • 305
3

The functionality you're looking for can be achieved through the use of the "defineProperty()" method--which is only available to modern browsers:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

I've written a jQuery extension that has some similar functionality if you need more cross browser support:

https://github.com/jarederaj/jQueue

A small jQuery extension that handles queuing callbacks to the existence of a variable, object, or key. You can assign any number of callbacks to any number of data points that might be affected by processes running in the background. jQueue listens and waits for these data you specify to come into existence and then fires off the correct callback with its arguments.

jarederaj
  • 1,210
  • 13
  • 18
3

Recently found myself with the same issue. Wanted to listen for on change of a variable and do some stuff when the variable changed.

Someone suggested a simple solution of setting the value using a setter.

Declaring a simple object that keeps the value of my variable here:

var variableObject = {
    value: false,
    set: function (value) {
        this.value = value;
        this.getOnChange();
    }
}

The object contains a set method via which I can change the value. But it also calls a getOnChange() method in there. Will define it now.

variableObject.getOnChange = function() {
    if(this.value) {
        // do some stuff
    }
}

Now whenever I do variableObject.set(true), the getOnChange method fires, and if the value was set as desired (in my case: true), the if block also executes.

This is the simplest way I found to do this stuff.

Rohan
  • 11,041
  • 19
  • 65
  • 131
2

In my case, I was trying to find out if any library I was including in my project was redefining my window.player. So, at the begining of my code, I just did:

Object.defineProperty(window, 'player', {
  get: () => this._player,
  set: v => {
    console.log('window.player has been redefined!');
    this._player = v;
  }
});
José Antonio Postigo
  • 1,812
  • 14
  • 16
1
//ex:
/*
var x1 = {currentStatus:undefined};
your need is x1.currentStatus value is change trigger event ?
below the code is use try it.
*/
function statusChange(){
    console.log("x1.currentStatus_value_is_changed"+x1.eventCurrentStatus);
};

var x1 = {
    eventCurrentStatus:undefined,
    get currentStatus(){
        return this.eventCurrentStatus;
    },
    set currentStatus(val){
        this.eventCurrentStatus=val;
      //your function();
    }
};

or

/*  var x1 = {
eventCurrentStatus:undefined,
currentStatus : {
    get : function(){
        return Events.eventCurrentStatus
        },
    set : function(status){
        Events.eventCurrentStatus=status;

    },
}*/
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="create"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="edit"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
console.log("currentStatus = "+ x1.currentStatus);

or

/* global variable ku*/
    var jsVarEvents={};
    Object.defineProperty(window, "globalvar1", {//no i18n
        get: function() { return window.jsVarEvents.globalvarTemp},
        set: function(value) { window.window.jsVarEvents.globalvarTemp = value; }
    });
    console.log(globalvar1);
    globalvar1=1;
    console.log(globalvar1);
Avatar
  • 29
  • 2
1

A rather simple and simplistic solution is to just use a function call to set the value of the global variable, and never set its value directly. This way you have total control:

var globalVar;

function setGlobalVar(value) {
    globalVar = value;
    console.log("Value of globalVar set to: " + globalVar);
    //Whatever else
}

There is no way to enforce this, it just requires programming discipline... though you can use grep (or something similar) to check that nowhere does your code directly set the value of globalVar.

Or you could encapsulate it in an object and user getter and setter methods... just a thought.

markvgti
  • 3,487
  • 6
  • 32
  • 49
  • For a variable that is not a property of an object one can access -- as is the case with variables declared with `var` in ES6 modules -- this is the *only* solution. – amn May 31 '20 at 15:51
1

Please guys remember the initial question was for VARIABLES, not for OBJECTS ;)

in addition to all answers above, I created a tiny lib called forTheWatch.js, that use the same way to catch and callback for changes in normal global variables in javascript.

Compatible with JQUERY variables, no need to use OBJECTS, and you can pass directly an ARRAY of several variables if needed.

If it can be helpful... : https://bitbucket.org/esabora/forthewatch
Basically you just have to call the function :
watchIt("theVariableToWatch", "varChangedFunctionCallback");

And sorry by advance if not relevant.

1

With the help of getter and setter, you can define a JavaScript class that does such a thing.

First, we define our class called MonitoredVariable:

class MonitoredVariable {
  constructor(initialValue) {
    this._innerValue = initialValue;
    this.beforeSet = (newValue, oldValue) => {};
    this.beforeChange = (newValue, oldValue) => {};
    this.afterChange = (newValue, oldValue) => {};
    this.afterSet = (newValue, oldValue) => {};
  }

  set val(newValue) {
    const oldValue = this._innerValue;
    // newValue, oldValue may be the same
    this.beforeSet(newValue, oldValue);
    if (oldValue !== newValue) {
      this.beforeChange(newValue, oldValue);
      this._innerValue = newValue;
      this.afterChange(newValue, oldValue);
    }
    // newValue, oldValue may be the same
    this.afterSet(newValue, oldValue);
  }

  get val() {
    return this._innerValue;
  }
}

Assume that we want to listen for money changes, let's create an instance of MonitoredVariable with initial money 0:

const money = new MonitoredVariable(0);

Then we could get or set its value using money.val:

console.log(money.val); // Get its value
money.val = 2; // Set its value

Since we have not defined any listeners for it, nothing special happens after money.val changes to 2.

Now let's define some listeners. We have four listeners available: beforeSet, beforeChange, afterChange, afterSet. The following will happen sequentially when you use money.val = newValue to change variable's value:

  1. money.beforeSet(newValue, oldValue);
  2. money.beforeChange(newValue, oldValue); (Will be skipped if its value not changed)
  3. money.val = newValue;
  4. money.afterChange(newValue, oldValue); (Will be skipped if its value not changed)
  5. money.afterSet(newValue, oldValue);

Now we define afterChange listener which be triggered only after money.val has changed (while afterSet will be triggered even if the new value is the same as the old one):

money.afterChange = (newValue, oldValue) => {
  console.log(`Money has been changed from ${oldValue} to ${newValue}`);
};

Now set a new value 3 and see what happens:

money.val = 3;

You will see the following in the console:

Money has been changed from 2 to 3

For full code, see https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab.

yusanshi
  • 121
  • 2
  • 5
  • awsome ... I want to expand on this code example just reaching out to know what license is code snippet. +1 for making it reusable through class and before and after handlers – Syed Dec 27 '20 at 21:12
0

It's not directly possible.

However, this can be done using CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent

The below method accepts an array of variable names as an input and adds event listener for each variable and triggers the event for any changes to the value of the variables.

The Method uses polling to detect the change in the value. You can increase the value for timeout in milliseconds.

function watchVariable(varsToWatch) {
    let timeout = 1000;
    let localCopyForVars = {};
    let pollForChange = function () {
        for (let varToWatch of varsToWatch) {
            if (localCopyForVars[varToWatch] !== window[varToWatch]) {
                let event = new CustomEvent('onVar_' + varToWatch + 'Change', {
                    detail: {
                        name: varToWatch,
                        oldValue: localCopyForVars[varToWatch],
                        newValue: window[varToWatch]
                    }
                });
                document.dispatchEvent(event);
                localCopyForVars[varToWatch] = window[varToWatch];
            }
        }
        setTimeout(pollForChange, timeout);
    };
    let respondToNewValue = function (varData) {
        console.log("The value of the variable " + varData.name + " has been Changed from " + varData.oldValue + " to " + varData.newValue + "!!!"); 
    }
    for (let varToWatch of varsToWatch) {
        localCopyForVars[varToWatch] = window[varToWatch];
        document.addEventListener('onVar_' + varToWatch + 'Change', function (e) {
            respondToNewValue(e.detail);
        });
    }
    setTimeout(pollForChange, timeout);
}

By calling the Method:

watchVariables(['username', 'userid']);

It will detect the changes to variables username and userid.

Alec Smart
  • 85,672
  • 35
  • 115
  • 178
Ketan Yekale
  • 1,821
  • 3
  • 20
  • 32
0

This is what I did: Call JSON.stringify twice and compare the two strings...

Drawbacks:

  • You can only know whether the whole object changes
  • You have to detect changes manually
  • You better have only primitive fields in the object(no properties, no functions...)
qiucw
  • 49
  • 8
-1

I came here looking for same answer for node js. So here it is

const events = require('events');
const eventEmitter = new events.EventEmitter();

// Createing state to watch and trigger on change
let x = 10 // x is being watched for changes in do while loops below

do {
    eventEmitter.emit('back to normal');
}
while (x !== 10);

do {
    eventEmitter.emit('something changed');
}
while (x === 10);

What I am doing is setting some event emitters when values are changed and using do while loops to detect it.

-1

I searched for JavaScript two-way data binding library and came across this one.

I did not succeed to make it work in DOM to variable direction, but in variable to DOM direction it works and that is what we need here.

I have rewritten it slightly, as the original code is very hard to read (for me). It uses Object.defineProperty, so the second most upvoted answer by Eliot B. at least partially wrong.

<!DOCTYPE html>
<html>
  <head>
    <title>TODO supply a title</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script>
        const dataBind = (function () {
            const getElementValue = function (selector) {
                let element = document.querySelectorAll(selector)[0];
                return 'value' in element ? element.value : element.innerHTML;
            };
            const setElementValue = function (selector, newValue) {
                let elementArray = document.querySelectorAll(selector);
                for (let i = 0; i < elementArray.length; i++) {
                    let element = elementArray[i];
                    if ('value' in element) {
                        element.value = newValue;
                        if (element.tagName.toLowerCase() === 'select'){
                            let options = element.querySelectorAll('option');
                            for (let option in options){
                                if (option.value === newValue){
                                    option.selected = true;
                                    break;
                                }
                            }
                        }
                    } else {
                        element.innerHTML = newValue;
                    }
                }
            };

            const bindModelToView = function (selector, object, property, enumerable) {
                Object.defineProperty(object, property, {
                    get: function () {
                        return getElementValue(selector);
                    },
                    set: function (newValue) {
                        setElementValue(selector, newValue);
                    },
                    configurable: true,
                    enumerable: (enumerable)
                });
            };
            return {
                bindModelToView
            };
        })();
    </script>
</head>
<body>
    <div style="padding: 20%;">
        <input  type="text" id="text" style="width: 40px;"/>
    </div>
    <script>
        let x = {a: 1, b: 2};
        dataBind.bindModelToView('#text', x, 'a'); //data to dom

        setInterval(function () {
             x.a++;
        }, 1000);
   </script> 
</body>

</html>

JSFiddle.

JSFiddle with original code.

In the provided example a property of object x updated by the setInterval and value of text input automatically updated as well. If it is not enough and event is what you looking for, you can add onchange listener to the above input. Input also can be made hidden if needed.

yurin
  • 3,512
  • 1
  • 29
  • 29
  • Could you use a MutationObserver to achieve the other side of your two-way bind? The `Object.define()` will bind variable to DOM, and the MutationObserver will bind DOM to variable. – Brandon McConnell Apr 21 '21 at 23:49
  • Also, your code throws this error for me when run as-is: `Uncaught TypeError: Cannot use 'in' operator to search for 'value' in undefined` – Brandon McConnell Apr 21 '21 at 23:56
  • @BrandonMcConnell. Two-way binding is not a point here. Maybe you can do it with MutationObserver. It can be done with a simple Change event for DOM elements that support it and DOMSubtreeModified for divs and spans. I cannot say why your code is not working. Check jSfiddle - it works. – yurin Apr 24 '21 at 12:17
  • 2-way binding was a point here. You mentioned in your solution that you "searched for a JavaScript two-way data binding library" – Brandon McConnell May 25 '21 at 13:35
-2

This is an old thread but I stumbled onto second highest answer (custom listeners) while looking for a solution using Angular. While the solution works, angular has a better built in way to resolve this using @Output and event emitters. Going off of the example in custom listener answer:

ChildComponent.html

<button (click)="increment(1)">Increment</button>

ChildComponent.ts

import {EventEmitter, Output } from '@angular/core';

@Output() myEmitter: EventEmitter<number> = new EventEmitter<number>();

private myValue: number = 0;

public increment(n: number){
  this.myValue += n;

  // Send a change event to the emitter
  this.myEmitter.emit(this.myValue);
}

ParentComponent.html

<child-component (myEmitter)="monitorChanges($event)"></child-component>
<br/>
<label>{{n}}</label>

ParentComponent.ts

public n: number = 0;

public monitorChanges(n: number){
  this.n = n;
  console.log(n);
}

This will now update non parent each time the child button is clicked. Working stackblitz

TabsNotSpaces
  • 837
  • 8
  • 17
-4
Utils = {
    eventRegister_globalVariable : function(variableName,handlers){
        eventRegister_JsonVariable(this,variableName,handlers);
    },
    eventRegister_jsonVariable : function(jsonObj,variableName,handlers){
        if(jsonObj.eventRegisteredVariable === undefined) {
            jsonObj.eventRegisteredVariable={};//this Object is used for trigger event in javascript variable value changes ku
        }
        Object.defineProperty(jsonObj, variableName , {
                    get: function() { 
                        return jsonObj.eventRegisteredVariable[variableName] },
                    set: function(value) {
                        jsonObj.eventRegisteredVariable[variableName] = value; handlers(jsonObj.eventRegisteredVariable[variableName]);}
                    });
            }
Avatar
  • 29
  • 2