Cannot get $get method called automatically in angularjs provider - angularjs

Here's the piece of code I'm writing in angularjs 1.5.5:
export default angular.module(
'myApp', [])
.provider('finder', function finderProvider() {
this.$get = $get;
function $get() {
console.log('GET');
}
this.callMe = () => {
console.log('CALLED');
};
})
.config(['finderProvider', function(finderProvider) {
//finderProvider.$get();
finderProvider.callMe();
}])
.name;
I'm trying to inject a service into this provider later. I read on documentation that I can only inject a service inside a provider using $get() method. However, I cannot call $get() automatically. The only way I can get this working is to call $get() manually in the section where I commented the call. But it doesn't seem right to me to call $get manually.
In this example only 'CALLED' is getting logged. Any idea what I'm missing here?
EDIT. I tried to use another syntax using the arrow function, but got the same result:
.provider('finder', () => {
return {
$get: () => {
console.log('GET');
},
callMe: () => {
console.log('CALLED');
}
};
})
Thanks in advance.

Angular services are lazily instantiated.
Service provider constructor function is called when it is injected during config phase (config block or other provider).
$get is called when service instance is injected after config phase.
In the example above finder service isn't injected anywhere, service instance isn't created and $get function isn't called.

Related

Non singleton service for angular directive

I have an angular component that is reused multiple time in the same page.
The angular component is dependent on a service, since it is reused multiple times, I would like to have a new instance of a service for each component, is that possible? Or do I have to use a for loop and create a separate object inside the service for each component to achieve the same effect?
Edit: I am using angular 1
You can create a factory (instead of a service) which returns a function to which you can even provide some data (think of it as a constructor).
angular
.module('app')
.factory('MyFactory', MyFactory);
function MyFactory() {
return (someOptions) => ({
myProperty: someOptions.myProperty,
myMethod: () => {
// ...
}
});
}
You can than use it in your controller like so. Every time you call MyFactory({ ... }) a new separate "instance" will be created.
angular
.module('app')
.controller('MyController', MyController);
function MyController(MyFactory) {
const myFactoryObj = MyFactory({ myProperty: 'test' });
}
You can use that service in each of your components as DI and then call that service to create a new instance every time. You can also set 'cache':true in your function so that you don't make multiple api calls for the same data. (Only for 'GET' method). Does that clarify your doubt?

What is the purpose of delete $get prop in angular Provider?

I saw a piece of code about Angular's Provider like below:
angular.module('BlurAdmin.theme')
.provider('baConfig', configProvider);
/** #ngInject */
function configProvider() {
var conf = {
theme: {
blur: false
}
};
conf.changeTheme = function(theme) {
angular.merge(conf.theme, theme)
};
conf.$get = function () {
// what does this code meaning? why delete $get?
delete conf.$get;
return conf;
};
return conf;
}
But i don't know the delete conf.$get to mean what?
Apparently, the author of this provider thought it was a good idea to return the provider itself from the $get method. So, the service created by the provider is the provider itself.
Since $get should obviously not be part of the methods of the service, but only of the methods of the provider, he/she removed the $get method after $get has been called by angular to create the service instance.
I wouldn't use that code as a good example of how to create a service using a provider. Just use a different object for the provider and the service.

Injected Dependency is undefined in Unit Test

I'm new to Angular and I'm not quite sure exactly how dependency injection works. My problem is that I have Service A which depends on Service B, but when I inject Service A into my test Service B becomes undefined.
I have seen Injecting dependent services when unit testing AngularJS services but that problem is a bit different than mine.
This Injected Dependencies become undefined in angular service is very similar to my issue but I couldn't translate the JS.
I have also changed my variable names and simplified the code to remove irrelevant lines.
Service.ts
export class ServiceA {
constructor(private $resource: ng.resource.IResourceService, private ServiceBInstance: ServiceB){
}
ServiceAMethod() {
var resources = ServiceBInstance.property; //ServiceBInstance is undefined here
}
}
factory.$inject = [
'$resource'
'ServiceBModule'
];
function factory($resource: ng.resource.IResourceService, ServiceBInstance: ServiceB): IServiceA {
return new ServiceA($resource, ServiceBInstance);
}
angular
.module('ServiceAModule')
.factory('ServiceA',
factory);
Spec.ts
describe('ServiceB', (): void => {
beforeEach((): void => {
angular.mock.module(
'ServiceAModule',
'ServiceBmodule',
'ngResource'
);
});
describe('ServiceAMethod', (): void => {
it("should...", inject(
['ServiceA', (ServiceAInstance: ServiceA): void => {
ServiceAInstance.ServiceAMethod(); //Problem Here
}]));
});
});
The problem I'm having is that when I call ServiceAMethod I get an error which states "TypeError: Cannot read property 'property' of undefined". The weird thing is that $resource is never undefined but my ServiceB is always undefined.
I thought that when I inject ServiceA into my test it should automatically also inject all of its dependencies.
Also note that I am not mocking ServiceB on purpose.
Update
I've also tried to inject an instance of my ServiceB into the it but when I print out the variable it also states that is it undefined, and when I try to to do the code below into the beforeEach
inject(function (_ServiceBInstance_: ServiceB) {
console.log(_ServiceBInstance_);
});
I get and Unknown Provider error.
Ok so after hours of searching I finally found the answer. I forgot to include a file which included Angular's .config method
When creating the provider make sure that you also call .config, here's an example:
angular
.module('ServiceBModule')
.config(['ServiceBProvider', (ServiceBClass) => { //Very Important
//Code here
}])
.provider('ServiceB', ServiceB);

How to use service in config / why is provider instance injected in config?

I read about how to inject dependency in cofing method of the module. I created provider for my service like this
app.provider("securitySvc", function securitySvcProvider () {
this.$get = ['Authorizations', '$q', 'routeSvc', function securitySvcFactory (Authorizations, $q, routeSvc) {
return new securityService(Authorizations, $q, routeSvc);
}];
});
When I try to use it I get instance with a $get method instead of newed up securityService. What am I doing wrong?
app.config( ['$routeProvider', 'routes', 'securitySvcProvider', routeConfigurator]);
function routeConfigurator($routeProvider, routes, securitySvc) {
// HERE securitySvc is instance with a $get method, not newed up securitySvc
}
Note that everywhere else, in controllers, securitySvc is injected correctly.
However, if do follwing in "run" method of module
app.run(function ($rootScope, securitySvc) {
$rootScope.hasPermission = function (authorizationName) {
return securitySvc.hasAuthorization(authorizationName);
};
}
Then if I reference it in routeConfiguration through $rootScope.hasPermission it works fine. My goal was to avoid using scope and just use service. Can it be done?
Although question was why instance of the provider is injected into config method of the module and PSL explained it in comments, what I really wanted was to find out how to use service in config method. Here is how I achieved what I wanted. I added a method on the provider that provides the check I needed.
app.provider("securitySvc", function securitySvcProvider() {
var me = this;
me.$get = ['Authorizations', '$q', 'routeSvc', function securitySvcFactory(Authorizations, $q, routeSvc) {
me.service = new securityService(Authorizations, $q, routeSvc);
return me.service;
}];
me.hasRoutePermission = function(routeName) {
return me.service.hasRoutePermission(routeName);
}
});
Since this check is attached to "resolve" object on the router, I can be sure that service will by then be instantiated.

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