-2

I'm trying to inject angular's $timeout service in a method that should be called in several controllers in my application. However, I'm always getting the error:

Error: [$injector:unpr] Unknown provider: $timeoutProvider <- >$timeout

Of course $timeout has to be known since it's an angular service, so I don't see why this error is happening.

This is my code:

HTML:
<div ng-app="app" ng-controller="sampleController">
    <button ng-click="doit()">Do It!</button>
</div>

JAVASCRIPT:
var app = angular.module('app', []);
app.controller('sampleController', ['$scope', function($scope) {
    var _this = this;

    $scope.doit = function() {
        var $injector = angular.injector();
        var $timeout = $injector.get('$timeout', _this);

        $timeout(function () { alert('ok'); });
    };
}]);

And here's a jsfiddle demonstrating the problem:

https://jsfiddle.net/fvw8zss5/


  • This is not a question about if that's a bad practice or not. Of course it is bad. It's a question about why it was not working.
  • The JsFiddle will work if you know how to use JsFiddle.
Rodrigo Lima
  • 101
  • 2
  • 6
  • I can't even run the fiddle. It says 'module "app" cannot be instantiated' or someodd. – sg.cc Oct 07 '15 at 19:36
  • Why are you using injector? It is really frowned upon, unless in very peculiar cases. – masimplo Oct 07 '15 at 19:38
  • 1
    @sg.cc you need to change the way angular is loaded. Change from onLoad to "no wrap - " in Frameworks & Extensions on top left – Julien Roy Oct 07 '15 at 19:39
  • @sg.cc What Julien Roy said. I tried saving as "no wrap - " but it always comes back to "On Load". – Rodrigo Lima Oct 07 '15 at 19:45
  • @mxa055 The reason is that this method will be used in several controllers of the application. I don't want to add the same functionality to something around 25 controllers. And I don't want to add the '$timeout' dependency to every controller either. – Rodrigo Lima Oct 07 '15 at 19:47
  • You could use a service that exposes the common function used by the 25 controllers then and inject $timeout to the service. Each controller would need to inject the service, but this is the whole point of dependency injection. Hiding dependencies will bite you in the rear soon or later – masimplo Oct 07 '15 at 19:50
  • I totally agree with you @mxa055, that's exactly what I commented on levi's answer. However, I have a problem called 'deadline'. This would solve the problem for the moment, until I make some modifications to the application, including creating this and several other services (it's a pre-existing application here in my company, with some serious architectural issues). But the main question is why doesn't this work? Shouldn't this work without issues, even if it's not pretty? – Rodrigo Lima Oct 07 '15 at 19:58
  • @RodrickLinr I see. Take a look at this answer then for a solution to your issue http://stackoverflow.com/questions/13400687/cant-retrieve-the-injector-from-angular It is not as straight forward as you would think to not use angular's DI – masimplo Oct 07 '15 at 20:07
  • @mxa055 Thank you sir! Problem solved! Yep, you're right, not straight forward at all. Never would have guessed you need to add the 'ng' module to the injector, and that it creates a new instance with every call. **Can you please add an answer so I can mark your answer as the accepted one?** Thanks again! – Rodrigo Lima Oct 07 '15 at 20:17

4 Answers4

2

angular.injector() can only be used once per application and not per module to get the injector and you should use $injector through the DI to get the instantiated injector. That being said, adding the ng module to the list would "solve" the problem by creating a new injector (not recommended)

Take a look at this answer for a complete explanation and more appropriate solutions to this issue.

Community
  • 1
  • 1
masimplo
  • 3,674
  • 2
  • 30
  • 47
1

I think I get what you are saying, you want a function available to all 25 controllers without having to do a dependency injection for all 25 controllers, and you don't want to do a service, because you'll have to inject this service into all 25 controllers.

Honestly if you want this function to be available across 25 controllers, you are going to have to do a DI on all 25 whether it is $timeout, service or a $rootScope method

Assuming you don't have a parent controller for all 25 of these controllers as well, here is the $rootScope version:

angular.module('fooApp').run(['$rootScope', '$timeout', function($rootScope, $timeout) { 
    $rootScope.doit = function() {
       //Insert function here with $timeout        
    };
}]);

Where you have to inject $rootScope into all of your controllers to run this function.

I think your best bet is using a service though IMHO, or at least setting up your app next time so that you have a parent controller.

devonj
  • 1,198
  • 1
  • 13
  • 24
  • I have a parent controller for each controller, this way I'm able to add a function to the parent controller and have it in every single one of them. This is definitely not a good practice (using $injector this way), but, for now, it solves my problem. Next I will abstract this functionality into a service and then inject this service to all controllers. – Rodrigo Lima Oct 07 '15 at 21:20
  • ah ok, if that's the case, then you could inject $timeout into your parent controller and then call the parent function from your 25 controllers without having to do $injector.get. thats if your parent controller is the parent to all 25 controllers – devonj Oct 07 '15 at 21:58
  • Well, I'm using typescript, and for every child of the parent controller I have to copy every single dependency of the parent. I didn't find a way to set the dependencies on the parent and, on the child, add only the ones that are needed there. Angular favors composition over inheritance (which is actually a way better option), and that's what I'm migrating the application's architecture to, but since I have to work with an already finished solution, I have to migrate to a new architecture slowly. – Rodrigo Lima Oct 07 '15 at 23:34
0

why not just inject it in your controller declaration

app.controller('sampleController', ['$scope','$timeout', function($scope,$timeout)
levi
  • 22,001
  • 7
  • 73
  • 74
  • 1
    Because this method will be used in around 25 controllers (it's a big application) and I don't want add this dependency to all those controllers. The correct way would be to turn this functionality into a service and inject it in all controllers, but this would be too much work right now. If I can't find any other solution, however, that's what I'm going to do. – Rodrigo Lima Oct 07 '15 at 19:52
0

Why not use a directive instead of repeating the work in every controller?

<div ng-app="app" ng-controller="sampleController">
    <button do-it>Do It!</button>
</div>

And for the script side:

app.directive('doIt', function ($timeout) {
    return {
        restrict: 'A',
        link: function ($scope, $element, $attr) {
            $element.on('click', function () {
                $timeout(function () { alert('ok'); });
            });
        }
    };
});
doogle
  • 3,376
  • 18
  • 23
  • This would also work, if the logic inside the $timeout didn't depend on the controller state also. It's really a lot easier, in this case, just to add a single method to the controller's super class. – Rodrigo Lima Oct 07 '15 at 21:23