Watch on all attributes of an object in AngularJS - javascript

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

Related

Angular 2 - pass an object field by reference. Reusable way to edit objects

I am creating reusable table component which will enable editing of objects fields to prepare them for sending to the API.
Having an object:
person: {
name: "John"
job: {
type: "IT"
title: "Software Engineer"
}
}
I would like to pass the object nested field to a component and edit. F.e:
<edit-field [field]="person.job.title"></edit-field>
Which results in an input field that edits exactly the title field in original object. The problem is that person.job.title is a string, not and object or array so it's not passed by reference.
I had 2 ideas haw the problem could be solved:
1) pass "person.job.title" as a string:
<edit-field [field]="'person.job.title'"></edit-field>
or
<edit-field field="person.job.title"></edit-field>
And in component class do split by a ".":
let fields = this.field.split('.');
and then do a while loop to access the field by reference.
We could also do 2 inputs:
<edit-field-component [fieldRef]="person.job" [field]="'title'"></edit-field-component>
and then inside component do this.fieldRef[this.field]
I am wondering if there is any other, more clean, way to achieve that.
Basically, you want to accomplish two-way binding - i.e. changes to the object value: eg person.job.title updates your new edit component, but also changes made from your edit component also get reflected back to the object value.
In Angular, that means you have to bind both ways:
<edit-field [field]="person.job.title" (change)="person.job.title=$event"></edit-field>
where your edit-field component has an #Output property that emits the changed value whenever someone types into it. The value emitted from the #Output property will be in the variable $event and you simply want to assign that back to the property that you want to update.
So, your EditFieldComponent can look something like:
#Component({
.....
template: `
<input (input)="change.emit($event.target.value)" .... />
`
})
export class EditFieldComponent {
change = new EventEmitter();
}
The above means that whenever an input event triggers on your input field, the component's change output property will emit the new value of the input field.
===========
If you understand everything above, then Angular provides a little shortcut for this exact scenario. If the output property of your component is named a specific way, you can simplify how you write the two way binding.
So, if your input property is field, and you name your output property fieldChange you can make use of some fancy syntax to cut down how much you have to type.
i.e.
<edit-field [field]="person.job.title" (fieldChange)="person.job.title=$event"></edit-field>
is equivalent to:
<edit-field [(field)]="person.job.title"></edit-field>
[field]="person.job.title" is one-way binding (changes of person.job.title are propagated to field but not the other way round)
[(field)]="person.job.title" would achieve two-way binding (changes made by fieldChange method are also propagated back to person.job.title)
If you want to reffer your object or property of your object to your component, you need to create an #Output property with type eventEmitter.
#Input('field') field: any;
#Output() fieldChange = new EventEmitter();
Be carefull to name your output property with "Change" suffix word. It will detect automatically change from your object and notify to your main object.
Javascript just like Java is passed by value, they have never offered passed by reference. So in your case, your best option is to pass your person object directly. Even though it will be copied inside your function, the copy still refers to the same object, so changing a field in the copy will also change the corresponding field in the original.

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.

Binding a Repeat Control to either a session or view scope variable

I have a repeat control which I have bound to a sessionScope variable and it works fine. I am trying to make the control a bit more extensible by dynamically binding it to either a viewScope or a sessionScope variable. I have added a Custom Property to the Control that holds the repeat and have called it scope. The problem is how to bind the repeat to the correct Scoped variable.
On the Repeat control I have selected JavaScript as the binding method and added this code:
switch (compositeData.scope){
case 'ss' :
return sessionScope.ssCat1
break;
case 'vs' :
return viewScope.vsCat1
break
}
I have checked and in my case compositeData.scope = 'ss' and the sessionScope.ssCat1 contains the correct data.
So am I missing something or is this even possible?
Is it not a better way to inject the repeat values in the custom control by a custom parameter
Name it repeat or something else with object as property type.
In your custom control you have
<xp:repeat id="repeat1" rows="30"
var="rowData" style="width:400px" value="#{compositeData.repeat}">
//do what ever you want with a rowData
</xp:repeat>

angularjs broadcast changes to an object in a service

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.

Categories