I have requirement where i need to share data between controllers,so i created factory
suppose i have url like: abc/123 (Note:123 is dynamic i need to pass it from controller)
MyCode
appServices.factory('classesService', function($http){
return {
getClasses: function(value) {
return $http.get(urlprefix + 'orgs/' + value + '/classes?with-users=true');
}
};
});
In Controller
classesService.getClasses($scope.organization.id).then(function(data){});
now suppose i am using it in 3 contollers 3 calls will go to server. i dont want three calls i want only one call.
Note:value is same for all three controller
is there any way i can acheive this.
Thanks
You can use UI-ROUTER-EXTRAS "https://christopherthielen.github.io/ui-router-extras/#/home"
The easiest solution is to just enable caching in your service. This way all your controllers can access the method and the request is made only once.
return $http.get(urlprefix + 'orgs/' + value + '/classes?with-users=true', {cache: true});
This is very basic caching. You may need a more advanced strategy -- but this is your first step.
UPDATE
You may run into a race condition with simple $http caching if your second or third request are made before the first request completes. We can overcome this easily.
appServices.factory('classesService', function($http) {
var orgsPromise;
return {
getClasses: function(value) {
if (orgsPromise) return orgsPromise;
orgsPromise = $http.get(urlprefix + 'orgs/' + value + '/classes?with-users=true');
return orgsPromise;
}
};
});
I believe you can do something like this;
var yourApp = angular.module('yourApp',[]);
yourApp.factory('classesService', function($http){
var classes;
return {
getClasses: function(value) {
if (!classes) {
classes= $http.get(urlprefix + 'orgs/' + value + '/classes?with-users=true');
}
return classes;
}
};
});
yourApp.factory('Classes', ['classesService',function(classesService){
return classesService.getClasses();
}]);
function yourControllerA($scope, Classes) {
$scope.value="Hello from Controller A";
$scope.sharedValue=Classes;
....
}
function yourControllerB($scope, Classes) {
$scope.value="Hello from Controller B";
$scope.sharedValue=Classes;
....
}
function yourControllerC($scope, Classes) {
$scope.value="Hello from Controller C";
$scope.sharedValue=Classes;
....
}
Related
I am trying to pass data from directive to controller via service, my service looks like this:
angular
.module('App')
.factory('WizardDataService', WizardDataService);
WizardDataService.$inject = [];
function WizardDataService() {
var wizardFormData = {};
var setWizardData = function (newFormData) {
console.log("wizardFormData: " + JSON.stringify(wizardFormData));
wizardFormData = newFormData;
};
var getWizardData = function () {
return wizardFormData;
};
var resetWizardData = function () {
//To be called when the data stored needs to be discarded
wizardFormData = {};
};
return {
setWizardData: setWizardData,
getWizardData: getWizardData,
resetWizardData: resetWizardData
};
}
But when I try to get data from controller it is not resolved (I think it waits digest loop to finish), So I have to use $timeout function in my controller to wait until it is finished, like this:
$timeout(function(){
//any code in here will automatically have an apply run afterwards
vm.getStoredData = WizardDataService.getWizardData();
$scope.$watchCollection(function () {
console.log("getStoredData callback: " + JSON.stringify(vm.getStoredData));
return vm.getStoredData;
}, function () {
});
}, 300);
Despite of the fact that it works, what I am interested in is, if there is a better way to do this, also if this is bug free and the main question, why we use 300 delay and not 100 (for example) for $timeout and if it always will work (maybe for someone it took more time than 300 to get data from the service).
You can return a promise from your service get method. Then in your controller, you can provide a success method to assign the results. Your service would look like this:
function getWizardData() {
var deferred = $q.defer();
$http.get("/myserver/getWizardData")
.then(function (results) {
deferred.resolve(results.data);
}),
function () {
deferred.reject();
}
return deferred.promise;
}
And in your ng-controller you call your service:
wizardService.getWizardData()
.then(function (results) {
$scope.myData = results;
},
function () { });
No timeouts necessary. If your server is RESTFULL, then use $resource and bind directly.
Use angular.copy to replace the data without changing the object reference.
function WizardDataService() {
var wizardFormData = {};
var setWizardData = function (newFormData) {
console.log("wizardFormData: " + JSON.stringify(wizardFormData));
angular.copy(newFormData, wizardFormData);
};
From the Docs:
angular.copy
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
Usage
angular.copy(source, [destination]);
-- AngularJS angular.copy API Reference
This way the object reference remains the same and any clients that have that reference will get updated. There is no need to fetch a new object reference on every update.
I have been stuck on this for quite a while now and cannot figure out why the value is not being returned. I am using Angular $resource to make a GET request to an API.
My $resource factory looks like this:
.factory("Bookings", function ($resource) {
return $resource("www.example/bookings_on_date/:day", {});
})
I have tried to implement promises but am unable to do so correctly.
function getBookings(day){
return Bookings.get({"day": day}).$promise.then(function(data) {
console.log(data.total)
return data.total;
});
}
$scope.todaysBookings = getBookings(TODAY);
$scope.tomorrowsBookings = getBookings(TOMORROW);
When I view either console.log($scope.todaysBookings) or $scope.tomorrowBookings in the console it returns undefined.
I have also tried everything from this jsfiddle but unfortunately have not had any luck.
I think it should be like this:
function getBookings(day) {
return Bookings.get({"day": day}).$promise.then(function(data) {
return data.total;
});
}
getBookings(TODAY).then(function(total) {
$scope.todaysBookings = total;
});
getBookings(TOMORROW).then(function(total) {
$scope.tomorrowsBookings = total;
});
Update: I think next code style could help you to prevent next extending method problems:
function getBookings(args) {
return Bookings.get(args).$promise;
}
getBookings({"day": TODAY}).then(function(data) {
$scope.todaysBookings = data.total;
});
getBookings({"day": TOMORROW}).then(function(data) {
$scope.tomorrowsBookings = data.total;
});
A little advantages here:
Pass object into function could help you easily pass different
arguments into method and arguments are very close to method call (a
little bit easy to read);
Return complete response from function
could help you to process different data (method could replay
different response depends on arguments, but it's not good practise
in this case);
p.s. Otherwise, you could remove function declaration and code like this (to keep it as simple, as possible):
Bookings.get({"day": TODAY}).$promise.then(function(data) {
$scope.todaysBookings = data.total;
});
Bookings.get({"day": TOMORROW}).$promise.then(function(data) {
$scope.tomorrowsBookings = data.total;
});
I have 2 services and would like to update a variable in the 1st service from the 2nd service.
In a controller, I am setting a scope variable to the getter of the 1st service.
The problem is, the view attached to the controller doesn't update when the service variable changes UNLESS I use angular.extend/copy. It seems like I should just be able to set selectedBuilding below without having to use extend/copy. Am I doing something wrong, or is this how you have to do it?
controller
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
$scope.building = BuildingsService.getSelectedBuilding();
});
service 1
app.factory('BuildingsService', function() {
var buildingsList = [];
var selectedBuilding = {};
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
angular.extend(selectedBuilding, _.find(
buildingsList, {'building_id': buildingId})
);
};
var getSelectedBuilding = function() {
return selectedBuilding;
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
service 2
app.factory('AnotherService', function(BuildingsService) {
...
// something happens, gives me a building id
BuildingsService.setSelectedBuilding(building_id);
...
});
Thanks in advance!
When you execute this code:
$scope.building = BuildingsService.getSelectedBuilding();
$scope.building is copied a reference to the same object in memory as your service's selectedBuilding. When you assign another object to selectedBuilding, the $scope.building still references to the old object. That's why the view is not updated and you have to use angular.copy/extend.
You could try the following solution to avoid this problem if you need to assign new objects to your selectedBuilding:
app.factory('BuildingsService', function() {
var buildingsList = [];
var building = { //create another object to **hang** the reference
selectedBuilding : {}
}
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
//just assign a new object to building.selectedBuilding
};
var getSelectedBuilding = function() {
return building; //return the building instead of selectedBuilding
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
With this solution, you have to update your views to replace $scope.building bindings to $scope.building.selectedBuilding.
In my opinion, I will stick to angular.copy/extend to avoid this unnecessary complexity.
I dont believe you need an extend in your service. You should be able to watch the service directly and respond to the changes:
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
// first function is evaluated on every $digest cycle
$scope.$watch(function(scope){
return BuildingsService.getSelectedBuilding();
// second function is a callback that provides the changes
}, function(newVal, oldVal, scope) {
scope.building = newVal;
}
});
More on $watch: https://code.angularjs.org/1.2.16/docs/api/ng/type/$rootScope.Scope
In my project i have to create dynamic factory in angular js with dynamic factory name like below
function createDynamicFactory(modId) {
return myModule.factory(modId + '-existingService', function (existingService) {
return existingService(modId);
});
}
I want to get new factory with dynamic name , when i called this function. unfortunately this is not working. how can i achieve this? and how to inject in my controller or in directive dynamically?
You can annotate your service generator like this. It takes the module and extension and then annotates a dependency on the "echo" service (just an example service I defined to echo back text and log it to the console) so the generated service can use it:
makeService = function(module, identifier) {
module.factory(identifier+'-service', ['echo', function(echo) {
return {
run: function(msg) {
return echo.echo(identifier + ": " + msg);
}
};
}]);
};
Then you can make a few dynamic services:
makeService(app, 'ex1');
makeService(app, 'ex2');
Finally, there are two ways to inject. If you know your convention you can pass it in with the annotation as the ext1 is shown. Otherwise, just get an instance of the $injector and grab it that way.
app.controller("myController", ['ex1-service',
'$injector',
'$scope',
function(service, $injector, $scope) {
$scope.service1 = service.run('injected.');
$scope.service2 = $injector.get('ex2-service').run('dynamically injected');
}]);
Here is the full working fiddle: http://jsfiddle.net/jeremylikness/QM52v/1/
Updated: if you want to create the service dynamically after the module is initialized, it's a few slight changes. Instead of trying to register the module, simply return an annotated array. The first parameters are the dependencies and the last is the function to register:
makeService = function(identifier) {
return ['echo', function(echo) {
console.log("in service factory");
return {
run: function(msg) {
return echo.echo(identifier + ": " + msg);
}
};
}];
};
Then you get a reference to the array and call instantiate on the $injector to wire it up with dependencies:
var fn = makeService('ex2');
$scope.service2 = $injector.instantiate(fn).run('dynamically injected');
Here's the fiddle for that version: http://jsfiddle.net/jeremylikness/G98JD/2/
I have a resource that returns an array from a query, like so:
.factory('Books', function($resource){
var Books = $resource('/authors/:authorId/books');
return Books;
})
Is it possible to add prototype methods to the array returned from this query? (Note, not to array.prototype).
For example, I'd like to add methods such as hasBookWithTitle(title) to the collection.
The suggestion from ricick is a good one, but if you want to actually have a method on the array that returns, you will have a harder time doing that. Basically what you need to do is create a bit of a wrapper around $resource and its instances. The problem you run into is this line of code from angular-resource.js:
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
This is where the return value from $resource is set up. What happens is "value" is populated and returned while the ajax request is being executed. When the ajax request is completed, the value is returned into "value" above, but by reference (using the angular.copy() method). Each element of the array (for a method like query()) will be an instance of the resource you are operating on.
So a way you could extend this functionality would be something like this (non-tested code, so will probably not work without some adjustments):
var myModule = angular.module('myModule', ['ngResource']);
myModule.factory('Book', function($resource) {
var service = $resource('/authors/:authorId/books'),
origQuery = service.prototype.$query;
service.prototype.$query = function (a1, a2, a3) {
var returnData = origQuery.call(this, a1, a2, a3);
returnData.myCustomMethod = function () {
// Create your custom method here...
return returnData;
}
}
return service;
});
Again, you will have to mess with it a bit, but that's the basic idea.
This is probably a good case for creating a custom service extending resource, and adding utility methods to it, rather than adding methods to the returned values from the default resource service.
var myModule = angular.module('myModule', []);
myModule.factory('Book', function() {
var service = $resource('/authors/:authorId/books');
service.hasBookWithTitle = function(books, title){
//blah blah return true false etc.
}
return service;
});
then
books = Book.list(function(){
//check in the on complete method
var hasBook = Book.hasBookWithTitle(books, 'someTitle');
})
Looking at the code in angular-resource.js (at least for the 1.0.x series) it doesn't appear that you can add in a callback for any sort of default behavior (and this seems like the correct design to me).
If you're just using the value in a single controller, you can pass in a callback whenever you invoke query on the resource:
var books = Book.query(function(data) {
data.hasBookWithTitle = function (title) { ... };
]);
Alternatively, you can create a service which decorates the Books resource, forwards all of the calls to get/query/save/etc., and decorates the array with your method. Example plunk here: http://plnkr.co/edit/NJkPcsuraxesyhxlJ8lg
app.factory("Books",
function ($resource) {
var self = this;
var resource = $resource("sample.json");
return {
get: function(id) { return resource.get(id); },
// implement whatever else you need, save, delete etc.
query: function() {
return resource.query(
function(data) { // success callback
data.hasBookWithTitle = function(title) {
for (var i = 0; i < data.length; i++) {
if (title === data[i].title) {
return true;
}
}
return false;
};
},
function(data, response) { /* optional error callback */}
);
}
};
}
);
Thirdly, and I think this is better but it depends on your requirements, you can just take the functional approach and put the hasBookWithTitle function on your controller, or if the logic needs to be shared, in a utilities service.