I have problem related AngularJS dependency injection and timing between them. Here is my code and error
var module = angular.module('Demo', []);
module.factory('demo', function () {
return {
data: {},
};
});
module.provider('foo', ['demo', function(demo) {
console.log(demo);
this.$get = function() {
};
}]);
Error:
Uncaught Error: [$injector:modulerr] Failed to instantiate module Demo due to:
Error: [$injector:unpr] Unknown provider: demo
But if I add setTimeout on last definition everything works fine, but its hacking code it shouldn't be like this.
var module = angular.module('Demo', []);
module.factory('demo', function () {
return {
data: {},
};
});
setTimeout(function(){
module.provider('foo', ['demo', function(demo) {
console.log(demo);
this.$get = function() {
};
}]);
});
Here is problem on fiddle:
http://jsfiddle.net/zcf7rb4s/1/
You cannot add demo as a dependency there because it does not yet exist. That's the way the $injector works. What you can do is list demo as a dependency in the $get function of the provider. That's going to be executed by the $injector after all providers have been defined.
Check this:
<div ng-app="Demo">
<div ng-controller="test">{{x}}</div>
</div>
And the definitions:
var module = angular.module('Demo', []);
module.factory('demo', function () {
return {
data: {x: 'x'},
};
});
module.provider('foo', function() {
this.$get = function(demo) {
return {
demo: demo
};
};
});
module.controller('test', ['$scope', 'foo', function($scope, foo) {
$scope.x = foo.demo.data.x;
}]);
The code inside the factory and provider is run at "step 1".
Then, in "step 2" AngularJS binds the controller. It first uses $injector to inject the dependencies (that have been previously defined in "step 1"). So in practice your $timeout "emulates" this behavior, that's why it works. But it's wrong, that's not the way you are supposed to use them.
Inject into the provider like this instead:
module.provider('foo', function() {
this.$get = ['demo', function(demo) {
console.log(demo);
}];
});
Related
I actually hate to be that guy, but I've been sitting with this
problem for some days now. I have these three files as a part of a
larger angularjs application. I can not get even this rudimentary test
to pass (or even work). I've been comparing files within the project,
I've read on-line (tried all those ways people have suggested). I have
even written the files from scratch a few times. I'm probably not able
to see my error anymore. I guess this is easier to spot (right away)
for a back-seat driver.
I'd be most appreciative for any help.
The output from gulp/karma
PhantomJS 2.1.1 (Linux 0.0.0) SiteDescriptionService the service should be defined FAILED
Error: [$injector:unpr] Unknown provider: SiteDescriptionServiceProvider <- SiteDescriptionService
http://errors.angularjs.org/1.5.8/$injector/unpr?p0=SiteDescriptionServiceProvider%20%3C-%20SiteDescriptionService (line 4511)
bower_components/angular/angular.js:4511:86
getService#bower_components/angular/angular.js:4664:46
bower_components/angular/angular.js:4516:48
getService#bower_components/angular/angular.js:4664:46
injectionArgs#bower_components/angular/angular.js:4688:68
invoke#bower_components/angular/angular.js:4710:31
workFn#bower_components/angular-mocks/angular-mocks.js:3085:26
loaded#http://localhost:8080/context.js:151:17
inject#bower_components/angular-mocks/angular-mocks.js:3051:28
app/service/sitedescriptor-service-test.js:10:19
app/service/sitedescriptor-service-test.js:4:13
global code#app/service/sitedescriptor-service-test.js:1:9
Expected undefined to be truthy.
app/service/sitedescriptor-service-test.js:17:32
loaded#http://localhost:8080/context.js:151:17
The module declaration
(function(){
'use strict';
angular.module('application.service', []);
})();
The service itself
(function () {
angular.module('application.service')
.service('SiteDescriptorService',
['$http', '$q', function ($http, $q) {
var lastRequestFailed = true,
promise,
items = [];
return {
name: 'SiteDescriptorService',
getItems: function () {
if (!promise || lastRequestFailed) {
promise = $http.get('site.json').then(
function (response) {
lastRequestFailed = false;
items = response.data;
return items;
}, function (response) { // error
lastRequestFailed = true;
return $q.reject(response);
});
}
return promise;
}
};
}]
);
})();
and the test
describe('SiteDescriptionService', function() {
'use strict';
describe('the service', function() {
var service, httpBackend;
beforeEach(module('application.service'));
beforeEach(inject(function(_SiteDescriptionService_, $httpBackend) {
service = _SiteDescriptionService_;
httpBackend = $httpBackend;
console.log(service);
}));
it('should be defined', function() {
expect(service).toBeTruthy();
});
});
});
Cheers
Mats
Looks like you just use incorrect name when injecting dependency, should be 'SiteDescriptorService' and not 'SiteDescriptionService'
I try to use ng-notifications-bar module, I have code like this:
angular.module('app', [
uiRouter,
Common.name,
Components.name,
angularMaterial,
'ngTable',
'gridster',
'ngNotificationsBar'
])
.factory('$exceptionHandler', ['notifications', function(notifications) {
return function(exception, cause) {
notifications.showError({message: exception});
};
}]);
but got error:
[$injector:cdep] Circular dependency found: $rootScope <- notifications <- $exceptionHandler <- $rootScope <- $timeout <- $$rAF <- $mdGesture
I've tried to modify the library to use $injector to get $timeout and $rootScope but that didn't help also tried to use $injector to get notifications in $exceptionHandler factory but got the same error.
Pretty poor design from angular it looks on this one. You can't inject $rootScope in any form into $exceptionHandler due to the dependency.
You can use $injector to get around these kinds of (out-of-your-hands) dependency problems, you just need to make sure that the injected module is used inside the return function to ensure that at the time of calling .get() the dependent module has actually loaded. For example:
// won't not be available here
var rootScope = $injector.get('$rootScope');
return function(exception, cause) {
// will be available here
var rootScope = $injector.get('$rootScope');
};
This is because $injector is used to grab the dependency at run time.
There is a nice clean object-oriented design to avoid circular dependencies: use the dependency inversion principle. Create a generic service to which you can attach handlers, and set it up from a run block. Basically all the other solutions suggest something similar, but with using global variables outside of angular, or bypassing the automatic dependency injection.
angular.module("App", [])
.factory("notifications", function($rootScope) {
$rootScope.notifications = [];
function showMessage(msg) {
$rootScope.notifications.push(msg);
}
return { showMessage };
})
.factory("$exceptionHandler", function(MyExceptionService) {
return function(e, cause) {
MyExceptionService.fire(e, cause);
};
})
.factory("MyExceptionService", function() {
const handlers = [];
return {
addHandler(h) { handlers.push(h); },
fire(e, cause) { handlers.forEach(h => { h(e, cause); }) }
};
})
.controller("MyCtrl", function($scope) {
$scope.clicked = () => {
throw new Error("Error made");
};
})
.run(function(MyExceptionService, notifications) {
MyExceptionService.addHandler(err => {
notifications.showMessage({ message: err.message });
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
<div ng-app="App" ng-controller="MyCtrl">
<button ng-click="clicked()">Make Error</button>
<div>Errors:</div>
<div ng-repeat="item in notifications">{{item}}</div>
</div>
For comparison, here is the wrong one (with the circular dependency):
angular.module("App", [])
.factory("notifications", function($rootScope) {
$rootScope.notifications = [];
function showMessage(msg) {
$rootScope.notifications.push(msg);
}
return { showMessage };
})
.factory("$exceptionHandler", function(notifications) {
return function(e, cause) {
notifications.showMessage({ message: err.message });
};
})
.controller("MyCtrl", function($scope) {
$scope.clicked = () => {
throw new Error("Error made");
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
<div ng-app="App" ng-controller="MyCtrl">
<button ng-click="clicked()">Make Error</button>
<div>Errors:</div>
<div ng-repeat="item in notifications">{{item}}</div>
</div>
I have the following Angular 1.5 component:
(function () {
'use strict';
angular
.module('EmployeeInformation')
.controller('EmployeeDetailCtrl', EmployeeDetailCtrl)
.component('employeeDetail', EmployeeDetail);
var EmployeeDetail = {
templateUrl: '../views/components/employeeDetail.html',
controller: 'EmployeeDetailCtrl as empdetail',
bindings: {
employee: '<'
}
};
function EmployeeDetailCtrl() { }
})();
Based on the examples on Angular's site, it seems correct to me. However, when I include the file on my page, I get an error saying that it cannot register the component:
[$injector:modulerr] Failed to instantiate module EmployeeInformation due to:
TypeError: Cannot read property 'controller' of undefined
at $CompileProvider.registerComponent [as component]
If I remove the reference to this component, the page loads fine. Does anyone have any idea what I'm doing wrong?
EDIT: I have a service in the same module composed similarly (that is wrapped in a function and without an empty array - see below) that works correctly:
(function () {
'use strict';
angular
.module('EmployeeInformation')
.service('EmployeeService', EmployeeService);
EmployeeService.$inject = ['$http'];
function EmployeeService($http) {
var apiPrefix = 'http://dvlemployees/api/employees';
this.find = find;
this.get = get;
this.getOne = getOne;
function find(queryObj) { }
function get() { }
function getOne(empNumber) { }
})();
EDIT2: Thanks to #Frondor, I figured out the true problem was that I was calling angular.component on the object before I had defined it. Moving the var EmployeeDetail section to the top fixed the problem.
JavaScript
(function(angular) {
'use strict';
angular.module('EmployeeInformation', [])
.controller('EmployeeDetailCtrl', EmployeeDetailCtrl)
.component('employeeDetail', {
templateUrl: 'view.html',
controller: EmployeeDetailCtrl,
bindings: {
hello: '='
}
});
function EmployeeDetailCtrl(){
this.world = 'World!';
}
})(window.angular);
HTML
<body ng-app="EmployeeInformation">
<!-- components match only elements -->
<div ng-controller="EmployeeDetailCtrl as ctrl">
<h1>
<employee-detail hello="'Hello'"></employee-detail>
</h1>
</div>
</body>
view.html
<span>{{$ctrl.hello}} {{$ctrl.world}}</span>
You need to specify an empty array after the module's name, even if you are not using any dependency on your app.
Live example:
http://plnkr.co/edit/8TbRcg5LBsdlqxLkUccJ?p=preview
Try it:
(function (angular) {
'use strict';
angular
.module('EmployeeInformation', [])
.controller('EmployeeDetailCtrl', EmployeeDetailCtrl)
.component('employeeDetail', EmployeeDetail)
;
var EmployeeDetail = {
templateUrl: '../views/components/employeeDetail.html',
controller: 'EmployeeDetailCtrl as empdetail',
bindings: {
employee: '<'
}
};
function EmployeeDetailCtrl() { }
})(angular);
Im trying to understand hows providers works and i make a test based in angularjs documentation and i wrote a simple provider :
(function( window, angular, undefined ){"use strict";
function MyProviderExample(foo)
{
this.testdrive = function()
{
console.log(foo);
}
console.log("init");
}
angular.module('app',[])
.provider('$myProvider',function (){
var foo = "bar";
this.$get = function()
{
return new MyProviderExample(foo);
}
console.log("ey....");
}).config(function($myProvider){
console.log("wut");
$myProvider.foo = "foo";
});
})(window, window.angular);
When i run the code always returns
Uncaught Error: [$injector:modulerr] Failed to instantiate module app due to:
Error: [$injector:unpr] Unknown provider: $myProvider
I was trying to understand what fails but i cant see my mistake, if someone can helps i appreciate
I think you need to remove function($myProvider) from the .config section. Like this:
angular.module('app', [])
.provider('$myProvider', function () {
this.$get = function () {
// --
}
console.log("loaded $myProvider");
})
.config(function(){
console.log("loaded config");
})
.controller('Main',
function main() {
console.log('loaded mycontroller')
});
What are you trying to do with $myProvider.foo = "foo";?
How can I run the working code in createAdmobBanner function in another controller?
angular.module('starter', ['ionic', 'starter.controllers'])
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
var admobid = {};
if (/(android)/i.test(navigator.userAgent)) {
admobid = {
banner: 'ca-app-pub-3815248714018431/123456789'
};
}
function createAdmobBanner() {
AdMob.createBanner({
adId: admobid.banner
adSize: 'SMART_BANNER',
position: 8
});
}
createAdmobBanner();
});
})
I got createAdmobBanner is not defined if I simply do createAdmobBanner() in my controllers. I tried $rootScope but the plugin doesn't seem work with that.
You need to add it into a service or attached in on $rootScope,
$rootScope solution - faster to implement but "dirty"
.run(function($ionicPlatform,$rootScope) { //add $rootScope dependency injection
$rootScope.createAdmobBanner = function(){
AdMob.createBanner( { adId:admobid.banner
adSize: 'SMART_BANNER',
position:8
});
}
$rootScope.createAdmobBanner()
into your controllers, add the dependency $rootScope and call your function $rootScope.createAdmobBanner
Service Solution - cleaner & reusable
Create a new service that has your function
Inject your service into run
call your service function into run
inject your service into controllers
call your service function into controllers
I just found this link here. Give it a try. The important code looks like this:
var admobApp = angular.module('myapp', ['ionic'])
.run(function($ionicPlatform, $ionicPopup) {
$ionicPlatform.ready(function() {
if(window.plugins && window.plugins.AdMob) {
var admob_key = device.platform == "Android" ? "ANDROID_PUBLISHER_KEY" : "IOS_PUBLISHER_KEY";
var admob = window.plugins.AdMob;
admob.createBannerView(
{
'publisherId': admob_key,
'adSize': admob.AD_SIZE.BANNER,
'bannerAtTop': false
},
function() {
admob.requestAd(
{ 'isTesting': false },
function() {
admob.showAd(true);
},
function() { console.log('failed to request ad'); }
);
},
function() { console.log('failed to create banner view'); }
);
}
});
});
The admob stuff is within $ionicPlatform.ready(function() { and is defined like this var admob = window.plugins.AdMob;
Does that help?
Try to define external angular service/factory and provide this service to any controller you need using dependency injection.
This is a good practice to share common logic or data in this way.
EDIT:
angular.module('starter', ['ionic', 'starter.controllers']);
angular.module('starter').factory('bannerFactory',function(){
return {
createAdmobBanner: function(){
window.plugins.AdMob.createBanner({ adId:admobid.banner
adSize: 'SMART_BANNER',
position:8
});
}
}
});
angular.module('starter').controller('anyController',['bannerFactory', function(bannerFactory){
bannerFactory.createAdmobBanner();
}]);
angular.module('starter').run(function ($ionicPlatform,bannerFactory) {
$ionicPlatform.ready(function () {
bannerFactory.createAdmobBanner();
});
});