How check whether variable have already been watched in angularjs? - javascript

This code reacts on variable my_var changes in scope:
$scope.$watch('my_var', function () {
//do somethins
});
Is there a way to check whether my_var is being watched or not? E.g. have somebody already called $scope.$watch function on my_var or not?

You can simply list all of your watchers and check if your variable has a watcher. I don't think there are any other ways to check if $scope.$watch already applied on it.
Here is the code that you can paste even in your console to check your watchers:
function getWatchers(root) {
root = angular.element(root || document.documentElement);
var watcherCount = 0;
function getElemWatchers(element) {
var isolateWatchers = getWatchersFromScope(element.data().$isolateScope);
var scopeWatchers = getWatchersFromScope(element.data().$scope);
var watchers = scopeWatchers.concat(isolateWatchers);
angular.forEach(element.children(), function(childElement) {
watchers = watchers.concat(getElemWatchers(angular.element(childElement)));
});
return watchers;
}
function getWatchersFromScope(scope) {
if (scope) {
return scope.$$watchers || [];
} else {
return [];
}
}
return getElemWatchers(root);
}
console.log(getWatchers())
// this prints the array of watchers, e.g:
// [ {fn: ƒ, last: "my_var", get: ƒ, exp: ƒ, eq: false} ]
The code is taken from "Watching your AngularJS Watchers" by Kent C. Dodds
(The code was modified to print watchers, instead of just displaying how many are used (array length), so be prepared if you have more that 1000+ watchers)

This may help.
let myVar = $scope.my_var;
Object.defineProperty($scope, 'my_var', {
get: function() {
console.log('Someone called me');
return myVar;
},
set: function(val) {
myVar = val;
}
});
$scope.$apply();
If this variable is being watched - you will see something in log. (Of course you may have some access from your js code, but you can debug ... Also this should be $scope, where variable is actuall defined)

You can use something like this:
function isWatched($scope, ref) {
for(var i = 0; i < $scope.$$watchers.length; i++) {
const w = $scope.$$watchers[i];
if (w && w.exp === ref)
return true;
}
return false;
}
$scope.$watch('my_var', function () {
//do somethins
});
var result = isWatched($scope, 'my_var');

Related

Save key=>value style with ngStorage/localstorage

In my Ionic app I've added the plugin 'ngStorage' and it comes with a little demo code:
var add = function (thing) {
$localStorage.things.push(thing);
}
This works exactly as told. I add("foo") it, and do getAll() and the value is there. I remove the add(), but keep the getAll(), I still have the value "foo" (as expected).
This isn't very usefull for me, I want to access it with keys, so I've made the following:
var addByKey = function (key, value) {
$localStorage.things[key] = value;
// Or, I've also tried:
$localStorage.things.key = value;
}
When I do the addByKey("foo","bar") and then the getAll() I get the values exactly as I want. When I remove the addByKey() and reload, I expect it to still remember the set information, but it doesn't exist. However, the first attempt via the add() function still exists, "foo" is still there (meaning the array doesnt reset).
How do I make a key->value type of structure?
In case it's usefull:
.factory ('StorageService', function ($localStorage) {
$localStorage = $localStorage.$default({
things: []
});
var _getAll = function () {
return $localStorage.things;
};
var _add = function (thing) {
$localStorage.things.push(thing);
}
var _addByKey = function (thing, value) {
$localStorage.things[key] = value;
// Or, I've also tried:
$localStorage.things.key = value;
}
return {
getAll: _getAll,
add: _add,
addByKey: _addByKey
};
})
Assuming that you want a key value storage system you can simply use an object instead of an array so that every key can be set as a property of this object.
.factory('StorageService', function($localStorage) {
$localStorage = $localStorage.$default({
things: {}
});
var _getAll = function() {
return $localStorage.things;
};
var _addByKey = function(thing, value) {
$localStorage.things[thing] = value;
}
return {
getAll: _getAll,
addByKey: _addByKey
};
})
However, assuming that you want to keep a reference of all values on the main collection and access them through keys, you can consider using an object to store the things intead of an array. So that you can use a property to store all items (you can store in a different place as well) and use this object to store your keys by referencing the to a desired value on your collection.
You may need to implement the deletion logic to maintain the consistence between the collection and the dictionary.
Your factory would look like this:
.factory('StorageService', function($localStorage) {
$localStorage = $localStorage.$default({
things: {
items: []
}
});
var _getAll = function() {
return $localStorage.things.items;
};
var _add = function(thing) {
$localStorage.things.items.push(thing);
}
var _addByKey = function(thing, value) {
var i = $localStorage.things.items.push(value) - 1;
$localStorage.things[thing] = $localStorage.things.items[i];
}
return {
getAll: _getAll,
add: _add,
addByKey: _addByKey
};
})

JavaScript calling a getter from its function

Trying to do something that in pseudo code would look like this:
(function(scope) {
scope.doSomenthin = function() {
if (x === y && this.onfinish) {
// If exists, run onfinish, should return 'fin'
this.onfinish();
}
}
})(scope);
window.scope = window.scope || (window.scope = {});
scope.doSomenthin().onfinish = function(){return 'fin'}
At run time if onfinish exists, run that function. Tried using getters/setter but at that point it will return undefined. Setting a timeout works but its not something I wish to do.
Any other ideas? Thanks.
I'm not sure if I completely understand the question, but I think what you want comes down to setting the context for the functions you are calling. Is this what you are after?
//create a function that accesses an object's properties and methods with 'this'
var doSomethin = function() {
var result = "nonfinish";
if (this.onfinish) {
// If exists, run onfinish, should return 'fin'
result = this.onfinish();
}
return result;
}
//add an 'onfinish' method to the 'scope' object
scope = {
onfinish: function(){return 'fin'}
}
//run the accessor function in the window context
alert(doSomethin());
//run the accessor function in scope's context
alert(doSomethin.call(scope));
I see several mistakes with your code. This may be the results you are trying to achieve..
window.scope = window.scope || (window.scope = {});
scope.onfinish = function(){return 'fin'};
(function(scope) {
scope.doSomenthin = function() {
if (this.onfinish) {
// If exists, run onfinish, should return 'fin'
return this.onfinish();
}
}
})(scope);
alert(scope.doSomenthin());
When you create the temporary scope here you give scope as a
parameter. But scope is not defined yet.
(function(scope) {
scope.doSomenthin = function() {
if (x === y && this.onfinish) {
// If exists, run onfinish, should return 'fin'
this.onfinish();
}
}
})(scope);
Your scope.doSomenthin function doesn't return any value. Because
of that the value of scope.doSomenthin() is undifined. Therefore
with scope.doSomenthin().onfinish = function(){return 'fin'} you
are trying to set a property of undifined.
What you want to approach is similar to event-driven programming. Don't just call the function right away, register it as an event handler instead. The following pseudo-code only shows my idea. It's not complete
//register the function here, instead of calling it immediately
event = document.createEvent("HTMLEvents");
event.initEvent("myEvent", true, true);
document.addEventListener("myEvent", function(e) {
e.scope.doSomenthin = function() {
if (this.onfinish) {
// If exists, run onfinish, should return 'fin'
return this.onfinish();
}
}
});
......
//call the handler to handle the below event
window.scope = window.scope || (window.scope = {});
scope.doSomenthin().onfinish = function(){return 'fin'}
event.scope = scope;
document.body.dispatchEvent(event);
The above code is kind of silly. You have to design where to put and trigger the events.

Create anonymous function dynamically?

I'm working on simple evolutionary AI. I need to generate an anonymous function dynamically. For it I have a list of conditions and actions:
var conditions = [
function () { return enemyNear(), },
function () { return mySpeed() > 5; },
function () { return 1 === 1; }];
var actions = [
function () { return alert('walk'); },
function () { return alert('jump'); }
function () { return alert('attack'); } ]
The code chooses one of each to generate a new function:
condition = conditions [Math.floor(Math.random()*conditions .length)];
actions = conditions [Math.floor(Math.random()*actions .length)];
Provided it the chosen condition is enemyNear() and the chosen action is walk(), how can I generate the simple anonymous function?
behavior = function() {
if(enemyNear()) {
walk();
}
}
I can change the way the arrays are saved if needed. How can this be done?
All characters behaviors are called inside a loop like this:
for(i=0,i<chars.length,i++) {
chars[i].behavior.call();
}
The simplest way would be to only put functions inside the arrays:
var conditions = [
enemyNear,
function () { return mySpeed() > 5; },
function () { return 1 === 1;
}];
var actions = [walk, attack, jump];
Then you could define behave as something like:
var behave = function(condition, action) {
if(condition()) {
action();
}
}
And use it like for example:
behave(conditions[2], actions[1]);
Here, 2 and 1 could be a randomly generated number like this:
var getRandomInt = function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var actRandomly = function (conditions, actions) {
behave(
conditions[getRandomInt(0, conditions.length -1)],
actions[getRandomInt(0, actions.length -1)]
);
};
Call it like:
actRandomly(conditions, actions);
Of course this only presents the idea, and is not neatly organized.
For the fun of it, I created a Basic jsFiddle Demo Version.
Note: Always use var when you are defining a variable. Don't pollute the global scope.
First, inside of the arrays you will need to have a reference to the condition and action function. Right now, you are calling them, so they basically are the same as a useless array like:
conditions = [true, false, true];
actions = [undefined, undefined, undefined];
I would correct it to something like this:
var conditions = [enemyNear,
function() { return mySpeed() > 5 },
function(){ return true; }];
var actions = [walk, attack, jump];
And then you can create a function that generates a behavior:
function generateBehavior(){
var condition = conditions[Math.floor(Math.random() * conditions.length)];
var action = actions[Math.floor(Math.random() * actions.length)];
return function() {
if(condition()) {
action();
}
}
}
You can see it in action on this JSFiddle demo.
Change your arrays from this:
conditions = [enemyNear(), mySpeed()>5, 1=1];
To this:
conditions = [
function() { return enemyNear() }, // or just enemyNear
function() { return mySpeed() > 5 },
function() { return 1 == 1 } // 1 = 1?
];
With your current code, your functions are being called and conditions becomes an array of the outputs of those functions.
condition = [
enemyNear,
function() { return mySpeed() > 5; },
function() { return 1 == 1; }
];
You need to store your functions in the array, not call them, otherwise you are storing the result of the functions in the array.
function enemyNear() {}
function walk() {}
conditions = [enemyNear]
actions = [walk]
behaviour = function() {
if(conditions[randomNumber]()) {
actions[randomNumber]();
}
}
Simple. Use eval()
var behavior;
eval("behavior = function() { if (enemyNear()) walk(); }");
behavior();

Any ways to short-up such chain call?

Is there any ways to short-up such chain call?
if (obj && obj.prop && obj.prop.subProp1 && obj.prop.subProp1.subPropFunc) {
obj.prop.subProp1.subPropFunc();
}
The only alternative I can imagine is try-catch. Any other ideas?
*I really tired of writing these. It's much easier in coffeescript using ?..
This should work given your sample code (haven't tested "all cases", just a copy of your sample):
function propsExist(obj) {
if (!obj) return false;
for (var i = 1; i < arguments.length; i++) {
if (!obj[arguments[i]]) return false;
obj = obj[arguments[i]];
}
return true;
}
if (propsExist(obj, "prop", "subProp1", "subPropFunc")) {
obj.prop.subProp1.subPropFunc();
}
The method propsExist() takes a variable number of arguments, the first of which being the original object you want to check properties/functions on. It will iterate through the list of properties you send to it and check them in-order. If one doesn't exist, it will return false. If it makes it through the whole loop, it validated successfully!
If you always want to call the sub-property's function if it validates, you could also just change the propsExist function to call it instead of returning true (then rename the function to something like callIfValid(obj, ...)
Same idea as the previous post, just a different solution.
function checkChain(variablePath,startingPoint){
var check = startingPoint || window,
parts = variablePath.split("."),
i;
for (i=0;i<parts.length;i++) {
check = check[parts[i]];
if (!check) {
return null;
}
}
return check;
}
var foo = { bar : { cat : { says : function(x){ alert(x); } } } };
var test1 = checkChain("foo.bar.cat.says");
if (test1) {
test1("meow");
}
var test2 = checkChain("foo.bar.cat.bark");
if (test2) {
test2("burp");
}
var test3 = checkChain("cat.says",foo.bar);
if (test3) {
test3("huh?");
}

How to create a javascript library using a closure

I have written some javascript that I would to encapsulate in a closure so I can use it elsewhere. I would like do do this similar to the way jQuery has done it. I would like to be able to pass in an id to my closure and invoke some functions on it, while setting some options. Similar to this:
<script type="text/javascript">
_snr("#canvas").draw({
imageSrc : someImage.png
});
</script>
I have read a lot of different posts on how to use a closure to do this but am still struggling with the concept. Here is where I left off:
_snr = {};
(function (_snr) {
function merge(root){
for ( var i = 1; i < arguments.length; i++ )
for ( var key in arguments[i] )
root[key] = arguments[i][key];
return root;
}
_snr.draw = function (options) {
var defaults = {
canvasId : 'canvas',
imageSrc : 'images/someimage.png'
}
var options = merge(defaults, options)
return this.each(function() {
//More functions here
});
};
_snr.erase = function () {};
})(_snr);
When ever I try to call the draw function like the first code section above, I get the following error, '_snr is not a function'. Where am I going wrong here?
EDIT
Here is what I ended up doing:
function _snr(id) {
// About object is returned if there is no 'id' parameter
var about = {
Version: 0.2,
Author: "ferics2",
Created: "Summer 2011",
Updated: "3 September 2012"
};
if (id) {
if (window === this) {
return new _snr(id);
}
this.e = document.getElementById(id);
return this;
} else {
// No 'id' parameter was given, return the 'about' object
return about;
}
};
_snr.prototype = (function(){
var merge = function(root) {
for ( var i = 1; i < arguments.length; i++) {
for ( var key in arguments[i] ) {
root[key] = arguments[i][key];
}
}
return root;
};
return {
draw: function(options) {
var defaults = {
canvasId : 'canvas',
imageSrc : 'images/someimage.png'
};
options = merge(defaults, options);
return this;
},
erase: function() {
return this;
}
};
})();
I can now call:
<script type="text/javascript">
_snr("#canvas").draw({
imageSrc : someImage.png
});
</script>
Because you declared _snr as an object and not a function. Functions can have properties and methods, so there's various ways to achieve what you want, for example one of them would be say...
_snr = function(tag) {
this.tag = tag;
}
_snr.foo = function() {
//Code goes here
}
You can also pass the outer context into a closure to hide your variables from accidentally polluting the global namespace, so like...
(function(global) {
var _snr = function(tag) {
this.tag = tag;
}
_snr.foo = function() {
//Code goes here
}
//export the function to the window context:
global._snr = _snr;
})(window);
window._snr('#tag').foo('wat');
Happy coding.
Because your _snr is an object, not a function. You have to call it like this:
_snr.draw({
canvasId: '#canvas',
imageSrc: 'someImage.png'
});
When you do _snr('#canvas') that is a function call which is why you're getting that error. _snr is an object with some methods attached to it such as draw() and erase(). The reason jQuery is able to pass arguments into the $ is because they return the $ as a function object which is why we're able to pass it various selectors as arguments.
You are going wrong at the first line _snr = {}
It needs to be
_snr = function(){
selector = arguments[0]||false;
//snr init on dom object code
return _snrChild;
}
Im on a mobile phone but when im on a pc I will maybe fix the whole code c:
Here you have a snr object and that has erase and draw methods. What you intend to do is to write a _snr function which will get an id and return a wrapper object. That returned object should have erase and draw methods. so you can do
var returnedObject = _snr("my_id");
returnedObject.draw("image.png");

Categories