AngularJS nested directives, inner directive's ng-click is not firing - angularjs

This is a problem I'm having in my app and I am about to reproduce it in a simple example:
<div f-outer>
<div f-inner dat="dat">
<a href="javascript:" ng-click="run(dat, $event)">
Click Me
</a>
</div>
</div>
Clicking "Click Me", I'd expect the run function defined on the f-inner directive to execute. But it's not.
.directive('fOuter', function () {
return {
link: function (scope) {
scope.dat = { x: 1, y: 2 };
console.log('fOuter.link');
}
};
})
.directive('fInner', function () {
return {
restrict: 'A',
scope: {
dat: '='
},
link: function (scope) {
console.log('fInner.link');
scope.run = function (dat, $event) {
console.log('fInner.scope.run()', dat, $event);
};
}
};
})
I also
Here's the example:
http://jsfiddle.net/5QtJs/2/
I also added translude with no luck:
transclude: true,
template: "<div ng-transclude></div>",
http://jsfiddle.net/5QtJs/3/

You are on the right track with using transclude, presuming you want to include arbitrary content. In your example transclude is unnecessary. The ng-click call you are making is not finding your 'run' method in your directive. (Since, run is used in angular modules, I would advise against using in code). I've refactored your directive below.
<div ng-controller="Ctrl3">
<div f-outer>
<div f-inner dat="data">Will not show since using 'replace'</div>
</div>
</div>
.directive('fOuter', function () {
return {
link: function (scope) {
scope.dat = { x: 1, y: 2 };
console.log('fOuter.link');
}
};
})
.directive('fInner', function () {
return {
restrict: 'A',
replace: true,
scope: {
dat: '='
},
template: '<div>' +
'<a ng-click="run(dat, $event)">Click Me</a>' +
'</div>',
link: function (scope) {
console.log('fInner.link');
scope.run = function (dat, $event) {
console.log('fInner.scope.run()', dat, $event);
};
}
};
});

Related

how to pass function defined inside directive to another directive?

I need to pass function defined in directive to another one , my code is something like below
angular
.module('myApp')
.controller('myAppCtrl', function($scope) {})
.directive('first', function() {
return {
restrict: 'E',
scope: {
originFn: '&',
},
template: '<button ng-click="originFn()" >Click</button>',
controller: function() {
var self = this;
self.originFn = function() {
console.log('called from First');
};
},
};
})
.directive('second', function() {
return {
restrict: 'E',
scope: {
passedFn: '&',
},
template: '<button ng-click="passedFn()" >Click</button>',
};
});
html
<div ng-controller="myAppCtrl">
<first origin-fn="originFn" />
<second passed-fn="originFn" />
</div>;
when click on second button origin function not called ,
How can pass function to be called inside second directive ?
Both of directives are siblings, there is nothing in common, one directive can't call other directive controller without being it child.
So, if you can nest second directive into the first, you can use require functionality to get the parent directive controller.
.directive('second', function () {
return {
restrict: 'E',
require: '^^first',
scope: {},
template: '<button ng-click="innerFunc()" >Click</button>',
link: function ($scope, $elem, $attr, firstCtrl) {
// -------------------------------------^
$scope.innerFunc = firstCtrl.originFn;
}
};
})
<div ng-controller="myAppCtrl">
<first>
<second></second>
</first>
</div>
For more complete example.

How to use require (webpack) with dynamic string, angularjs

export function triMenuItemDirective() {
var directive = {
restrict: 'E',
require: '^triMenu',
scope: {
item: '='
},
// replace: true,
template: require('./menu-item-dropdown.tmpl.html'),
controller: triMenuItemController,
controllerAs: 'triMenuItem',
bindToController: true
};
return directive;
}
I need to load different html depending on item.
With the old way you could do:
template: '<div ng-include="::triMenuItem.item.template"></div>',
And in Controller
triMenuItem.item.template = 'app/components/menu/menu-item-' + triMenuItem.item.type + '.tmpl.html';
How do I achive this with webpack?
Something like
template: require('./menu-item-{{triMenuItem.item.type}}.tmpl.html'),
I think that to do this, you have at least three different approaches:
1- Use $templateCache and then pass a string variable to ng-include
.directive('myDirective', ['$templateCache', function ($templateCache) {
return {
scope: {
item: '='
},
template: '<div ng-include="content"></div>',
link: function (scope) {
$templateCache.put('a.tpl.html', require('./a.html'));
$templateCache.put('b.tpl.html', require('./b.html'));
scope.content = (scope.item === 'a') ? 'a.tpl.html' : 'b.tpl.html';
}
}
}]);
2- Use ng-bind-html.
app.directive('myDirective', ['$sce', function ($sce) {
return {
scope: {
item: '='
},
template: '<div ng-bind-html="content"></div>',
link: function (scope) {
if(scope.item === 'a')
scope.content = $sce.trustAsHtml(require('./a.html'));
}
}
}]);
3- Use ng-if. Maybe the less dynamic solution of the three, but is pretty simple if your requirements let you do it.
app.directive('myDirective', function () {
return {
scope: {
bool: '='
},
template: `
<div>
<div ng-if="item === 'a'">${require('./a.html')}</div>
<div ng-if="item === 'b'">${require('./b.html')}</div>
</div>
`
}
});

What is the scope of templates bindings in a directive without a controller

Here is a simple angular application. It shows/hides a text based on user clicking a button.
<div ng-controller="exampleController as ctrl">
<example></example>
</div>
app.controller('exampleController', function () {});
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
When I remove the controller it doesn't work. Note that the directive creates an isolate scope, so my understanding is that it doesn't depend on the controllers scope.
<div>
<example></example>
</div>
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
What is wrong here?
But anyways you should have ng-app
<body ng-app="demo">
<div>
<example></example>
</div>
</body>
var app = angular.module("demo", [])
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
Fiddle

Angularjs controller required by directive can not be found in transclude content

Recently I use angular to develop a directive, there is an directive which like ng-repeat to generate some records, I used transclude to implement it. but it raise an error that "Controller 'aArea', required by directive 'bSpan', can't be found!".
1. ModuleA code
var moduleA = angular.module("moduleA", []);
moduleA.directive("aArea", function () {
return {
restrict: 'E',
transclude:'element',
scope: {
amount:"="
},
template: '<div id=\"cc\" ng-transclude></div>',
controller: function ($scope,$element,$attrs) {
this.getData = function (data) {
return data + " is ok";
}
},
compile: function (tElement, attrs, linker) {
var parentElement = tElement.parent();
return {
pre: function () {
},
post: function (scope) {
linker(scope.$parent,function (clone,scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
}
}
}
}
});
moduleA.directive("bSpan", function () {
return {
restrict: 'E',
scope: {
data: "=",
},
template: '<span style=\"background-color:gray;color:orange\">{{data}}</span>',
require: "^aArea",
link: function ($scope, $element, $attrs, controller) {
var data = "abc";
}
}
});
2. ModuleB COde
var moduleB = angular.module("moduleB", []);
moduleB.directive("myItem", function () {
return {
restrict: 'E',
scope: {
item: "=",
itemTemplate: '='
},
priority: 1000,
terminal:false,
template: '<ng-include src=\"itemTemplate\"/>',
controller: function ($scope, $element, $attrs) {
var data = "";
}
}
})
3. ModuleC Code
var moduleC = angular.module("moduleC", ["moduleA", "moduleB"]);
moduleC.controller("Ctr", function ($scope) {
$scope.item = {};
$scope.item.dataAmount = 1000;
$scope.item.templateUrl = "item-template.html";
})
4. Html Code
<body>
<div ng-app="moduleC">
<div ng-controller="Ctr">
<a-area>
<my-item item="item" item-template="item.templateUrl"></my-item>
</a-area>
</div>
</div>
</body>
5. template code
<div>
<span style="display:block">hello every one</span>
<b-span data="item.dataAmount"></b-span>
</div>
You should not use the transclude function (that you called linker) of the compile function - it is deprecated.
From $compile documentation:
Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.
Following this guidance (and a few other minor changes for the better), change the aArea directive as follows:
compile: function(tElement, tAttrs) {
// don't use the template element
//var parentElement = tElement.parent();
return function(scope, element, attrs, ctrls, transclude) {
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
};
}
In fact, you don't even need the transclude function at all and you don't need to do transclude: "element". You could just change to transclude: true and use <div ng-transclude> 3 times in the template.

How to expose behavior from Element directive?

I ran into a problem adapting the solution from How to expose behavior from a directive with isolated scope?. I wanted to expose my directive as an Element rather than as an Attribute:
Here's a JSFiddle. When you click the first button, which uses the Attribute approach, everything is ok. But the second button uses the Element approach and it gives an error.
Here is the code as well:
HTML:
<div ng-app="main">
<div ng-controller="MyCtrl">Click the first button and everything is ok:
<br>
<button ng-click="callFnInAttribute()">Call Function in Attribute Directive</button>
<br>{{errorViaAttribute}}
<div my-attribute my-fn-via-attribute="fnInCtrlViaAttribute"></div>
<br>
<br>But click the second button and you get an error:
<br>
<button ng-click="callFnInElement()">Call Function in Element Directive</button>
<br>{{errorViaElement}}
<my-element my-fn-via-element="fnInCtrlViaElement"></my-element>
<br>
<br>The only difference is the type of directive used. Why does it work with an Attribute type of directive but not with an Element directive?</div>
</div>
JavaScript:
angular.module("main", []).controller("MyCtrl", function ($scope) {
$scope.callFnInAttribute = function () {
try {
$scope.fnInCtrlViaAttribute();
$scope.errorViaAttribute = "OK";
} catch (anError) {
$scope.errorViaAttribute = "Error: " + anError;
}
};
$scope.callFnInElement = function () {
try {
$scope.fnInCtrlViaElement();
$scope.errorViaElement = "OK";
} catch (anError) {
$scope.errorViaElement = "Error: " + anError;
}
};
}).directive("myAttribute", function () {
return {
require: 'A',
scope: {
myFnViaAttribute: '='
},
controllerAs: 'chartCtrl',
bindToController: true,
controller: function ($scope) {
$scope.myFnViaAttribute = function () {
console.log("myFnViaAttribute called");
}
}
};
}).directive("myElement", function () {
return {
require: 'E',
scope: {
myFnViaElement: '='
},
controllerAs: 'chartCtrl',
bindToController: true,
controller: function ($scope) {
$scope.myFnViaElement = function () {
console.log("myFnViaElement called");
}
}
};
});
This is using the following AngularJS version: https://code.angularjs.org/1.1.0/angular.min.js
How do I correctly expose the behavior from an Element?
I think your error simply comes from the fact that you wrote require instead of restrict in your directives. require is to make sure another directive is present in the same element, restrict is to define the HTML structure of your directive.
.directive("myAttribute", function () {
return {
restrict: 'A', // <-- and not "require"
scope: {
myFnViaAttribute: '='
},
controllerAs: 'chartCtrl',
bindToController: true,
controller: function ($scope) {
$scope.myFnViaAttribute = function () {
console.log("myFnViaAttribute called");
}
}
};
})

Resources