Best way to pass variables between controllers - javascript

I am using three Angular controllers:
**Controller1**
var fetchStudentDetails = function(){
var sDetails = myService.getList(//url-1 here);
sDetails.then(function (data) {
$scope.studentData = data.list;
var studentId = $scope.studentData[0].id;
});
}
fetchStudentDetails();
$scope.loadSecondLevel = function(){
$state.go('secondLevel');
}
**Controller2**
var fetchClassDetails = function(){
var sDetails = myService.getList(//url-2 here);
sDetails.then(function (data) {
$scope.classData = data.list;
var className = $scope.classData[0].name;
});
}
fetchClassDetails();
$scope.loadThirdLevel = function(){
$state.go('thirdLevel');
}
**Controller3**
$scope.putStudentDetails = function(){
// Here I need studentId,className for updateResource
var sDetails = myService.updateResource(//url-3 here);
sDetails.then(function (data) {
});
}
Where I have to pass studentId (in Controller1), className (in Controller2) into a function which in Controller3. I tried with $rootScope, it is working but when refresh the page $rootScope values become empty. Does anyone know how to do this?

Your question could be split into two aspects:
1. How to share data between controllers
The best practice to share data in Angular 1.x is using factory, store the shared data in a factory service, and expose access methods to controllers:
factory('DetailData', function(myService, $q){
var _details;
function __getDetailData(){
return details
}
function __setDetailData(){
return myService.getList().then(function(data){
_details = data;
})
}
return {
getDetailData: __getDetailData,
setDetailData: __setDetailData
}
})
controller('myContrller', function(DetailData, $scope){
$scope.data = DetailData.getDetailData();
})
2. How to persist data when page refreshed,
you can use localStorage to keep data persistent during page reloading, many tools & libraries can achieve this, for example ngStorage, or you could reset the data from server every time your angular application started:
//this would register work which would be performed
//when app finish loading and ready to start.
angular.module('app').run(function(DetailData){
DetailData.setDetailData();
})

Depending on what problem you are solving.
There are three options:
Is to save data to $rootScope
Is to use $scope.$emit & $scope.$on functions.
Use a custom Service to store the data
And if you need to save data, so it was available after full page reload - localStorage.

Hey this question are responded in Passing data between controllers in Angular JS?
But the simple response is in the services.

Related

How to make html wait for a function in AngularJS controller

I have a object in mainController.js that is set as default as 99.
I am obtaining user location and do running some other function with it to calculate this value.
However, When I load the page, the page seems to load faster than this process. Therefore it displays 99 instead of the calculated value.
If I put console.log after the calculation, the object is successfully changed.
edit1:
status.success( function(data)
{
$scope.current = data;
$scope.$broadcast('back_end_connected');
});
$scope.getLocation = function()
{
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function (position){
$scope.location = {lat: position.coords.latitude, lng: position.coords.longitude};
$scope.$broadcast('location_obtained');
$scope.buildDist();
$scope.fetch();
//$scope.getRec();
});
}
else{
alert("Geolocation is not supported by this browser.");
}
};
var dirBuilt = false;
$scope.$on('location_obtained', function(){
$scope.buildDist = function()
{
if(dirBuilt === false)
{
$scope.facilities[0].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[0].location.lat,$scope.facilities[0].location.lng);
$scope.facilities[1].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[1].location.lat,$scope.facilities[1].location.lng);
$scope.facilities[2].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[2].location.lat,$scope.facilities[2].location.lng);
$scope.facilities[3].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[3].location.lat,$scope.facilities[3].location.lng);
$scope.facilities[4].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[4].location.lat,$scope.facilities[4].location.lng);
$scope.facilities[5].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[5].location.lat,$scope.facilities[5].location.lng);
$scope.$broadcast('dist_obtained');
dirBuilt = true;
alert("aaa: "+ $scope.facilities[0].distance);
}
};
});
that "alert("aaa: "+ $scope.facilities[0].distance);" returns the value I want it to display but it is not displayed on the page....
(ng-bind would not work for some reason...)
How can I make the html wait for the operation? Thanks
You should not make HTML wait until you finish your Js code ! What you should be doing is showing a placeholder value - Loading image so that user know the page is loading some data.
Once you are done with your calculation, hide /replace the loading image with the data you want to show.
Quick example
Your view markup will have some HTML element to show the progress bar.And all your other contents will be in another div
<body ng-app="yourApp" ng-controller="yourCtrl as vm">
<div ng-show="loaderCount>0"> Loading something..</div>
<div ng-show="loaderCount==0">
<h4>{{userScore}}</h4>
</div>
</body>
And in your angular controller, You have a scope variable called loaderCount which you will increase everytime when you are doing some operation (http call/Long running function execution etc..). When you get your result, You decrease this variable value back. In your View You are hiding and showing the Loading Pane based on this value.
var yourApp= angular.module('yourApp', []);
var ctrl = function($scope, $http) {
var vm = this;
vm.loaderCount = 0;
vm.someValue = "";
vm.actionItems = [{ Name: "Test" }, { Name: "Test2" }];
vm.loaderCount++;
$http.get("../Home/GetSlowData").then(function(s) {
vm.loaderCount--;
vm.someValue = s.data;
});
};
yourApp.controller('yourCtrl', ['$scope', '$http', ctrl]);
This might not be the best angular code. But this will give you an idea about how to handle this use case. You should be using services to talk to Http endpoints instead of directly using $http in your angular controller.
Note that, $http.get returns a promise which allows you do things when you get the response from this asynchronous operation (the then event). You should make sure that your time taking calculation is returning a promise.
You can bind with the ngBind directive instead of {{}} and not write to the binded property when the calculation is done.
This will hide the result from the view while it is null.
Try to do something like this:
$scope.value = "Loading";
or
$scope.value = "";
$scope.calculate = function() {
$scope.value = yourcalculation;
}
$scope.calculate();
Or in your case i think if you use $scope.$apply() after you add data to your scope element will do the trick
Try this :
$scope.$on('location_obtained', function(){
$scope.buildDist = function()
{
if(dirBuilt === false)
{
$scope.facilities[0].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[0].location.lat,$scope.facilities[0].location.lng);
$scope.facilities[1].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[1].location.lat,$scope.facilities[1].location.lng);
$scope.facilities[2].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[2].location.lat,$scope.facilities[2].location.lng);
$scope.facilities[3].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[3].location.lat,$scope.facilities[3].location.lng);
$scope.facilities[4].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[4].location.lat,$scope.facilities[4].location.lng);
$scope.facilities[5].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[5].location.lat,$scope.facilities[5].location.lng);
$scope.$broadcast('dist_obtained');
dirBuilt = true;
alert("aaa: "+ $scope.facilities[0].distance);
$scope.$apply(); // This should do the trick
}
};
});
Are you using a promise on the service? The function running your calculation can be in a separate angular factory which has .success and .error events to tap into. If the calculation is a success. You can pass the data back to your controller and then bind that data to the controller scope. I'll be at a computer soon and will add some code to explain further if needed.
This would be your distance calculator, I used a Factory over a service but you can read about why to use one over the other
A couple of things to keep in mind. You geolcation is more of a service then something to control since you're requesting from the user, you could add it to the below factory to expand the factories capabilities and allow you to use getLocation in other controllers.
make sure you add the distance service to your html document
also make sure you put the distance service in your controller and on the main angular app.
distanceService.js
(function(){
'use strict'
var distSrvc= angular.module("distanceService",[])
distSrvc.factory('DistanceCalc',['$http',function($http){
return{
getDistance:function(facilitiesData,locationData){
Object.keys(facilitiesData).length; // or if it's already an array of facilities use facilitiesData.length
// if its a javascript object and distance is already defined
for (var distance in facilitiesData) {
if (facilitiesData.hasOwnProperty(distance)) {
facilitiesData.distance = distCalc(locationData.lat,locationData.lng,facilitieData.location.lat,facilitieData.location.lng);
}
}
// if its an array of objects
for (var i = 0 ; i< facilitiesData.length;i++){
facilitiesData[i].distance = distCalc(locationData.lat,locationData.lng,facilitieData.location.lat,facilitieData.location.lng);
}
return facilitiesData
}
})();
Then in your controller you'll need to load the service for use.
yourcontroller.js this will give errors if you don't load it on the html page and add it to the main angular app.
(function(){
'use strict'
var myController= angular.module("distanceService",[])
myController.controller('myController',['$scope','DistanceCalc',function($http,DistanceCalc){ // see how i added the service and passed it to the function
$scope.getLocation = function(){
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function (position){
$scope.location = {lat: position.coords.latitude, lng: position.coords.longitude};
$scope.$broadcast('location_obtained');
$scope.buildDist();
$scope.fetch();
//$scope.getRec();
});
}
else{
alert("Geolocation is not supported by this browser.");
}
};
// this line below sends the data to the service
DistanceCalc.getDistance($scope.facilities,$scope.location)
.success(function(data){
//success i'm done running distance calculations and now i want to update the facilties object with the added distance
$scope.facilities = data
})
.error(function(data){
// some error occurred and data can tell you about that error
})
}
})();

AngularJS multiples http.get

Hi guys im a newbie in AngularJS I have a problem calling multiples http.get. $scope.countries is getting values from cities. What happend?
How can calling multiple http.get?
$scope.getInfo = function(){
$scope.refreshing=true;
//cities
$http.get(baseUrl+'cities/GET_INFO/ALL').success(function(data) {
$scope.cities = data[0];
$scope.cities.signal = $scope.getSignal(data[0].status);
$scope.refreshing=false;
alert('city');
});
//countries
$http.get(baseUrl+'countries/GET_INFO/ALL').success(function(data) {
$scope.countries = data[0];
$scope.countries.signal = $scope.getSignal(data[0].status);
$scope.refreshing=false;
// alert('countries');
});
}
Also I tried with:
$scope.getInfo2 = function(){
$scope.refreshing=true;
alert ('start');
$scope.urlcities = $http.get(baseUrl+'cities/GET_INFO/ALL');
$scope.urlcountries = $http.get(baseUrl+'cities/GET_INFO/ALL');
$q.all([$scope.urlcities, $scope.urlcountries]).then(function(values) {
alert('finish');
$scope.refreshing=false;
});
}
But this code get an error.. Thanks so much for your help !
Carlos,
You may have a race condition with the AJAX calls. Try chaining them together using promises:
$scope.getInfo = function(){
$scope.refreshing=true;
//cities
$http.get(baseUrl+'cities/GET_INFO/ALL').then(function(data) {
$scope.cities = data[0];
$scope.cities.signal = $scope.getSignal(data[0].status);
$scope.refreshing=false;
alert('city');
return $http.get(baseUrl+'countries/GET_INFO/ALL');
}).then(function(data) {
// countries
$scope.countries = data[0];
$scope.countries.signal = $scope.getSignal(data[0].status);
$scope.refreshing=false;
// alert('countries');
});
};
To learn more, watch the screencast:
https://egghead.io/lessons/angularjs-chained-promises
You can also learn more about promises here:
https://docs.angularjs.org/api/ng/service/$q
NOTE:
It is a best practice to move your data preparation, business logic and calculations out of the controller and into a service. Consider revising your code to encapsulate your AJAX request (using the $http service) into a service and then inject that service into the controller that is being used to present the data to the view.
You need to make Syncronous calls.
And in Angular world it is achieved using $q or promise.
Good article on that http://haroldrv.com/2015/02/understanding-angularjs-q-service-and-promises/
Hope it helps..

AngularJS - Improving service call and data binding performance

I have an Angular service that goes away to retrieve a pretty big JSON file (nearly 10,000 lines).
The problem i am facing, is that it is taking some time to bind the data to the front-end (as expected).
Sample controller:
$scope.dataLoaded = false;
serviceReport.getData( function (data) {
$scope.data1 = data.data1;
$scope.data2 = data.data2;
$scope.data3 = data.data3;
$scope.data4 = data.data4;
$scope.data5 = data.data5;
$scope.data6 = data.data6;
$scope.data7 = data.data7;
$scope.data8 = data.data8;
$scope.data9 = data.data9;
$scope.data10 = data.data10;
$scope.data11 = data.data11;
$scope.data12 = data.data12;
$scope.data13 = data.data13;
$scope.data14 = data.data14;
$scope.data15 = data.data15;
$scope.data16 = data.data16;
$scope.data17 = data.data17;
$scope.dataLoaded = true;
});
Service:
app.factory('serviceReport', function($http) {
return {
getData: function(value,done) {
$http.get('data.json', {
})
.success(function(data) {
done(data);
})
.error(function(error) {
alert('An error occured');
});
}
}
});
I have ng-cloak on my HTML element, when dataLoaded = true, this is removed as it indicates the data is available to be displayed.
How can i improve the service call/data bind? Would splitting the call help?
Server-side solution would be to reduce the size of the response and make more requests with smaller responses. Do you actually need the whole response at start? You have to be aware that binding the whole response will generate many watchers, which will slow down all subsequent digests.
Client-side solution would be to bind the response part by part in a loop as a callback parameter for $scope.$apply() or even $timeout().

Passing data between controllers using service and confusion with using [ '$scope' , 'service' , function($scope, service){}]

I am badly stuck with this problem of passing data from one controller to other in angularJS. Before my code was: whenever I click on templateController's div, it would trigger setTemplate with the help of ng-click... Now my objective was to send templateController's selected data to ReplyController...
After reading forum posts here, i decided to create a service called 'selectionService', so that i can transmit data between 2 controllers...
//Defined Service
proApp.service('selectionService', function() {
var selected_template;
addTemplate = function(newObj) {
selected_template = newObj;
};
getTemplate = function(){
return selected_template;
};
});
//Controller 1... where templates are selected
proApp.controller('TemplateController',function($scope, selectionService)
{
$scope.templates = [
{template_name:"Template 1", template_text:"We apologize for the interruption."} ,
{template_name:"Template 2", template_text:"Thank you for contacting us."} ,
} ,
];
// on ng-click
$scope.setTemplate = function(tmp)
{
selectionService.addTemplate(tmp);
}
});
// Controller 2 ... supposed to catch the selected template.
proApp.controller('ReplyController', function($scope, selectionService)
{
$scope.template = selectionService.getTemplate();
});
So whenever i run this code, i started getting this error
Object [object Object] has no method addTemplate...
Again I tweeked the code where they were suggesting to use Squarebrackets and put $scope , servicename and then write function with same parameters.. I don't understand why I should do this? Event after doing some changes like
[ '$scope' , 'service' , function($scope, service){}] ,
I am still not able to figure out the solution to pass data from one controller to other using service.
Could you help? What am I missing? I am very new to angularJS way of doing stuff.
I think it's actually quite simple. You just need to add your methods to the service by using this.. Currently they are declared on window. Change your service declaration to use this...
proApp.service('selectionService', function() {
var selected_template;
this.addTemplate = function(newObj) {
selected_template = newObj;
};
this.getTemplate = function(){
return selected_template;
};
});
As for using the array notation for dependencies, it's a good practice but it's not required. It'll save you from headaches if you ever run your code through a minifier.

save $location parameters state AngularJS

How do I save URL parameters state throughout lifecycle of application using pushState?
Page load.
Go to "/search" via href
submitSearch() through filter fields where $location.search(fields)
Go to "/anotherPage" via href
Go back to "/search" via href
Search paramters are set back to what they last were.
Is this a built in feature somewhere?
If not what's the best way to go about this?
If you're planning on a mostly single page website through pushState, you might want to get an intimate understanding of $routeProvider (http://docs.angularjs.org/api/ngRoute.%24routeProvider).
To go further down the rabbit hole, I would recommend looking at the ui-router module: (https://github.com/angular-ui/ui-router). $stateProvider (from ui-router) and $routeProvider work very similar, so sometimes the ui-router docs can give insights that you can't find in the poor documentation of the $routeProvider.
I reccomend going through the five page ui-router documentation (https://github.com/angular-ui/ui-router/wiki) page by page.
After all that preamble, here's the practical: you would set up a factory that holds history data and use the controller defined in your $routeProvider/$stateProvider to access and manipulate that data.
Note: the factory is a service. A service is not always a factory. The namespace goes:
angular.module.<servicetype[factory|provider|service]>.
This post explains the service types: https://stackoverflow.com/a/15666049/2297328. It's important to remember that they're all singletons.
Ex:
var myApp = angular.module("myApp",[]);
myApp.factory("Name", function(){
return factoryObject
});
The code would look something like:
// Warning: pseudo-code
// Defining states
$stateProvider
.state("root", {
url: "/",
// Any service can be injected into this controller.
// You can also define the controller separately and use
// "controller: "<NameOfController>" to reference it.
controller: function(History){
// History.header factory
History.pages.push(History.currentPage);
History.currentPage = "/";
}
})
.state("search", {
url: "/search",
controller: function(History, $routeParams) {
History.lastSearch = $routeParams
}
});
app.factory('<FactoryName>',function(){
var serviceObjectSingleton = {
pages: []
currentPage: ""
lastSearch: {}
}
return serviceObjectSingleton
})
If you're wondering what the difference between $routeProvider and $stateProvider is, it's just that $stateProvider has more features, mainly nested states and views... I think.
The easiest way is using cookies, angularjs provides a wrapping service for that.
Simply when you go to "/search" save your current URL parameters with "$cookieStore.put()" and once you've back you've got what you need with "$cookieStore.get()".
See the documentation at angularjs cookie store
I made a locationState service, you simply give it the values you want to persist and it stores them in the URL. So you can store all the state you want across all routes in your app.
Use it like this:
angular.module('yourapp')
.controller('YourCtrl', function ($scope, locationState) {
var size = locationState.get('size');
;
// ... init your scope here
if (size) {
$scope.size = size;
}
// ...and watch for changes
$scope.$watch('size', locationState.setter('size'));
}
Here's the code:
// Store state in the url search string, JSON encoded per var
// This usurps the search string so don't use it for anything else
// Simple get()/set() semantics
// Also provides a setter that you can feed to $watch
angular.module('yourapp')
.service('locationState', function ($location, $rootScope) {
var searchVars = $location.search()
, state = {}
, key
, value
, dateVal
;
// Parse search string
for (var k in searchVars) {
key = decodeURIComponent(k);
try {
value = JSON.parse(decodeURIComponent(searchVars[k]));
} catch (e) {
// ignore this key+value
continue;
}
// If it smells like a date, parse it
if (/[0-9T:.-]{23}Z/.test(value)) {
dateVal = new Date(value);
// Annoying way to test for valid date
if (!isNaN(dateVal.getTime())) {
value = dateVal;
}
}
state[key] = value;
}
$rootScope.$on('$routeChangeSuccess', function() {
$location.search(searchVars);
});
this.get = function (key) {
return state[key];
};
this.set = function (key, value) {
state[key] = value;
searchVars[encodeURIComponent(key)] = JSON.stringify(value);
// TODO verify that all the URI encoding etc works. Is there a mock $location?
$location.search(searchVars);
};
this.setter = function (key) {
var _this = this;
return function (value) {
_this.set(key, value);
};
};
});

Categories

Resources