I have two directives. First is using (including) second.
This is my child directive:
angular.module('myApp')
.directive('radioType', function() {
return {
restrict: 'E',
scope: {
question: '=',
onAnswer: '&'
},
template: [
'<div>{{vm.question.text}}<br/>',
'<div>',
'<button ng-repeat="a in vm.question.answers track by $index" ng-click="onSelected(a)">{{a.text}}</button>',
'</div>',
'<button ng-click="myFunction()">Radio</button>',
'</div>'
].join(''),
controller: ['$scope', function($scope) {
$scope.myFunction = function() {
console.log("hello from radio controller");
$scope.vm.onAnswer();
};
$scope.onSelected = function(question) {
$scope.vm.question.answer = question.text;
console.log(question);
};
}],
controllerAs: 'vm',
bindToController: true
};
});
and this is my main directive:
angular.module('myApp')
.directive('question', function($compile) {
var combo = '<div>COMBO - {{vm.content.text}}</div>';
var radio = [
'<radio-type question="vm.content" onAnswer="vm.onAnswer()"></radio-type>'
].join('');
var input = [
'<input-type question="vm.content"></input-type>',
].join('');
var getTemplate = function(contentType) {
var template = '';
switch (contentType) {
case 'combo':
template = combo;
break;
case 'radio':
template = radio;
break;
case 'input':
template = input;
break;
}
return template;
}
var linker = function(scope, element, attrs) {
scope.$watch('vm.content', function() {
element.html(getTemplate(scope.vm.content.type))
$compile(element.contents())(scope);
});
}
return {
restrict: "E",
link: linker,
scope: {
content: '='
},
controller: ['$scope', function($scope) {
$scope.onAnswer = function() {
console.log("answer");
};
}],
controllerAs: 'vm',
bindToController: true
};
})
Inside child directive I have buttons, when someone clicks on one of them I'd like to call function from main directive.
I've included onAnswer: '&' in my child directive, and I'm assigning function inside main directory template, but I can get this working.
Here is Plunker showing my problem: http://plnkr.co/edit/iWU4L3IJLHtADHBl2dIh?p=preview
Any other advice are welcome.
You have 2 errors on scripts.js.
On line 6 your attribute should not be camelCase:
'<radio-type question="vm.content" on-answer="vm.onAnswer()"></radio-type>'
and on line 46 your function should be inside object vm:
$scope.vm.onAnswer = function() {
Check this plunker: http://plnkr.co/edit/8O7BRjfE3ZWFLrvMkbXB?p=preview
Related
I have a directive Foo in Directive Bar i am trying to call a function in Foo
but it is not working.
http://jsfiddle.net/4d9Lfo95/3/
example fiddle is created.
angular.module('ui', []).directive('uiFoo',
function() {
return {
restrict: 'E',
template: '<p>Foo</p>',
link: function($scope, element, attrs) {
$scope.message = function() {
alert(1);
};
},
controller: function($scope) {
this.message = function() {
alert("Foo Function!");
}
}
};
}
).directive('uiBar',
function() {
return {
restrict: 'E',
template: '<button ng-click="callFunction()">Bar</button> <ui-foo></ui-foo>',
require: 'uiFoo',
scope: true,
link: function($scope, element, attrs, uiFooController) {
$scope.callFunction = function() {
alert('Bar Function');
uiFooController.message();
}
}
};
}
);angular.module('myApp', ['ui']);
where as the UI looks like this
<div ng-app="myApp"> <ui-bar> </ui-bar></div>
You left out this error message:
Controller 'uiFoo', required by directive 'uiBar', can't be found!
The problem is that the require hierarchy searches up the tree, not down it. So, ui-bar is trying to find a uiFoo directive controller either on itself or (with the ^ symbol) in one of it's ancestors, not one of it's children.
If you want to call a method from the child directive, just use the scope: http://jsfiddle.net/4d9Lfo95/5/
angular.module('ui', []).directive('uiFoo',
function() {
return {
restrict: 'E',
template: '<p>Foo</p>',
controller: function($scope) {
$scope.message = function() {
alert("Foo Function!");
}
}
};
}
).directive('uiBar',
function() {
return {
restrict: 'E',
template: '<button ng-click="callFunction()">Bar</button> <ui-foo></ui-foo>',
scope: true,
controller: function($scope) {
$scope.callFunction = function() {
alert('Bar Function');
$scope.message();
}
}
};
}
);
I have a scenario where i need to apply different directives (attribute) to a DIV inside a Angular bootstrap Modal at runtime (button click).
I would know the name of the directive to apply. But i am not able to figure out how to change the template at runtime to add necessary directive name as an attribute to the DIV.
consider this plunker
Basically i want the div to have child directive as an attribute using synstax like this
<div {{child}}></div>
So when it works, it should generate <div child-directive></div>
How can this be done? is this even possible? What is the best way to change the template before opening the Modal so that it wires up correctly when loaded.
// Code goes here
var app = angular.module('main-module', ['ui.bootstrap']);
app.directive('parentDirective', function($uibModal, $compile) {
return {
restrict: 'E',
template: "<h2>I am Parent</h2><button ng-click='click()'>Click Me</button>",
scope: {
child:'#'
},
link: function($scope, elem, attrs) {
console.log('?',$scope.child, $scope);
var template = "<div><h3>This is modal</h3>"
+ "Ideally you should see the child directive below"
+ "<hr />"
+ "<div "+ $scope.child + "></div></div>";
$scope.click = function() {
$uibModal.open({
template: template,
scope: $scope,
size: 'lg',
});
}
}
};
})
.directive('childDirective', function() {
return {
restrict: 'A',
template: "<div><h4>I am Child</h4><a ng-click='click()'>Click Me!!</a></div>",
replace: true,
scope: {},
link: function($scope, elem, attrs) {
$scope.click = function() {
alert("I am in child scope");
}
}
};
}).directive('anotherChildDirective', function() {
return {
restrict: 'A',
template: "<div><h4>I am another Child</h4><a ng-click='click()'>Click Me!!</a></div>",
replace: true,
scope: {},
link: function($scope, elem, attrs) {
$scope.click = function() {
alert("I am in child scope");
}
}
};
});;
I wan't to program a flexible angular directive with it's properties defined in an own, simple object.
Angular:
contentFactory.directive("listViewDir", function ($compile) {
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link:
return $scope.config.link(scope, element, attrs);
}
}
});
Own Configuration Object:
contentFactory.controller("indexCtrl", function ($scope) {
$scope.config = oLiftTabs;})
var configurations = [{
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = "... myTemplate ..";
element.html(template);
$compile(element.contents())(scope);
},
}]
While my solution for the controller works well, it doesn't for the link.
Is there a more proper way for my approach? Can I realize access in my encapsulated method to the services (like $compile) without declaring it in the directive declaration?
Is this what you're trying to achieve? You didn't make it clear where you expect this object to live so I've assumed you want it a parent controller. This doesn't feel like a good idea but without knowing more about your use case it's hard to say.
DEMO
html
<body ng-controller="MainCtrl">
<list-view-dir config="config"></list-view-dir>
</body>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
$scope.config = {
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
},
};
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});
Update
From your comments it sounds like you need to use a factory. Maybe something like this? It feels pretty ugly but it could be what you're looking for.
DEMO2
var app = angular.module('plunker', []);
app.factory('directiveConfigurations', function($compile){
var configurations = {
'listViewDir': {
controller: function ($scope) {
$scope.ButtonClicked = function(){
alert('Button wurde geklickt!');
};
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
}
}
};
return {
get: get
};
////////////////////////
function get(key){
return configurations[key];
}
});
app.controller('MainCtrl', function($scope, directiveConfigurations) {
$scope.config = directiveConfigurations.get('listViewDir');
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});
I want to create a directive that has dynamic view with dynamic controller. the controller and the template view is coming from the server.
The Directive
var DirectivesModule = angular.module('BPM.Directives', []);
(function () {
'use strict';
angular
.module('BPM.Directives')
.directive('bpmCompletedTask', bpmCompletedTask);
bpmCompletedTask.$inject = ['$window'];
function bpmCompletedTask ($window) {
// Usage:
// <bpmCompletedTask></bpmCompletedTask>
// Creates:
//
var directive = {
link: link,
restrict: 'E',
scope: {
type: '=',
taskdata: '=',
controllername:'#'
},
template: '<div ng-include="getContentUrl()"></div>',
controller: '#',
name: 'controllername'
};
return directive;
function link(scope, element, attrs) {
scope.getContentUrl = function () {
return '/app/views/TasksViews/' + scope.type + '.html';
}
scope.getControllerName = function ()
{
console.warn("Controller Name is " + scope.type);
return scope.type;
}
}
}
})();
Here how I'm trying to use the directive
<div ng-controller="WorkflowHistoryController as vm">
<h2>Workflow History</h2>
<h3>{{Id}}</h3>
<div ng-repeat="workflowStep in CompletedWorkflowSteps">
<bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome" type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
</bpm-completed-task>
</div>
</div>
The problem now is when the directive gets the controller name it get it as literal string not as a parameter.
Is it doable ?
if it's not doable, What is the best solution to create dynamic views with its controllers and display them dynamically inside ng-repeat?
Thanks,
Update 20 Jan I just updated my code in case if some one interested in it. All the Credit goes to #Meligy.
The First Directive:
(function () {
'use strict';
angular
.module('BPM.Directives')
.directive('bpmCompletedTask', bpmCompletedTask);
bpmCompletedTask.$inject = ['$compile', '$parse'];
function bpmCompletedTask ($compile, $parse) {
var directive = {
link: function (scope, elem, attrs) {
console.warn('in the first directive - before if');
if (!elem.attr('bpm-completed-task-inner'))
{
console.warn('in the first directive');
var name = $parse(elem.attr('controllername'))(scope);
console.warn('Controller Name : ' + name);
elem = elem.removeAttr('bpm-completed-task');
elem.attr('controllernameinner', name);
elem.attr('bpm-completed-task-inner', '');
$compile(elem)(scope);
}
},
restrict: 'A',
};
return directive;
}
})();
The Second Directive
angular
.module('BPM.Directives')
.directive('bpmCompletedTaskInner',['$compile', '$parse',
function ($window, $compile, $parse) {
console.warn('in the second directive');
return {
link: function (scope, elem, attrs) {
console.warn('in the second directive');
scope.getContentUrl = function () {
return '/app/views/TasksViews/' + scope.type + '.html';
}
},
restrict: 'A',
scope: {
type: '=',
taskdata: '=',
controllernameinner: '#'
},
template: '<div ng-include="getContentUrl()"></div>',
controller: '#',
name: 'controllernameinner'
};
}]);
The Html
<div ng-repeat="workflowStep in CompletedWorkflowSteps">
<div bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome"
type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
</div>
</div>
Update:
I got it working, but it's really ugly. Check:
http://jsfiddle.net/p6Hb4/13/
Your example has a lot of moving pieces, so this one is simple, but does what you want.
Basically you need a wrapper directive that takes the JS object and converts into a string property, then you can use هى your directive for everything else (template, scope, etc).
.
Update 2:
Code Inline:
var app = angular.module('myApp', []).
directive('communicatorInner', ["$parse", "$compile",
function($parse, $compile) {
return {
restrict: 'A',
template: "<input type='text' ng-model='message'/><input type='button' value='Send Message' ng-click='sendMsg()'><br/>",
scope: {
message: '='
},
controller: '#'
};
}
]).
directive('communicator', ['$compile', '$parse',
function($compile, $parse) {
return {
restrict: 'E',
link: function(scope, elem) {
if (!elem.attr('communicator-inner')) {
var name = $parse(elem.attr('controller-name'))(scope);
elem = elem.removeAttr('controller-name')
elem.attr('communicator-inner', name);
$compile(elem)(scope);
}
}
};
}
]).
controller("PhoneCtrl", function($scope) {
$scope.sendMsg = function() {
alert($scope.message + " : sending message via Phone Ctrl");
}
}).
controller("LandlineCtrl", function($scope) {
$scope.sendMsg = function() {
alert($scope.message + " : sending message via Land Line Ctrl ");
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>
<div ng-app="myApp">
<div ng-init="test = {p: 'PhoneCtrl', l: 'LandlineCtrl' }">
<communicator controller-name="test.p" message="'test1'"></communicator>
<communicator controller-name="test.l"></communicator>
</div>
</div>
.
Original (irrelevant now but can help other related issues)
Yes, it should work.
A test with Angular 1.3:
http://jsfiddle.net/p6Hb4/9/
Things to check:
Is the controller defined and added to the module? It will not work
If the controller is just a global function it won't work. It has to be added via the <myModule>.controller("<controllerName>", <functiion>) API
Does ng-controller work? Just adding it to the template
Similarly, does using ng-controller directly outside of the directive work?
I wrote two directives in angularjs, one is embedded in the other one.
below is the scripts for the directives:
module.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
this.scope = $scope;
return $scope.panes = [];
},
link: function(scope, element, attrs) {
return $log.log('test1', scope.panes);
}
};
}
]);
module.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
controller: function($scope) {
return this.x = 1;
},
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, fooCtrl) {
return $log.log('test2', fooCtrl);
}
};
}
]);
below is the piece of html:
<foo ng-controller="IndexController">
<bar></bar>
</foo>
below is the element generated, inspected from the chrome developer tools
<div id="test" ng-transclude="" ng-controller="IndexController" class="ng-scope">
<div ng-transclude="" class="ng-scope"></div>
</div>
below is chrome console output:
test2
Array[0]
length: 0
__proto__: Array[0]
angular.js:5930
test1
Array[0]
length: 0
__proto__: Array[0]
Question:
The child directive can not get the parent directive's controller, so the fourth parameter "fooCtrl" for link function of "bar" is an empty array. what's the thing that I do it wrong?
update and answer:
At last I found the reason that made the weird result. It's just a stupid mistake that I have made:
// in directive "foo"
controller: function($scope) {
this.scope = $scope;
// This line below is wrong. It is here
// because I transcompiled coffeescript to js.
// return $scope.panes = [];
// It should be like below:
$scope.panes = []
// I should have written .coffee like below
// controller: ($scope) ->
// #scope = $scope
// $scope.panes = []
// return # prevent coffeescript returning the above expressions.
// # I should rather have added the above line
}
After correcting the mistake, I tried and found there's nothing to prevent using controller or providing empty content in child directives.
AFAIK, you cannot have a controller in the child directive.
Demo: http://plnkr.co/edit/kv9udk4eB5B2y8SBLGQd?p=preview
app.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
$scope.panes = ['Item1','Item2','Item3']
return {
getPanes: function() { return $scope.panes; }
};
},
link: function(scope, element, attrs, ctrl) {
$log.log('test1', ctrl, ctrl.getPanes(), scope.panes);
}
};
}
]);
I removed the child controller.
app.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, ctrl) {
scope.x = 1;
$log.log('test2', ctrl, ctrl.getPanes(), scope.panes, scope.x);
}
};
}
]);