Firebase $save doesn't work inside an event handler function - angularjs

I must be missing something really basic. I have an input box where the list name is entered. The name is then saved to the Firebase.
When using $watch, it works just fine. However, if done through ng-keyup event, it returns the following error
TypeError: undefined is not a function.
What am I missing?
HTML:
<input id="which_list" ng-keyup="enterThis($event)" ng-model="which_list.name" >{{which_list.name}}</span>
Controller:
$scope.which_list = sync.$asObject();
$scope.$watch('which_list.name', function() {
gDataService.which_list.name= $scope.which_list.name;
$scope.which_list.$save() // THIS WORKS
// $scope.which_list => d {$$conf: Object, $id: "id", $priority: null, name: "to1_list", $save: function…}
.then(function(){
console.log($scope.which_list.name);
});
});
$scope.enterThis = function(event){
if (event.keyCode === 13) {
gDataService.which_list.name= $scope.which_list.name;
$scope.which_list.$save(); // THIS DOESN't WORK
// $scope.which_list = Object {name:"list_name"}
}
};
EDIT: In the comment, I included the value of $scope.which_list shown at the breakpoint.

Currently as you are changing in scope which_list converting to plain old JavaScript objects (POJO), I believe you need to unable 3 way binding between scope variable and $asObject().
Code
var which_list = sync.$asObject();
// set up 3-way data-binding
which_list.$bindTo($scope, "which_list");
Update
Also as you are using $scope.which_list object which contains name and other property,So do initialize it on starting of your controller like
$scope.which_list = {}
Hope this could help you, Thanks.

Related

Value from $rootScope is not loaded properly

So I'm basically trying to get a property from my $rootScope when the page loads. I need this property so I can display the value in my form.
After testing this:
console.log("DEBUG $rootScope", $rootScope);
console.log("DEBUG $rootScope.localClient", $rootScope.localClient);
I've noticed that $rootScope contains a localClient property, but $rootScope.localClient is undefined. Why is this?
See console screen below.
Here is where I fill the localClient object
function setClient(client, tvaNumber) {
if (tvaNumber) {
if (angular.isUndefined($rootScope.localClient))
$rootScope.localClient = {};
$rootScope.localClient[tvaNumber] = client;
}
}
Try accessing it like this,
console.log("DEBUG $rootScope.localClient", $rootScope['localClient']);
You must make sure the attribute loaded before use it, because JavaScripte always pass a reference to an object. Or you can try console.log(JSON.parse(JSON.stringify($rootScope)) get the real value.
One example:
var a = {}; console.log(a);a.test = '1';

I am losing my definition of a form controller when the controller is set in an ng-repeat

I have code that uses AngularJS v1.5.0 and creates multiple forms with an ng-repeat like this. Note that inside the form I show the form details between xx and xx:
<div ng-click="wos.wordFormRowClicked(wf)"
ng-form="wos.wordFormNgForm_{{wf.wordFormId}}"
ng-repeat="wf in wos.word.wordForms">
xx {{ wos['wordFormNgForm_1465657579'] }} xx
When the runs I am able to see the form details appear between the xx and xx and I am able to query the state of the form like this:
wordFormCheckAndUpdate = (): ng.IPromise<any> => {
var self = this;
var wordFormNgForm = 'wordFormNgForm_' + wf.wordFormId;
self[wordFormNgForm].$setDirty();
However in my code after calling this procedure the form becomes undefined and also nothing shows between the xx and xx. As I step through this procedure with the debugger the last line I see is the line setting the value of a and then as soon as the function finishes the information between the xx and xx disappears and the form becomes undefined:
wordEditSubmit = (): ng.IPromise<any> => {
var self = this;
return this.wordFormCheckAndUpdate().then(
() => {
return self.$http({
url: self.ac.dataServer + "/api/word/Put",
method: "PUT",
data: self.word
})
.then(
(response: ng.IHttpPromiseCallbackArg<IWordRow>): any => {
self.word = angular.copy(response.data);
self['wordNgForm'].$setPristine();
self.uts.remove(self.words, 'wordId', self.word.wordId);
response.data.current = true;
self.words.push(response.data);
var a = 99;
},
(error: ng.IHttpPromiseCallbackArg<any>): any => {
self.ers.error(error);
return self.$q.reject(error);
});
}
);
}
My problem is that if I then try to repeat this:
setDirty = (): ng.IPromise<any> => {
var self = this;
var wordFormNgForm = 'wordFormNgForm_' + wf.wordFormId;
self[wordFormNgForm].$setDirty();
}
then the controller object self[wordFormNgForm] is no longer defined.
For reference. Here is how new wordForms are created:
wordFormAdd = () => {
this.wordFormId = Math.floor(Date.now() / 1000);
var emptyWordForm: IWordForm = <IWordForm>{
wordId: this.word.wordId,
wordFormId: this.wordFormId,
posId: 1,
statusId: Status.New
};
this.word.wordForms.push(emptyWordForm);
this.wordNgForm.$setDirty();
}
Here is the remove function:
remove = (arr, property, num) => {
arr.forEach((elem, index) => {
if (elem[property] === num)
arr.splice(index, 1);
})
};
Does anyone have any advice as to how I could solve this problem
Your problem could have been explained well with a demo reproducing the issue. Nevertheless, I've partially reproduced your problem in a fiddle here (not with Typescript though, it's just vanilla JS).
What I suspect happens when you first add a wordForm object with the wordFormAdd() method, is that, if you try to reference the FormController object associated with the ng-form in the same method immediately, then it might be too early to do so, because the $digest loop might not have been completed.
This is because as soon as you click and trigger the wordFormAdd() function, a new emptyWordForm object is added to the word.wordForms array and immediately ng-repeated in your view. But, the controller hasn't had enough time to associate the newly created ng-form object with itself, so you may end up with referencing an undefined object.
wordFormAdd = () => {
this.wordFormId = Math.floor(Date.now() / 1000);
var emptyWordForm: IWordForm = <IWordForm>{
wordId: this.word.wordId,
wordFormId: this.wordFormId,
posId: 1,
statusId: Status.New
};
this.word.wordForms.push(emptyWordForm);
this.wordNgForm.$setDirty(); //<== too early to do so
}
To overcome this, you should wrap that portion of the code within a $timeout wrapper. This ensures that Angular's so-called dirty check (or simply the digest loop) is finished.
Also note that keeping a single wordNgForm or wordFormId reference doesn't make sense, because you might dynamically add other forms, each of which may be associated with a new wordNgForm key and wordFormId.
I would suggest doing the above like so:
wordFormAdd = () => {
this.wordFormId = Math.floor(Date.now() / 1000);
...
this.word.wordForms.push(emptyWordForm);
this._timeout(function(){ // $timeout injected and assigned to this._timeout in controller definition
var wordFormNgForm = 'wordFormNgForm_' + this.wordFormId;
this[wordFormNgForm].$setDirty(); // <==
});
}
However in my code after calling this procedure the form becomes undefined and also nothing shows between the xx and xx. As I step through this procedure with the debugger the last line I see is the line setting the value of a and then as soon as the function finishes the information between the xx and xx disappears and the form becomes undefined:
A possible reason where the watched value in your view ({{ wos['wordFormNgForm_1465657579'] }}) becomes undefined, is that you are fetching new values and storing a copy of them in the controller's self.word property:
...
.then((response: ng.IHttpPromiseCallbackArg<IWordRow>): any => {
self.word = angular.copy(response.data); // <==
...
},
By doing so, the collection under word.wordForms that was previously ng-repeated in the view is changed and the watched value is no longer a valid reference to an item of this collection.
Meanwhile, self['wordNgForm'] in the wordEditSubmit certainly isn't associated with a FormController object as far as the ng-repeat in your view is concerned. This is because the FormController object keys associated with an ng-form must have a format (as imposed by you) similar to something like wordFormNgForm_1465657579. Therefore, here too, you are referencing an undefined property under self['wordNgForm']:
...
.then((response: ng.IHttpPromiseCallbackArg<IWordRow>): any => {
self.word = angular.copy(response.data);
self['wordNgForm'].$setPristine(); // <==
...
},
This looks like standard issue with java script that this means different things depending what called the function.
What I would advise is to generate form names and place them in wos.word.wordForms collection and bind them from there. Doing gymnastics like ng-form="wos.wordFormNgForm_{{wf.wordFormId}}" and var wordFormNgForm = 'wordFormNgForm_' + wf.wordFormId; feel quite awkward.
If there is a reason you are not using this approach please tell me, there might be a different solution :)

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.

Getting id undefined in angularJs service

I have followed tutorial : http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
But, when I try to save a new record, I am getting newcontact.id as undefined and unable to save new record.
What would be the problem?
you need to add this piece of code
var original = {itemCategory:null,itemName:null,
itemPrice:null,purchaseDate:null,applianceType:null,
insStatus:null,id:null};
$scope.newitem = angular.copy(original);
and edit the saveItem function as this
$scope.saveItem = function () {
alert("add new");
DataStoreService.save($scope.newitem);
$scope.newitem = angular.copy(original);
}
here is the working Plunker
please not that i remove the <script src="angular-dropdowns.min.js"></script> and the ngDropdowns dependency of var app = angular.module('app', ['ngDropdowns']);, to get rid some console errors which are not causing this issue.
The cause of the problem
the problem of getting id undefined is, you didn't define a variable called $scope.newitem with in the controller, instead you straightly use newitem with in the HTML, then your controller dosen't know about this newitem until you change any input.
if you change any input say itemName then the controller knows there is no $scope variable called newitem, then the angular will create the newitem object and a itemName property inside that newitem object , after changing itemName input say u change the itemPrice then the controller search for the newitem object and put the property itemPrice in to the object, ok now you have two properties inside the newitem, like wise the process goes with your inputs, but the id field is a hidden field and your not going to change the value inside html, that means in your newitem object you don't have id property. that`s why its getting undefined.
to scarify,
add a line as the first statement as console.log($scope.newitem); in the $scope.saveItem function and check the $scope.newitem object, here is the Plunker to test it
press save without changing any input
this red color can not read id of undefined error is because we pass the $scope.newitem to the service which is undefined, so the script can not get the id field of item which is undefined in the line of alert("save"+ item.id);). item is undefined
change only the itemName and press save
here you will get a alert saying saveundefined this is because of the alert in line alert("save"+ item.id);). In this line item which is not undefined but the id property of the object is not set , so item.id is undefined, that's why its alert saveundefined. item is defined but item.id is undefined
Solution
create a object to hold default values of all the newitem properties
var original = {itemCategory:null,itemName:null,
itemPrice:null,purchaseDate:null,applianceType:null, insStatus:null,id:null};
get a deep copy of that object and assign it to newitem
$scope.newitem = angular.copy(original);
after saving the newitem reset the newitem object to default object we defined,
$scope.saveItem = function () {
alert("add new");
DataStoreService.save($scope.newitem);
$scope.newitem = angular.copy(original);
}

need help to understand angular $watch

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.

Resources