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.
Related
I am creating an email directive as below,
angular.module('app')
.directive('email', function () {
return {
restrict: 'EA',
transclude: true,
template: '{{email}}',
link: function (scope, element, attrs, ctrl, transclude) {
transclude(scope, function (clone) {
scope.email = clone.text();
});
}
};
});
I am using it in the HTML as below,
<email>{{emailId}}</email>
However, I am unable to pass the interpolated value of emailId inside the directive. The output of the directive ends up as,
{{emailId}}
How do we pass the actual interpolated value inside the directive?
Plnkr - http://plnkr.co/edit/AeBfp3VayKihygelKr9C?p=preview
To pass a variable to directive scope, simply use an attribute:
angular.module('app')
.directive('email', function () {
return {
restrict: 'EA',
̶t̶r̶a̶n̶s̶c̶l̶u̶d̶e̶:̶ ̶t̶r̶u̶e̶,̶
template: '{{email}}',
link: function (scope, element, attrs, ctrl, transclude) {
//transclude(scope, function (clone) {
// scope.email = clone.text();
//});
scope.email = scope.$eval(attrs.addr);
//OR
scope.$watch(attrs.addr, function(value) {
scope.email = value;
});
}
};
});
Usage:
<̶e̶m̶a̶i̶l̶>̶{̶{̶e̶m̶a̶i̶l̶I̶d̶}̶}̶<̶/̶e̶m̶a̶i̶l̶>̶
<email addr="emailId"></email>
There is no need to use transclusion to evaluate an Angular Expression.
For more information, see
AngularJS scope.$eval API Reference
AngularJS scope.$watch API Reference
You Just have to add Only on directive template ng-transclude on your element
here is the code
angular.module('app')
.directive('email', function () {
return {
restrict: 'EA',
transclude: true,
template: '<a href="{{email}}" ng-transclude>{{email}}</a>',
link: function (scope, element, attrs, ctrl, transclude) {
}
};
});
here is the link
Plunker http://next.plnkr.co/edit/SY3ih9NwAqz7ZhZg?open=lib%2Fscript.js&preview
Let's imagine, we have one child directive <child/>, that takes ng-model and ng-change and does some actions with them. And we have two kind of wrappers <w1/> and <w2/>, that contain <child/>.
W1 should pass ng-model through directly to <child/>
W2 should pass some inner object as model to <child/>
in first case i would use require: '^ngModel'
in second : require: 'ngModel' but i need them to work simultaneously
so model is simple object that can be passed easily through.
<wrapper ng-model="foo"></wrapper>
Wrapper:
module
.directive('wrapper', function () {
return {
restrict: 'E',
template: "<child ng-model='ngModel'></child>",
scope: {
ngModel:'='
},
link: function ($scope) {
}
};
});
Child:
module
.directive('child', function () {
return {
restrict: 'E',
require: 'ngModel',
template: "<div>some wierd stuff</div>",
scope: {
},
link: function ($scope, iElem, iAttr, ngModel) {
var a = ngModel.$viewValue;
}
};
});
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");
}
};
});
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();
}
};
});
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 = '';
}