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.
Related
I have defined a custom click directive as below:
(function() {
'use strict';
angular.module('myApp.core')
.directive('customClick', customClick);
customClick.$inject = ['$rootScope'];
function customClick() {
return {
restrict: 'A',
/*scope: {
customClick: '&'
},*/
link: function(scope, element, attrs){
$(element).on('click', function(e) {
scope.$apply(function() {
console.log("Hello..customClick..");
scope.customClick();
});
});
}
};
}
})();
And I get the following error on this;
Error logged by WDPR Angular Error handler service {xx.."stacktrace":"TypeError: a.customClick is not a function","cause":"unknown cause"}(anonymous function)bowerComponents.js:5745
How can I resolve this? If I add scope with '&' I get demanding isolated scope. Hence how to resolve it?
If I remove - scope.customClick();, it does not show anything on second html for custom-click, it has impact on only 1 html, and its controller. I want to use it in multiple controller + html.
customClick is a function on the directive itself. It is not a function on the scope. That's why the error has occurred.
link is used to manipulate dom/add event handlers on elements, which you have rightly done with element.bind('click', function() {
Whenever click occurs, the function binded to click automatically gets invoked. There is no need of watch and the invoking statement.
Your link can just be
link: function(scope, element){
element.bind('click', function() {
console.log("Hello..customClick..");
});
}
As you have used camel case in naming the directive, exercise caution in its usage in template.
You can use it as <p custom-click>hi</p>
I would recommend you to avoid using jQuery in angular apps. Try following
angular.module('newEngagementUI.core')
.directive('customClick', customClick);
customClick.$inject = ['$rootScope'];
function customClick() {
return {
restrict: 'A',
scope: {
customClick: '&'
},
link: function(scope, element, attrs){
element.bind('click', function () {
scope.customClick();
})
}
};
}
In your template:
<div custom-click="clickFunction"></div>
And your template controller should be like:
angular.module('myApp', []).controller(['$scope', function ($scope) {
$scope.clickFunction = function () {
alert('function passed');
}
}])
Working fiddle here: https://jsfiddle.net/xSaber/sbqavcnb/1/
I have a view that contains a template specified in a custom directive. The template that is used in the custom directive depends on 'dynamicTemplate':
View:
<div ng-controller="MyController">
<custom-dir dynamicTemplate="dynamicTemplateType"></custom-dir>
<button ng-click="ok()">Ok</button>
</div>
View's Controller:
myModule
.controller('MyController', ['$scope', function ($scope) {
$scope.dynamicTemplateType= 'input';
$scope.myValue = "";
$scope.ok = function()
{
// Problem! Here I check for 'myValue' but it is never updated!
}
Custom Directive:
myModule.directive("customDir", function ($compile) {
var inputTemplate = '<input ng-model="$parent.myValue"></input>';
var getTemplate = function (templateType) {
// Some logic to return one of several possible templates
if( templateType == 'input' )
{
return inputTemplate;
}
}
var linker = function (scope, element, attrs) {
scope.$watch('dynamicTemplate', function (val) {
element.html(getTemplate(scope.dynamicTemplate)).show();
});
$compile(element.contents())(scope);
}
return {
restrict: 'AEC',
link: linker,
scope: {
dynamicTemplate: '='
}
}
});
In this above example, I want 'myValue' that is in MyController to be bound to the template that is in my custom directive, but this does not happen.
I noticed that if I removed the dynamic templating (i.e. the contents in my directive's linker) and returned a hardcoded template instead, then the binding worked fine:
// If directive is changed to the following then binding works as desired
// but it is no longer a dynamic template:
return {
restrict: 'AEC',
link: linker,
scope: {
dynamicTemplate: '='
},
template: '<input ng-model="$parent.myValue"></input>'
}
I don't understand why this doesn't work for the dynamic template?
I am using AngularJS 1.3.0.
Maybe you should pass that value into your directives scope, instead of only dynamicTemplate, i think it should work.
You have a good answer about directives scope here: How to access parent scope from within a custom directive *with own scope* in AngularJS?
Hope I was of any help.
js directive :
angular.module('directive')
.directive('myDirective', function ($compile) {
var tpl1 ='<div class="template1">{{data.title}}</div>';
var tpl2 ='<div class="template2">Hi</div>';
var getTemplate = function (data) {
if (data.title == 'hello') {
template = tpl1;
}
else {
template = tpl2;
}
return template;
};
var linker = function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$render = function () {
// wait for data from the ng-model, particulary if you are loading the data from an http request
if (scope.data != null) {
element.html(getTemplate(scope.data));
$compile(element.contents())(scope);
}
};
};
return {
restrict: "E",
require: 'ngModel',
link: linker,
scope: {
data: '=ngModel'
}
};
});
html :
<my-directive
ng-model="myData">
</my-directive>
js controller:
$scope.myData = {title:'hello'};
I have two custom angular directives and one appends the second repeatedly. The problem is that although the tag is appended, the template of the directive is not. When I manually put it in, it works.
See this jsfiddle: http://jsfiddle.net/HB7LU/5555/
Here is the code where the appending takes place:
myApp.directive('formList', function () {
return {
template: '<my-form></my-form>',
require:'^repeatableForm',
restrict: 'E',
link: function (scope, element, attrs, repeatableFormCtrl) {
scope.add = function () {
console.log("test");
element.append('appended <my-form></my-form>'); // apended<my-form></my-form> will appear but not the contents of <my-form>
};
}
};
});
You have to use $compile service to manually compile your my-form directive like this:
myApp.directive('formList', function ($compile) {
return {
template: '<my-form></my-form>',
require:'^repeatableForm',
restrict: 'E',
link: function (scope, element, attrs, repeatableFormCtrl) {
scope.add = function () {
console.log("test");
var newForm = $compile('<span>appended </span><my-form></my-form>')(scope);
element.append(newForm);
};
}
};
});
Example JSFiddle: http://jsfiddle.net/9L3whcqc/
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();
}
};
});
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