Angular.js trigger another directive - angularjs

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

Related

combining transclude and directive inheritance in AngularJS

i have this html
<div ng-app="myApp">
<parent>
<child></child>
</parent>
</div>
and the following Angular code
var myApp = angular.module('myApp',[]);
myApp.directive('parent', function() {
return {
transclude: true,
link: function(scope, element, attrs) {
scope.getContent = function () {
console.log('called getContent');
}
},
restrict: 'E',
template: '<span>parent <span ng-transclude></span></span>'
}
});
myApp.directive('child', function() {
return {
restrict: 'E',
scope:true,
template: '<div>child</div>',
link: function(scope, element, attrs) {
scope.getContent();
}
}
});
JSfiddle of the above is here
my problem in this example is i can see the inheritance working and transclusion working in isolation but when i try to combine the two, the child directive has no knowledge of the parent scope and hence the function getContent. So no console.log and before that the child directive errors that scope.getContent is undefined.
I realise that this might be that the child directive is no longer a child having been transcluded so i was thinking i need to start playing with post and prelink functions in the compile method maybe? or do i need to define a custom transclude function in the parent?
Any pointers for the code or further reading appreciated - i have read and similar questions on here about this kind of thing but am finding it difficult to follow and hoping some one can solve my hello world to kickstart my understanding
thanks in advance
You should read that article: http://www.undefinednull.com/2014/07/07/practical-guide-to-prelink-postlink-and-controller-methods-of-angular-directives/
Basically, your child link is called before the parent one. What you want to do is use your parent pre-link function so scope.getContent is defined beforte the child is linked.
link: {
pre: function (scope, element, attrs) {
scope.getContent = function () {
console.log('called');
}
}
}
JSfiddle: http://jsfiddle.net/floribon/gpwasrkz/3/
Have a look at "Creating Directives that Communicate" in the docs. Maybe this is a solution for what you are trying o achieve. You can require the parent controller and call it's functions:
var myApp = angular.module('myApp', []);
myApp.directive('parent', function () {
return {
transclude: true,
controller: function ($scope) {
this.getContent = function () {
console.log('called')
}
},
link: function (scope, element, attrs) {},
restrict: 'E',
template: '<span>parent <span ng-transclude></span></span>'
}
});
myApp.directive('child', function () {
return {
restrict: 'E',
scope: true,
require: '^parent',
template: '<div>child</div>',
link: function (scope, element, attrs, ctrl) {
ctrl.getContent();
}
}
});
See this fiddle.

Directive to directive communication using a service

I am attempting to perform the following. Use one directive to set information in a service.
Use another directive to retrieve information from this service. The directive setting the information seems to be doing it's job fine, however the one receiving the information does not react to it.
Below are how the directives look:
app.service('theStore',function(){
this.data;
});
app.directive('theOneThatSets', ['theStore', function(theStore){
return {
restrict: 'E',
link: function(scope, element, attrs) {
element.click(function(event){
theStore.data = attrs.val;
});
}
};
}]);
app.directive('theOneThatReads', ['theStore', function(theStore){
return {
restrict: 'E',
template: '<stong>Received Text is - {{receivedValue}}</strong>',
link: function(scope, element, attrs) {
scope.$watch('theStore.data',function(newVal){
scope.receivedValue = theStore.data;
});
}
};
}]);
plnkr here: http://plnkr.co/edit/9EMIwhUcneQoopNqqWtV
I don't know if you can do watchers on things that are not in scope. The best way to communicate between controllers/services/directives is to use $rootScope, $broadcast, and $on.
Example using your code:
app.directive('theOneThatSets', ['$rootScope', function(theStore){
return {
restrict: 'E',
link: function(scope, element, attrs) {
element.click(function(event){
//theStore.data = attrs.val;
$rootScope.$broadcast('changeThisValue', attrs.val); // Send
});
}
};
}]);
app.directive('theOneThatReads', [function(theStore){
return {
restrict: 'E',
template: '<stong>Received Text is - {{receivedValue}}</strong>',
link: function(scope, element, attrs) {
scope.$on('changeThisValue', function($event, value){
scope.receivedValue = theStore.data;
});
}
};
}]);
also, try creating a listener in your service like so:
app.service('myservice',function(){
this.listen = function($scope) {
$scope.$watch(function(){return someScopeValue},function(){
//$scope.dosomestuff();
});
}
});
//your controller
function myCtrl($scope,myservice) {
$scope.listen = function() {
myservice.listen($scope);
}
//call your method
$scope.listen();
}

$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.

How can I get my directive to access the controllers scope

I have a setup like this:
<controller>
<directive>
in my controller that has a function that returns an html string. How can I get my directive to render this by accessing the controllers scope?
Or maybe I should just put the controller in the directive?
app.controller('controller', ['$scope', 'DataService', function ($scope, DataService) {
$scope.parseJson = function () {
//returns the html
};
}]);
directive
app.directive('Output', function () {
return {
restrict: 'A',
replace: true,
template: '<need html from controller>',
link: function(scope, element, attr) {
//render
//scope.parseJson();
}
};
});
You should use the isolated scope: '&' option
app.directive('output', ['$sce', function ($sce) {
return {
restrict: 'A',
replace: true,
template: "<div ng-bind-html='parsed'></div>",
scope:{
output: "&"
},
link: function(scope){
scope.parsed = $sce.trustAsHtml(scope.output());
}
};
}]);
Template:
<div output="parseJson()"></div>
The directive and the controller should be sharing the scope already. Don't bother using a template for the directive, just get the HTML string in you linking function (you already have the method call in there) and modify the element directly using element.html(). Take a look at the element docs for more info.
app.directive('Output', function ($compile) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var templateString = scope.parseJson();
var compiledTemplate = $compile(templateString)(scope);
compiledTemplate.appendTo("TheElementYouWishtoAppendYourDirectiveTo");
}
};
});

Angularjs: set parent directive scope value with child directive

I'm not sure this is the way to do this, but my goal is the following:
I have a parent directive
Inside the parent directive's block, I have a child directive that will get some input from the user
The child directive will set a value in the parent directive's scope
I can take it from there
Of course the problem is that the parent and child directives are siblings. So I don't know how to do this. Note - I do not want to set data in the
Fiddle: http://jsfiddle.net/rrosen326/CZWS4/
html:
<div ng-controller="parentController">
<parent-dir dir-data="display this data">
<child-dir></child-dir>
</parent-dir>
</div>
Javascript
var testapp = angular.module('testapp', []);
testapp.controller('parentController', ['$scope', '$window', function ($scope, $window) {
console.log('parentController scope id = ', $scope.$id);
$scope.ctrl_data = "irrelevant ctrl data";
}]);
testapp.directive('parentDir', function factory() {
return {
restrict: 'ECA',
scope: {
ctrl_data: '#'
},
template: '<div><b>parentDir scope.dirData:</b> {{dirData}} <div class="offset1" ng-transclude></div> </div>',
replace: false,
transclude: true,
link: function (scope, element, attrs) {
scope.dirData = attrs.dirData;
console.log("parent_dir scope: ", scope.$id);
}
};
});
testapp.directive('childDir', function factory() {
return {
restrict: 'ECA',
template: '<h4>Begin child directive</h4><input type="text" ng-model="dirData" /></br><div><b>childDir scope.dirData:</b> {{dirData}}</div>',
replace: false,
transclude: false,
link: function (scope, element, attrs) {
console.log("child_dir scope: ", scope.$id);
scope.dirData = "No, THIS data!"; // default text
}
};
});
If you want that kind of communication, you need to use require in the child directive. That will require the parent controller so you need a controller there with the functionality you want the children directives to use.
For example:
app.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>{{message}}<span ng-transclude></span></div>',
controller: function($scope) {
$scope.message = "Original parent message"
this.setMessage = function(message) {
$scope.message = message;
}
}
}
});
The controller has a message in the $scope and you have a method to change it.
Why one in $scope and one using this? You can't access the $scope in the child directive, so you need to use this in the function so your child directive will be able to call it.
app.directive('child', function($timeout) {
return {
restrict: 'E',
require: '^parent',
link: function(scope, elem, attrs, parentCtrl) {
$timeout(function() {
parentCtrl.setMessage('I am the child!')
}, 3000)
}
}
})
As you see, the link receives a fourth param with the parentCtrl (or if there is more than one, an array). Here we just wait 3 seconds until we call that method we defined in the parent controller to change its message.
See it live here: http://plnkr.co/edit/72PjQSOlckGyUQnH7zOA?p=preview
First, watch this video. It explains it all.
Basically, you need to require: '^parentDir' and then it will get passed into your link function:
link: function (scope, element, attrs, ParentCtrl) {
ParentCtrl.$scope.something = '';
}

Resources