Murali
Murali

Reputation: 3542

Injecting service/factory to directive

I have two/more services with same signature. Can I dynamically inject then into the directive? something like below

var app = angular.module('app',[]);
app.factory('myData', function(){
    return {
        name : "myName",
        id : 1,
        create: function(){
            //do something
        }
    }
});
app.factory('yourData', function(){
    return {
        name : "yourName",
        id : 1,
        create: function(){
            //do something
        }
    }
});
app.directive('changeIt',function($compile){
    return {
        restrict: 'CA',
        scope:{
            data : '=' //or some oether syntax?
        },
        link: function (scope, element, attrs) {
            scope.name = data.name;
        }
    }
}); 

Then I should be able to use the directive as below

<div class='change-it' data='myData'>{{name}}</div>
<div class='change-it' data='yourData'>{{name}}</div>

I would be adding more services with the same signature and I should be able to use the directive without changing, is it possible?

Upvotes: 6

Views: 21834

Answers (2)

Julien Moumn&#233;
Julien Moumn&#233;

Reputation: 101

Here is a solution without the need for a parent controller or a factory of factories.

In the directive, inject the $injector service to retrieve the factory instance:

app.directive('changeIt',function(){
        return {
            scope:{
                factoryName : '@'
            },
            controller: function ($scope, $injector) {

                var factoryInstance = $injector.get($scope.factoryName);

                $scope.name =  factoryInstance.name;
            }
        }
    }); 

Notice it's happening in the controller method. I could not make $injector.get() work in the link function.

Template :

<div class='change-it' factory-name="myData"> {{ name }} </div>
<div class='change-it' factory-name="yourData"> {{ name }} </div>

-- Edit --

Working solution within the link function :

 app.directive('changeIt',function(){
            return {
                scope:{
                    factoryName : '@'
                },
                link: function (scope, element) {

                    var factoryInstance = element.injector().get(scope.factoryName);

                    scope.name =  factoryInstance.name;
                }
            }
        }); 

Upvotes: 10

Michael Benford
Michael Benford

Reputation: 14114

That's not possible that way. The best you can do is bind the directive scope to a function of its parent scope that returns an instance of your service:

app.directive('changeIt', function(){
  return {
    restrict: 'CA',
    scope: { getDataFn : '&' },
    link: function (scope) {
      scope.name = getDataFn().name;
    }
  }
});     

and then in your view:

<div class='change-it' get-data-fn='getMyData()'></div>
<div class='change-it' get-data-fn='getYourData()'></div>

Finally, you need to add getMyData() and getYourData() to the parent scope:

app.controller('Ctrl', function($scope, myData, yourData) {
  $scope.getMyData = function() {
    return myData;
  };

  $scope.getYourData = function() {
    return yourData; 
  };
});

Plunker script here.

I can think of another approach, though: you could create an abstract factory and inject it into the directive, and then pass a parameter to the directive so it could tell the abstract factory to create the correct service. Something like this:

app.service('dataFactory', function(myData, yourData) {
  this.create = function(type) {
    if (type === 'myData')
      return myData;
    else if (type === 'yourData')
      return yourData;
  };
});

app.directive('changeIt', function(dataFactory){
  return {
    restrict: 'CA',
    scope: true ,
    link: function (scope, element, attrs) {
      scope.name = dataFactory.create(attrs.type).name;
    }
  }
});

And now you need to pass the type to the directive:

<div class='change-it' type="myData"></div>
<div class='change-it' type="yourData"></div>

Plunker here.

Upvotes: 14

Related Questions