Use case: A user creates a new task, which has to be sent upstream through an API. When that API returns success, I add it to the scope for display. I have that working fine with:
$http.post('some_url', newtask).success(function(data) {
$scope.tasks.push(data);
});
(newtask is a simple object defined earlier, not shown here).
The problem is, the API is quite slow (though reliable), whereas I want this to feel like a real-time app. So I'd like to push the new task to the $scope immediately, then replace it with the "real" one when the API returns success. So I prepend the above with:
$scope.tasks.push(newtask); // Add to UI immediately (provisionally)
What happens now is that the new task is added immediately, then a second copy of the task is added when the API returns. So what I'd like to do is remove the first copy as soon as the second copy is added.
I can't seem to find a find a way to do that. Or is my approach all wrong? (I admit, the approach does feel like a bit of a hack, so I'm open to suggestions).
In the success callback function, I suggest you compare the new task (coming back from the API) to the last task in your tasks array. If they match, you don't have to do anything. (If they don't match, you'll need to remove the last item and throw up some kind of error indication.)
$http.post('some_url', newtask).success(function(data) {
if($scope.tasks[$scope.tasks.length-1] === data) {
// do nothing, we already added it before
} else {
... error handling here
}
});
If the user can't add more than one task at a time (before the API returns), and you really want to remove then add, just use normal JavaScript operations on the array to remove the last item, then add the API value:
$http.post('some_url', newtask).success(function(data) {
$scope.tasks.pop();
$scope.tasks.push(data);
});
Angular should notice the model change and automatically update the view for you.
//Push the new task immediately
$scope.tasks.push(newtask);
//When API return check for error if error just pop out the added task
$http.post('some_url', newtask).success(function(data) {
if($scope.tasks[$scope.tasks.length-1] !== data) {
$scope.tasks.pop();
}
});
Related
Due to low query result, in my AngularJS application, when I click a search button to request a new search, my page will display my pre-define error message "No related search result as you want", after one/two seconds, my query finish, my page refresh to display my new search result.
I want to keep my displayed result before new search result display?
Any suggestion or hint to do that? Thanks.
This comes down to where you put the code to clear or replace the list of results.
It seems like you currently do something like this in your controller:
runSearch(searchText) {
this.results = []; // this shows your "no results" text
this.$http.get('/search?q=' + searchText).then(res => this.results = res.data);
}
Instead, just remove the first line so you only ever replace the results once the HTTP call returns:
runSearch(searchText) {
this.$http.get('/search?q=' + searchText).then(res => this.results = res.data);
}
Explanation
There are two points inside runSearch where Angular will update the page for you:
After the function is run, but before the HTTP call returns
After the HTTP call returns
Here they are in the code:
runSearch(searchText) {
// #1 - any code here will update the page immediately
this.$http.get('/search?q=' + searchText)
.then(function (res) {
// #2 - any code here will update the page when the HTTP call returns
});
// (#1 - if you have any code here it will also update the page immediately)
}
The technical reason for this is that Angular calls your runSearch() function inside a $scope.$apply() call, which calls your function and then runs a full digest of all the scopes in the application. That's what allows Angular to see the change and update the page. However, the function you pass to .then() on the $http promise executes some time later, after the initial digest completes. But Angular will also call this other function (the one passed to .then()) inside $scope.$apply(), which triggers another digest and allows Angular to see and apply any changes to the page at that time.
If you think about what you wanted to achieve, you only wanted to update the page after the search results are returned (#2 in the code above). So by only putting your code there, you got the page to update at the correct time, not before.
I working on this angular-meteor tutorial step 12
an I have a question in
Stopping a subscription Topic
you can use ctrl+f using "meteorSubscribe"
then the key sentence on that topic is
The reason is that we are calling a different subscription on the same collection inside the partyDetails controller.
the code before correction is
$scope.party = $meteor.object(Parties, $stateParams.partyId).subscribe('parties');
$scope.users = $meteor.collection(Meteor.users, false).subscribe('users');
then after correction
$scope.party = $meteor.object(Parties, $stateParams.partyId);
$scope.users = $meteor.collection(Meteor.users, false).subscribe('users');
$scope.$meteorSubscribe('parties');
I try to run before correction code and nothing(error) show in cmd but it just cause the wrong result as tutorial say
if you navigate to the party details page and then go back, pagination and search will stop working.
Then i got two question
Why no error show on cmd?
Why error from partyDetails controller affect to partiesList controller search and pagination? What is their relation?
EDIT: If you don't cancel a subscription, then if you navigate away and back again you will end up trying to subscribe twice to the same publication, resulting in the error, because subscriptions in meteor last until you end them.
There are two ways to get rid of a subscription with angular-meteor. One you can assign a handle variable to the subscription and then on navigating away from the page you can stop it. Two (the recommended way) is to use $scope.$meteorSubscribe instead of $meteor.subscribe() because it is set up to automatically remove the subscription when the scope is destroyed.
I can't see all of your code to know for sure why you are or are not getting the errors you think you should, hopefully this sheds some light on what is going on in the tutorial.
The very end result would be something like:
$meteor.autorun($scope, function() {
$meteor.subscribe('parties', {
limit: parseInt($scope.perPage),
skip: parseInt(($scope.page - 1) * $scope.perPage),
sort: $scope.sort
}).then(function() {
$scope.partiesCount = $meteor.object(Counts, 'numberOfParties', false);
$scope.parties = $meteor.collcetion(function() {
return Parties.find({}, {
sort: $scope.getReactively('sort');
});
});
});
});
Notice that he's also changing the publish function on the server. It helps to understand if you click the links to show the git diffs.
I am currently making an application in angular which does this:
(On page load) Make an api call in angular controller (to symfony2 end point) to get: items.
$scope.items = ItemsService.query(function(data){
$scope.loading = false;
}, function(err){
$scope.loading = false;
});
items is an array containing many item objects.
Each item contains parameters e.g. item.param1 item.param2.
I have built it in a similar way to this tutorial:
http://www.sitepoint.com/creating-crud-app-minutes-angulars-resource/
i.e. The angular controller calls a service which calls the (symfony2) backend api endpoint.
The endpoint passes back items which is gets from a database. Items are then put into the view using ng-repeat (item in items).
This all works fine.
Now, I have a button (in the ng-repeat) which effectively causes a PUT request to be made to (another symfony2 endpoint), thus updating item.param1in the database. This also happens in the tutorial I linked to.
The problem is that (in my application and in the tutorial) I have to again make an api call which updates ALL the items, in order to see the change.
I want to just update the data in the view (immediately) for one object without having to fetch them all again.
i.e. something like:
$scope.items[4] = Items.get({id: item.id}, function(){});
Except the application array key isn't known so I cant do that.
(so something like: $scope.items.findTheOriginalItem(item) = Items.get({id: item.id}, function(){});.
Another possible solution (which seems like it may be the best?). I have looked here:
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
And tried doing the equality $watch: $scope.$watch(…, …, true);. Thus using watch to see when the item sub-array is updated. This doesn't seem to update the data in the view though (even though it is updating in the database).
Any advice on the best way of doing this (and how to do it) would be great! Thanks!
Essentially the button click should use ng-click to execute a function and pass the item to that function. Example:
...ng-repeat="item in items"...
<button type="button" ng-click="updateItem(item)">Update</button
...
Then in the function you have the exact item that you want to update. If you are using $resources, it would be something like:
$scope.updateItem = function(item) { item.$update(...); };
Unless I didn't understand you
I've been following this tutorial http://draptik.github.io/blog/2013/07/28/restful-crud-with-angularjs/. I implemented a Grails backend with it instead of the Java one in the tutorial.
I've got the data coming back and forth, with one issue. If I create/update/delete a user, I don't see the changes reflected on my user list when I am redirected back. I have to refresh the page to see the updates.
Looking at the network traffic for an edit, it looks like it does a PUT and fires off the GET before the PUT is complete. Assuming this is because $resource returns a promise so things can be done asynchronously. So how do I handle this so that when $location redirects me, my list is up to date?
I'm guessing the options are to wait for the PUT to complete before redirecting/querying for the list, or to somehow manually manage the $scope.users to match the request?
Or maybe this tutorial is just a bad example? Maybe there is a better way to do it (still using $resource)?
Note: I've seen Restangular out there, and I've seen $http with success callbacks, but I would like to understand the situation above.
One way to overcome this issue would be to not redirect to the list page, till you get a callback, and then do a redirect. You can show some busy indicator till that time. The resource call looks like this.
resource.update(config,data,function() { //gets called on success},
function(error) { //gets called on failure});
In real life scenario waiting for the response of update makes sense as you want to handle the error and success scenarios on the same page.
I don't see your code anywhere so i'm just assuming (based on what you wrote and your current problem)
You are probably doing a full (or partial) get each time you changed a user and (re)binding the result to your scope. Doing this in the callback of the resource should actually start the digest cycle angular does to update modified objects. If you had been doing the fetching outside $resource - for example with custom/jquery ajax you would need to execute $scope.$apply()
What i really don't understand you would need to wait for the callback. You already know you added/modified a user. Instead of 'detaching' that user from your scope, modify it, post it to your rest server, then wait for callback, and reinserting it into the scope - why not modify it directly in the list/array you put on your scope?
var users = Users.get(function () {
$scope.users = users.record; // bind the resulting records to the scope
});
$scope.updateUser = function (user) {
resource.update(...); //pseudo
};
Then in your html, you will keep a reference to the currentUser and the div-list will update automaticly.
<div ng-repeat="user in users" ng-click="currentUser=user">{{user.Name}}</div>
<input ng-model="currentUser.Name">
<button ng-click="updateUser(currentUser);">Update</button>
If you don't want to see the update in the list while you type, but only once your callback fires or when you hit the button, would would instead use another ng-model for your input like this:
<input ng-model="tempUser.Name">
And you would then copy the value other in either the updateUser method or in the resource callback like this:
$scope.updateUser = function (user) {
user.Name = $scope.tempUser.Name; // should update automaticly
resource.update(...) // pseudo
}
Hope it helped!
I have a collection that I'm appending to. On my app, I destroy all the models when I log out - with this piece of code :
logout: function(event) {
$.post('./logout');
App.Contacts.each(function (contact){
console.log(contact);
contact.destroy();
});
}
The problem is that not all the models are being deleted. If i press the logout button a couple more time to trigger this function, they eventually end up getting deleted.
The console complains that they are undefined - so it doesn't have a handle to it.
What could possibly be going on?
Why don't you use reset. Calling collection.reset() without passing any models as arguments will empty the entire collection.
Try :
logout: function(event) {
$.post('./logout');
App.Contacts.reset();
}
If you want to destroy all of them in order to fire destroy or remove events (which you won't get from reset), you can try using the invoke method.
For example,
logout: function(event) {
$.post('./logout');
App.Contacts.invoke('destroy');
}
In your code, every time you destroy a model, the length of collection will change, that may casue some problems, so if you want to handle it properly, use:
App.Contacts.reset();
Just as what Niranjan Borawake said.