Change controller variable from within a service - angularjs

In the "ok"-function of a modal, I'm trying to update a variable from the scope that opened the modal. This
$scope.modalOptions.assets.length = 0;
perfectly works: the variable "assets" in the "parent" scope immediatly changes and, while the modal is still open, the data represantation of "assets" is updated and emptied in the main page.
What bugs me is that changing above to
$scope.modalOptions.assets = $scope.modalOptions.assetFactory.query();
has no effect at all. I can verify that the API Controller is called and that it returns new data which should, in effect, change the representation of "assets" as well.
The variable itself is defined in the controller like this:
$scope.assets = bondFactory.query();
And I pass it in to the Modal-Service like this:
assets: $scope.assets
I'd be thankful for tips and ideas..
EDIT
How the Modal is called:
$scope.postModal = function () {
//Pass view
var customModalDefaults = {
templateUrl: 'scripts/app/views/postBond.html'
}
//Pass data
var customModalOptions = {
asset: angular.copy($scope.asset),
assets: $scope.assets,
currencies: globalVariables.currencies,
assetFactory: bondFactory,
validationErrors: [],
action: 'POST'
};
//Show & Callback
ModalAssetService.showModal(customModalDefaults, customModalOptions).then(function (result) {
// fill me with usefull content
});
};
The Modal Service itself:
app.service('ModalAssetService', ['$modal',
function ($modal) {
var modalDefaults = {};
var modalOptions = {};
this.showModal = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
//Create controller
tempModalDefaults.controller = function ($scope, $modalInstance, $resource, errorService) {
$scope.modalOptions = tempModalOptions;
// exit modal with "ok"
$scope.modalOptions.ok = function (result) {
//POST
if ($scope.modalOptions.action == 'POST') {
// try and post asset
$scope.modalOptions.assetFactory.save($scope.modalOptions.asset, function () {
// success
$scope.modalOptions.assetFactory.query({}, function (data) {
$scope.modalOptions.assets = data;
// $modalInstance.close();
});
}, function (error) {
// error
$scope.modalOptions.validationErrors = errorService.fn(error);
});
};
//PUT
if ($scope.modalOptions.action == 'PUT') {
//TODO
alert("put");
};
};
// exit modal with "cancel"
$scope.modalOptions.close = function (result) {
$modalInstance.dismiss('cancel');
};
}
return $modal.open(tempModalDefaults).result;
};
}
]);
EDIT 2: the bondFactory
app.factory('bondFactory', ['$resource', function ($resource) {
return $resource('../api/bond/:id', null, {
'update': { method: 'PUT' }
})
}]);
SOLUTION
I still did not figure out exactly how to fix the problem of the scope not being notified about a change if the change results from an assignment of a $resource GET to a variable from said scope.
Anyways, for my specific case, I found a better solution: when clicking "OK" in the modal, the new asset is being sent to the API. If errors happen there (validation failed etc.), the Modal will stay open and some notifications will inform the user. If the new asset was posted successfully, then a new GET is sent to the API and only the last new asset is being added to the existing array of asset-elements. This means no "flashing", just one more row is added to the list of assets.
It's not a good form of programming to assume that the last element from the GET-Request is definitly the new asset and there is of course some overhead in retrieving the complete list of all assets when just the very last of these would suffice, but I guess it works and I'll just add some sort to the API to make sure it always is in right order. Code:
$scope.modalOptions.assetFactory.save($scope.modalOptions.asset, function () {
//success
$scope.modalOptions.assetFactory.query({}, function (result) {
$scope.modalOptions.assets.push(result[result.length - 1]);
$modalInstance.close();
});
}, function (error) {
//error
$scope.modalOptions.validationErrors = errorService.fn(error);
});

Your factory is performing an async operation, so you need to use callbacks to access the data and have the view update:
$scope.modalOptions.assetFactory.query({}, function(data) {
$scope.modalOptions.assets = data;
});

Without knowing how you are calling the modal, or if you are using a hand-rolled one or the one from UI-Bootstrap, I can only guess at how things are working. Having said that, if you are not using the modal from UI-Bootstrap you really should be. It uses promises and has the ability to return just about anything from the modal.
$scope.myModalPromise = $modal.open(modalConfig);
$scope.myModalPromise.result.then(function(data){
//things to do upon closing the modal with $close
//data is any value or object that you want to pass back
}, function(data){
//things to do upon closing the modal with $dismiss
//data is any value or object that you want to pass back
});
The setup lets your modal perform asynch operations and only return the value when they have completed. The beauty of this is that they are returned to the calling controller cleanly and in an Angular way.

Related

AngularJS calling a method when its get new data in localStorage

I am using angularJS localstorage and i injected this very well but problem in code.
I want when localstorage gets new data, so that its call a function $scope.getAllContact() automatically.
I am trying to solve this issue coz, for example, i opened two tab in browser, if i change anything in one tab, the latest change should reflect in other tab too without any reload and refresh.
At first see my code:
app.controller('crudCtrl', function($scope, $http, $timeout, $localStorage, $sessionStorage) {
$scope.getAllContact = function() {
var data = $http.get("http://127.0.0.1:8000/api/v1/contact/")
.then(function(response) {
$scope.contacts = response.data;
// show something if success
}, function(response) {
//show something error
});
};
$scope.getAllContact();
// below method will post data
$scope.formModel = {};
$scope.onSubmit = function () {
$http.post('http://127.0.0.1:8000/api/v1/contact/', $scope.formModel)
.then(function(response) {
$localStorage.name = response.data;
$timeout(function() {
$scope.successPost = '';
}, 4000);
//below $scope will push data in client site if the request is success
$scope.contacts.push(response.data);
//if any error occurs, below function will execute
}, function(response) {
// do nothing for now
});
};
});
above in $scope.onSubmit() methond, i send the submitted data to localstorage too, so i need if localstorage gets new data, it should execute $scope.getAllContact() always automatically without any refresh.
Can anyone fix me this issue?
Use the storage event:
angular.element(window).on("storage", $scope.getAllContact);
$scope.$on("$destroy", function() {
angular.element(window).off("storage", $scope.getAllContact);
});
For information, see
MDN Window API Reference - storage_event
MDN Web API Reference - StorageEvent

AngularJS Promises: Put promise in Factory so it is globally accessible and *editable*

I'm trying to pull data from an external JSON file and display it for the user to see. Through various actions, the user would then be able to change the data returned from the JSON file, without writing those changes to the file (in this example, incrementing values by one by clicking on a div). I've created a promise service that successfully pulls the data and displays it. I can even get it so the data can be changed in individual controllers.
This is where I get stuck: I cannot find a way to make any changes to the data in the PromiseService, so changes cannot propagate globally. How do I make it that any change in the promise data at the controller level will be reflected in the PromiseService and, thus, reflected in any data binding in the app? I'm new to promises, so I'm open to a completely different approach.
Plunker
HTML:
<body ng-app="pageApp" ng-controller="pageCtrl" nd-model="items">
{{items}}
<div class="button" ng-controller="buttonCtrl" ng-click="incrementValues()">
Click to increment:
<br>{{items}}
</div>
</body>
PromiseService:
pageApp.factory('PromiseService', function($http) {
var getPromise = function() {
return $http.get('items.json').then(function(response) {
return response.data;
});
};
return {
getPromise: getPromise
};
});
Button Controller (Page Controller in Plunker):
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.incrementValues = function()
{
PromiseService.getPromise().then(function(data) {
$scope.items = data;
for(var i = 0; i < data.items.length; i++)
{
data.items[i]['value']++;
}
}).catch(function() {
});
};
});
The incrementValues function works successfully the first time, but each consecutive click re-pulls the promise and resets the data. To sum up: how do I reflect the incremented values in the PromiseService, as opposed to local variables?
You could add to your factory a private property where you store the items. Then create 3 different methods to update and access to that property.
pageApp.factory('PromiseService', function($http) {
var items = {}; // [] in case it is an array
var updateData = function(updatedData){
items = updatedData;
}
var getUpdateData = function(){
return items;
}
var getPromise = function() {
return $http.get('items.json').then(function(response) {
items = response.data;
return response.data;
});
};
return {
getPromise: getPromise,
updateData : updateData,
getUpdateData : getUpdateData
};
});
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.items = [];
//You should call this method to retrieve the data from the json file
$scope.getData = function(){
PromiseService.getPromise().then(function(data) {
$scope.items = data;
}).catch(function() {
});
}
$scope.incrementValues = function(){
for(var i = 0; i < $scope.items.length; i++){
$scope.items[i]['value']++;
}
PromiseService.updateData($scope.items); //This could be skipped in case you do not want to 'store' these changes.
};
});
Then in others controller you could use the same service to retrieve the updated Data like this:
$scope.items = PromiService.PromiseService();
In the future you could also create a new method to update the json itself instead of stored internally
Your function creates a new $http call every time it's called, and thus returns a new promise, encspsulating new data, every time it's called.
You need to return the same promise every time:
var thePromise = $http.get('items.json').then(function(response) {
return response.data;
});
var getPromise = function() {
return thePromise;
};

AngularJS proper way of refreshing data in Controller

I have a testing "hello" view showing "Hello {{username}}!" or "Hello anonymous!".
This view has its own controller and is accesible via url (configure by ui.router).
Then I have a UserModel with methods setUsername(newUsername) and getUsername().
There is also logging view with a controller that uses setUsername() method on logging in success and then navigates to "hello" view.
The code looks like this:
HelloController:
anguler.module('hello', ...
.config(function($stateProvider){
$stateProvider
.state('hello', {
url: '/hello',
views: {
'main#': {
controller: 'HelloController as helloController',
templateUrl: 'app/hello/hello-tmpl.html'
}
},
});
})
.controller('HelloController', function (UserModel) {
var helloController = this;
helloController.username = UserModel.getUsername();
})
There is also a "log out" button in the top bar. So in order to show the changes in "hello" view I added a list of function that UserModel would call when user state changes:
.service('UserModel', function() {
var model = this;
var username = '';
var onChangesFunctions = [];
function onChange() {
onChangesFunctions.forEach(function(f) {
f();
});
}
model.onChange = function(f) {
onChangesFunctions.push(f);
};
model.setUsername = function(newUsername) {
username = newUsername;
onChange();
};
model.clearUserData = function() {
username = '';
onChange();
};
and added a code in HelloController to add a listener to the UserModel.onChangesFunctions.
The problem with that approach is that HelloController is initialized many times (everytime that user navigates to it) and every time it is registering new function as the listener.
Is there any better way to refresh user data?
The problem of your approach is memory leaks. As you said when your controller is destroyed and the new one is created your function will still persist in the service and the controller which should have been killed is still most likely in the memory because of the function.
I don't clearly understand what your goal is; however what you can do is destroying the functions when the controller is destroyed:
.controller('HelloController', function (UserModel, $scope) {
var helloController = this;
helloController.username = UserModel.getUsername();
$scope.$on('$destroy', function() {
// either destroy all functions you add to the service queue
// or
// simply call the controller specific logic here, this will be called when your controller is destroyed
});
});
Another approach is listening on '$stateChangeStart' / '$stateChangeSuccess' event.
Regardless of the way you choose I would highly recommend to avoid binding services to the controller instance specific logic. This is a way to hell

passing data to a controller using service

On the code below, I am trying to reference the photos array in the appService service from the pictureCtrl controller, but it won't work well. The only thing that works is $scope.photoService referencing appService when I try to call the model on the HTML. When I try to call currentPhoto model on the HTML it won't show anything.
I would like to get the currentPhoto model on the HTML page referencing the first index of the photos array which will be an object and when I click on the favorite button on HTML it will run the sendFeedback method and will change currentphoto. How can do it?
.controller('pictureCtrl', function ($scope, appService) {
// angular.extend($scope, appService);
$scope.photoService = appService;
$scope.photos = appService.photos;
$scope.currentPhoto = appService.photos[0];
$scope.sendFeedback = function (bool) {
if(bool) {}//add it to favorite
var randomIndex = Math.round(Math.random() * flickrData.photos.length-1);
$scope.currentPhoto = angular.copy($scope.service.flickrData.photos[randomIndex]);
};
})
.factory('appService', function ($http) {
var photoService = {};
photoService.photos = [];
photoService.feelingText = "";
photoService.getFlickrPictures = function (text) {
console.log('this has been called');
return $http({
method: "JSONP",
url: apiUrl+'&text='+text+'&format=json&jsoncallback=JSON_CALLBACK'
}).then(function (data) {
photoService.photos = data.data.photos.photo;
console.log('flickrData', photoService.photos);
}, function(err){
throw err;
});
};
return photoService;
}
It seems that the photos array never has been populated. Since you are using a factory, the thing that will get injected into your controller will be a fresh instance of photoService.
You will only see photos if you first call your function to fetch the data from the $http service.
I would create a function loadPhotos on your service that would return the data as a promise to your controller.

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

Resources