Access controller of directive in template - angularjs

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.

Related

Unable to call Angular Directiive Method on Button Click

I'm trying to call directive method on button click from the calling controller.
Here is the directive code:
myApp.directive("helloDirective", function() {
return {
restrict: "E",
template: '<input type="text" data-ng-model="model.msg" />',
scope: {},
bindToController: {
param: "="
},
controller: 'helloDirectiveController',
controllerAs: 'model'
}
})
.controller("helloDirectiveController", function() {
var self = this;
self.actions = {
get: function() {
return self.msg;
},
set: function(msgData) {
self.msg = msgData;
}
});
I have call the get and set method from controller..
myApp.controller("indexController", [function() {
var self = this;
self.helloParam ={};
self.get = function() {
//how to call the Directive get method from here
}
}]);
i tried to create a fiddle here
plnkr
The idea
For me, the cleanest solution (so far) for your problem is a solution used by Angular Material developers (I don't know if they were the authors, but I found it there). I've used it once in my project and it worked like a charm.
The idea is to create a global registry for directives' actions. Directives would be stored there by unique ids. We can also create a service dedicated for each directive, just in case we need some external logic.
The solution
1. Components registry
Firstly, we need a components registry. It can be a really simple service:
angular.module('app');
.service('$componentsRegistry', function() {
var self = this;
var components = {};
self.put = function(id, actions) {
components[id] = actions;
}
self.get = function(id) {
return components[id];
}
})
The components registry has methods for storing and getting components by ids. Of course, there might be much more methods and they might be more complicated, but this is just a simple example.
2. Service for our directive
Let's say we have a simple show-message directive, so we can create a $showMessageService:
angular.module('app')
.service('$showMessageService', ['$componentsRegistry', function($componentsRegistry) {
return function(id) {
return $componentsRegistry.get(id);
}
}])
For now, the only task of the service is to return our directive's actions. But it can be extended in the future, of course.
3. Directive's code
The last thing we need is our show-message directive:
angular.module('app')
.directive('showMessage', function($componentsRegistry) {
return {
restrict: 'E',
scope: {
directiveId: '#' // Unique id is passed from the view
},
template: '<div>{{ text }}</div>',
link: function(scope) {
scope.text = 'TEST TEXT';
// Create actions
scope.actions = {
set: function(value) {
scope.text = value;
}
}
// Store actions in the components registry
$componentsRegistry.put(scope.directiveId, scope.actions);
}
}
})
In a link function, we need to simply register our actions in components registry. We pass the unique id from the view so that developer has control over it inside views/controllers.
Example of usage
And now we can finally use the directive in our application. Here is a simple code which shows how to do that:
View
<div ng-controller="testController as test">
<show-message directive-id="testDirective"></show-message>
<button ng-click="test.changeText()">CHANGE</button>
</div>
Controller
angular.module('app')
.controller('testController', function(['$showMessageService', $showMessageService) {
var self = this;
self.changeText = function() {
$showMessageService('testDirective').set('NEW');
}
}])
I've Change The Directive Code, moved Controller code to link and it's working fine.
testApp.directive("helloDirective", function () {
return {
restrict: "E",
template: '<input type="text" data-ng-model="model.msg" />',
scope: {},
bindToController: {
param: "="
},
link: function (scope, element, attrs) {
var self = scope.model;
var assignMethod = function () {
if (self.param == undefined) {
self.param = {};
}
self.param.actions = {
get: function () {
return self.msg;
},
set: function (msgData) {
self.msg = msgData;
}
};
};
assignMethod();
},
controller: function () { },
controllerAs: 'model'
}
});
now i can call the directive get Method from calling controller like,
self.helloParam = {};
self.click = function () {
alert(self.helloParam.actions.get());
}

Call method in controller from directive

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

how to create custom directives in Angularjs that changes its state of being used in application using an id?

I am working on an application that has same directives but the content change and template depends on the id of a controller in a module.
app.directive('glTranslate', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var key = element.text();
attrs.$observe('options', function(value) {
var options;
try {
options= JSON.parse(value);
element.text( I18n.t( key, options ) );
} catch (err) {
// SyntaxError maybe thrown as the options string is dynamically updated through Angular bindings
// and the options string may be momentarily be syntactically incorrect
console.log("Error parsing options: " + value + "\nError:"+ err.message + "\n");
}
});
}
};
});
If I understand you correctly, to set a directive's template dynamically based on some condition, you can use ng-include inside the directive. The pseudocode below works. It includes an example directive with dynamic template chosen based attribute passed to it and simple HTML usage. Needless to say, although the template contents vary, here they use the same directive controller and its accompanying functionality. Hope this helps.
.directive("dynaDirective", function() {
return {
template: '<ng-include src="getTemplateUrl()"/>',
scope: {
someData: '='
},
restrict: 'E',
controller: function($scope) {
//function used on the ng-include to resolve the template
$scope.getTemplateUrl = function() {
//basic handling
return "templateName" + $scope.someData.type;
}
$scope.doSomething1 = {
//......
}
$scope.doSomething2 = {
//......
}
}
};
});
<dyna-directive some-data="someObject"></dyna-directive>

Can I require generic parent directive in AngularJS

Can a child directive require a parent without knowing exactly which directive that parent is, just that it "implements an interface"?
For example:
<parentImplX>
<child></child>
</parentImplX>
In the above example I want the controller injected into child to be ParentImplXCtrl. But If I do:
<parentImplY>
<child></child>
</parentImplY>
I want the controller to be ParentImplYCtrl.
directives.directive("parentImplX", function () {
return {
scope: {},
restrict: "E",
controller: ParentImplXCtrl
}
});
directives.directive("parentImplY", function () {
return {
scope: {},
restrict: "E",
controller: ParentImplYCtrl
}
});
directives.directive("child", function () {
return {
scope: {},
restrict: "E",
require: "?^^parentInterface",
link: function ($scope, $element, attributes, parent /* type ParentInterface */) {
parent.method();
}
}
});
I've found that angular 'require' does not support this. However, AngularJS stores the controllers as well as the $scopes of $elements in the $element.data() construct. So it was very simple to write your own 'interface require'. You need to traverse $element.parent().data() and make sure there is an identifier to look for. In my case isFocusNode. Note: `FocusNode can have many implementations. It is the whole point.
function findFocusNodeParent(_element) {
var data = _element.data();
for (var key in data) {
var angularObject = data[key];
if (angularObject.isFocusNode && angularObject.isFocusNode()) {
return angularObject;
}
}
var _parentElement = _element.parent();
if (_parentElement.length > 0) {
return findFocusNodeParent(_parentElement);
} else {
// No parent FocusNode found. Must be root
return null;
}
}
var parentFocusNodeController = findFocusNodeParent($element);

Outputting a Service Property Value Through a Directive in Angular

My goal is to output a value (from a service) through a element directive so that the html will look like this <msg msg="alertMsg"></msg> and out pops a value from the service.
Here is my code thus far:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: {//something here to pass MsgService to template },
template: 'Message:{{MsgService.getAlertMsg()}}'
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
HTML would parse/compile to...
<msg msg="alertMsg">Message: default</msg>
What other code do I need?
If a service wont work directly, Should I access it through a controller?
app.directive("msg", function() {
return {
restrict: "E",
scope: {
getMsg: '&msg'
},
controller: 'MsgController',
template:'Message:{{getMsg()}}'
};
}]);
app.controller('MsgController', ['MsgService' , function(MsgService){
this.getAlertMsg = function(){
return MsgService.getAlertMsg();
};
}]);
HTML would parse/compile to...
<msg msg="getAlertMsg()">Message: default</msg>
Sorry for any errors in code or function use, I'm fairly new to Angular.
You can use the link function of the directive. This function is called once for every rendered instance of your directive. It receives, among other things, the scope of your directive. You can extend your scope very easily with the result of calling the MsgSevice.getAlertMsg() service method:
var app = angular.module("app", []);
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: true,
template: 'Message:{{msg}}',
link: function (scope, $element, attrs) {
scope.msg = MsgService.getAlertMsg();
}
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
Later on, I presume you will want to just display the alert message from the msg DOM attribute of the msg directive. Achieving this is much more simple, since AngularJS is already prepared for this common use case. The solution involves creating an isolate scope. The isolate scope can be populated with properties from the parent environment. One possibility is to use the value of a DOM attribute from your directive's element using the "#" syntax. In this case you won't even need the entire MsgService service:
app.directive("msg", function () {
return {
restrict: "E",
scope: {
"msg": "#"
},
template: 'Message:{{msg}}'
};
});
Simplest would be to set the service on your scope and use that in your template:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: { },
template: 'Message:{{MsgService.getAlertMsg()}}',
link: function(scope, element, attrs) {
scope.MsgService = MsgService;
}
};
}]);

Resources