Execute scope function from service parameter - angularjs

I am trying to create a service when I can set my formSubmit.
For example. In controller A I call "service.setFormSubmit(doThis(obj))" and in controller B I call "service.getFormSubmit()". Where it will execute the function doThis(obj) in controller B.
UPDATE - Re-formulated question.
I have 1 view where I want to edit or create a category. This means I need a dynamic ng-submit. I want to to this in the controller. So like this:
$scope.editCategory = function(obj) {
$scope.formSubmit = 'editCategory'
}
And on the create I want to change the formSubmit var to createCategory of course.
So I can make a difference between creating and editing the category.
Is this possible? Would be really nice if someone has a way to do this..!
Thanks in advance!

Instead of passing around strings which need to be eval'ed, use the service to share functionality directly between controllers.
The service can be dirt-simple:
.factory('MyService', function(){
var service = {};
return service;
});
Once injected and assigned to scope variables in both controllers you have an intermediary unit which can act as a modifiable channel for cross-controller collaboration.
.controller('FirstController', function($scope, MyService){
$scope.service = MyService;
})
.controller('SecondController', function($scope, MyService){
$scope.service = MyService;
$scope.service.create = function(obj){
console.log('Creating');
}
$scope.service.edit = function(obj){
console.log('Editing');
}
})
From the scope of FirstController, you can now call the function also available on the scope of SecondController:
<div ng-controller="FirstController">
<input type="checkbox" ng-model="button.type"> Toggle create/edit<br/>
<button ng-if="button.type" ng-click="service.create(obj)">Create</button>
<button ng-if="!button.type" ng-click="service.edit(obj)">Edit</button>
</div>
Demo

If you aren't reloading the page you can create an encapsulated variable in your service. Your set call would assign the value passed to that variable and your get call would return that variable to the caller.
One way I have achieved passing the data is to submit the form using the service and return a Json result to the service. Store the Json object in the encapsulated variable on the return and then pass a success or failure to the controller. When successful, let the controller redirect the view which will redirect using angular routing and ng-view. Once the new view, along with the new controller is loaded into the page, you can call the variable in your service to retrieve the data on the next controller.
Example Code:
app.factory('service', function ($q, $http) {
var savedData;
return {
loadData: function() {
return data;
},
search: function (parameters) {
var searchURL = '/MVCController/Search?parameter1=' + parameters.one +
'&parameter2=' + parameters.two;
var deferred = $q.defer();
$http.get(searchURL).success(function (data) {
savedData = data;
deferred.resolve(true);
}).error(function(data) {
data = 'An error occurred while searching: ' + data;
savedData = data //(if you want to save the error)
deferred.reject(data);
});
return deferred.promise;
}
}
});

Related

Make data from factory http.get accessible to entire controller AngularJS

I am trying to give access to a json file that contains config information for my project (things like rev number, project name, primary contact, etc) I created a factory that retrieves the json file using http.get, I can then pull that data into my controller but I am unable to access it from anywhere in the controller.
I did not write the factory, I found it as an answer to another person's question and it is copied almost entirely so if it not the right way to accomplish what I am trying to do please correct me.
here is the factory:
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
}
};
return configFactory;
}]);
and here is my controller:
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
$scope.data = d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
So essentially I have access to the data within the function used to retrieve it but not outside of that. I can solve this with rootScope but I am trying to avoid that because I think its a bandaid and not a proper solution.
Any help would be great but this is my first experience with http.get and promises and all that stuff so a detailed explanation would be very much appreciated.
[EDIT 1] The variables from the config file will need to be manipulated within the web app, so I can't use constants.
Don't assign your response data to scope variable , create a property in your factory itself and assign the response to this property in your controller when your promise gets resolved.This way you will get the value in all the other controllers.
I have updated your factory and controller like below
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
},
config:'' // new proprety added
};
return configFactory;
}]);
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
// $scope.data = d;
configFactory.config=d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
Have you looked into using angular constants? http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/ You can leverage them as global variables accessible from any controller without the ramifications of assigning the values to rootScope

Fetching an object from a service which hasn't yet been created

Folks:
I have 2 controllers, ctrlA and ctrlB - both unrelated to each other but are within the same page.
ctrlA queries an end point and returns a json object tags, which is then passed to a service method MyService.saveTags(tags) to store the object.
ctrlB then needs to populate a $scope variable $scope.tags by fetching the tags object created via ctrlA.
The service:
.factory('MyService', function($http, $q, $window) {
var myserviceFactory = {};
var savedTags = {};
// ..other methods..
myserviceFactory.saveTags = function(tags) {
if(!savedTags.tags){
console.log('saving tags..');
savedTags.tags = tags;
}
};
myserviceFactory.getSavedTags = function() {
console.log('returning tags..');
return savedTags.tags;
};
return myserviceFactory;
})
This issue appears to be ctrlB gets called first, so when $scope.savedTags = MyService.getSavedTags(); runs, it returns undefined.
Question: Angular n00b here - what would be the best way to fetch the tags after ctrlA has populated the object?
You can use a watch on the getSavedTags service method inside your ctrlB to know when is it populated. Something like
$scope.$watch(function() { return MyService.getSavedTags() },function(newValue) {
if(newValue) {
$scope.savedTags = newValue;
}
});

Bind the value of a service to a controller so it updates if the service updates

There are a lot of references that discuss this, but I just need someone to confirm if this is right or not. If i have a service which I want to share information with a controller, and the controller should update on changes to the service I need to return an object from the service, like:
.factory('myService', ['$http', function($http) {
var data = {};
var service = {
constant: 1234,
getData: function() {
return data;
},
doCalculation: function() {
service.constant = data.const*25;
},
requestData: function() {
return $http.get('/blah')
.then(function( response ) {
data = response.data;
}
}
}
return service;
}])
Now to pass it to a controller for use and have it update if requestData is invoked again during maybe a route resolve() I would and can't do:
.controller('myCtrl', ['myService', function(myService) {
var self = this;
// PART 1
self.data = myService.constant; // this is not bound and will not update, yes?
self.data1 = myService.getData(); // this is not bound and will not update, yes?
// So, the above would be assigned or invoked only once on init of controller and
// would have to reset manually by assigning either a value or the result of the
// the function call again
self.myService = myService; // pass entire service
// Now, in controller functions or in the UI I can invoke the functions or access
// values, and those results will be bound and update on changes to the service
// since I've passed it in its entirety
self.getData = function() {
return self.myService.getData();
}
// PART 2
self.getData = myService.getData; // would you ever do this?
// You wouldn't have to pass the entire service if it had a bunch of different
// parts that maybe you didn't want to be available...
}]);
PART 1
<div ng-show="myCtrl.myService.doCalculation() === someNumber">You can't see me unless doCalculation resolves to someNumber</div>
PART 2
<div ng-show="myCtrl.getData() === anotherNumber">Would this work? and would you want to do this?</div>
I just can't seem to get the concept of how sharing data between a service(s) and a controller(s) works, and when it is working and when it won't. If all you can do is say correct, wrong, wrong, oh man so wrong, that's kewl, but if you can also say and this is why, I'd be ecstatic to put this away as finally resolved so I don't keep questioning it.
I wouldn't go too far here..
A controller is your view's helper. You need to generate vars and functions on your scope to help your view accomplish things.
Your business model however, is something that you would like to have one reference.
What I do is create my business model on a service, so multiple entities can share it(e.g. other services, directives, controllers etc.).
When my controller kicks in, I add a pointer to the model from the service and use the same reference between them. I bind the models properties to the view.
So:
A controller has it's own methods(dont share the service's methods). A controllers method should be short and use a service method as a helper.
A controller should have a reference to the business model which is created by a service. All your ajax calls should come from the service and populate\send the model that the service is holding.
A controller should have basic view functions(e.g. decide which css class to apply to an element). When you submit a form, the controller function should call the service's submit which will perform you ajax call.
Example:
Html:
<div ng-app="app">
<div ng-controller="myCtrl">
<input type="text" ng-model="Model.propA" />
<br/>
<input type="text" ng-model="Model.propB" />
<div ng-show="ShouldShowSecondDiv()">Second Div.</div>
<br/>
<button ng-click="SubmitForm()">Submit</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('myCtrl', function ($scope, myService) {
// simple controller "view method".
$scope.ShouldShowSecondDiv = function () {
return $scope.Model.propB > 12;
};
// "complex" "non-view method" -> use service.
$scope.SubmitForm = function () {
myService.SubmitModelToServer();
};
// get the ref. from the service.
$scope.Model = myService.GetModel();
});
app.service('myService', function () {
this.Model = {};
// perform an ajax to get the model or whatever.
this.GetModel = function () {
this.Model = {
propA: 'Im prop A',
propB: 12
};
return this.Model;
};
// submit it to the server via $http. Check the log and you will see the binding(if you changed a value in the view).
this.SubmitModelToServer = function () {
console.log("ajax or whatever....submitted");
console.log(this.Model);
};
});
JSFIDDLE.

Restangular in service

I have service:
angular.module('app1App')
.service('Fullcontactservice', function Fullcontactservice(Restangular, $http, $q) {
// AngularJS will instantiate a singleton by calling "new" on this function
var self = this;
self.apiKey = "..........."
self.route = "person"
self.getProfile = function(email){
console.log("called")
console.log(email)
Restangular.one("person").get({email: email, apiKey: self.apiKey})
.then(function(response){
console.log(response)
return response
})
}
return self;
});
Controller:
angular.module('app1App')
.controller('FullcontactCtrl', function ($scope, Fullcontactservice) {
$scope.searchFullcontact = function(){
$scope.data = Fullcontactservice.getProfile($scope.email)
}
});
When I call the searchFullcontact(), Restangular calls fullcontact and returns data but that's not pushed to the scope - I understand why. When I use promises, just results to a {} and no data is pushed.
How can I have it do that. I am trying to avoid the .then() function within my controller to keep it being slim because traditionally I had very large controllers.
Thanks!
Restangular has a handy feature allowing you to make this code work:
self.getProfile = function(email){
return Restangular.one("person").get({email: email, apiKey: self.apiKey}).$object
}
$object is effectively a shortcut where Restangular first creates an empty object which is filled with the data from the REST call once it is available. The code working with $scope.data inside your controller must be flexible to handle the initially empty object. This is usually not an issue if you use data inside the template (html) as angular gracefully handles missing (undefined).

Angularjs service to manage dataset across multiple controllers

Basically the core of my app centers around a set of data retrieved from the server via a $http request. Once the data is available to the client (as an array of objects) I require it for multiple views and would like to maintain it's state between them, for example, if it has been filtered I would like only the filtered data to be available in the other views.
Currently I have a basic service retrieving the data and am then managing the state of the data (array) in an app-wide controller (see below). This works Ok but it is beginning to become a mess as I try to maintain the array length, filtered status, visible / hidden objects across controllers for each view as I have to keep a track of currentVenue etc in the app-wide controller. Note: I am using ng-repeat in each view to show and filter the data (another reason I would like to just have it filtered in a central spot).
Obviously this is not optimal. I assume I should be using a service to maintain the array of venue objects, so it would contain the current venue, current page, be responsible for filtering the array etc. and just inject it into each controller. My question is, how can I set up a service to have this functionality (including loading the data from the server on start; this would be a good start tbh) such that I can achieve this an then bind the results to the scope. ie: something $scope.venues = venues.getVenues and $scope.current = venues.currentVenue in each views controller.
services.factory('venues', function ($http, $q) {
var getVenues = function() {
var delay = $q.defer();
$http.get('/api/venues', {
cache: true
}).success(function (venues) {
delay.resolve(venues);
});
return delay.promise;
}
return {
getVenues: getVenues
}
});
controllers.controller('AppCtrl', function (venues, $scope) {
$scope.venuesPerPage = 3;
venues.getVenues().then(function (venues) {
$scope.venues = venues;
$scope.numVenues = $scope.venues.length;
$scope.currentPage = 0;
$scope.currentVenue = 0;
$scope.numPages = Math.ceil($scope.numVenues / $scope.venuesPerPage) - 1;
}
});
Sorry for the long wording, not sure how to specify it exactly. Thanks in advance.
The tactic is to take advantage of object references. If you move your shared data to an object, then set that object to $scope, any change on $scope is directly changing the service object since they are the same thing ($scope is referencing the service).
Here's a live sample demonstrating this technique (click).
<div ng-controller="controller-one">
<h3>Controller One</h3>
<input type="text" ng-model="serv.foo">
<input type="text" ng-model="serv.bar">
</div>
<div ng-controller="controller-two">
<h3>Controller Two</h3>
<input type="text" ng-model="serv.foo">
<input type="text" ng-model="serv.bar">
</div>
js:
var app = angular.module('myApp', []);
app.factory('myService', function() {
var myService = {
foo: 'abc',
bar: '123'
};
return myService;
});
app.controller('controller-one', function($scope, myService) {
$scope.serv = myService;
});
app.controller('controller-two', function($scope, myService) {
$scope.serv = myService;
});
I threw this together quickly as a starting point. You can restructure factory any way you want. The general idea is all data in scope has now been moved to an object in factory service.
Instead of resolving the $http with just the response array, resolve it with a much bigger object that includes the array from server. Since all data is now in an object it can be updated from any controller
services.factory('venues', function ($http, $q) {
var getVenues = function(callback) {
var delay = $q.defer();
$http.get('/api/venues', {
cache: true
}).then(function (response) {
/* update data object*/
venueData.venues=response.data;
venueData.processVenueData();
/* resolve with data object*/
delay.resolve(venueData);
}).then(callback);
return delay.promise;
}
var processVenueData=function(){
/* do some data manipulation here*/
venueData.updateNumPages();
}
var venueData={
venuesPerPage : 3,
numVenues:null,
currentVenue:0,
numPages:null,
venues:[],
updateNumPages:function(){
venueData.numPages = Math.ceil(venueData.numVenues / venueData.venuesPerPage) - 1;
},
/* create some common methods used by all controllers*/
addVenue: function( newVenue){
venueData.venues.push( newVenue)
}
}
return {
getVenues: getVenues
}
});
controllers.controller('AppCtrl', function (venues, $scope) {
venues.getVenues(function (venueData) {
/* now have much bigger object instead of multiple variables in each controller*/
$scope.venueData=venueData;
})
});
Now in markup reference venueData.venues or venueData.numPages
By sharing methods across controllers you can now simply bind a form object with ng-model's to a button that has ng-click="venueData.addVenue( formModel)" (or use ng-submit) and you can add a new venue from any controller/directive without adding a bit of code to the controller

Resources