need help to understand angular $watch - angularjs

say I have a album list and user can add album
controller.albumList = function($scope, albumService) {
$scope.albums = albumService.query();
$scope.$watch('$scope.albums',function(){
$scope.albums.$save($scope.albums)
})
$scope.addalbum= function(){
$scope.albums.objects.push(album);
}
};
get a album list from server and show them
user can add album
watch the albums list ,when change happend save them to the server.
the problem is the $watch always fired, I did not even trigger the addalbum method, and every time I refresh the page a new album is created.
I follow the the code in todeMVC angular
here is the example code
var todos = $scope.todos = todoStorage.get();
$scope.newTodo = '';
$scope.editedTodo = null;
$scope.$watch('todos', function () {
$scope.remainingCount = filterFilter(todos, { completed: false }).length;
$scope.completedCount = todos.length - $scope.remainingCount;
$scope.allChecked = !$scope.remainingCount;
todoStorage.put(todos);
}, true);
please help me understand this

this is a solution:
$scope.$watch('albums', function(newValue, oldValue) {
if (angular.equals(newValue, oldValue)) {
return;
}
$scope.albums.$save($scope.albums);
}
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.
More about a $watch listener: $watch at angularjs docs

Firstly, you do not have to specify the scope object when referencing to a property of the scope. So, replace:
$scope.$watch('$scope.albums', ...)
with the following:
$scope$watch('albums', ...)
Now about your issue. $watch is triggered each time the value of the object / property being watched changes. This includes even those cases when the values are yet to be assigned, such as undefined and null. Thus, if you wish that the save should happen only when a new album is added, you can have code similar to:
//Makes assumption that albums has a length property
$scope.$watch('albums.length', function () {
//Check for invalid cases
if ($scope.albums === undefined || $scope.albums === null) {
return;
}
//Genuine cases.
//Proceed to save the album.
});
With this, the $watch is still triggered in unwanted scenarios but with the check, you avoid saving when the album has not changed. Also, note that your $watch triggers only when the length of the albums object changes. So, if an album itself is updated (say an existing album name is changed), then this watch is not triggered. You can change the watch property based on your needs. The watch property mentioned here works only when a new album is added.

Related

Search method to show record before data update in sql server Angularjs asp.netmvc

Angular js function updating some record. After updating record i am calling search method to show data on view.
But record does not updated before that search method call that does not get data so show null on view.
I have separate button for search on its ng-click this search method call. After some second if i click that button it shows data on view.
my code is,
vm.Update = function (value)
{
var test = value;
searchCriteria = {
From: vm.From,
To: vm.To,
Region: vm.Region,
City: vm.SelectedCity
}
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
});
vm.searchVisit(0);
}
This searchvisit call and service unable to update data in database so i do not get any record on view. When i call this searchvisit method from separate button for searching it shows record with updated data.
Hopes for your suggestions how to pause execution before calling searchvisit method or any alternative that it gets any response than move execution control to searchvisit method.
Thanks
This is due to the asynchronous nature in JS.
From your code, surveyService.UpdateVisit(searchCriteria,value) returns a promise. Thus, when vm.searchVisit(0); is called, surveyService.UpdateVisit(searchCriteria,value) has not been resolved yet, meaning updating is still in progress and have not been completed. There for vm.searchVisit(0); shows records that are not updated.
If your second function is dependent on the values of the first function call, please add it as shown below inside the success callback.
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
//Add this here.
vm.searchVisit(0);
});

angular-dialog-service update parent scope data object

I've a template:
<p class="text-right">
<a ng-click="editTherapeuticProposal(meow.accepted_tp)" class="fa fa-pencil"></a>
</p>
which calls the editTherapeuticProposal function defined in its controller, passing it the meow.accepted_tp object (here I use angular-dialog-service: https://github.com/m-e-conroy/angular-dialog-service):
// here tp is equal to meow.accepted_tp
$scope.editTherapeuticProposal = function(tp) {
dialogs.create('surgeon/templates/create_edit_therapeutic_proposal.tpl.html', 'SurgeonCreateEditTherapeuticProposalCtrl', {scope: $scope, tp: tp}, { copy: false });
};
tp is an object.
Then in the dialog controller I display a form in order to let the user modify tp. I do some stuff, the relevant ones are:
// data is the object received by the dialog controller: {scope: $scope, tp: tp}
if(typeof data.tp != 'undefined') {
$scope.therapeuticProposal = angular.copy(data.tp);
}
I copy the object to work on a different object (I don't want data to be updated if not saved)
When pressing the save button in the dialog, the following function runs:
var complete = function(tp) {
data.tp = tp;
//...
}
Ok, the problem is that meow.accepted_tp in the parent scope doesn't get updated. If I do
var complete = function(tp) {
data.tp.title = 'meow';
//...
}
Its title gets updated. There is clearly something wrong with the prototypal inheritance, I know that in order to get variables updated they should be properties of an object, but tp is already passed as an object property (of the data object). Any ideas?
Edit
After re-reading the angular-dialog-service docs, you can pass a result back using modalInstance. It sounds like this is what you want to do.
The reason your binding isn't working is because you're changing the object reference from a child scope, rather than a property on the object bound (which is why data.tp.title = 'meow' works).
Anyway, for your case, try this:
// here tp is equal to meow.accepted_tp
$scope.editTherapeuticProposal = function(tp) {
var dlg = dialogs.create('surgeon/templates/create_edit_therapeutic_proposal.tpl.html', 'SurgeonCreateEditTherapeuticProposalCtrl', {scope: $scope, data: data}, { copy: false });
dlg.result.then(function(tp) {
// Get the result and update meow.accept_tp
$scope.meow.accepted_tp = tp;
});
};
Then in the dialog, when you complete, do:
var complete = function(tp) {
$modalInstance.close(tp);
}
For an example, see http://codepen.io/m-e-conroy/pen/rkIqv, in particular the customDialogCtrl (not customDialogCtrl2) is what you want.

AngularJS watch model value if model is not null

Simple question here.
I have this watch:
// Watch our model
$scope.$watch(function () {
// Watch our team name
return self.model.team.data.name;
}, function (name) {
console.log(name);
// if we have a name
if (name) {
// Store our model in the session
sessionStorage.designer = angular.toJson(self.model);
}
});
The team model is pull in from the database as a promise (hence the data) so when the watch first fires self.model.team has not been set so it is null.
How can I get my watch to either wait until it has been set or add a check into the return function of the watch?
Use a watch expression instead of a function. This will catch any errors with missing objects and return undefined.
// Watch our model
$scope.$watch('self.model.team.data.name', function (name) {
console.log(name);
// if we have a name
if (name) {
// Store our model in the session
sessionStorage.designer = angular.toJson(self.model);
}
});
There is no magic here - if one of the variables you are accessing could be null/undefined, then you cannot get its property if it's null/undefined. So, you have to guard against that:
$scope.$watch(
function(){
return (self.model.team && self.model.team.data.name) || undefined;
},
function(v){
// ...
});
The only "magic" is when you "$watch" for expressions, but the expressions need to be exposed on the scope. So, you could do:
$scope.model = self.model;
$scope.$watch("model.team.data.name", function(v){
// ...
});
But, really, you have to ask yourself why you need a $watch here to begin with. It seems to me that you are getting the team asynchronously once - it does not look like it will change except by maybe another async call. So, just handle that when you receive the data without the $watch:
someSvc.getTeam() // I made an assumption about a service that pulls the data from db
.then(function(team){
var name = team.data.name;
// if we have a name
if (name) {
// Store our model in the session
sessionStorage.designer = angular.toJson(self.model);
}
});
An unnecessary $watch is expensive - it is evaluated on every digest cycle, so, it's best to reduce the number of $watchers.

KO.Computed equivalent in Angular / Breeze Initializer

Trying to get a more in-depth understanding of how Angular treats data binding and understanding it better and one thing is difficult to get my head around -
In Knockout I use a computed to keep track of changes to a property. In Angular it to move this logic into the view, which is a it trivial to me, but if that is the way to do it I understand.
My question is when I am initializing a new entity with Breeze/Angular how do I create computed-like properties that are notified when changes occur to the entities property?
myEntity.fullName = ko.computed(function () {
return myEntity.firstName + ' ' + myEntity.LastName;
});
in Angular would the equivalent be
myEntity.fullName = function () {
return myEntity.firstName + ' ' + myEntity.LastName;
};
And does that properly track the entity?
You are correct to simply make it a function. If your entity as shown is added to the $scope, then you would access the property like so:
<span class="fullname">{{ user.fullName() }}</span>
Whenever Angular runs a $digest cycle, it will check for a change to the bound property. In this instance, it means it will call the fullName() function and check to see if the result has changed. If it has, anything that has a $watch attached to that item — including simple binding — will be notified of the change.
One caveat of this technique, however, is to make sure that the operations being performed within your function are relatively fast, and also have no side effects. Bound functions like this will be called many times throughout the application.
If you need to have a more complex function, it would be better to handle that within the controller, and update a property on the object manually when it changes.
I found the answer on the following website. If you don't do something similar, what you will find is that all functions are ran during the digest phase and are not triggered by the change of a dependent observable or property. The solution below allows you to only trigger the function when a value it uses is changed.
http://www.jomendez.com/2015/02/06/knockoutjs-computed-equivalent-angularjs/
Explains how to duplicate the subscribe and computed feature in knockoutjs
var myViewModel = {
personName: ko.observable('Bob')
};
myViewModel.personName.subscribe(function(oldValue) {
alert("The person's previous name is " + oldValue);
}, null, "beforeChange");
This is what I found as result of my research (this is the AngularJs equivalent) Using the $scope.$watch method see the AngularJs life cycle https://docs.angularjs.org/guide/scope
$scope.myViewModel = {
personName: 'Bob'
};
$scope.$watch(‘myViewModel.personName’, function(newValue, oldValue){
//we are able to have both the old and the new value
alert("The person's previous name is " + oldValue);
});
//knockout computed
var isVendorLoading = ko.observable(),
isCustomerLoading = ko.observable(),
isProgramLoading = ko.observable(),
isWaiting = ko.observable();
var isDataLoading = ko.computed(function () {
return isVendorLoading() || isCustomerLoading() || isProgramLoading() || isPositionLoading() || isWaiting();
});
This is the AngularJs Equivalent for KnockoutJs computed:
$scope.isDataLoading = false;
$scope.$watch(
function (scope) {
//those are the variables to watch
return { isVendorLoading: scope.isVendorLoading, isCustomerLoading: scope.isCustomerLoading, isProgramLoading: scope.isProgramLoading, isWaiting: scope.isWaiting };
},
function (obj, oldObj) {
$timeout(function () {
//$timeout used to safely include the asignation inside the angular digest processs
$scope.isDataLoading = obj.isVendorLoading || obj.isCustomerLoading || obj.isProgramLoading || obj.isPositionLoading || obj.isWaiting;
});
},
true
);

How to rollback backbone.js model changes?

I have a "Cancel" button on my page which should reverts all the changes I made back to the state it was loaded from server..
I guess I need to store an initial state of Backbonejs model and restore a current (changed) state back to initial.
What is the best way to achieve that?
Thank you
FWIW - i wrote a plugin to handle this automatically, specifically with the idea of "cancel" buttons in mind: http://github.com/derickbailey/backbone.memento
model.previousAttributes() returns all of the previous attributes, while model.changedAttributes() returns all the changed attributes, but with their new values (or false if nothing has changed). So you could combine them to write a cancelChanges method in your prototype :
var MyModel = Backbone.Model.extend({
cancelChanges: function() {
var changed = this.changedAttributes();
if(!changed)
return;
var keys = _.keys(changed);
var prev = _.pick(this.previousAttributes(), keys);
this.set(prev, {silent: true}); // "silent" is optional; prevents change event
},
});
I dont believe there's a single method call for returning a model to its unedited state.. but the unedited values are available individually through model.previous(attribute) and collectively via model.previousAttributes.
Here is what I came up with:
var RollbackEnabledModel = Backbone.Model.extend({
initialize: function() {
this._initAttributes = _.clone(this.attributes);
},
parse: function(data) {
this._initAttributes = _.clone(data);
return data;
},
rollback: function() {
this.set(this._initAttributes);
}
});
Take a look at NYTimes' backbone.trackit. It tracks multiple changes to the model instead of only the most recent change like model.changedAttributes() and model.previousAttributes(). From the README:
var model = new Backbone.Model({id:1, artist:'Samuel Beckett', 'work':'Molloy'});
model.startTracking();
model.set('work', 'Malone Dies');
console.log(model.unsavedAttributes()); // >> Object {work: "Malone Dies"}
model.set('period', 'Modernism');
console.log(model.unsavedAttributes()); // >> Object {work: "Malone Dies", period: "Modernism"}
model.save({}, {
success: function() {
console.log(model.unsavedAttributes()); // >> false
}
});
In addition, the library adds functionality to resetAttributes to
their original state since the last save, triggers an event when the
state of unsavedChanges is updated, and has options to opt into
prompting to confirm before routing to a new context.

Resources