Back button behavior in AngularJS apps - angularjs

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

Related

How to pass data stored in controller from one page to another

I'm currently using one controller for my web app. Data is loaded from a JSON file and shown to the user via ng-repeat. When a user makes a selection, only data for the user's selection is in the scope.
I'd like to be able to keep the same scope data and use it across different web pages (or states using UI-Router).
I've looked into using ui-router but it seems like the controller would be refreshed with every state change.
I'd prefer to use ui-router due to design requirements.
Part of my controller code:
(function() {
"use strict";
angular
.module("parkit", ["ngMap"])
.controller("parkitController", function($scope, $rootScope, $http,
parkitFactory, NgMap) {
parkitFactory.getSpots().then(function(spots) {
$scope.spots = spots.data;
});
$scope.showSpot = function(spot) {
$scope.spot = spot;
}
});
})();
Factory code for loading JSON data:
(function() {
"use strict";
angular.module("parkit")
.factory("parkitFactory", function($http) {
function getSpots() {
return $http.get('/data/spots.json');
}
return {
getSpots: getSpots
}
});
})();
As it has been answered before, you can use a factory or service to keep track of the selected item(s). This would store the selected values in the instance of the service/factory and therefore would be lost if someone refreshes the page.
A more resilient, and in my opinion beautiful solution, would be to add the selected item(s) as state parameter in ui-router. Using this method, you will also be able to deep-link to certain selected states and if someone refreshes the page, the same items would still be selected, as you would add your state parameters in the url.
See URL Parameters in the documentation: https://github.com/angular-ui/ui-router/wiki/URL-Routing
You may probably create a new property in the factory function to keep track of the selected item.Set the property when user does a selection. Get the property in other components whereever you need to use the data.
use $rootScope instead of $scope to save your data, That will allow you to use it anywhere in controllers of same domain.
example: $rootScope.yourData = yourData; and then you can assign $rootScope.yourData to any controller in same domain.

Angularjs JavaScript to fast when using PUT in a CRUD SPA

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

Is it okay to use the scope of one controller from another using a service?

I was told if you need to share between controllers you should use a service. I have controller A, which is a list of news websites, and controller B which is a list of articles on the sites from controller A. Controller B contains the list of articles and an iframe to display the articles. But when you click on controller A it should fade out the iframe and fade in the list. In order to accomplish this I give Controller B's scope to a service that is injected into both controller A and controller B. My question is whether or not it's okay to do that.
Basically, I do this:
app.factory("sharedService", function () {
var $scope = null;
return {
SetScope: function (scope) {
$scope = scope;
},
ControllerB_Action: function () {
$scope.doSomething();
}
};
});
app.controller("controllerA", ["$scope", "sharedService", function ($scope, sharedService) {
$scope.onaction = function () {
sharedService.ControllerB_Action();
}
}]);
app.controller("controllerB", ["$scope", "sharedService", function ($scope, sharedService) {
sharedService.SetScope($scope);
}]);
I would say its not a good pattern, since basically, the $scope is an Object that represents your current view or DOM-State. A controllers (and/or Link-Functions of directives) are the Glue between this state and your Application-Logic - so in my opinion, the $scope-Object should always remain inside the Controllers/Links.
Therefore if you wanna share Data between 2 Controllers, you should extract what you wanna share inside the Service, but not put the whole scope there (since it has lots of additional information that you dont need and want inside both controllers).
What you can do is simply link the data you wanna share by reference - that way, your Service will also Sync the Data between the two Scopes.
There's probably a world of ways of doing this, but I'll tell you what I would do:
I'd make use of event emmiters.
Disclaimer: I haven't tested this code
Assuming that Controller B is nested in Controller A:
Controller A
var controllerBFunction_A;
$scope.$on('EventFromControllerB',function(data){
controllerBFunction_A = data.sharedFunctions.controllerBFunction_A;
});
Controller B
$scope.$emit('EvenFromControllerB',{
sharedFunctions: [
controllerBFunction_A,
controllerBFunction_B,
someOtherObject
]
});
I think this could work. In my opinion this has the benefit of selecting what you want to share between those controllers...but probably there's a more elegant way of doing this.

Update variable in other controller

I have a page with a form in a modal. When the form in the modal is ready, I need to close the modal and display a message on the page. I would like the page and the modal to be separate controllers because they have different responsibilities. There are two methods I have found to notify the page that the form is ready:
Create a service which both controllers get injected and call methods on that
Make the modal controller a child of the page controller and let them share an object, like here: http://fdietz.github.io/recipes-with-angular-js/controllers/sharing-models-between-nested-controllers.html
The Angular documentation on scopes seem to say that controllers should not share variables but use services, but is that really the best way to go in this case?
Use services to share information about controllers, but instead of injecting the controllers to your service, inject the service to the controllers. Also, if you want to remain the binding between your view and your data, you need to use objects instead of primitive variables.
angular.module("MyApp", [])
.factory("Data", function() {
return { msg: "Shareable/Bindable data" }
})
.controller("One", function($scope, Data){
$scope.foo = Data;
})
.controller("Two", function($scope, Data){
$scope.bar = Data;
})
In this example, I could had just returned the data directly, instead of wrapping it in an object on my service. However, if we had done that (return "..." and $scope.foo = Data) , the variables {{foo}} or {{bar}} would only have a "shadow" copy of the factory information. Thus, we need to use {{foo.msg}} in our view and the message wrapping.
The full example is here in Codepen.io. Remove the { msg } and return the string instead to see what I mean.

Best (idiomatic) way to refresh data from a service in AngularJS

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.

Resources