Angular Select not updating if model changes - angularjs

I am working on a website which uses Angular (1.6.4) Select. The content for the select element is loaded via REST if it is requested the first time. But the response of the REST call is cached so that following calls load the data from memory. If I load the website the select element is empty and I can't select any value. If I visit the site again with the data cached, the selectbox allows you to select items from a list. If I redirect the REST call to a file containing the data it works on the first attempt and I can select items as expected
So it seems that the code works in principle but if the model is updated too late the select element does not notice the changes.
Here is the code I am using:
<select ng-model="myData" chosen
class="select--chosen"
ng-change="handleSelection()"
ng-options="myData as myData.value for myData in dataArray">
</select>
The controller code looks like this (called when site is opened):
$scope.dataArray = [];
//$scope.dataArray = [{value : "just a test value"}];
$scope.$watch('dataArray ', function() {console.log("dataArray was changed...")}, true);
getArray();
function getArray() {
DataFactory.getArray().then(function (data) {
$scope.dataArray = data;
});
}
I do get the watch message when I load the site for the first time. When looking for a solution I found several hints but none of them worked for me. This is what I tried:
1) Add
$scope.$apply(function(){ /* code */ });
to set the dataArray inside this function or call it inside of the watch-function. In both cases I got the error on the console that the digest is already updating or so, indicating that it is not neccessary to use scope.$apply
2) Use
$scope.onChange($scope.dataArray);
after setting dataArray = data.
Unfortunately nothing worked. If I uncomment the line:
$scope.dataArray = [{value : "just a test value"}];
I can choose this entry after loading the page and the select view then shows the first entry of the dataArray and afterwards I can access the whole list and select items from it.
So I would like to know what I can do to update the select view after the data is available. Either by adding a Listener or by manually calling the select view to update(), refesh() or so. Is there such a function available?

You can show your select element by some boolean flag, which sets true, when
data loaded.
You can do something like below code.
In controller :
$scope.dataArray = [];
$scope.myData= null;
$scope.isDataLoaded = false; //flag for data loading.
function getArray() {
DataFactory.getArray().then(function (data) {
$scope.isDataLoaded = true; // make true now
$scope.dataArray = data.data; //your result might be data.data
$scope.myData = $scope.dataArray[0]; // you may select 1st as default
});
}
getArray();
In html:
<select ng-if="isDataLoaded" ng-model="myData" ng-class="select-chosen"
ng-options="myData as myData.value for myData in dataArray">
</select>

Related

how to retrieve data nested in two collections from firebase with angular

I'm new in Angular - Firebase development, and I am having problems to understand how to retrieve data nested in two collections.
I have a collection named "Orders", which includes a field call "auth", which is the user ID, and I have another collection that is the "User Profile", wich it's $id is the value of "auth". Inside the User Profile I have a field named roomNumber, and it's content I that I want to retrieve every time I read, in ng-repeat of the Orders.
In my view I was trying to do something like this :
<tr ng-repeat="item in items | filter: searchKeyword ">
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
roomNumber is a function in my controller
$scope.roomNumber = function(id) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
});
return childKey
}
When I run this code and see results in my js console, strange things happend:
1. It repeat a lot of times
2. I can never get the childKey value
I have been reading Firebase documentation, but really I do not understand how to do this "silly" thing, does anybody give me a tip of how to do it?
When you bind a function to the $scope and call it within the html it expects to get an answer back right away when called. So when you query firebase and it takes its sweet time getting you back an answer, angularjs has already gotten an answer of undefined from the function.
So what is happening is that you are registering a callback when you provide the function to rootRef.on and then right after you register the callback you are returning the value of childKey. Unfortunately, childKey only gets set by the callback function (which firebase hasn't executed yet). Therefore angularjs gets an answer of undefined from your roomNumber function.
In order to make this work, you are going to have to get the room numbers beforehand and then probably add them to each of your items in $scope.items then use
<td align="left">{{item.$id}} - {{item.room}}</td></tr>
instead of
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
To load all the room numbers you could call some function like this one after $scope.items has loaded
for (var i = 0; i < $scope.items.length; i++) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + $scope.items[i].$id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.val().room;
$scope.items[i].room = childKey;
});
}
It would change each of the items to have a reference to the room. Unfortunately, that list wouldn't update as the data updates, so the better solution would be to do that same query in whatever function was getting your items from the server and add the room to each item as it was being added to the items list.
To fix the issue with childKey not reading you need to use this:
var childKey = rootSnapshot.val().room;
instead of this:
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
Reference: https://www.firebase.com/docs/web/guide/retrieving-data.html

after angular $save and push to array item is not added unless I re-run a query

I have a single page app built using the MEAN stack. I am trying to get a list of items to update after a new item is added in angular but it's not working.
$scope.storages = Storages.query();
This works fine and returns my array of storages that I display in the view.
I then have a field to add a new array... to simplify the code it looks like this:
// create a new storage object from $scope.newStorage
var storage = new Storages($scope.newStorage);
I then do various things to the data and finally...
storage.$save(function(){
$scope.storages.push(storage); // doesn't seem to do anything
console.log("new storage", storage); //shows up fine
$scope.newStorage = []; // clear textbox
$scope.showHide.addItemPanel = false; // hides the newStorage form
return $scope.storages;
});
It seems like $scope.storages.push(storage) is not doing anything.
If I use this code at the end:
$scope.storages = Storages.query();
return $scope.storages;
Then it works. But I don't want to have to keep getting all the data from the server each time. How can I solve this and why isn't this working?
If I console.log(JSON.stringify($scope.storages)) it looks like this:
[{"_id":"XXXXXXXXXXXXXXX","_title":"XXXXXXXXX","__v":0,"files":[],"comments":[],"fields":[{"0":{"Title":"XXXXXXXXXXXXX"},"1":{"Category":"coding"},"2":{"Details":"XXXXXXXXX
Thanks in advance!
Read the comments below
storage.$save(function(){
//storage is promise here, not a Storage object you expect
$scope.storages.push(storage);
//because console.log binds to reference
//so after promise resolves, value on reference is changed
console.log("new storage", storage); //shows up fine
//not required at all
return $scope.storages;
});
Simple solution
//bind this to $scope
var storage = new Storages($scope.newStorage);
//like this
$scope.storage = new Storages($scope.newStorage);
//so Resource.$save is promise, when it completes, it will trigger digest
//and your array will be populated
$scope.storage.$save();
$scope.newStorage = [];
$scope.showHide.addItemPanel = false;
$scope.storages.push($scope.storage);
Better solution
//$save accepts callback
storage.$save(function(newStorageFromServer){
//newStorageFromServer is not promise, but actual result from server
//which is Storage instance by default
$scope.storages.push(newStorageFromServer);
//other staff
$scope.newStorage = []; // clear textbox
$scope.showHide.addItemPanel = false; // hides the newStorage form
//return statement is useless
});

How to set a DropDown List value taken from a promise with a value taken from another promise

I want to understand how to successfully set a value inside a DropDown List after it has been populated by a promise. The value to be set will be taken from another promise which will fill a form with a JSON structure.
In my particular case the incidence is as follows
The Drop-Down List is built as :
1) HTML Template (Jade)
select(name="inputUserRelationship",
ng-model="myForm.relationship",
ng-options="relationshipOption as relationshipOption.value for relationshipOption in relationships track by relationshipOption.id",
ng-init="myForm.insuredRelationship = relationships[0]")
option(value="") -- SELECT --
2) Controller:
$scope.getRelationTypes = function(){
HttpSrv.get('api/getRelationTypes/').then(function(results){
$scope.relationships = results;
}); };
The form gets filled in the Controller as follows:
$scope.getFormInformation = function(ID){
HttpSrv.get('/api/getFormInfo/' + ID).then(function(results){
if(results)
{
$scope.fillForm(results);
}
}); };
$scope.fillForm = function(filledFormData){
$scope.myForm.relationship = filledFormData.relationnshipID; };
This produces the following issues on my JS Debugging Console:
The value gets set on the model
The Drop-Down List stays on the default empty value ([0]).
When I try to change the selected option on my Drop-Down list it then produces the following JS Console Error.
TypeError: Cannot assign to read only property 'id' of 9
at setter (vendor.js:42989)
at Lexer.readIdent.token.fn.extend.assign (vendor.js:42424)
at validationErrorKey.$setViewValue (vendor.js:49629)
at vendor.js:53523
at Scope.promises.$get.Scope.$eval (vendor.js:44729)
at Scope.promises.$get.Scope.$apply (vendor.js:44827)
at HTMLSelectElement. (vendor.js:53465)
at HTMLSelectElement.jQuery.event.dispatch (vendor.js:4641)
at HTMLSelectElement.jQuery.event.add.elemData.handle (vendor.js:4309)
Any information is greatly appreciated. I have already researched & tested the $scope.apply() and $q options and neither have been successful to me even though I know they point to the right direction.
Cheers!
if your $http API call returns an json in the format:
[{"id":"1", "value":"Dropdown desc"}, {...}, {}]
You should set an object literal with the same structure to set the dropdownlist to a specific values like:
$scope.myForm.relationship = {"id":"1", "value":"Dropdown desc"};

Updating the Model and then Getting a Document Element

I'm trying to do the following:
vm.request.StatusDescription = 'In Progress';
var contents = $document[0].getElementById(elementId).innerHTML;
The contents I get from the DOM, within the requested element, contain a binding for {{ vm.request.StatusDescription }}, but when I use the HTML contents in a new window (intended for printing a subset of the screen), the status description hasn't updated. It still reads 'New'.
Is there any simple way to deal with this? I've looked at using $scope.$apply(); with no success.
Here is an example on how to update an value in AngularJS. You can then read from $scopeStatusDescription where you need the current status in your controller or in your view.
$scope.StatusDescription = 'New';
$timeout(function() {
$scope.StatusDescription = 'In Progress';
}, 1000);
http://jsfiddle.net/NBhn4/101/

Angular use $cacheFactory data in controller

I hava a ProdCache service used to cache a products array.
...
var products = ProdCache.get('all');
if(typeof products == 'undefined'){
products = Product.query();
ProdCache.put('all',products);
}
If I put products on the scope the products are shown as expected, but I need only a few products to be shown.
My try:
$scope.related = (function(){
var res = [];
if(products.length>0){
for (var i = 0, key; i < 3; i++) {
key = Math.floor(Math.random() * products.length);
res.push(products[key]);
}
}
return res;
})();
That function wont work the first time because the xhr request is being processed and the returned data is not reactive.
The proper way is to use filters docs here and here.
Assuming the filter you wrote is a mock, and you need a complex filter, you just have to create a filter function at $scope and reference it at ng-repeat expression:
$scope.isRelated = function isRelatedFilter(item) {
// if return is true, the item is included.
return item.someProperty === 'someCriteria';
}
<span ng-repeat="product in products | filter:isRelated">...</span>
The someCriteria could be another $scope/controller property or defined by a service. If you need custom parameters, than you can't use the filter filter and should create your own. Take a look at the docs.
Without the HTML code you are using for binding, it's a little difficult to determine how you are using your $scope.related function. However, if the only problem you are having is that, when the data comes back from the xhr request, the UI does not update, it is likely that your $digest phase has already ended and therefore the binding to $scope.related is not causing the function to re-fire until the next $digest phase.
This would probably work to handle the issue you are having:
...
var products = ProdCache.get('all');
if(typeof products == 'undefined'){
products = Product.query();
ProdCache.put('all',products);
// Add this line to force re-digest if needed
if(!$scope.$$phase) $scope.$digest();
}
Without seeing the HTML though, I'm not sure that this will solve your issue. If not, post a plunker so we can see the problem better.

Resources