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();
    }

}]);

5 Answers 5

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');
        }
    }
]);
Sign up to request clarification or add additional context in comments.

4 Comments

Cool! I want them backport $injector.has to 1.0.7!
I like this answer over @madhead's because it uses $injector.has instead of try/catch
@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?
@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.
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.

1 Comment

Thank you! Exactly what i need. I'm using latest Angular, so use 'has'!
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

3 Comments

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.
This is best answer for my project with optional parameters.
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.
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');
    }
});

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.