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.
Related
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>
In my application, in the initialize, i have stated that
vm.allStates = [];
And then i trigger a function using ng-click, and update states based on country.
vm.allStates = result;
console.log(vm.allStates);
i get the desired output by doing so. My output is something like
["selangor"];
However in the HTML blade(as i am using Laravel 5.4), where i write
<option ng-repeat="option in vm.allStates" value="{{ option }}">#{{ option }}</option>
It does not output anything.
Meanwhile, if in the initialization, i stated that,
vm.allStates = ["selangor"];
It does display, however this becomes not dynamic.
I am wondering if this has something to do with ng-model?
As it does not update the value, although, i can console.log the output.
I do not user ng-options, as i needed one selected value as placeholder,(in this case an option which is disabled and selected by default.
Thank you
EDIT 1:
storeService.storeByCountry(vm.countryId).then(function (response){
vm.totalStores = response.length;
if(response[0].country.has_zone){
vm.showZones = true;
vm.allZones = [...new Set(response.map(response => response.zone))];
vm.totalZones = vm.allZones.length;
vm.showFilterZones = true;
}
if(response[0].country.has_province){
vm.showProvinces = true;
vm.allProvinces = [...new Set(response.map(response => response.province))];
vm.totalProvinces = vm.allProvinces.length;
vm.showFilterProvinces = true;
}
if(response[0].country.has_state){
vm.showStates = true;
vm.allStates = [...new Set(response.map(response => response.state))];
vm.totalStates = vm.allStates.length;
vm.showFilterStates = true;
}
angular.forEach(response, function(value, key) {
addMarker({lat : +value.latitude, lng : +value.longitude}, vm.countryFilter);
});
console.log(vm.allStates);
panToCountry(vm.countryFilter);
return;
}, function(error){
APP.func.alerror(error);
});
The issue with your code might be that you are using vm.allState in your html. You should not be doing that (unless vm is indeed the alias name). I believe vm referes to your controller instance (as it usually does). In that case you should be using any variable inside your controller using the controller alias in html.
Explanation:
If your controller has vm.name = "Superman";
This will be available in html as ctrl.name and not as vm.name. You might have named your alias differently and you should use the same alias.
After day of debugging, and thinking, it turns out that this issue is not caused by the angular not updating the array. It does , in fact.
The issue here, is the select style plugin that i used.
I used bootstrap-select for the looks of the select box, it turns out, that the value there is not updated,
and so, when i remove this plugin, i can see the results i wanted.
Of course, there are workaround on this issue, however i decided not to look further on it
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
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
});
Iterating over objects in CoffeScript I want to calculate and display a certain value for every entry (number of assets in pool)
Controller function:
archivedBidParts = []
for day in BidService.activeDays(bid) when DateService.isBetween(day, from, to, true, true)
splitBidPart = angular.copy(bid_part)
splitBidPart.hours = BidService.hoursActiveOnDay(day, bid)
splitBidPart.number_of_assets_in_pool = number_of_assets_in_pool(bid)
archivedBidParts.push(splitBidPart)
$scope.data.splitBidParts = sort(archivedBidParts)
Helper function:
number_of_assets_in_pool = (bid) ->
Pool.query().$promise.then(pool_memberships.bind(null, bid)).then((pool) -> pool.pool_memberships.length)
The view:
<tr ng-repeat="bid_part in data.splitBidParts">
...
<td ng-hide="bid_part.holidayName">{{ bid_part.number_of_assets_in_pool }}</td>
Problem:
The helper function returns a promise. When trying to console.log the return value inside the promise (in the last .then()-statement) the right number gets printed out in the console.
Does someone have an idea how to use the return value to be displayed properly?
Thanks in advance
I think you should change the following line in your controller
splitBidPart.number_of_assets_in_pool = number_of_assets_in_pool(bid)
To something like this:
number_of_assets_in_pool(bid).then(function(retval) {
splitBidPart.number_of_assets_in_pool = retval;
});
For your example you could assign to the splitBidPart object in the promise resolution.
If you need to know when all the promises are resolved you will need to collect them and then resolve them with a call to $q.all() (note this can be expensive to perform if there are lots of promises).
Note in the example below all the instances of splitBidPart are lost once the loop is complete so this is not a working stand alone example.
Note also you need to provide $q via injecting it into the controller.
promises = []
for day in BidService.activeDays(bid) when DateService.isBetween(day, from, to, true, true)
splitBidPart = angular.copy(bid_part)
splitBidPart.hours = BidService.hoursActiveOnDay(day, bid)
number_of_assets_in_pool(bid).then (theAnswer) ->
splitBidPart.number_of_assets_in_pool = theAnswer
$q.all(promises).then ->
console.log "all the promises are resolved"
Note that in some cases Angular can deal with promises intelligently and it will 'just work'. Would like to articulate this last point but can't really without digging into the docs.
Does the sort function preserve the binding? Can you try what happens when leaving out the sort?