To create a directive/controller/factory whatever, you provide a function serving as the "injection point":
angular.module('myApp').directive('myDirective', ['dependencies', function injectionPoint(dependencies) {}]);
Can you provide that injectionPoint via another function registered with Angular? e.g. a factory? I should note that I've declared everything in separate files, and I'm trying to do it 'the angular way' and not create unnecessary globals.
Specifically, I've got two input directives that are basically the same, just with different templates and isolate scope declaration.
I thought to create a "directive factory" I could use, like:
function directiveFactory(scopeOverride, extraInit) {
return function directiveInjectionPoint(depedencies) {
return { restrict...,
template...,
scope: angular.extend({/*defaults*/}, scopeOverride),
link: function(...) {
// default stuff
if(angular.isDefined(extraInit)) extraInit($scope, el, attrs);
}
}
}
which I would then use like:
angular.module('myApp')
.directive('myDirective1', directiveFactory({/* new stuff */))
.directive('myDirective2', directiveFactory({/* other stuff */, fn2...));
But how do I register that function with Angular, and then use it in the directive declaration?
You can use a service and directives like this:
app.factory('MyService', function() {
var MyService = {};
MyService.createDirective = function(scopeOverride, extraInit) {
return {
restrict: 'E',
transclude: true,
scope: scopeOverride,
template: '<pre ng-transclude></pre>'
};
};
return MyService;
});
app.directive('drzausDirective1', function(MyService) {
return MyService.createDirective(true);
});
app.directive('drzausDirective2', function(MyService) {
return MyService.createDirective(false);
});
http://plnkr.co/edit/BuKMXxMQ4XVlsykfrDlk?p=preview
Related
HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}
I saw the following question (among other similar questions) and it solves the problem of trying to inject a factory into a directive's link function:
Injecting service to Directive
The solutions I've seen keep the link function within the scope of the directive:
angular.module('myapp')
.directive('myDir', function(myService){
return {
restrict: 'E',
scope: {
frame: '='
},
link: function postLinkFn(scope, elem, attr) {
myService.doSomething();
}
};
});
However, I want to be able to separate the postLinkFn outside of the .directive scope for organization, just like I can do with controllers.
Is it possible to separate this function while also injecting a service into it?
.directive('myDir', function(myService){
var deps = { myService: myService };
return {
...
// myService is available as this.myService inside postLinkFn
link: angular.bind(deps, postLinkFn)
};
});
link function doesn't make use of dependency injection and doesn't have lexical this, binding injected dependencies to this is a reasonable move.
Sure you can put your code in a factory and then refer it globally across the app.
angular.module('myapp').factory('myfactory', myService, function(){
return{
var myfac;
my fac = function (myService){
var myItem = myService.doSomething;
return myItem;
};
};
}).
.directive('myDir', function(myService){
return {
restrict: 'E',
scope: {
frame: '='
},
link: myfactory.myItem;
};
});'
Just a little care you need to take is binding your factory with an angular promise $q if your service deals with asynchronous calls.
I have several very similar directives which share some code. I'm looking for a way to have those directives inherit that shared code.
The approach I'm considering is having each directive's template include another directive with the shared code. Is there any way then to access that template's directive's controller?
app.directive('d1', function($compile) {
return {
restrict: 'E',
require: 'sharedCtrl', // Is anything like this possible?
template: '<shared></shared>',
link:function(scope,element,attr, sharedCtrl) {
var res = sharedCtrl.getResult();
}
}
});
app.directive('shared', function($compile) {
return {
restrict: 'E',
controller: function($scope, $element) {
var resultObject = sharedCode();
this.getResult = function() { return resultObject; };
}
link:function(scope,element,attr, sharedCtrl) {
}
}
});
Or alternatively, is there a better way to achieve the same thing?
Edit: The shared code manipulates the DOM (it injects a leaflet map, and I want to return the map js object), so I think it is best to keep DOM manipulation in a directive.
This is exactly what services are for.
Turn your shared directive into a service and inject it into the first one.
app.directive('d1', function($compile, shared) {
return {
restrict: 'E',
template: '<shared></shared>',
link:function(scope,element,attr) {
var res = shared.getResult();
}
}
});
app.factory('shared', function($compile) {
var resultObject = sharedCode();
return {
getResult: function() { return resultObject; };
}
});
I'm currently adding a function in the directive module that does the shared DOM manipulation.
function sharedFoo(element) {
// DOM manipulation on element
return result;
}
app.directive('d1', function($compile) {
return {
restrict: 'E',
link:function(scope,element,attr, sharedCtrl) {
var res = sharedFoo();
}
}
});
Works fine, but I'm not sure how much it follows or breaks angular conventions.
I have several directives that use the same link function. (The link function adds some extra state and html depending on the use case.) So I declared this as follows:
function common_linkfunc(){...}
var Directive_1 = function($scope, etc) {
return {restrict:"E", ...
link: common_linkfunc,
controller: function($scope, etc) {...}
};
}
Directive_1.$injects = ["$scope", "etc"];
angular.module("module").directive("D1", Directive_1)...;
First change was when link function required $compile. Next I need to add $templateCache and my question is how can I do this systematically?
My first approach was to rewrite common_linkfunc as
function foo($compile, $templateCache) {
return common_linkfunc($compile, $templateCache) {...}
}
and then use this in every directive:
...
link: foo($compile, $templateCache),
...
But this is copy-and-paste! Is there an easier and less error prone way to do the same?
Regardless of the solution, you'll need to pass some argument to your common link function, because Angular won't inject anything into it for you. That being said, I can think of two different approaches:
1) Use arguments
app.directive('foo', function($http, $timeout) {
return {
restrict: 'E',
link: linkFn1.apply(null, arguments)
}
});
function linkFn1($http, $timeout) {
return function(scope, element, attrs) {
// ...
};
}
The downside here is that the order of the arguments in the directive function matters. If some other directive uses a different order, the code won't work properly.
2) Use $injector
app.directive('bar', function($injector) {
return {
restrict: 'E',
link: linkFn2($injector)
}
});
function linkFn2($injector) {
var $http = $injector.get('$http'),
$timeout = $injector.get('$timeout');
return function(scope, element, attrs) {
// ...
};
}
Working Plunker
How does one abstract a directive properly?
As a really basic example, let's say I have this:
http://plnkr.co/edit/h5HXEe?p=info
var app = angular.module('TestApp', []);
app.controller('testCtrl', function($scope) {
this.save = function() {
console.log("hi");
}
this.registerListeners = function() {
console.log('do stuff to register listeners');
}
this.otherFunctionsNotToBeChangedWithDifferentInstances() {
console.log('these should not change between different directives')
}
return $scope.testCtrl = this;
});
app.directive("tester", function() {
return {
restrict: 'A',
controller: 'testCtrl',
template: '<button ng-click="testCtrl.save()">save</button>'
};
});
The tester directive has some methods on it, but only two will be changed or used depending on where the directive is placed. I could pass in the function as a directive attribute, but I am wondering if there is a better way to do this. I have been looking at providers, but I am unsure how or if those would even fit into this.
Instead of letting your directive assume that testCtrl.save() exist on the scope, you would pass in that function as an attribute. Something like this: http://jsbin.com/jidizoxi/1/edit
Your directive binds the value of the my-on-click attribute as a callable function. Your template passes in the controllers ctrlOnClick() function, and when the buttons ng-click calls myOnClick() Angular will call ctrlOnClick() since they are bound to each other.
EDIT:
Another common approach is to pass in a config object to the directive. So your controller would look something like:
$scope.directiveConfig = {
method1: function() { ... },
method2: function() { ... },
method3: function() { ... },
...
}
And your template:
<my-directive config="directiveConfig"></my-directive>
The directive then gets a reference to that object by:
scope: {
config: '='
}
The directive can then call methods on the object like this: $scope.config.method1().