I need to get the instances of my Services/Factory in my Angular JS application. For that reason I would want to use $injector so as to get instances and not depend on DI. I tried to create a wrapper method over $injector which is a seperate js file so that any other modules can call this helper method and get necessary instances. I know this is not straight forward but still wanted to try.
For this my code looks like
//helper.js file
export default function returnInstances(service) {
const app = angular.module('moduleName');
let instance;
app.run(() => {
injector = angular.injector(['ng', 'moduleName']);
instance = injector.get(service);
});
return instance;
}
// some other file
const instance = returnInstances('serviceName');
As expected this does not work. So I was wondering if anything like this is possible.
const helper = {
init: (injector) => this.injector = injector;
get: (name) => this.injector.get(name);
}
export default helper;
And in file where you bootstrap your module:
app.run((['$injector'], $injector) => helper.init($injector));
angular.bootstrap(...
And then u can use it.
Related
I have an Angular 1.x application. Now that there is a use case where in I have to access the Angular 1.x Services / Factories in ES6 Classes which are not part of or registered with Angular JS. In short I would want to do something like export an Angular Service and import in ES6 class. I know this is not directly possible but will there be a work around ?
So I have a service like
(function () {
angular.module('myApp').service('ServiceOne', function () {
return {
property: value
};
});
})();
and now there is an ES6 class another (service file)...
import angular from 'angular';
// const $injector = angular.injector(['ng', 'myApp']);
// const ServiceOne = $injector.get('ServiceOne');
// const ServiceTwo = $injector.get('ServiceTwo');
class Es6Service {
constructor() {
//this.serviceOne = ServiceOne;
//this.serviceTwo = ServiceTwo;
}
methodOne() {
// code
}
methodTwo() {
// code
}
}
export default Es6Service;
I know that injector.get('serviceName') can get us the service instance, in my case I am getting an error of unknown provider. My assumption is that the angular module reference is missing perhaps here.
If that is the case then how can I get the Module reference here ? or there is some other way to achieve this ?
Thanks
Looks strange but should work:
import angular from 'angular';
let _$injector;
angular.module('myApp').run($injector => _$injector = $injector);
class Es6Service {
methodOne() {
// Cannot call this method until angular initialize
return _$injector.get(...)
}
Working on converting some old broken AngularJS unit tests to TypeScript and came across this error:
Error: [$injector:unpr] Unknown provider: myComponentDirectiveProvider <- myComponentDirective
In every example of AngularJS unit testing I've seen, they have a main module with everything on it, and they inject that main module into the unit test with angular.mock.module('theWholeStinkingApp'). That is nice for a little tutorial, but I'm dealing with a very large application with hundreds of components and dozens of services. Not to mention filters and other directives. It doesn't work to just inject the whole app into a unit test. Nor do I think it's a good idea to start up the whole app just to unit test a component (kind of defeats the purpose of unit testing when you have to include everything you own in every test).
For testing services I can just create my module in the test in a beforeEach like this, so I'm mocking dependencies, but injecting the real service I want to test:
angular.mock.module(($provide) => {
$provide.service('firstDependency', FirstDependencyMock);
$provide.service('secondDependency', SecondDependencyMock);
$provide.service('serviceIWantToTest', ServiceIWantToTest);
});
I can't figure out how to do this and inject a Component. Any help would be appreciated.
Please keep in mind, I do not want to have to angular.mock.module('theWholeApp') to get this working. I just want to create a mock module and attach my component to it.
Here's a slimmed down version of what I'm doing.
Component looks something like this
angular.module('theWholeApp', [])
.component('myComponent', {
controller: MyComponentController, // class is defined elsewhere
templateUrl: 'path/to/my/template.html'
)
... // 100+ other components;
Here's the test:
describe('MyComponent', () => {
beforeEach(() => {
// angular.mock.module('theWholeApp'); This is not an option.
// Creating a mock module with just the dependencies I need for this test
angular.mock.module(($provide) => {
$provide.service('firstDependency', FirstDependencyMock);
$provide.service('secondDependency', SecondDependencyMock);
});
// Tried adding this to register the controller, but it doesn't help.
angular.mock.module(($controllerProvider) => {
$controllerProvider.register('MyComponentController', MyComponentController);
});
angular.mock.inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$componentController = $injector.get('$componentController');
firstDependency= $injector.get('firstDependency');
secondDependency= $injector.get('secondDependency');
$scope = $rootScope.$new();
});
});
describe('doSomething', () => {
it('exists', () => {
controller = $componentController('myComponent', {$scope});
expect(controller.doSomething).toBeDefined();
});
});
});
Obviously, this isn't the live code, just a representation. Hopefully I got all the made up names right. The point is, I want to create a mock module and add my component to it so a call to $componentController('myComponent', {$scope}) works.
Thanks!
Alright, the answer I came up with is deceptively simple, but I've never seen it done anywhere. If anyone can tell me why it's a bad idea and suggest something better, I'm all for it.
The answer is to just create the module you want then immediately add it to the test.
describe('MyComponent', () => {
// Create a new module with the things you need.
// Putting it outside the "beforeEach" because it only needs to be created once.
angular.module('myComponentTestModule', [])
.component('myComponent', {
controller: MyComponentController, // class is defined elsewhere
templateUrl: 'path/to/my/template.html'
)
.service('firstDependency', FirstDependencyMock)
.service('secondDependency', SecondDependencyMock);
beforeEach(() => {
// Add the new module to the test suite
angular.mock.module('myComponentTestModule');
angular.mock.inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$componentController = $injector.get('$componentController');
firstDependency= $injector.get('firstDependency');
secondDependency= $injector.get('secondDependency');
$scope = $rootScope.$new();
});
});
describe('doSomething', () => {
it('exists', () => {
controller = $componentController('myComponent', {$scope});
expect(controller.doSomething).toBeDefined();
});
});
});
I have the following file: classAModel.js with the following code:
class classAModel{
constructor(model) {
'ngInject';
if (!model) return {};
this.id = model.id;
this.name = model.name;
this.displayName = model.displayName;
}
}
export default classAModel;
This code is defined as a factory in another file: module.js:
import classAModelfrom './classAModel'
module.factory('ClassAModel', ClassAModel);
This code works perfectly when not in a testing context. It works using Webpack to create a bundle that is loaded and runs. So far so good. Now, the question is how do I test this class. Before I changed my code to es6 style, it used be a function and it worked. The test first loads the bundle, but when I try to inject the factory (again, same as before), I get an error: Unknown provider: modelProvider <- model <- classAModel. I can understand why he thinks there is a problem, but I can't understand how to fix it.
Moreover, I'm wondering if this is the correct way to work with the factory rather then create a factory method inside the class, that gets the model, and then create my object.
Thanks
Based on the information you've provided, here's a simple test case for testing your factory. Hope this is what you're looking for.
import classAModel from './classAModel'
let classAInstance;
describe('classAModel', function() {
beforeEach(function() {
modelInstance = new Model();
classAInstance = new classAModel(modelInstance);
});
it('should have id provided by model', () => {
expect(classAInstance.id).toBe(modelInstance.id);
});
});
I know there are similar questions, but somehow they all have different code and i cant seem to find something that answers my specific problem, im sorry.
Im following several exmaples, and i got here:
This is my soon-to-be map service (maps.service.js):
import angular from 'angular'
import angularConfig from '../angularConfig/angularConfig'
export default class Maps {
initializeMap (element, options){
return new Promise(function(resolve){
return resolve(new google.maps.Map(document.getElementById(element), options))
})
}
}
Maps.$inject = ['$http']
angular.module(Maps, [])
.service('maps.service', Maps)
You can see its a simple class with a method inside.
At the bottom based on several examples i placed a call to angular.service, gave it a name and used the class i created. I think this is wrong, but i dont know why. I also dont know if thats the correct way to inject a service i need from angular into it.
Then, in order to consume this service i have a small controller (catalog.controller.js):
// import mapService from '../../common/maps/maps.service'
class catalogController {
constructor($scope, mapsService) {
this.$scope = $scope
}
$onInit () {
// map init
mapService.initializeMap(param1, param2)
.then(() => {
console.log('it worked!!')
})
}
}
catalogController.$inject = ['$scope']
export default catalogController;
The problem is no matter how i write the code in the service, i always end up with an error when i try to use it, when i inspect mapService to see what i has inside, the function is not there.
I tried importing it and passing it as a parameter to the constructor, i tried with different ways i found to create the service, but i cant make it work, everyone seems to be using something different.
Both this files are inside a folder named 'catalog', outside this folder i have this file:
import angular from 'angular'
import catalog from './catalog/catalog'
let componentModule = angular.module('app.components', [
catalog
])
.name
export default componentModule
which is in turn used by another file. This whole structure works, as i have things working already with controllers and views. My only problem is how to create and use a service.
Any help will be appreciated, as i have already spent many days on this!
I can provide more details if needed.
Thank you :)
According to DI, do some changes:
Maps Service:
import angular from 'angular'
import angularConfig from '../angularConfig/angularConfig'
export default class Maps {
constructor($http){}
initializeMap (element, options){
return new Promise(function(resolve){
return resolve(new google.maps.Map(document.getElementById(element), options))
})
}
}
Maps.$inject = ['$http']
angular.module(Maps, [])
.service('maps.service', Maps)
catalogController
class catalogController {
constructor($scope, mapsService) {}
$onInit () {
// map init
this.mapService.initializeMap(param1, param2)
.then(() => {
console.log('it worked!!')
})
}
}
catalogController.$inject = ['$scope', 'mapsService']
export default catalogController;
PS: Here is a good example: https://github.com/FountainJS/generator-fountain-angular1
What is the best practice when you have a Factory with like 4 related methods, each of those is really long (200+ lines of code) and you want to avoid having a huge file of 800+ lines of code?
One solution is to create 4 factories under the same module , each one exposing a single method and in its own file. Then inject all of them in the controller that requires them.
Is there a better solution? I'd like to create the Factory once, then add methods to it like I was doing module augmentation using the module pattern. Then I just need to inject the Factory once and have all its methods available.
I'd like to create the Factory once, then add methods to it like I was doing module augmentation using the module pattern. Then I just need to inject the Factory once and have all its methods available.
Yes, that will work:
// In your main module file.
angular.module('myModule', []);
// file1.js
angular.module('myModule').factory('BigService1', function(){
// Your actual implementation here.
console.log('I am BigService1');
});
// file2.js
angular.module('myModule').factory('BigService2', function(){
// Your actual implementation here.
console.log('I am BigService2');
});
// ... additional files
// Separate file/service to aggregate your big factory services.
angular.module('myModule').service('AggregateService', [
// Add dependencies
'BigService1','BigService2',
function(BigService1, BigService2){
// Return an object that exposes the factory interfaces.
return {
service1: BigService1,
service2: BigService2
};
}]);
You could also arrange your code the old vanilla js style and then access those libraries in your services like this:
var Mirko = { };
Mirko.FunService = {
getAllSomething : function (p1, p2) {
},
...
};
angular.module('myModule').factory('BigService', function(){
return {
methodOne : Mirko.getAllSomething,
...
};
});
You will end up with one object Mirko that you can access outside the scope of your angular app, but it will in no way differ from other externals api's (not written for angular) you would want to use in your app. The way you handle your own 'external' api can be done the oldschool fashion way, one file per 'class' e.g. 'FunService'.
It might not be the prettiest solution but it will be an easy abstraction.
Just saying...
Maybe segment your methods through other factories, which can be injected to your "main" factory :
// file 1
angular.module('foo').factory('segment1', function () {
return {
method: function () {
// ... super long method
}
};
});
// file 2
angular.module('foo').factory('segment2', function () {
return {
method: function () {
// ... super long method
}
};
});
// your main factory file
angular.module('foo').factory('myFactory', function (segment1, segment2) {
return {
method1: segment1.method,
method2: segment2.method
};
}