Do angular directives on the same DOM element share a scope? - angularjs

If two angular directives are attached to the same DOM element do they both share the same scope? The angular documentation implicitly indicates this is the case, If so, what is the best practice for preventing name spacing issues between directives?
E.g.
<div directive-1 directive-2>
</div>
where directive-1 and directive-2 have set scope: true or scope: {...}

Best practice for these kind of things is the require property in directives, you should be able to require the controller of directive-1 and through that access its scope.
But now you also have a shareable API between directives.
app.directive('directive1', function () {
return {
require: '^directive1',
controller: ['$scope', function ($scope) {
this.$scope = $scope;
this.sharedFn = function () {
};
return this;
}]
};
});
app.directive('directive2', function () {
return {
require: '^directive1',
link: function (scope, element, attrs, directive1Ctrl) {
console.log(directive1Ctrl);
}
};
});
app.directive('directive3', function () {
return {
require: '^directive1',
link: function (scope, element, attrs, directive1Ctrl) {
console.log(directive1Ctrl);
}
};
});
Hope this helps

Related

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

Bind directive scope without having controller scope in Angular Js?

html :
<div ng-app="appMod">
<div task-info>{ { data.name } }</div>
</div>
script :
var appmod = angular.module('appMod', []);
appmod.directive("taskInfo", function () {
return {
restrict: 'A',
scope: {},
link: function ($scope, $element, attr) {
$scope.taskdat = '{"name":"Task name","status":"Completed"}';
$scope.data = JSON.parse($scope.taskdat);
scope = $scope; //scope data
},
};
});
is it possible to bind directive scope without having controller scope in Angular Js? If yes, please give me some solution examples.
You don't need a controller scope for writing a directive , see this fiddle.
Here, there is no controller scope, and the value hero is bound within the directive as:
myApp.directive('myDirective', function() {
return {
restrict: 'EAC',
link: function($scope, element, attrs, controller) {
var controllerOptions, options;
$scope.hero='superhero'
}
};
});
Works fine :)
Also the example you provided is similar, but you just need to remove scope from returned JSON object(from directive), as it is being defined as $scope inside the link fucntion.
see : http://jsfiddle.net/bg0L80Lx/
controller option ?
.directive('mydirective', function() {
return {
restrict: 'A', // always required
//controller: 'SomeController'
template:'<b>{{status}}</b>',
controller:'YourCtrl'
}
})

$compile nested directive from within another directive

TL;DR; jsFiddle here.
I want to use two directives (kmOuter and kmInner) as nested directives:
<div km-outer>
<div km-inner></div>
<div km-inner></div>
<!-- ... -->
</div>
So I declared them as follows. Please note that inner directive requires outer's controller:
app.directive('kmOuter', function () {
return {
restrict: 'AC',
scope: null,
controller: function ($scope) {
this.childAdded = function () {
console.log('Child added.');
};
}
};
});
app.directive('kmInner', function () {
return {
restrict: 'AC',
require: '^kmOuter',
template: '<div>Lorem ipsum</div>',
link: function (scope, elem, attrs, kmOuterController) {
kmOuterController.childAdded();
}
};
});
That works just fine (.childAdded() is being called, among others). Now, I want to dynamically insert new instances of inner directive. This action is being triggered from some third, unrelated directive:
app.directive('kmChildAdder', function ($compile) {
return {
restrict: 'AC',
scope: {
target: '#kmChildAdder'
},
link: function (scope, elem) {
console.log(scope);
var target = document.querySelector(scope.target);
angular.element(elem[0]).bind('click', function () {
// Error is here
var newInner = $compile('<div km-inner></div>')(scope)[0];
target.appendChild(newInner);
});
}
};
});
Used like this:
<button km-child-adder="[km-outer]">Add child</button>
But it breaks with the following message:
Error: [$compile:ctreq] Controller 'kmOuter', required by directive 'kmInner',
can't be found!
.childAdded() isn't called anymore.
How can I fix this? Or maybe this design is itself broken and I should reorganise my code?
I think I made it, borrowing from #Mobin Skariya's answer.
Key was to $compile only the inserted element, not all elements:
link: function (scope, elem) {
var target = angular.element(document.querySelector(scope.target));
angular.element(elem[0]).bind('click', function () {
var newInner = angular.element('<div km-inner="param"/>');
target.append(newInner);
scope.$apply(function () {
$compile(newInner)(scope);
});
});
}
I've prepared jsFiddle with example where third, unrelated directive inserts ad compiles inner directive with working, two-way data bindings - you will find it here. Nice thing about it is that third directive (kmChildAdder) can insert inner directives taking bindings from multiple, separate controllers.
Made some edits in your code. Code given in jsFiddle link
link: function (scope, elem) {
console.log(scope);
var target = document.querySelector(scope.target);
angular.element(elem[0]).bind('click', function () {
var newInner = '<div km-inner></div>';
angular.element(target).append(newInner);
$compile(target)(scope)
});
}
Check whether this is what you expect.

Angular.js trigger another directive

I'm trying to create a sort of generic toggle feature between directives, where one directive which contains a template does not render, until an event occurs from another directive. Any suggestions on how to link this together?
Thanks!
There are many ways to achieve this.
A
Using events (but be careful, when used exessively, especially for interaction between directives, you can get lost easily! This is why I didnt create a http://plnkr.co for it, even worse: code A is untested!
so pls edit this in case of errors
Use $rootScope.$on('myEvent', function(e, eargs) {...}) on the
master directive.
dispatch the event from some directive:
$rootScope.$broadcast('myEvent', {foo: 'bar'}).
remember to inject $rootScope in both directives.
angular.module('masterDirective', [])
.directive('masterDirective', function ($rootScope, $compile /**injects here*/) {
var templ = '<p ng-bind="someVar"></p>';
return {
restrict: 'EA',
scope: {},
link: function (scope, element, attrs) {
scope.someVar = "I am a template and I was born and visible to the world, because slaveDirective send me an event to do so.";
$rootScope.$on('myEvent', function(e, eArgs) {
// eArgs.myVar will be 'Jackson';
element.append($compile(templ)(scope));
});
}
}
});
angular.module('slaveDirective', [])
.directive('slaveDirective', function ($rootScope) {
return {
restrict: 'EA',
scope: {},
link: function (scope, element, attrs) {
$rootScope.$broadcast('myEvent', {myArg: 'Jackson'});
}
}
});
B
Using a "shared controller" is the cleaner, but more complicated way. This approach is more strongly typed, you express the workflow and once it works, it is not as easy to break.
Demo: http://plnkr.co/WaqKzP
Use a controller on your master directive: controller(scope,element,attrs) {...}
require your masterDirective in slave directive: require: 'myMasterDirective'
the controller of the master directive is the fourth parameter of your slave's link function (because you required it), you can call a function to let the master include the template.
<body ng-app="myApp">
<button ng-click="includeSlave=true">include slave directive</button>
<master-directive>
<div ng-if="includeSlave==true">
<slave-directive></slave-directive>
</div>
</master-directive>
</body>
angular.module('myApp', [])
.directive('masterDirective', function ($rootScope, $compile /**injects here*/) {
var templ = '<p ng-bind="someVar"></p>';
return {
restrict: 'E',
controller: function ($scope, $element) {
return {
slaveLink: function() {
$element.append($compile(templ)($scope));
}
}
},
link: function (scope, element, attrs) {
scope.someVar = "I am a template and I was born and visible to the world, because slaveDirective called a function on myself to do so.";
}
};
})
.directive('slaveDirective', function () {
return {
require: '^masterDirective',
restrict: 'E',
link: function (scope, element, attrs, myMasterController) {
myMasterController.slaveLink();
}
};
});

How do directives communicate by a controller?

In this document: http://docs.angularjs.org/guide/directive, it says directives may communicate with each other by controllers.
controller - Controller constructor function. The controller is instantiated before the pre-linking phase and it is shared with other directives if they request it by name (see require attribute). This allows the directives to communicate with each other and augment each other's behavior.
I don't understand it well, how they communicated with controllers? Is there any example or demo for it?
Perhaps you're confusing a controller that is created when a route change occur with a directive controller. That section is only talking about directive controllers, so what that section means is that if you have two directives on the same HTML element, they can communicate by requiring each others controllers.
A good example of that is if your creating a directive that needs to communicate with ng-model. Since ng-model exposes a controller, you can require it like this:
myApp.directive('myDirective', function() {
return {
require: 'ngModel',
link: function($scope, elem, attrs, ngModelCtrl) {
// Here you can listen to different DOM events, and
// call ngModelCtrl when the model value needs to update
}
}
});
And the HTML:
<input type="text" ng-model="myModel" my-directive>
Your directive can expose a controller by implementing it in the object that your directive function returns, like this:
myApp.directive('myDirective1', function() {
return {
link: function($scope, elem, attrs) {
},
controller: function() {
this.sayHello = function() {
alert("hello!");
}
}
}
});
myApp.directive('myDirective2', function() {
return {
require: 'myDirective1',
link: function($scope, elem, attrs, myDirective1Ctrl) {
myDirective1Ctrl.sayHello();
}
}
});
And the HTML:
<input type="text" my-directive2 my-directive1>
You can also require a directive controller from a parent directive by writing require: '^myParentDirective', like this:
myApp.directive('myDirective1', function() {
return {
link: function($scope, elem, attrs) {
},
controller: function() {
this.sayHello = function() {
alert("hello!");
}
}
}
});
myApp.directive('myDirective2', function() {
return {
require: '^myDirective1',
link: function($scope, elem, attrs, myDirective1Ctrl) {
myDirective1Ctrl.sayHello();
}
}
});
And the HTML:
<div my-directive1>
<div my-directive2></div>
</div>

Resources