Looking at a RESTful CRUD SPA with angularjs for example.
When using the RESTful approach with Angularjs, I am running into cases where an update/delete/etc. isn't reflected in the list without a hard refresh (F5). It's occurring because the save/update/delete are taking longer than JavaScript takes to run the next command which should update the list.
app.controller('UserDetailCtrl', ['$scope', '$routeParams', 'UserFactory', '$location',
function ($scope, $routeParams, UserFactory, $location) {
// callback for ng-click 'updateUser':
$scope.updateUser = function () {
UserFactory.update($scope.user); // Before this is done
$location.path('/user-list'); // this has already fetched the (outdated) list
};
Coming from e.g. .NET MVC where in a situation like this I would return the List of items from the update (return value). The scaffolded Web API controller is RESTful, and it doesn't return anything from POST, PUT, or DELETE.
// DELETE api/<controller>/5
public **void** Delete(int id)
{
// delete...
}
In Angularjs I could imagine a couple approaches to ensure that the List of users is always up to date. (For example maintain a list in the $scope and modify it simultaneously with the POST/PUT/DELETE calls but it seems cumbersome)
What is the best approach in Angularjs using RESTful style to ensure e.g. a list has the accurate up to date data? If there isn't a general approach, what would be the best way to handle it in this example app.
You could wait for the AJAX request to complete before changing the path:
UserFactory.update($scope.user, function() {
$location.path('/user-list');
});
In a scenario like this I have used the $http or $resource services.
https://docs.angularjs.org/api/ng/service/$http
https://docs.angularjs.org/api/ngResource/service/$resource
Using $http you would redirect in the success callback
Using $resource you can redirect inside the promise .$promise.then
Related
Laravel 5 uses put/patch verbs to update a resource while Angular ng.resource uses post by default for both create and update. How to globally set Laravel's Route::resource to follow the Angular behavior (a Laravel route for each ng resource)?
(It's also possible to make Angular compatible to Laravel, but I am not sure which approach is better.)
I don't know laravel's REST capabilities. But still i would suggest to modify Angular's behaviour.
PUT
Implementing PUT its quite easy.
You can modify behaviour of ng-resource while you are creating factory with $resource(url, parameters, actions), third parameter describes custom actions ...
In https://docs.angularjs.org/api/ngResource/service/$resource there is example about creating PUT method which will be available as update on service and $update on instance. :
app.factory('Notes', function($resource) {
return $resource('/notes/:id', null,
{
'update': { method:'PUT' }
});
}]);
// In our controller we get the ID from the URL using ngRoute and $routeParams
// We pass in $routeParams and our Notes factory along with $scope
app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
function($scope, $routeParams, Notes) {
// First get a note object from the factory
var note = Notes.get({ id:$routeParams.id });
$id = note.id;
// Now call update passing in the ID first then the object you are updating
Notes.update({ id:$id }, note);
// This will PUT /notes/ID with the note object in the request payload
});
PATCH
Creating PATCH behaviour is theoretically also possible, it is described here - Partial Updates (aka PATCH) using a $resource based service?
But I wouldn't do that with ng-resource. You can do many things by defining transformRequest or transformResponse function (i use this for utilizing Java Spring REST API). But still ng-resource doesn't support PATCH on its own so If you need I'd rather try different layer for REST.
That's wrong, you can use PUT/PATCH with angularjs too, please read about it at $http reference page
Shortcut methods
Shortcut methods are also available. All shortcut methods require passing in the URL, and request data must be passed in for POST/PUT requests.
$http.get('/someUrl').then(successCallback);
$http.post('/someUrl', data).then(successCallback);
Complete list of shortcut methods:
$http.get
$http.head
$http.post
$http.put
$http.delete
$http.jsonp
$http.patch
This may be a very basic question but I hope the gurus here at Stackoverflow will be able to provide a comprehensive and educating answer.
When I press the back button in my angular app, are the controllers fetch data from the backend again? And is that possible to avoid that, and just load what was in the page previously, including various states it had such as ordering of rows in a table?
Thanks
When you change routes, your attached controller functions will rerun. Inside your controllers, or services, whatever is fetching the data, you can save contents to a parent scope, such as $rootScope or you can save to the browser session storage, and check to see if either of those things have been populated before fetching data.
function controller ($scope, $rootScope, $http) {
if (! $rootScope.savedData) {
$http.get('data').success(function (data) {
$rootScope.savedData = data;
$scope.data = data;
});
}
else $scope.data = $rootScope.savedData;
}
I am building a single page app using angular and .net backend.
Aside from the individual restful resources that the controllers call, I also have a System Resource which provides the app with system wide information that is used/needed by most of my controllers.
I call the system service and store the data in the rootScope when the user first logs on.
However if for some reason the user refreshes the page which will lose the rootScope then the system data is lost and no longer available to the controller.
I have managed to get the System Service to trigger if a page reload happens BUT the problem is that in many cases the services running from the controller happen BEFORE the system service call which means that the data is not always available when the controller runs.
Whats the correct way to handle this in angular? Is there a way to make my controllers dependent on a certain rootScope state which if necessary will cause my system service API to be called before the controller makes its own service calls?
Thanks
One approach could be creating a factory/service which is injected to and called by every controller needing this information. This way you don't have to mess with $rootScope.
The request to get information should be cached so that you don't have to fire off a get everytime you switch controller. If you want to persist the information even after a page refresh you could use localstorage to store your data.
Example factory:
angular.module('appName')
.factory('Settings', ['$http', function ($http) {
function getData() {
var url = 'url/to/endpoint';
return $http.get(url, {cache: true});
}
return {
promise: getData()
};
}]);
By returning a promise from the factory we ensure that the getData() call is only run once. While in this particular instance it makes almost no difference (since it is returning an inner promise already), it is a pattern to follow for best practice.
This pattern also means that getData() is called on first use regardless of if the consuming controller accesses the promise. This allows for data to be exposed easily (data binding) without the need to use the promise in some use cases.
Used in controller:
angular.module('appName')
.controller('VeryNiceCtrl', ['$scope','Settings', function ($scope, Settings) {
Settings.promise.success(function(response){
var appSettings = response;
// do stuff
})
}])
As my app initializes, the call to the api happens:
.run(function($ionicPlatform, $http, $localstorage, $model) {
$http.get($model.apiurl).success(function(data) {
$localstorage.setObject('data', data);
// reload template here!
});
})
When the api call has succeeded and the localstorage object is set, I want to reload my template (tab-categories.html) so the data can be displayed. How do I do this, ngRoute, stateProvider, ... ?
You might be missing the point of angular if you ask this question. If your template has values which are bound to a model, then changing those values will automatically update the view on the next digest. It is possible that your asynchronous code (the request) does not trigger a digest, in which case you will have to do it manually. There are many ways to do that: digest and apply
One simple way is to inject $timeout, and do a zero duration timeout (no time argument) with the sensitive code in the body of the function you pass in
Edit: so to answer your question more directly, you should be storing your data somewhere in your application when the call succeeds, and then rely on the angularjs digest loop to update your view. That's one of angulars big work saving features.
Use $route.reload(); method to reload entire page after your successful Transaction, be sure to add dependency injection '$route' in your Controller.
I am facing a "problem" with AngularJS, services and scope.
It is not really a problem (I found a couple of ways to make it work), but I would like to know if I am doing the right thing, or if what I am doing can lead to problems in the future
I have a service that holds some global data; the service has two methods:
getData()
refreshData()
refreshData triggers some work (rest invocation etc.) and it is called at precise points inside different controllers, in response to user actions (button clicks etc).
getData is (obviously) called to get the data back.
In the controllers, how should I use it to access the data and put it in scope, so that it can be accessed from the view(s)?
Alternative 1:
controller('MyController', function ($scope, myService) {
$scope.data = myService.getData();
//use data in functions and in the view, ex: ng-hide="data.forbidden"
Alternative 2:
controller('MyController', function ($scope, myService) {
$scope.data = function() { return myService.getData(); }
//use data() in functions and in the view, ex: ng-hide="data().forbidden"
Alternative 3:
controller('MyController', function ($scope, myService) {
$scope.forbidden = function() { return myService.getData().forbidden; }
//... one function for each data.member used in this view
//usage in the view: ng-hide="forbidden()"
Alternative 4: use $apply or $watch
I am currently using the second approach, as it works even when a new controller is not created (think about different partials in the same page, with different controllers).
Does it make any sense? Or is there a better approach?
It depends on the usage. The Alternative 1 or 3 maybe used when you want to populate the data when the page is loaded or when the controller is initialized. The Alternative 2 can be used when you want to trigger the data refresh by clicking on a button or some other actions. Alternative 4 can be used when you want data load is driven by data change on other data model. So I think every alternative you posted makes sense in the correct scenario.