53

I'm trying to implement a controller in AngularJS which is used across multiple pages. It makes use of some services. Some of them are loaded on all pages, some - not. I mean it is defined in different files, and these files are loaded independently. But if I do not load these services on all pages I got error:

Error: Unknown provider: firstOtionalServiceProvider <- firstOtionalService

So, I need to load scripts on all pages. Can I declare dependency as optional in Angular? E.g:

myApp.controller('MyController', ['$scope', 'firstRequiredService', 'secondRequiredService', 'optional:firstOptionalService', 'optional:secondOptionalService', function($scope, firstRequiredService, secondRequiredService, firstOptionalService, secondOptionalSerivce){

    // No need to check, as firstRequiredService must not be null
    firstRequiredService.alwaysDefined();

    // If the dependency is not resolved i want Angular to set null as argument and check
    if (firstOptionalService) {
        firstOptionalService.mayBeUndefinedSoCheckNull();
    }

}]);
T J
  • 42,762
  • 13
  • 83
  • 138
molaccha
  • 533
  • 1
  • 4
  • 6

5 Answers5

62

Apparently not using automatic injection. However, you can inject the injector and check for the service:

myApp.controller('MyController', [
    '$scope', '$injector', 'firstRequiredService', 'secondRequiredService', 
    function ($scope, $injector, firstRequiredService, secondRequiredService) {
        if ($injector.has('firstOptionalService')) {
            var firstOptionalService = $injector.get('firstOptionalService');
        }
    }
]);
Jeremy Moritz
  • 13,864
  • 7
  • 39
  • 43
Problematic
  • 17,567
  • 10
  • 73
  • 85
  • Cool! I want them backport `$injector.has` to 1.0.7! – madhead Aug 30 '13 at 22:10
  • 2
    I like this answer over @madhead's because it uses `$injector.has` instead of `try`/`catch` – Peter Davis Apr 07 '14 at 20:12
  • @Problematic is there a way to use this to pass an optional variable to the controller? I'm not looking to create a service but simply pass an optional boolean or object variable. Is this possible? – Jeremy Moritz Jul 31 '14 at 16:26
  • @JeremyMoritz if I'm understanding what you're asking, you can't use it with arbitrary values, but you could register a constant or value provider, and use it in the same way as above. – Problematic Jul 31 '14 at 16:35
55

No, Angular does not yet support optional dependencies out of the box. You'd better put all your dependencies into a module and load it as one Javascript file. If you need another set of dependencies - consider creating another module in another JS and putting all common dependencies to common JS.

However, behavior you've described can be achieved with $injector service. You simply inject $injector instead of all your dependencies to a controller and pull dependencies from it manually, checking if they exist. That's it:

index.html:

<!DOCTYPE html>
<html data-ng-app="myApp">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
    <script src="app.js"></script>
    <script src="1.js"></script>
    <script src="2.js"></script>
    <title>1</title>
  </head>
  <body data-ng-controller="DemoController">
  </body>
</html>

app.js:

var myApp = angular.module('myApp', []);

myApp.service('commonService', function(){
    this.action = function(){
        console.log('Common service is loaded');
    }
});

myApp.controller('DemoController', ['$scope', '$injector', function($scope, $injector){
    var common;
    var first;
    var second;

    try{
        common = $injector.get('commonService');
        console.log('Injector has common service!');
    }catch(e){
        console.log('Injector does not have common service!');
    }
    try{
        first = $injector.get('firstService');
        console.log('Injector has first service!');
    }catch(e){
        console.log('Injector does not have first service!');
    }
    try{
        second = $injector.get('secondService');
        console.log('Injector has second service!');
    }catch(e){
        console.log('Injector does not have second service!');
    }

    if(common){
        common.action();
    }
    if(first){
        first.action();
    }
    if(second){
        second.action();
    }
}]);

1.js:

myApp.service('firstService', function(){
    this.action = function(){
        console.log('First service is loaded');
    }
});

2.js:

myApp.service('secondService', function(){
    this.action = function(){
        console.log('Second service is loaded');
    }
});

See it live in this plunk! Try to play with <script> tags and watch for console output.

P.S. And, as @Problematic said, you can use $injector.has(), starting from AngularJS 1.1.5.

madhead
  • 31,729
  • 16
  • 153
  • 201
14

I'd probably go with @Proplematic's suggestion of using $injector. However, there is another solution I can think of: register all services with their default values (null for example) in your bootstrap file. When additional files are loaded, later definitions will override the default definitions, somewhat creating the effect you desire.

var app = angular.module('plunker', []);

app.value("service1", null)
   .value("service2", null)
   .factory("service1", function() { return "hello"; });

app.controller('MainCtrl', function($scope, service1, service2) {
  console.log(service1); // hello
  console.log(service2); // null
});

Demo link

Buu
  • 49,745
  • 5
  • 67
  • 85
  • This is hacky, but works. I am using it where I have multiple routes that share a controller, but only a few need a resolve: {} property injected. – httpete Oct 16 '15 at 18:56
  • This is best answer for my project with optional parameters. – sean Feb 03 '16 at 03:08
  • This is exactly what I wanted! Thanks! I need to use the same controller as modal controller and as ng-controller too. And for modal controller I need additional resolvers. – Alexey Vol Apr 04 '17 at 12:59
10

This is how i solved it:

var deps = [];

try {
    //Check if optionalModule is available
    angular.module('app').requires.push('optionalModule');
    deps.push('optionalModule');
} catch(e){
    console.log("Warn: module optionalModule not found. Maybe it's normal");
}

angular.module('app', deps).factory('stuff', function($injector) {
    var optionalService;

    if($injector.has('optionalService')) {
        optionalService = $injector.get('optionalService');
    } else {
        console.log('No waffles for you, dear sir');
    }
});
jedi
  • 839
  • 12
  • 33
Kenneth Lynne
  • 15,461
  • 12
  • 63
  • 79
5

Try this way..

try {
    angular.module('YourModule').requires.push('Optional dependency module');
} catch(e) {
    console.log(e)
}

'requires' is an Array of dependency modules.

jedi
  • 839
  • 12
  • 33
Simbu
  • 51
  • 1
  • 2