angularjs share variable between functions - angularjs

Within a controller, I'd like to share data between two functions. For example:
controllers.controller('rightDrawerCtrl', function ($scope, $http) {
$scope.shared_id;
$scope.getId = function () {
// get the id via $http, using a secondary id to retrieve the shared_id
}
$scope.useId = function () {
// use the shared_id to make a different api call
}
});
In this example however, when useId() is called, shared_id is undefined.
I presume this is because useId was 'created' while shared_id was undefined, so hasn't been notified of the new value.
The only way I've found to fix this is by using two controllers: Share data between AngularJS controllers
Is there a better way to do this, using only 1 controller?

$scope.shared_id;
After the above statement the shared_id variable of $scope should be undefined - because you haven't assigned it any value.
Despite this the variable is still accessible inside any scope functions because $scope is global for them. And don't forget that you should access it like $scope.shared_id in all the places.
However if you initialize it in the following manner:
$scope.shared_id = 'no id';
.. it would not return undefined.
You can initialize this variable via ng-init too.

Call userId() function when getId() function get result from $http request.

Looks like I simply had to wrap the variable assignment in $scope.$apply
$scope.$apply(function() {
$scope.shared_id = 1234;
});

You can use like this with this is much easier.
var self = this;
this.shared_id;
$scope.getId = function () {
console.log(self.shared_id) ;
}
$scope.useId = function () {
console.log(self.shared_id) ;
}

Related

Angular - Moving function into a service

I have a function that I'm now needing to use in multiple page so decided to move into a service - however its not going as I'm expecting.
So in my HTML i have:
<li ng-init="bg = underQBar(work.options)">
Then in the controller (before I moved the common function) it looked like:
$scope.underQBar = function(toWorkArray) {
//some implementation, with a return at the end
}
Now I've made my service:
function barService($window){
var self = this;
self.getBarColours = function(toWorkArray) {
//copied the implementation with the return here
}
}
And therefore trying this in my controller:
$scope.underQBar = barService.getBarColours(toWorkArray);
However this doesnt work, its not getting the parameter I'm sending from the HTML - is this a trivial fix or something wrong with the implementation?
This is the problem:
$scope.underQBar = barService.getBarColours(toWorkArray);
Here, you're assigning the result of the service function call to $scope.underQBar, when you meant to assign it the function itself.
This should work:
$scope.underQBar = barService.getBarColours;
If you want to make it more clear to the reader that it's a function, just do:
$scope.underQBar = function (toWorkArray) {
return barService.getBarColours(toWorkArray);
}
Here is a correct definition for your service :
angular.module("myModule").factory("BarService", ["$window", function($window){
var service = {};
service.getBarColours = function(toWorkArray){
//copied the implementation with the return here
};
return service;
}]);
And here is a correct way to inject the service in your controller :
angular.module("myModule").controller("controllerName", ["BarService", function(BarService){
var self = this;
self.getBarColours = BarService.getBarColours;
}]);
and here is the way to use it:
<li ng-init="bg = controllerName.underQBar(work.options)">
Explanation :
A service or a factory in angular cannot be accessed by your view. Your view can only make a call to your controllers.
If your function should have been called by many controllers, you can put this function in a global controller that will be responsible for controlling your whole view.

how to pass values between functions in the same scope

I have 2 functions in the same controller and I am trying to pass the a value from one function to another. But I am getting an error TypeError: Cannot read property 'activeDataset' of undefined
Here is my code:
angular.module('daModule').controller("Controller1",Controller1);
Controller1.$inject = ['$scope', '$timeout'];
function Controller1($scope, $timeout) {
var ct = this;
ct.datasetName = "demo";
...
ct.activeDataset = function activeDataset(){
return ct.datasetName;
};
}
Here is my other function in the same file
function fn1(Controller1) {
...
var currentDataSet = Controller1.activeDataset();
...
}
Don't know where I am going wrong.
There are a couple of things going wrong here.
One is that the functions Controller1 and fn1 are being defined on the global name space. If another module were to use the same function names, there would be problems. We call this global name space pollution and it should be avoided.
The second problem is that in Controller1 the value of this is not defined until after the function is called. And when it is called, it is set to the local context of when it was called. Attaching properties to this in a function call does not make them available as a global property of the function. That is why you are getting "activeDataset undefined" in fn1.
To answer your question of how to pass values between functions in the same scope: The functions need to be inside the controller and they should put those values on the $scope.
angular.module("myApp").controller("myController", [$scope, myController($scope){
$scope.datasetName = "demo";
$scope.activeDataSet = function(){
return $scope.datasetName;
};
$scope.fn1 = function() {
return $scope.activeDataSet();
};
}]);

AngularJs service returns full object, but returns undefined when i try to access property

UPDATE 2:
I found out where the problem was coming from and I left out the important part.
I'm setting the property in the first controller INSIDE this
$rootScope.$on('anEvent', function (event, data) {
InjectedService.setToken(data.value);
})
But I can't grab it from outside that callback scope. The event that is listened for depends on an $http request so I'm guessing that is the problem. Are there any workarounds to this?
UPDATE:
The order of controllers are the other way around so that the code in secondController is actually being called first in my actual app. I have reordered them accordingly
Question:
I'm trying to grab the services specific property but when I try to grab the service property, I get undefined (using a getter function or not). But when I try to grab the full service, I get everything with the correct property and value.
main.js
angular.module('myApp', ['myModule', 'myServices'])
.controller('firstController', ['InjectedService', function(InjectedService) {
InjectedService.setProperty('Hello World')
}])
othermodule.js:
angular.module('myModule', [])
.controller('secondController', ['InjectedService',
function (InjectedService) {
console.log(InjectedService); // Full object with property == 'hello world'
console.log(InjectedService.property); // I get undefined
console.log(InjectedService.getProperty()); // I get undefined
// interesting to note:
InjectedService.setToken('A different string');
console.log(InjectedService.property); // I get 'A different string'
console.log(InjectedService); // Full object with property == 'Hello World' which was set in second controller
}])
services.js:
angular.module('myServices', function
.service('InjectedService', function(){
var Service = this;
Service.setProperty = function (value) {
Service.property = value;
}
Service.getProperty = function () {
return Service.property;
}
Service.unsetProperty = function () {
Service.property = null;
}
return Service;
})
It seems to me a scope problem, but the variable isn't a primitive type. Any suggestions?

Adding function to controllers in AngularJs

I'm trying to add function to controller in angularJS, the way I think should be working but sadly not.
Here is my code.
//this is working.
$scope.adddata = function () {
$scope.names.push({name:$scope.newdata.name,city:$scope.newdata.city});
//$scope.names.push($scope.newdata);
}
//this is not working
function adddata() {
$scope.names.push({name:$scope.newdata.name,city:$scope.newdata.city});
//$scope.names.push($scope.newdata);
}
$scope.adddata = adddata();
Both functions above are in the definition of controller, hence $scope variable is available.
Can I only use $scope.functionname = functionname(){....}
or I can create a function and later assign it to controller / scope.
Do $scope.adddata = adddata; (no parentheses). Using the parentheses will assign the result of calling adddata() to the scope variable, which is undefined (adddata() does not return a value).
This would work:
$scope.adddata = adddata;
instead of:
$scope.adddata = adddata();
Don't invoke the function, pass a reference to it.

Update scope value when service data is changed

I have the following service in my app:
uaInProgressApp.factory('uaProgressService',
function(uaApiInterface, $timeout, $rootScope){
var factory = {};
factory.taskResource = uaApiInterface.taskResource()
factory.taskList = [];
factory.cron = undefined;
factory.updateTaskList = function() {
factory.taskResource.query(function(data){
factory.taskList = data;
$rootScope.$digest
console.log(factory.taskList);
});
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.startCron = function () {
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.stopCron = function (){
$timeout.cancel(factory.cron);
}
return factory;
});
Then I use it in a controller like this:
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
$scope.taskList = uaProgressService.taskList;
});
}
);
So basically my service update factory.taskList every 5 seconds and I linked this factory.taskList to $scope.taskList. I then tried different methods like $apply, $digest but changes on factory.taskList are not reflected in my controller and view $scope.taskList.
It remains empty in my template. Do you know how I can propagate these changes ?
While using $watch may solve the problem, it is not the most efficient solution. You might want to change the way you are storing the data in the service.
The problem is that you are replacing the memory location that your taskList is associated to every time you assign it a new value while the scope is stuck pointing to the old location. You can see this happening in this plunk.
Take a heap snapshots with Chrome when you first load the plunk and, after you click the button, you will see that the memory location the scope points to is never updated while the list points to a different memory location.
You can easily fix this by having your service hold an object that contains the variable that may change (something like data:{task:[], x:[], z:[]}). In this case "data" should never be changed but any of its members may be changed whenever you need to. You then pass this data variable to the scope and, as long as you don't override it by trying to assign "data" to something else, whenever a field inside data changes the scope will know about it and will update correctly.
This plunk shows the same example running using the fix suggested above. No need to use any watchers in this situation and if it ever happens that something is not updated on the view you know that all you need to do is run a scope $apply to update the view.
This way you eliminate the need for watchers that frequently compare variables for changes and the ugly setup involved in cases when you need to watch many variables. The only issue with this approach is that on your view (html) you will have "data." prefixing everything where you used to just have the variable name.
Angular (unlike Ember and some other frameworks), does not provide special wrapped objects which semi-magically stay in sync. The objects you are manipulating are plain javascript objects and just like saying var a = b; does not link the variables a and b, saying $scope.taskList = uaProgressService.taskList does not link those two values.
For this kind of link-ing, angular provides $watch on $scope. You can watch the value of the uaProgressService.taskList and update the value on $scope when it changes:
$scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) {
if (typeof newVal !== 'undefined') {
$scope.taskList = uaProgressService.taskList;
}
});
The first expression passed to the $watch function is executed on every $digest loop and the second argument is the function which is invoked with the new and the old value.
I'm not sure if thats help but what I am doing is bind the function to $scope.value. For example
angular
.module("testApp", [])
.service("myDataService", function(){
this.dataContainer = {
valA : "car",
valB : "bike"
}
})
.controller("testCtrl", [
"$scope",
"myDataService",
function($scope, myDataService){
$scope.data = function(){
return myDataService.dataContainer;
};
}]);
Then I just bind it in DOM as
<li ng-repeat="(key,value) in data() "></li>
This way you can avoid to using $watch in your code.
No $watch or etc. is required. You can simply define the following
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
});
$scope.getTaskList = function() {
return uaProgressService.taskList;
};
});
Because the function getTaskList belongs to $scope its return value will be evaluated (and updated) on every change of uaProgressService.taskList
Lightweight alternative is that during controller initialization you subscribe to a notifier pattern set up in the service.
Something like:
app.controller('YourCtrl'['yourSvc', function(yourSvc){
yourSvc.awaitUpdate('YourCtrl',function(){
$scope.someValue = yourSvc.someValue;
});
}]);
And the service has something like:
app.service('yourSvc', ['$http',function($http){
var self = this;
self.notificationSubscribers={};
self.awaitUpdate=function(key,callback){
self.notificationSubscribers[key]=callback;
};
self.notifySubscribers=function(){
angular.forEach(self.notificationSubscribers,
function(callback,key){
callback();
});
};
$http.get('someUrl').then(
function(response){
self.importantData=response.data;
self.notifySubscribers();
}
);
}]);
This can let you fine tune more carefully when your controllers refresh from a service.
Like Gabriel Piacenti said, no watches are needed if you wrap the changing data into an object.
BUT for updating the changed service data in the scope correctly, it is important that the scope value of the controller that uses the service data does not point directly to the changing data (field). Instead the scope value must point to the object that wraps the changing data.
The following code should explain this more clear. In my example i use an NLS Service for translating. The NLS Tokens are getting updated via http.
The Service:
app.factory('nlsService', ['$http', function($http) {
var data = {
get: {
ressources : "gdc.ressources",
maintenance : "gdc.mm.maintenance",
prewarning : "gdc.mobMaint.prewarning",
}
};
// ... asynchron change the data.get = ajaxResult.data...
return data;
}]);
Controller and scope expression
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.get.maintenance}}</span>
</div>
The above code works, but first i wanted to access my NLS Tokens directly (see the following snippet) and here the values did not become updated.
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService.get;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>

Resources