Can I pass a param from controller into service in AngularJS? - angularjs

Let's assume I have some service and some controller. What that service will return depends of what the controller will pass into it. But is it possible indeed? I suspect a service may look like this:
var app = angular.module('tApp',[])
.provider('getFileContents', function(param){
this.paramId = param;
this.$get = function(){
var par = this.paramId;
return{
getContents:function(){
return "paramId comes here: "+par;
}
}
}
});
then I think my controller should look like this:
app.controller('test_controlController',
function test_controlController($scope,getFileContents){
$scope.contents = getFileContents.getContents('test_control');
console.dir($scope.contents);
});
...but it doesn't work. It says:
Uncaught Error: Unknown provider: param from tApp
So is it possible to make it working?

You are adding a parameter to the service constructor instead to the service function. and you are using a provider instead of a service or factory, you can get some information about the difference between services/factories and providers here:
Angular Service VS Provider VS Factory
Back to your code, make to following changes:
Service:
app.service('FileService', function () {
return {
getFileContents : function (fileID) {
//function logic goes here
console.log(fileID);
}
}
});
Controller:
app.controller('TestController', function ($scope,getFileContents) {
$scope.contents = getFileContents.getFileContents(123);
});

Add a parameter for your getContents method in your service
return{
getContents:function(foo){
return "paramId comes here: "+ foo;
}
}

Related

http post and get service

i have a service as follows
angular.module('starter.service')
.factory('authService', function($http) {
var service = {};
service.GetByUsername = function() {
return $http.get('/js/user.json');
}
return service;
})
i just need to know two things,
1. why its is declaring a object named service ?
2.its a service for getting an object, what changes should i change to add another function to post a object(do it in the same code)? dont remove current functionality.
You are using the factory method to create a service, so you need to create an object and return it (Dependency Injection will make sure this object is only instantiated once so it is used as a singleton).
Whether this object you are creating is called "service" or any other way, it doesn't matter, it will work anyway.
As Shankar shown in his example, adding more methods to your service is as easy as adding more methods to the object you are declaring. To clarify the example, I'll add the argument you want to post, and let whoever is using the service to decide what to do with the returned promise (as you do in GET method):
angular.module('starter.service')
.factory('authService', function($http) {
var service = {};
service.GetByUsername = function() {
return $http.get('/js/user.json');
}
service.PostUser = function(user) {
return $http.post("/url/to/post/user", user);
}
return service;
})
You question is not clear, but you can simply add new function to you factory service,
angular.module('starter.service')
.factory('authService', function($http) {
var service = {};
service.GetByUsername = function() {
return $http.get('/js/user.json');
}
service.PostUser = function() {
var data = {}
$http.post("/js/user.json", data).success(function(data, status) {
})
}
return service;
})

passing data from controller to service in angularjs

I am new to angular js.
I need to access data from my controller to service.
I tried using $rooyScope but my service needs to be loaded first and then my controller so using $rooyScope gives me error.
so i am unable to get the data stored in $rooyScope on service.
Can any suggest me a options that suits my expectation
Thank you for your help
Use functions in the service to set a service variable
angular.module('myServiceModule', []).
factory('testService', function() {
var myVariable;
return setVariable: function(parameter) {
myVariable = parameter;
// DO SOMETHING ELSE IF YOU WANT
},
return getVariable: function() {
return myVariable;
}
});
then in your controller inject the service and call the service function
angular.module('myControllerModule', []).
controller('testController, ['testService', function(testService) {
testService.setVariable('TEST');
console.log(testService.getVariable());
}]);

Can the Angular $injector be decorated with $provide.decorator?

Perhaps this is a terrible idea, but if it is then please tell me why and then pretend that it's an academic exercise that won't see the light of day in production.
I'd like to add some logic to the Angular $injector service, to monitor when certain services are injected into other services. Since it seems that Angular provides a mechanism for decorating services, I thought this would be the way to go. However, the following code throws an error.
(function () {
'use strict';
var app = angular.module('app');
app.config(['$provide', function ($provide) {
$provide.decorator('$injector', ['$log', '$delegate', addLoggingToInjector]);
}]);
function addLoggingToInjector($log, $delegate) {
var baseInstantiate = $delegate.instantiate;
var baseInvoke = $delegate.invoke;
$delegate.instantiate = function (type, locals) {
// $log.debug('Calling $injector.instantiate');
baseInstantiate(type, locals);
};
$delegate.invoke = function (fn, self, locals) {
// $log.debug('Calling $injector.invoke');
baseInvoke(fn, self, locals);
};
return $delegate;
};
})();
The specific error is:
Uncaught Error: [$injector:modulerr] Failed to instantiate module app
due to: Error: [$injector:unpr] Unknown provider: $injectorProvider
The answer is: no.
$provide.decorator is used to intercept service creation -- that is why it is called from .config block, when there is still time to configure all services, as none of them has been created. $provide.decorator basically gets the Provider of the service and swaps its $get with newly delivered decorFn.
$injector is not like other services. It is created, as the very first step of bootstrapping an application -- way before app.config is called. [look at functions: bootstrap and createInjector in angular source code]
But hey, you can achieve your goal quite easily by tweaking the source code just a bit :-) Particularly look at function invoke(fn, self, locals).
UPDATE I got some inspiration from #KayakDave. You actually do not have to dig in the source-code itself. You can use the following pattern to observe each call to any of $injector methods:
app.config(['$injector', function ($injector) {
$injector.proper =
{
get : $injector.get,
invoke : $injector.invoke,
instantiate : $injector.instantiate,
annotate : $injector.annotate,
has : $injector.has
}
function getDecorator(serviceName)
{
console.log("injector GET: ", serviceName);
return this.proper.get(serviceName);
}
function invokeDecorator(fn, self, locals)
{
console.log("injector INVOKE: ", fn, self, locals);
return this.proper.invoke(fn, self, locals);
}
function instantiateDecorator(Type, locals)
{
console.log("injector INSTANTIATE: ", Type, locals);
return this.proper.instantiate(Type, locals);
}
function annotateDecorator (fn)
{
console.log("injector ANNOTATE: ", fn);
return this.proper.annotate(fn);
}
function hasDecorator(name)
{
console.log("injector HAS: ", name);
return this.proper.has(name);
}
$injector.get = getDecorator;
$injector.invoke = invokeDecorator;
$injector.instantiate = instantiateDecorator;
$injector.annotate = annotateDecorator;
$injector.has = hasDecorator;
}]);
PLNKR
You can't use the Angular decorator service on $injector. As Artur notes $injector is a bit different from other services. But we can create our own decorator.
Why we can't use Angular's decorator
At the code level the issue is that $injector doesn't have a constructor function- there's no $injectorProvider.
For example both of these return true:
$injector.has('$location');
$injector.has('$locationProvider')
However, while this returns true:
$injector.has('$injector')
this returns false:
$injector.has('$injectorProvider')
We see the importance when we look at the Angular decorator function:
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
And
providerSuffix = 'Provider'
So the Angular decorator expects to operate on the service's constructor (serviceName + providerSuffix). Pragmatically, since we don't have an $injectorProvider we can't use decorator.
Solution
What we can do is override the Angular injector's get function ourselves by replacing the injector's default get with one that calls the original, Angular defined, get followed by our function.
We'll apply this to $injector rather than the nonexistent $injectorProvider like so:
app.config(['$provide','$injector', function ($provide,$injector) {
// The function we'll add to the injector
myFunc = function () {
console.log("injector called ", arguments);
};
// Get a copy of the injector's get function
var origProvider = $injector,
origGet = origProvider.get;
//Override injector's get with our own
origProvider.get = function() {
// Call the original get function
var returnValue = origGet.apply(this, arguments);
// Call our function
myFunc.apply(this,arguments);
return returnValue;
}
}]);
You'll see the provider being injected is the first augment, so app.value('aValue', 'something'); yields the following log statement:
injector called ["aValueProvider"]
Demo fiddle

Share methods between custom directives

What is the most simple way to share a method between 2 directives?
I've tried using a factory and injecting that in my directives. But then I can't pass parameters to the factory. So I can get data back from my factory but I can't make the factory dynamic.
.directive('myFirstDirective', [...])
.directive('seconDirective', [...])
.factory('MenuItems', [function(){
return "testString";
}]);
By adding the factory to my code I can do in any directive:
var test = MenuItems;
But what I wan't to do is:
var test = MenuItems(myParameter); //so I can change the return in menuItems depending on myParameter
You can use a service to do that:
https://gist.github.com/anonymous/50b659c72249b58c31bf
.factory('MenuItemsService', [function(){
return {
getMenuItems : function(parameter){
if ( parameter === 'foo' ){
return ['bar', 'jar', 'tar'];
} else {
return ['asd', 'bsd', 'csd'];
}
}
};
}]);
Then in each directive you can inject the service, e.g:
MenuItemsService.getMenuItems('foo');
MenuItemsService.getMenuItems('bar');
To share data creating a service is the right thing to do.
Create a function on your service to process the data
.factory('MenuItems', function(){
var someDataToShare = ...
return {
someFunction: function(data) {
// process data here
return someDataToShare
}
}
});
call it like this:
$scope.processedData = MenuItems.someFunction($scope.someData)

How can I directly inject $http into a provider?

All I need to do is to download a json file and assign it to OCategories in PCategory provider after I set the path. However I get an error that $http doesnt exist. How can I inject it into my provider and download inside of the setPath function?
var app = angular.module('NSApp',
[
'ui.bootstrap',
'MDItem',
'MDUser',
'MDNotification',
'MDUpload'
]
);
app.config(function(PCategoriesProvider)
{
PCategoriesProvider.setPath('data/csv/categories.json');
});
MDItem/provider/category.js
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set. Trying to download categories.');
OCategories = $http.get(oPath);
},
$get : function() {
return {
categories : OCategories
}
}
}
});
You can never inject service instances into config functions or providers, since they aren't configured yet. Providers exist to configure specific services before they get injected. Which means, there's always a corresponding provider to a certain service. Just to clarify, here's a little example configuring $location service using $locationProvider:
angular.module('myModule').config(function ($locationProvider) {
$locationProvider.html5Mode(true);
});
So what happens here, is that we configure $location service to use its html5mode. We do that by using the interfaces provided by $locationProvider. At the time when config() is executed, there isn't any service instance available yet, but you have a chance to configure any service before they get instantiated.
Later at runtime (the earliest moment ist the run() function) you can inject a service. What you get when injecting a service is what its providers $get() method returns. Which also means, each provider has to have a $get() function otherwise $injector would throw an error.
But what happens, when creating custom services without building a provider? So something like:
angular.module('myModule').factory('myService', function () {
...
});
You just don't have to care about, because angular does it for you. Everytime you register any kind of service (unless it is not a provider), angular will set up a provider with a $get() method for you, so $injector is able to instantiate later.
So how to solve your problem. How to make asynchronous calls using $http service when actually being in configuration phrase? The answer: you can't.
What you can do, is run the $http call as soon as your service gets instantiated. Because at the time when your service get instantiated, you're able to inject other services (like you always do). So you actually would do something like this:
angular.module('myModule').provider('custom', function (otherProvider, otherProvider2) {
// some configuration stuff and interfaces for the outside world
return {
$get: function ($http, injectable2, injectable3) {
$http.get(/*...*/);
}
};
});
Now your custom provider returns a service instance that has $http as dependency. Once your service gets injected, all its dependencies get injected too, which means within $get you have access to $http service. Next you just make the call you need.
To make your this call is getting invoked as soon as possible, you have to inject your custom service at run() phrase, which looks like this:
angular.module('myModule').run(function (custom, injectable2) {
/* custom gets instantiated, so its $http call gets invoked */
});
Hope this makes things clear.
Since all services are singletons in angular you could simply store a variable in a factory with the $http promise. And then when the factory is called at startup it will download the json.
You can then also expose a method on the factory that refreshes the data.
I know this is not the exact answer to your question, but I thought I'd share how I would do it.
angular.module('MDItem').factory('PCategories', function ($http, PCategoriesPath) {
var service = {
categories: [],
get: function () {
if (angular.isUndefined(PCategoriesPath)) {
throw new Error('PCategoriesPath must be set to get items');
}
$http.get(PCategoriesPath).then(function (response) {
service.categories = response.data;
});
}
};
// Get the categories at startup / or if you like do it later.
service.get();
return service;
});
// Then make sure that PCategoriesPath is created at startup by using const
angular.module('MDItem').const('PCategoriesPath', 'data/csv/categories.json');
angular.module('app').controller('myCtrl', function ($scope, PCategories) {
$scope.categories = PCategories.categories;
// And optionally, use a watch if you would like to do something if the categories are updated via PCategories.get()
$scope.$watch('categories', function (newCategories) {
console.log('Look maa, new categories');
}, true); // Notice the true, which makes angular work when watching an array
})
You have to inject $http in the function $get, because that's the function called by the injector.
However, to download the categories you would be better off using promises:
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set');
},
$get : function($http) {
return {
fetch: function () {
var deferred = $q.defer();
$http.get(oPath).then(function (value) {
deferred.resolve(value);
}
return deferred.promise;
}
}
}
}
});
I implemented what I wanted with a diffrent approach which is quite simple and effective. Just add a dummy controller in the main index.html(NOT PARTIAL). Data is now shared between all my modules and controllers and everything is downloaded once. :) Oh I love AJ.
...
<div ng-controller="initController" hidden></div>
...
initController:
angular.module('NSApp',[]).controller("initController",function($scope, $http, FCategory, FLocation){
$http.get('data/json/categories.json').then(function (response) {
FCategory.categories = response.data;
});
$http.get('data/json/cities.json').then(function (response) {
FLocation.cities = response.data;
});
$http.get('data/json/regions.json').then(function (response) {
FLocation.regions = response.data;
});
});
And now you can access it:
angular.module('MDTest', []).controller("test",function($scope, FCategory, FLocation){
$scope.categories = FCategory.categories;
FCategory factory
angular.module('MDItem').factory('FCategory', function ($http) {
var service = {
categories: [],
....
};
return service;
});

Resources