Data from UI updating with delay - ng-repeat (ngAnimate issue) - angularjs

I have the following problem and I would need some opinions about the possible causes/solutions of it.
I have a table with its body containing an ng-repeat
<tr data-ng-repeat="product in shoppingCart"> ... </tr>
with a delete button on each line:
<td class="total"><i data-ng-click="removeProduct(product, $index)" class="icon-remove-circle"> </i></td>
and the function:
removeProduct: function (removedProduct, index) {
var _this = this;
_this.$scope.shoppingCart.splice(index, 1);
// + DELETE API Request
},
The problem is that even if I do not wait for the API request response and the data in the model is updating immediately (the length and content of the shoppingCart object), the changes appear in the UI with a significant delay and not instantly as it should be.
EDIT: I just figured out that by removing angular-animate (ngAnimate) from the project, the problem is solved. The thing is that I am using angular-animate in the project and I cannot remove it.

What you're doing, if I'm not wrong, is deleting an item from your shoppingCart list without even checking the response from your delete API. Possibilities are the API could even fail and the item gets deleted despite that. Perform the deletion based on the API response, maybe something similar to -
removeProduct: function (removedProduct, index) {
var _this = this;
$http.post('DELETE API').then(function(success) {
_this.$scope.shoppingCart.splice(index, 1);
}, function(failure) {
console.log("Error in deleting", failure)
});
},
After you get the response from your delete API, depending on the response you could either update the cart(delete item from cart) or throw an error.

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);
});

How to know when angular-busy's cg-busy is actively displaying (or how do I enforce a minDuration for a promise chain)?

I'm using angular-busy on an old angular 1.5.9 codebase (yeah, yeah, we're migrating soon, ...)
I am using it to do a "Check for updates ..." overlay and I want the busy message to show for a bit, so I set delay=0, minDuration=1500 just so it doesn't look like it flickers. The updates generally take < 200ms.
As part of the updates the content of what's in my $uibModal dialog updates and I'm using ng-show to hide it until my call has returned. That part works great. But because I'm using cg-busy's minDuration, the content shows before cg-busy's overlay has left - which I don't want.
cg-busy know's when it's active and not. Internally it uses a child scope for it's tracker class which has a method called $cgBusyIsActive() that it uses for it's own ng-show. It would great if I had access to that, but after researching a bit, alas, I can not find a way to get to it (and probably shouldn't even it I could).
Question 1: Does anyone know of a way to detect when cg-busy is active and use that to ng-show or ng-hide?
Here's a quick snippet showing what I would like:
<div cg-busy="$ctrl.updatesBusy">
<div ng-show="!$cgBusyIsActive() && $ctrl.updateResult.done">
...
</div>
</div>
Question 2: Or are there better/different ways of accomplishing my workaround below of forcing the promise chain to have a minDuration?
To get around this currently, I just create my own timer and shove a timeout in my promise chain that fires if there's any time left in minDuration, but I'd love a solution that uses cg-busy's minDuration.
const cgTimerStarted = Date.now();
$ctrl.updatesBusy.promise = myUpdateCallAsync()
.then(function(result) {
// blah blah, blah blah
return $q.resolve(); // return a promise obj or cg-busy will go away
})
.catch(function(err) {
// boo hoo, boo hoo
return $q.resolve(); // return a promise obj or cg-busy will go away
})
.then(function() {
const minDuration = $ctrl.updatesBusy.minDuration;
const timeTaken = Date.now() - cgTimerStarted;
const timeLeft = Math.max(minDuration - timeTaken, 0);
return $timeout(timeLeft);
})
.finally(function() {
$ctrl.updatesResult.done = true;
});

ng-repeat not reordering when change is made to underlying array

I'm using ui.sortable to display a reorderable list of items. I get my data like this:
context.rules.getAll()
.then(
function (data) { // success
$scope.rules = data;
cachedRules = data.slice();
$scope.loaded = true;
},
function (response) { // failure
console.log(response);
});
I use cachedRules so I can compare the array that is being reordered to the original and detect if a change has been made or not. My view looks like this:
<tbody ui-sortable="sortableOptions" ng-model="rules">
<tr ng-repeat="rule in rules|orderBy:'RuleSequence'" ng-class="{'unsortable': !reorder, 'inactive': !rule.Active}">
<td><i ng-show="reorder" class="fa fa-reorder"></i></td>
<td>{{rule.RuleSequence}}</td>
<td>{{rule.ProxyType}}</td>
<td>{{rule.ProxyDesc}}</td>
<td>
<i class="fa fa-download" title="Download CSV" ng-click="getAssignments(rule.RuleID)"></i>
<i class="fa fa-gears" title="Edit Rule" ng-click="editRuleShow(rule)"></i>
</td>
</tr>
</tbody>
Whenever something is reordered, this code gets called so that RuleSequence (what I'm using in my OrderBy) is updated:
$scope.rules.map(function (r) {
r.RuleSequence = $scope.rules.indexOf(r) + 1;
return r;
});
And then a "Save" button becomes enabled if the order of $scope.rules is different from cachedRules. This all works perfectly.
However, I want to have a "Cancel" button that when clicked will revert the display on the page to the original order. Given that I store a copy of the original data this should be easy, I use ng-click to do $scope.rules = cachedRules.slice();, however the order isn't updated on the page after I do that, is stays in it's changed state even though $scope.rules is back to it's unchanged state. How can I get the display to revert back to its original order?
After looking further, it looks like .slice() doesn't do a deep-copy like I thought it did (my in-experience with javascript shining through). So when I did my initial get of the data, and set cachedData, the array itself wasn't a reference but the objects inside of the array still were, so when I updated them here
$scope.rules.map(function (r) {
r.RuleSequence = $scope.rules.indexOf(r) + 1;
return r;
});
It was updating both, so cachedRules would have the updated RuleSequence. When I clicked "Cancel" and set it back to cachedRules, RuleSequence would remain the same. So when I do a proper deep copy there with loDash
context.rules.getAll()
.then(
function (data) { // success
$scope.rules = data;
cachedRules = _.cloneDeep($scope.rules);
$scope.loaded = true;
},
function (response) { // failure
console.log(response);
});
Everything went off without a hitch.

How to append a new value to an item within an array in Firebase?

Within Firebase, I have a list of 'ideas.' If a user presses a button associated with the idea, I'd like a value to be appended to that idea under an attribute called 'newValue.'
For example, the below html, uses ng-repeat to show the array of ideas and creates an associated button called 'Append Value.' I want a new value to be appended to the idea's attribute called 'newValue' every time a user presses 'Append Value.'
<body ng-controller="ctrl">
<table>
<tr class="item" ng-repeat="(id,item) in ideas">
<td>{{item.idea}}</td>
<td><input ng-model="newValue"></td>
<td><button ng-click="ValueAppend(id,newValue)">Append Value</button></td>
</tr>
</table>
</body>
Below is my attempt to create this function.
var app = angular.module("app", ["firebase"]);
app.factory("Ideas", ["$firebase", function($firebase) {
var Ref = new Firebase('https://crowdfluttr.firebaseio.com/');
var childRef = Ref.child('ideas');
return $firebase(childRef).$asArray();
}]);
app.controller("ctrl", ["$scope","Ideas", function($scope,Ideas) {
$scope.ideas = Ideas;
$scope.idea = "";
$scope.ValueAppend = function (id,newValue) {
var URL = "https://crowdfluttr.firebaseio.com/ideas/" + id + "newValue";
var IdeaRef = new Firebase(URL);
var IdeaData = $firebase(IdeaRef);
$scope.IdeaAttributes = IdeaData.$asArray();
$scope.IdeaAttributes.$add({
newValue: newValue,
timestamp: Date.now()
});
};
}]);
See my codepen for my working example: http://codepen.io/chriscruz/pen/PwZWKG
More Notes:
I understnad that AngularFire provides $add() and $save() to modify this array, but how could I use these methods so that I can add a new 'string' under an item in an array.
I'm not sure if these are your problems, but they are two typoes of mistakes in the code above and the codepen: typos and conceptual.
Typos
You forgot to inject $firebase into the controller, which leads to:
"ReferenceError: $firebase is not defined"
Solution is simply of course:
app.controller("ctrl", ["$scope","Ideas", "$firebase", function($scope,Ideas,$firebase) {
In addition you seem to be missing a slash before newValue, which means that you're trying to create a new idea instead of adding the value to an existing one. Solution is simple again, add a slash before newIdea as in:
var URL = "https://crowdfluttr.firebaseio.com/ideas/" + id + "/newValue";
If you find yourself making this mistake more often, you might be better server by the child function. Although it typically is a bit more code, it lends itself less to this typo of typo. Creating the ref to the newValue node becomes:
var URL = "https://crowdfluttr.firebaseio.com/ideas/";
var IdeaRef = new Firebase(URL).child(id).child("newValue");
Conceptual
With those trivial typos out of the way, we can focus on the real problem: which is easiest to see if you console.log the URL that you generate:
https://crowdfluttr.firebaseio.com/ideas/0/newValue
Yet if you look up the same data in the Firebase forge (by going to https://crowdfluttr.firebaseio.com/ideas/ in your browser), you'll see that the correct URL is:
https://crowdfluttr.firebaseio.com/ideas/-JbSSmv_rJufUKukdZ5c/newValue
That '0' that you're using comes from the id and it is the index of the idea in the AngularJS array. But it is not the key that Firebase uses for this idea. When AngularFire loads your data with $asArray it maps the Firebase keys to Angular indexes. We need to perform the reverse operation to write the new value to the idea: we need to map the array index (in id) back to the Firebase key. For that you can call [$keyAt(id)][1]. Since you keep the array of ideas in Ideas, it is simply:
var URL = "https://crowdfluttr.firebaseio.com/ideas/";
var IdeaRef = new Firebase(URL).child(Ideas.$keyAt(id)).child("newValue");
So the controller now becomes:
app.controller("ctrl", ["$scope","Ideas", function($scope,Ideas) {
$scope.ideas = Ideas;
$scope.idea = "";
$scope.ValueAppend = function (id,newValue) {
var URL = "https://crowdfluttr.firebaseio.com/ideas/";
var IdeaRef = new Firebase(URL).child(Ideas.$keyAt(id)).child("newValue");
var IdeaData = $firebase(IdeaRef);
$scope.IdeaAttributes = IdeaData.$asArray();
$scope.IdeaAttributes.$add({
newValue: newValue,
timestamp: Date.now()
});
};
}]);
I quickly gave it a spin in your codepen and this seems to work.

angular-ui-select2 and breezejs: load ajax list after typing in 2 characters

I have a project where I'm using BreezeJS to fetch data from my webserver. I'm using AngularJS with the ui-select2 module. Currently, I have it where when I load my page, breezejs makes a call to fetch the data that I dump into a scope variable. From there, select2 can easily make the reference to it and build accordingly.
If I want to ajaxify things, it gets really tricky. I want to have the ability to use select2's ajax or query support, but instead of using it to fetch data, I want to use breezejs to do it. So during a page load, nothing is loaded up until I start typing in X minimum characters before it makes an ajax fetch.
Constraints:
I do not want fetch data using select2's "ajax". I want BreezeJS to handle the service calls. When I use ajax, it makes an ajax call everytime I press a character in order to filter the results (and resemble autocomplete). I just want the list to load up once and use the native filtering after that.
Here is what I have so far:
breezejs - StateContext.JS
m.app.factory('StateContext', ['$http', function ($http) {
configureBreeze();
var dataService = new breeze.DataService({
serviceName: "/Map/api",
hasServerMetadata: false
});
var manager = new breeze.EntityManager({ dataService: dataService});
var datacontext = {
getAllStates: getAllStates
};
return datacontext;
function getAllStates() {
var query = breeze.EntityQuery
.from("States");
return manager.executeQuery(query);
}
function configureBreeze() {
breeze.config.initializeAdapterInstances({ dataService: "webApi" });
}
}]);
This works and returns my json object correctly.
Here is how I call the service:
m.app.controller('LocationCtrl', ['$scope', 'StateContext', function ($scope, StateContext) {
$scope.getAllStates = function () {
StateContext.getAllStates().then(stateQuerySucceeded).fail(queryFailed);
}
$scope.getAllStates();
$scope.states = [];
function stateQuerySucceeded(data) {
data.results.forEach(function (item) {
$scope.states.push(item);
});
$scope.$apply();
console.log("Fetched States");
}
function queryFailed(error) {
console.log("Query failed");
}
$scope.select2StateOptions = {
placeholder: "Choose a State",
allowClear: true,
minimumInputLength: 2
};
}
and here is my html:
<div ng-app="m" id="ng-app">
...
...
<select ui-select2="select2StateOptions" ng-model="LocationModel.State">
<option value=""></option>
<option ng-repeat="state in states" value="{{state.id}}">{{state.name}}</option>
</select>
</div>
Currently the html select2 control loads up when the page loads. But I want to have it so when I type in more than 2 characters, I'll be able to make the call to $scope.getAllStates(); as an ajax call. BreezeJS already uses ajax natively when configuring the BreezeAdapter for webapi.
I was thinking about using select2's ajax, or query calls.. but I'd rather use breeze to fetch the data, since it makes querying extendable, and I don't want to violate my design pattern, or make the code harder to maintain, and I don't want the ajax calls to be made everytime I enter a new character into the textbox, I just want it to occur once.
Close attempt:
changed my html to:
<!-- Select2 needs to make this type="hidden" to use query or ajax, then it applies the UI skin afterwards -->
<input type="hidden" ui-select2="select2StateOptions" ng-model="LocationModel.State" /><br />
in my controller, changing select2StateOptions:
$scope.select2StateOptions = {
placeholder: "Choose a State",
allowClear: true,
minimumInputLength: 2,
query: function (query) {
debugger;
var data = StateContext.getAllStates().then(stateQuerySucceeded).fail(queryFailed);
}
};
Here's the problem. BreezeJS uses a Q library, which makes use of a thing called a "promise"; which is a promise that data will be returned after making the ajax call. The problem with this, the query function is expecting data to be populated, but the promise to call the "stateQuerySucceeded" function is made after returning from the query function.
So it hits the query function first. Then hits getAllStates(). Returns from the query (nothing is populated), then "stateQuerySucceeded" is called after that.
In otherwords, even though I have been able to fetch data, this is done too late.. select2's query function did not receive the data at the right time, and my html select is hanging on "Searching ... " with a search spinner.gif.
I don't really know this angular-ui-select2 control. I think the relevant part of the documentation is this example:
$("#e5").select2({
minimumInputLength: 2,
query: function (query) {
var data = {results: []}, i, j, s;
// simulate getting data from the server
for (i = 1; i < 5; i++) {
s = "";
for (j = 0; j < i; j++) {s = s + query.term;}
data.results.push({id: query.term + i, text: s});
}
query.callback(data);
}
});
I will leave aside the fact that you don't seem to be interested in using the two-or-more characters that the user enters in your query (maybe you just left that out). I'll proceed with what seems to me to be nonsense, namely, to fetch all states after the user types any two letters.
What I think you're missing is the role of the query.callback which is to tell "angular-ui-select2" when the data have arrived. I'm guessing you want to call query.callback in your success function.
$scope.select2StateOptions = {
placeholder: "Choose a State",
allowClear: true,
minimumInputLength: 2,
query: function (query) {
StateContext.getAllStates()
.then(querySucceeded).catch(queryFailed);
function querySucceeded(response) {
// give the {results:data-array} to the query callback
query.callback(response);
}
function queryFailed(error) {
// I don't know what you're supposed to do.
// maybe return nothing in the query callback?
// Tell the user SOMETHING and then
query.callback({results:[]});
}
}
};
As I said, I'm just guessing based on a quick reading of the documentation. Consider this answer a "hint" and please don't expect me to follow through and make this actually work.

Resources