I've created a service using the CacheFactory. I was expecting it to be a singleton. I inject it into my controller and it works fine within the scope of the controller. But once I go to a different page with a different scope, I don't seem to have the values in the cache that I stored in the same controller in a different scope. Shouldn't the behavior of the CacheFactory be a singleton where I have those same cached objects everywhere I inject the CacheService?
This is my service as an example:
angular.module('MyService', []).factory('CacheService', function($cacheFactory) {
return $cacheFactory('cacheService', {
capacity: 3 // optional - turns the cache into LRU cache
})
});
Then in my controller:
function MyController($scope, CacheService) {
var results= CacheService.get('storedvalue');
if(!results){
CacheService.put('storedvalue', results);
alert('results not stored');
}
else
alert('results stored');
}
$cacheFactory is actually not the service you want to use - it's a factory that you use to create the singleton service you want to use. Eyeballing your code, it seems like it should work. But I went ahead and created a demo to prove that it does. Here's the service:
.factory('CacheService', function($cacheFactory) {
var cache = $cacheFactory('cacheService', {
capacity: 3 // optional - turns the cache into LRU cache
});
return cache;
});
In this example, CacheService is the singleton that has a local cache created with $cacheFactory, which is what we return from the service. We can inject this into any controller we want and it will always return the same values.
Here's a working Plunker: http://plnkr.co/edit/loKWGms1lMCnmiWa1QA7?p=preview
If for some reason your post doesn't contain what broke your code, please feel free to update my Plunker to break it and we can go from there.
Related
I have an extremely edge case scenario where I have a callback method I have to define during config. Which means no scope, no factories, etc... My work around is to use the root injector ($injector) and get my other modules at runtime.
However, when I call $injector.get('myServiceName') in my call back (after the application is running) I get "unknown provider". The same service has no problem (and actually is) being injected into a before my line of code is running. If I call $injector.get("myServiceNameProvider") then I can get the provider back in my callback.. But another service that only has a factory I can't get at all.
So in this extremely bad practice, how can I snag the service I configured. Heck I can't even seem to get $rootScope..
Angular inits providers first, then factories/services - services are not available in app.config.
You can deo like this:
app.config(...
window.on('scroll', function() {
// this is executed later
$injector.get()...
})
Or use app.run where services are available.
I think I had similar problem few months ago... I need to construct breadcrumbs, but they had to be created in config phase (ui-router). I have done it like this (breadcrumbs-generation-service.provider.js):
var somethingYouHaveToHaveLater = undefined;
function BreadcrumbsGenerationService () {
this.createStateData = createStateData;
function createStateData (arg) {
somethingYouHaveToHaveLater = arg;
};
}
function BreadcrumbsGenerationServiceProvider () {
this.$get = function BreadcrumbsGenerationServiceFactory () {
return new BreadcrumbsGenerationService();
}
}
angular
.module('ncAdminApp')
.provider('BreadcrumbsGenerationService', BreadcrumbsGenerationServiceProvider);
Because service is used inside Angular configs, needs to be injected as provider to be available in config phase: Similar SO Question. Despite the fact is registered as BreadcrumbsGenerationService needs to be injected as BreadcrumbsGenerationServiceProvider to config phase and used with $get():
BreadcrumbsGenerationServiceProvider.$get().createStateData(someParams);
But in controller, inject it without Provider suffix (BreadcrumbsGenerationServic) and it behaves as normal service.
I am trying to use the same service for different modules. There are many modules so i tried to inject them in a parent module. Something like this:
var app=angular.module('myapp',['module_1','module_2',....,'module_n']);
var module_1=angular.module('myapp1',[]);
var module_2=angular.module('myapp2',[]);
var module_3=angular.module('myapp3',[]);
.
.
.
var module_n=angular.module('myappN',[]);
and the service which is common to all the n modules is like this:
.service('myService',function(){
...doing something here...
});
Now I am not able to figure out how to use this service for all the submodules.
With which module should I associate this service ?
I tried doing app.service('myService',function(){...}), but it did'nt work.
Where am I going wrong?
EDIT 1:
Moreover I am trying to share a variable with all these submodules using the service. I am not sure if, I am doing the right thing by using a service for sharing variable or should I use a Provider or Factory for this job.
EDIT 2:
I found these links, but I could not grasp the answer. Refer to them and please provide my answer
How to share a variable between multiple modules in AngularJS
Passing variable between controllers which are on different modules
Lets suppose you want to build a Service to share a certain variable between two Controllers. You should be able to use your Service doing the following:
MyService.js
// Lets suppose you want to share a certain variable between controllers
angular
.module('myApp')
.service('myService', function () {
// If you wish you can inject and use $scope
var vm = this;
// Variable to share
vm.sharedItem;
// Method to set a certain value into a variable
function setItem(item){
vm.sharedItem = item;
}
// Method to get that variable
function getItem(){
return vm.sharedItem;
}
// Exposing your methods
return {
setItem : setItem
getItem : getItem
}
});
SetController.js
angular
.module('myApp')
.controller('SetController', SetController);
// Inject your Service
function SetController(myService) {
var vm = this;
// variable used to set the value
vm.setMe = 'hello';
// Call `setItem()` method from `myService` -> sharedItem will get setMe value
myService.setItem(vm.setMe);
console.log("Set shared item "+vm.setMe);
};
GetController.js:
angular
.module('myApp')
.controller('GetController', GetController);
// Inject your Service
function SetController(myService) {
var vm = this;
// variable used to get shared the value
vm.getMe= null;
/* Call `getItem()` method from `myService` to get the shared
* value and assign it to `getMe`*/
vm.getMe = myService.getItem();
console.log("Got shared item "+vm.getMe);
};
I remind you can access this.var in your view using controllerName.var. It is a good solution to make sure you are using a certain controller. You can always use $scope if you wish.
I hope I've been helpful.
I'm using Angular to develop commenting functionality for a web app.
Currently there are two sections in the application were a user can comment:
Category
Product
About 90% of the commenting functionality is the same for both sections and as such I would like to make this reusable - i.e write some service or controller that I can reference/use as a base.
So far, my research seems to point to using a factory service but unfortunately this doesn't seem to work (I've spent the whole day running through various tutorials).
It is quite possible that I am over thinking this and making it far too complicated but I honestly don't know which way to turn anymore.
Herewith a quick and dirty overview of what I have so far:
HTML view for the category
Controller for the category (receives data from service and posts data to service in order to bind data to model)
Service for the category (retrieve and stores all the necessary
data)
The product uses the same logic and a lot of the code in the service and controller will be duplicated.
I've merged the two services into one service successfully but I'm having trouble doing the same for the controller.
Do I:
Write a base controller that will communicate with the above mentioned service and that will hookup with the two existing controllers
OR
Write a factory/provider service that hooks up to the two existing controllers as well as the above mentioned service.
If you go the route of using a factory, you could put all the common functionality into its return object and reference that from your controllers.
Factory
angular.module('myModule').factory('CommonFunctions', function(){
return {
foo : foo,
bar : bar
}
function foo(){
console.log('foo');
};
function bar (){
console.log('bar');
};
}
Controller
angular.module('myModule')
.controller('myController', ['CommonFunctions', function(CommonFunctions) {
var vm = this;
vm.foo = CommonFunctions.foo();
vm.bar = CommonFunctions.bar();
}
angular's separation of service types ie:
for specific values
constant
value
(constant for specific values needed before other services are created)
for functions
factory
service
provider
(provider for specific instances when you need a services before other services are created, usually taking advantage of constants)
allow the ability to share data and ways to process that data between controllers and or directives, anything that can be a value can also be a constant, the only difference there being where they can be injected. Similarly any service can be rewritten to a factory or a provider, it is more your specific use case / what your more comfortable writing that would determine which to use, but really the best way to think about it is if you have a value that needs to be shared but is not needed inside angular.module.config then use value, otherwise use constant, now if you have a single function that you want to share, (maybe it processes that value in some way or maybe it just does something else) you should write it as a factory, then when you have a few of those factory's that deal with either that value, or anything else, you can combine them into a service or configure and combine them using a provider. here is a simple example (note i am using the recommended syntax for writing angular services):
'use strict';
var app = angular.module('test.app',[]);
app.constant('configureableValue',{defaultValue:55});
app.value('editableValue',{defaultValue:100,editedValue:null});
app.provider('configureValue',configureValueProvider);
configureValueProvider.$inject - ['configureableValue'];
function configureValueProvider(configureableValue){
var defaultVal = configureableValue.defaultValue,
originalVal = defaultVal;
return {
getValue:getValue,
setValue:setValue,
resetValue:resetValue,
'$get':providerFunc
};
function getValue(){
return defaultVal;
}
function setValue(val){
defaultVal = val;
}
function providerFunc(){
return {
get:function(){ return getValue(); },
reset:function(){ resetValue(); }
};
}
function resetValue(){
defaultVal = originalVal
}
}
// this factory is an example of a single function service, this should almost always be defined as a factory
app.factory('getEditableValue',getEditableValue);
getEditableValue.$inject = ['editableValue'];
function getEditableValue(editableValue){
return function(){
return editableValue.editedValue ? editableValue.editedValue : editableValue.defaultValue;
};
}
// same with this one
app.factory('setEditableValue',setEditableValue);
setEditableValue.$inject = ['editableValue'];
function setEditableValue(editableValue){
return function(val){
editableValue.editedValue = val;
}
}
// now this is an example of a service service collecting the factorys for an object with all the related behavior we need
app.service('editableService',editableService);
editableService.$inject = ['getEditableValue','setEditableValue'];
function editableService(getEditableValue,setEditableValue){
var self = this;
self.setVal = setEditableValue;
self.getVal = getEditableValue;
}
app.config(appConfig);
appConfig.$inject = ['configureValueProvider'];
function appConfig(configureValueProvider){
configureValueProvider.setValue('i changed '+ configureValueProvider.getValue() +' to this!!!!');
}
app.run(appRun);
appRun.$inject = ['configureValue','editableService'];
function appRun(configureValue,editableService){
console.log('before editing: ',editableService.getVal());
editableService.setVal('changed!!!');
console.log('after editing: ',editableService.getVal());
console.log('we changed this in the config func: ',configureValue.get());
configureValue.reset();
console.log('and now its back to the original value: ',configureValue.get());
}
i know thats a lot for a simple example, but there are a lot of features provided by angular, and many ways to use them, hopefully this helps.
Today I found, that $injector injected to config or provider is different from $injector injected to service, factory or controller.
And get() function from this $injectors works differently.
$injector from config or provider, can't get() any service! $injector.get('myService') throws Error: [$injector:unpr] Unknown provider: myService, but $injector.has('myService') return true. That's very very strange.
$injector from service or controller works normally.
Here is a code sample for better understanding:
angular.module('app', [])
.provider('myProvider', function ($injector) {
this.$get = ['$injector', function (serviceInjector) {
return {
providerInjector: $injector,
serviceInjector: serviceInjector
};
}];
})
.service('myService', function () {})
.controller('myCtrl', function ($scope, myProvider) {
var providerInjector = myProvider.providerInjector;
var serviceInjector = myProvider.serviceInjector;
console.log(providerInjector === serviceInjector); // -> false
console.log(serviceInjector.has('myService')); // `serviceInjector` has `myService`
console.log(getMyService(serviceInjector)); // `serviceInjector` can get `myService`
console.log(providerInjector.has('myService')); // `providerInjector` has `myService` too!
console.log(getMyService(providerInjector)); // but `providerInjector` can't get `myService`! =(
function getMyService(injector) {
try {
injector.get('myService');
return "OK";
} catch (e) {
return e.toString();
}
}
});
Here is a plunker to play
Can anybody explain why there is two different injectors?
And how can I use $injector from provider/config to inject service(after service was initialized, of course)?
P.S. I use angular 1.3.13
I found this issue on github: https://github.com/angular/angular.js/issues/5559
In the config function, $injector is the provider injector, where in the run function, $injector is the instance injector.
One's the $injector at the config stage (only providers and constants accessible), and one's the $injector at the run stage. The confusion may be that you're thinking the $injector modifies itself to include the new stuff as it crosses the line from config to run, but that's not true. They're two separate (although related) objects, with their own caches of instances.
A more in-depth reason for this dichotomy will probably come from a deep learning of the $injector internals, but it seems like it's been DRY-ed pretty hardcore, and the two types of injectors share almost all the same behavior, except in how they deal with "cache misses" in their instance caches.
We are going to overhaul the injector in v2, so this will get fixed there (getting rid of the config phase is one of the objectives of the injector v2).
Seems like there is really two different injectors, and angular developers will not fix that behavior(in versions <2.0). And nobody added a note about that aspect to $injector docs for some reason.
I was unable to find a way how to really get instance injector inside a configuration block without hacky tricks. So, I write a cute provider to solve that kind of problems.
.provider('instanceInjector', function () {
var instanceInjector;
function get() {
return instanceInjector;
}
function exists() {
return !!instanceInjector;
}
angular.extend(this, {
get: get,
exists: exists
});
this.$get = function ($injector) {
instanceInjector = $injector;
return {
get: get,
exists: exists
};
}
})
// We need to inject service somewhere.
// Otherwise $get function will be never executed
.run(['instanceInjector', function(instanceInjector){}])
Ok. After reading your comments, here is my answer.
I edited code in plunk to make it work, when invoking the providerInjector.get() the code should be as follows:
$scope.getMyServiceFromProviderInjector = function () {
try {
myProvider.providerInjector.get('myServiceProvider');//here is change in provider name
return "OK";
} catch (e) {
return e.toString();
}
};
According to angular docs the following is quoted for config and run blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants
can be injected into configuration blocks. This is to prevent
accidental instantiation of services before they have been fully
configured.
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be
injected into run blocks. This is to prevent further system
configuration during application run time.
This simply means, you cannot get instances of services inside config blocks.
I wrote this a while back, which explains the lifecycle of both injectors of AngularJS, i.e providerInjector and instanceInjector.
http://agiliq.com/blog/2017/04/angularjs-injectors-internals/
In my Angular app, I have some resource modules, each containing some cache factories.
For example,
projectRsrc.factory('getProjectCache', ['$cacheFactory', function($cacheFactory){
return $cacheFactory('getProjectCache');
}]);
I have a few of these to cache values received from the servers.
The problem is that at times I'd like to clear all the caches. So I want to put all the cacheFactories into one CacheCentralApp module and delete all the caches with a single call.
The trouble is, I don't know of any way to access other factories inside my module. So for instance, if I create a module CacheCentralApp, and in it, declare factories that provide cacheFactorys, how can I create a function in there that calls removeAll() on every cacheFactory?
I don't think it is possible to target all the factories of a certain module. I think however that another solution to your problem is to send a event that all factories has to be cleared. This will prevent that you will have to loop through all your factories and call a .clear() function on everyone.
You could send a event request with the following code:
$scope.$broadcast('clearAllCaches');
And listen to this event in every factory with:
$scope.$on('clearAllCaches', function() {
clearCache();
}
In a separate module you might create a factory for that:
var cacheModule = angular.module('CacheCentralApp', []);
cacheModule.factory('MyCacheFactory', ['$cacheFactory', function($cacheFactory) {
var cacheKeys = [];
return {
clearAll: function() {
angular.forEach(cacheKeys, function(key) {
$cacheFactory.get(key).removeAll();
});
},
get: function(key) {
if(cacheKeys.indexOf(key) == -1) {
cacheKeys.push(key);
return $cacheFactory(key);
} else {
return $cacheFactory.get(key);
}
}
}
}]);
To create new or get existing Cache you simply call MyCacheFactory.get(cacheName). To clear all the caches ever created in the factory you call MyCacheFactory.clearAll().
Note: I am not quite sure that Array.indexOf is available in every browser, you might want to use Lo-Dash or another library to make sure your code works.