I have the same issue with this post Pass Angular scope variable to Javascript . But I can't achive my solution with their answers.
My Angular Controller
angular.module('App').controller('HomeController', [
'$rootScope', '$scope', '$state', '$timeout', 'ReportService', 'MsgService',
function($rootScope, $scope, $state, $timeout, ReportService, MsgService) {
$scope.$on('$viewContentLoaded', function() {
console.log('HomeController');
$scope.get_locations();
});
// get locations
$scope.get_locations = function() {
var data = {};
// call http get to my api
MsgService.get_all_locations(data, function(response) {
if (response.code == 1) { // success
$scope.locations_array = response.data; // data that I want to access to script
} else {
alert(response.message);
}
});
}
}
]);
My Html
<div id="map" ng-controller="HomeController">{{locations_array}}</div> // {{locations_array}} scope have the result that I want
<script type="text/javascript">
$(document).ready(function() {
var data = $('[ng-controller="HomeController"]').scope().$parent.locations_array;
console.log(data); // underfined
//var $element = $('#map');
// var scope = angular.element($element).scope();
// console.dir(scope.$parent.locations_array); // underfined
});
</script>
I tried access from browser develop tool then It can access scope. But My code can't access this.
How to solve this?
The immediate problem here is a timing issue - you are trying to read the locations_array value off the scope long before the value is populated.
The sequence of events is something like this:
ready event for document triggers, and before Angular has even thought about starting, your inline JS code runs, trying to read the value from the scope, which doesn't exist yet.
Angular bootstraps your Angular application in response to the document's ready event (this may be before #1, depending on the order of scripts on the page). This will call the HomeController constructor, that only sets up a listener for the $viewContentLoaded event.
The $viewContentLoaded event gets broadcast, and you initiate an asynchronous request for the locations.
When that returns with the locations some time later, it populates them on the scope.
Don't rely on .scope()
In addition to the timing issues, there is another major problem with your solution - it relies on the debug information being included by AngularJS. Obviously, it is by default, but it is possible to disable this debug information for significant performance gains in production.
If someone else comes along, possibly after you have left, and tries to disable debug information to improve performance or for some other reason (it is a recommended practice in production), it will stop .scope() from working.
So by relying on .scope(), you are making it so that disabling debug info, a best practice and performance booster, is not possible now or in the future for your app, because it will break things. And it won't be at all obvious to that developer that it would break anything.
So relying on .scope() for anything other than debugging should always be a very last resort.
So what do I do instead?
Like I mentioned, this is a timing problem - you need to wait until the locations are eventually loaded before running code that relies on them.
Luckily, we have many options in JS to deal with asynchronous values - callbacks, promises, RxJS observables, etc. Pick your favourite.
Example: using a global promise
In your controller, create a promise on the global scope (icky, but it needs to be outside Angular somewhere), and resolve that promise with the location data when it is loaded.
var resolveLocations;
window.locationsPromise = new Promise(function (resolve) {
resolveLocations = resolve;
});
angular.module('App').controller('HomeController', [
'$rootScope', '$scope', '$state', '$timeout', 'ReportService', 'MsgService',
function($rootScope, $scope, $state, $timeout, ReportService, MsgService) {
$scope.$on('$viewContentLoaded', function() {
console.log('HomeController');
$scope.get_locations();
});
// get locations
$scope.get_locations = function() {
var data = {};
// call http get to my api
MsgService.get_all_locations(data, function(response) {
if (response.code == 1) { // success
resolveLocations(response.data); // resolve the promise
$scope.locations_array = response.data; // data that I want to access to script
} else {
alert(response.message);
}
});
}
}
]);
Then, your normal (non-angular) javascript (which needs to run after your Angular javascript file is loaded) could use that promise to do something with the data when available:
<script type="text/javascript">
$(document).ready(function() {
window.locationsPromise.then(function (locations_array) {
console.dir(locations_array);
// do something with the data
});
});
</script>
There is probably a better way
Without knowing why you think you need access to this data outside of Angular, it's hard to say for sure, but there are likely other better ways of handling the interplay between Angular code and other Javascript code that depends on it.
Maybe you create a directive to integrate a jQuery plugin, or another service, or whatever, but since AngularJS code is just normal JS, there is no need to think of them as separate from each other. You just have to get the timing right so you have the data available. Good luck!
Related
I'm working on a pretty big application in AngularJS and to avoid memory leaks we're implementing the memory release in the $onDestroy method, the problem is that there are variables that become undefined however, ng-change events keep coming from HTML and I have some errors. Is there any way to disconnect all the HTML from the controller? or at least to stop all the events coming from the frontend? I'm working in AngularJS 1.6.
This is an example of how I have defined the components:
function requestListController($uibModal, urlRest, $stateParams, $state, uiGridConstants, $filter, httpService) {
var ctrl = this;
ctrl.$onInit= function() {
// ALL DATA INITIALIZATION
ctrl.requestListGridOptions.data = [];
// GETTING EXTERNAL DATA
httpService.get(url, true)
.then(function(response){
console.log("initRequestList - data.RequestListTO : " , response.RequestListTO);
angular.copy(response.RequestListTO.requests, ctrl.requestListGridOptions.data);
}) .catch(function onError(response) {
// Handle error
var status = response.status;
console.log("initRequestList - error : " + status);
});
};
//////////////////////////////
// //
// on$Destroy method //
// //
//////////////////////////////
ctrl.$onDestroy = function() {
ctrl.status=undefined;
ctrl.requestListGridOptions=undefined;
};
// OTHER METHODS
};
//Inject dependencies
requestListController.$inject = [ '$uibModal', 'urlRest', '$stateParams', '$state', 'uiGridConstants', '$filter', 'httpService'];
pomeApp.component('requestList', {
templateUrl: 'request/requestList/requestList.template.html',
controller: requestListController
});
This is more less the structure of my components.
I guess you misinterpret the onDestroy event. It's mainly to remove timeouts or intervals or events for $rootScope.$on(...).
The ng-change event is bind to the scope. This means it will automatically destroyed if the scope is removed. Therefore the whole scope won't be destroyed and you have another problem.
If you have one big application with one scope or something similar you should use ng-if to remove the parts that should not be shown. This will remove the DOM element and with it all the watchers if the variable for ng-if is false.
Without any proper code from your side no one can really help you and just make some guesses what your problem could be.
You first need to see how many events have been subscribed. Then in the destroy, you can unsubscribe all those events. Sometimes, we also use directives which have to be destroyed. Or there is some logic inside those directives which needs cleanup. Also, if you have subscribed to any events on the root scope, it will live even after a local scope has been destroyed.
I have the following which works fine, drawing info from a RESTful api feed
app.controller('servicesController', ['$scope', '$location', '$http', '$interval',
function($scope, $location, $http, $interval) {
var getData = function() {
// Initialize $scope using the value of the model attribute, e.g.,
$scope.url = "https://(remote link to JSON api)";
$http.get($scope.url).success(function(data) {
$scope.listOfServices = data.runningServices; // get data from json
});
};
getData();
$interval(getData(), 10000);
}
]);
However my view is not updating every 10 seconds as expected. I have read that I need to use $scope.apply() somewhere in this above code.
I tried placing the following (in the appropriate place above)
$http.get($scope.url).success(function(data) {
$scope.listOfServices = data.runningServices; // get data from json
$scope.apply(); //I also tried $scope.runningServices.apply()
});
$scope.apply is not your problem, the scope will be digested automatically at the end of the $http request and $interval. Certain actions automatically "inform" Angular that the scope may have changed and trigger a digest; only if you're writing "non-Angular" code may you have to explicitly trigger a scope digest, since otherwise Angular wouldn't notice any changes.
No, your issue is that you're calling getData(), and then have its return value (undefined) execute every ten seconds. Which is obviously nonsense. You just want to pass the function itself to $interval:
$interval(getData, 10000);
// look ma, ^^^^^, no parentheses
I have some parameters in the $rootScope as specified below:
myApp.factory('itemService', function($http) {
return $http.get('/items');
});
myApp.run(function($rootScope, itemService) {
itemService.success(function(response) {
$rootScope.items = response;
});
});
myApp.controller('displayCtrl', function($rootScope, $scope) {
$scope.items = $rootScope.items;
});
When I run the above code, I get this error from firebug
TypeError: $rootScope.items is undefined. I really do not know what is happening.
Here is a small addition. items is an array with a list of objects like this:
items = [
{'name': 'spoon', 'price': 200},
{'name': 'table', 'price': 400},
{'name': 'shoe', 'price': 250}
];
I wish to make items available constantly in my app such that I can display each item on the item list (items) without making another request to the server. I intend to achieve this by simply displaying an item using $scope.item = items[$routeParams.id] each time I need to display an item.
I look forward to implement this using either a function attached to ng-click or the normal #/route/:param mechanism.
Thanks
TypeError: $object.property is undefined is usually because a request to a reference of an object is made before that specific object (or its property) has been set. $http requests are asynchroneous by nature so other processes do not get blocked. It should be obvious that trying to make requests synchroneous could cause a major issue for people with very slow connections.
Apart from that, polluting the $rootScope is generally a bad idea. You can find a topic about global variables on the following link so that you investigate why the $rootScope is not such a good place.
Having said all that, it seems to me that you didn't want to make multiple requests to retrieve the same data. If so, you can use the cache option for $http.get methods.
e.g:
myApp.factory('itemService', function($http, $q) {
return {
get: function() {
return $http({
url: 'items.json',
cache: true //keep the result in memory
});
}
};
})
myApp.controller('aCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
myApp.controller('bCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
This will make sure the information gets requested once and put into a cache. The data is accessible in different places.
<div ng-controller="aCtrl as a">
{{a.items}}
</div>
<div ng-controller="bCtrl as b">
{{b.items}}
</div>
This leaves me with another 'good' practice: the usage of the controllerAs syntax. Which provides a way to use namespaces in AngularJS.
Ofcourse, these are just tips and you should always consider the requirements!
You run asynchronious method at run block :
itemService.success(function(response){
$rootScope.items = response;
});
But initialization goes on, so probably you access $rootScope.items before itemService succeed (or it fails, and you didnt predict such situation). I suggest you to do this (if you want to follow $rootScope convension.. which is bad by the way) :
$rootScope.items = [];
itemService.success(function(response){
$rootScope.items = response;
});
You are setting items in the callback of an asynchronous process, so you are trying to access items on the $rootScope before its actually set.
If you are trying to initialize items when the controller is loaded, then there are other ways to do that such as using the resolve block of a route or manually calling the $http.get on the factory when the controller loads.
Finally, I was able to come up with a solution. I realized that the problem was to have $rootScope.items available in displayCtrl at the same time it loads. But $rootScope.items is available in my view when my html page loads.
So I simply passed the item id as a parameter and obtained it using $routeParams as follows
myApp.controller('displayCtrl', function($routeParams, $scope) {
$scope.item_id = $routeParams.id; //given that the route looks like '/item/:id'
});
Then in my HTML file this what I did
<div ng-bind="items[item_id].name"></div>
<div ng-bind="items[item_id].price"></div>
This actual solved my problem.
I have a directive which is associated with one controller and the functions in my controller defined as
MyFormController.prototype.addNewRow = function addNewRow() {
//Adding row code
};
I want to call this method from another controller, possible ways?
I ve user the service and moved the code into that service which is shared across the controllers, however the service code does the DOM manipulation, and then i guess the next question would be that can we use $compile in a service test case
service or factory is used to share data between controller.so it would be best to define function in service and factory.
demo:
(function() {
angular.module('app', [])
.service('svc', function() {
var svc = {};
svc.method = function() {
alert(1);
}
return svc;
})
.controller('ctrl', [
'$scope', 'svc', function($scope, svc) {
svc.method();
}
]);
})();
You should not!!!
That defeats the whole purpose of modularity.
If possible try to make the function generic and create a service/factory. Now both the places where you need, use the same generic function defined in service and do their stuff.
Otherwise you can also look at events to make changes accordingly.
Look at this blog post:
http://ilikekillnerds.com/2014/11/angularjs-call-controller-another-controller/
Last but the worst solution is (avoid using this, this is literally an aweful way) is catching the element inside directive and getting its scope and taking the function from it.
Example,
var otherControllerFunc = $(".inside-directive").scope().yourfunc;
I was reading this article: http://eviltrout.com/2013/06/15/ember-vs-angular.html
And it said,
Due to it’s lack of conventions, I wonder how many Angular projects
rely on bad practices such as AJAX calls directly within controllers?
Due to dependency injection, are developers injecting router
parameters into directives? Are novice AngularJS developers going to
structure their code in a way that an experienced AngularJS developer
believes is idiomatic?
I am actually making $http calls from my Angular.js controller. Why is it a bad practice? What is the best practice for making $http calls then? and why?
EDIT: This answer was primarily focus on version 1.0.X. To prevent confusion it's being changed to reflect the best answer for ALL current versions of Angular as of today, 2013-12-05.
The idea is to create a service that returns a promise to the returned data, then call that in your controller and handle the promise there to populate your $scope property.
The Service
module.factory('myService', function($http) {
return {
getFoos: function() {
//return the promise directly.
return $http.get('/foos')
.then(function(result) {
//resolve the promise as the data
return result.data;
});
}
}
});
The Controller:
Handle the promise's then() method and get the data out of it. Set the $scope property, and do whatever else you might need to do.
module.controller('MyCtrl', function($scope, myService) {
myService.getFoos().then(function(foos) {
$scope.foos = foos;
});
});
In-View Promise Resolution (1.0.X only):
In Angular 1.0.X, the target of the original answer here, promises will get special treatment by the View. When they resolve, their resolved value will be bound to the view. This has been deprecated in 1.2.X
module.controller('MyCtrl', function($scope, myService) {
// now you can just call it and stick it in a $scope property.
// it will update the view when it resolves.
$scope.foos = myService.getFoos();
});
The best practise would be to abstract the $http call out into a 'service' that provides data to your controller:
module.factory('WidgetData', function($http){
return {
get : function(params){
return $http.get('url/to/widget/data', {
params : params
});
}
}
});
module.controller('WidgetController', function(WidgetData){
WidgetData.get({
id : '0'
}).then(function(response){
//Do what you will with the data.
})
});
Abstracting the $http call like this will allow you to reuse this code across multiple controllers. This becomes necessary when the code that interacts with this data becomes more complex, perhaps you wish to process the data before using it in your controller, and cache the result of that process so you won't have to spend time re-processing it.
You should think of the 'service' as a representation (or Model) of data your application can use.
The accepted answer was giving me the $http is not defined error so I had to do this:
var policyService = angular.module("PolicyService", []);
policyService.service('PolicyService', ['$http', function ($http) {
return {
foo: "bar",
bar: function (params) {
return $http.get('../Home/Policy_Read', {
params: params
});
}
};
}]);
The main difference being this line:
policyService.service('PolicyService', ['$http', function ($http) {
I put an answer for someone who wanted a totally generic web service in Angular. I'd recommend just plugging it in and it will take care of all your web service calls without needing to code them all yourself. The answer is here:
https://stackoverflow.com/a/38958644/5349719