angularjs broadcast changes to an object in a service - javascript

Suppose I have a service that returns an object with a few values. This is my settings object. I use this service in controller 1 to get the values from the object and assign it to the $scope. In controller2 I use the same service to add a few more values to the object. Now I need controller1 to realize that those changes have been made and update the $scope accordingly.
I tried using $watch in controller1 like so
$scope.$watch(myservice.settings, function(oldvalue,newvalue) {
console.log(newvalue);
});
But it seems that the digest cycle isn't run when the object is updated in controller2. Apart from manually running the $digest cycle, which as I understand it is generally bad parctice, how else can I get the functionaliy I'm looking for?

$watch accepts a function which returns a value or a string to be evaluated as an expression:
It also will not handle internal comparison for objects unless you pass true as a third argument:
$scope.$watch('myservice.settings', function(oldvalue,newvalue) {
console.log(newvalue);
}, true);
This will perform a deep comparison using angular.equals instead of comparing for reference equality, which is the default. If you need to perform only a shallow comparison (ie your object is only one level deep), you can use $watchCollection instead:
$scope.$watchCollection('myservice.settings', function(oldvalue,newvalue) {
console.log(newvalue);
});
The advantage of $watchCollection over $watch(func, func, true) is that it is less expensive.

Related

AngularJS : deep watch for map

How can I deep watch for map?
$scope.map = new Map();
$scope.$watch('map' function() {
// Do Something
});
I would like to fire $watch when elements of the $scope.map has added or deleted or changed.
In case of object, I know I can deep watch by $scope.$watch('obj', function(){}, true). But I don't know how to deep watch for map.
How can I do this?
You can also try to use $watchCollection instead of $watch.
From the Documentation
Shallow watches the properties of an object and fires whenever any of
the properties change (for arrays, this implies watching the array
items; for object maps, this implies watching the properties). If a
change is detected, the listener callback is fired.
The obj collection is observed via standard $watch operation and is
examined on every call to $digest() to see if any items have been
added, removed, or moved.
The listener is called whenever anything
within the obj has changed. Examples include adding, removing, and
moving items belonging to an object or array.
Are you sure the deep watch dont work for js Map ?
from $watch documentation :
When objectEquality == true, inequality of the watchExpression is
determined according to the angular.equals function. To save the value
of the object for later comparison, the angular.copy function is used.
This therefore means that watching complex objects will have adverse
memory and performance implications.
angular.equals work for js maps. So the deep watch should be fine with maps.

Watch on all attributes of an object in AngularJS

I have an object in my $scope which contains some attributes such as following:
$scope.content = {
name : 'myname',
description : 'mydescription',
keyword : 'some keyword'
}
Now I want to listen on every change in each attribute. I set watch on its own object but I could not get changes on its attributes (its attributes are changed from UI by binding to some input fields).
$scope.$watch('content', function(){
// do some work
}
When I set watch on some attribute as follow, I can get changes in that attribute.
$scope.$watch('content.name', function(){
// do some work
}
Is there any way to listen to change of attributes of object without set watch on all attributes?
Use the third argument of $watch, like this :
$scope.$watch('content', function(){
// do some work
}, true);
If this is true, the expression is evaluated for object equality, which means properties are included. Otherwise it's just evaluated for reference.
We can use Equality $watch by passing third argument as true , as per API reference $watch accepts three arguments as below
$watch(watchExpression, listener, [objectEquality]);
if third argument is true , it will watch all properties inside a object
$scope.$watch("content",function(){},true);
for more info on $watch you can refer below video
https://www.youtube.com/watch?v=IKPrhB8alV4

In declaring properties for a polymer element, why is value sometimes a function that returns {}?

The part I'm curious about is why use
value: function() { return {}; }
instead of
value: {}
Here's the sample code:
<script>
Polymer({
is: 'polymer-demo',
properties: {
data: {
type: Object,
notify: true,
value: function() { return {}; }
}
},
});
</script>
This is explained in the example:
When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.
Although you can use {}, this will be the same object shared by each element, so that if the value is mutated for one element, all other elements would see the same change applied to it. This is not what you want to happen. By using a function, the function will be called for each element, and each call will produce a new, separate object. Then a mutation will only apply to that single element without affecting the others.
I feel it is the same reason that vuejs requires their data component to be rendered as a function rather than the object that it is:
In the basic examples, we declare the data directly as a plain object.
This is because we are creating only a single instance with new Vue().
However, when defining a component, data must be declared as a
function that returns the initial data object. Why? Because there will
be many instances created using the same definition. If we still use a
plain object for data, that same object will be shared by reference
across all instance created! By providing a data function, every time
a new instance is created, we can simply call it to return a fresh
copy of the initial data.
Thus, making sure that the data being given to the component is always indicative of a fresh "load"
Hope this helps!
Usually this is done in cases where you want to show, that you can not only return a fixed value but calculate a dynamic value and return this instead but you were to lazy to return something more than {}. And sometimes the things you are declaring a used in some place by the framework and are there expected to be a function returning an object, and not just a fixed value or object.
You can find out by changing the code, executing it, and watching for errors or different behaviour.

What is "=*" in AngularJS

I've come across this way of isolate binding specification:
scope: {
property: "=*"
}
What does the asterisk mean here? Can someone please provide an example?
The isolate binding with =* is to shallow watch for the change in the collection.
Let me explain a bit:
Normally watchCollection variables we use in the scripts like below:
$scope.arr = ['foo', 'bar', 'lorem', 'ipsum'];
$scope.arrCount = 4;
$scope.$watchCollection('arr', function(new, old) {
$scope.arrCount = new.length;
});
But what if you want to do object binding in html attribute itself?
<my-directive my-attr="arr"><!--$scope.arr-->
And, if you do this:
scope: {
myAttr: "=*"
}
Now, the directive attributes are assigned as it should shallow watch. And the use of watchCollection is good of use this time.
So, the =* is to be used when we need to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use =* or =attr (=? or =*?attr if the property is optional) as described in the docs.
Although, we can use = for watchCollection to deeply watch the object or array we can use =* for the same. But only difference is that using =* method the collection becomes true and when collection becomes true angularjs use the $watchCollection to remove the watcher and it uses the $watch to remove the watcher when collection is false:
if (definition.collection) { //using `=*`
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else { //using `=`
removeWatch = scope.$watch($parse(attrs[attrName],
parentValueWatch), null, parentGet.literal);
}
So, when you want to shallow watch for the collection then you need to use =*.
The asterisk is used to change the default watch behavior.
From the angular docs:
By default, the $watch method is used for tracking changes, and the equality check is based on object identity. However, if an object literal or an array literal is passed as the binding expression, the equality check is done by value (using the angular.equals function). It's also possible to watch the evaluated value shallowly with $watchCollection: use =* or =attr (=? or =*?attr if the attribute is optional).
According to Doc:
= or =attr - sets up a bidirectional binding between a local scope property and an expression passed via the attribute attr. '
By default $watch method is used for tracking changes, and the equality check is based on object identity.
In the case of =* is used to watch evaluated value shallowly using $watchCollection.
There is difference in evaluation using $watch vs $watchCollection.
The following link gives down the difference between them.

When should I use parentheses in knockout

I am a beginner in Knockout and I must say I often get confused regarding when to use (). Is there any general tip/trick regarding when would you use () against when you would not because at the moment I am just doing trial and error. If the binding throws error or doesn't update values I remove () else I put.
I feel like the existing answers skip over a very important point of confusion: data-bind attributes.
It is true that you use the parens when you are in Javascript, and getting or setting observables. But when you are writing data-bind="text: property", you leave out the parens even when working with observables.
Edit
As noted in the comment below, bindings that are expressions, or access properties of observbles, require parens
visible: personName().length > 0
visible: person().Name().length > 0
visible: person().isVisible
Note that the last one person and isVisisble are both observables, but the last property doesn't use parens! The reason for this is that we would be passing a value to the binding instead of an observable, and it wouldn't update.
You use () in knockout when using observables or when executing any other method.
Knockout observables are functions, invoked to return you what you looking for or allow you to assign new values.
In knockout you use object.property() to retrieve a value and object.property(newValue) to assign a value to that property.
On the knockout website checkout the documentation, specifically the section on observables, which shows you the use of the () when querying and writing observables.
To quote:
var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(123)
};
To read the observable’s current value, just call the observable with no parameters. In this example, myViewModel.personName() will
return 'Bob', and myViewModel.personAge() will return 123.
To write a new value to the observable, call the observable and pass the new value as a parameter. For example, calling
myViewModel.personName('Mary') will change the name value to 'Mary'.
To write values to multiple observable properties on a model object, you can use chaining syntax. For example,
myViewModel.personName('Mary').personAge(50) will change the name
value to 'Mary' and the age value to 50.
Knockout's interactive tutorial is also quite nice and well worth going through.
Basically whenever you're working with an observable value (array or otherwise) you should use the parentheses to get the value and set the value.
var something = ko.obserbable();
something(5); //set value
console.log(something()); //get value: 5
The reason being that most JS implementations do not support getters and setters for properties yet, so observables were implemented like this to get around this limitation.

Categories