How to stop propagation of & functions into nested directives - angularjs

here I got one ctrl
controllers.ctrl = function($scope){
$scope.trData = [
{name : 'elem1',children : []},
{name : 'elem2',children :
[{name : 'elem3',children : []}]
}
};
$scope.testFunction = function(tr){
console.debug(tr);
}
}
And a nested directive
directives.nested = function($compile){
restrict: 'E',
replace: false,
scope: {
tr: '=',
test : '&'
},
link: function(scope, element, attrs) {
scope.setActive = function(){
console.debug("A");
console.debug(scope.tr);
scope.test({tr:scope.tr});
console.debug("B");
};
var template = '<span data-ng-click="setActive();">{{tr.name}} - </span>';
if(scope.tr.children.length > 0){
template += '<ul><li data-ng-repeat="tr0 in tr.children">';
template += '<nested tr="tr0" test="test(tr)"></nested>';
template += '</li></ul>';
}
var newElement = angular.element(template);
$compile(newElement)(scope);
element.replaceWith(newElement);
}
And in my template, of course :
<ul>
<li data-ng-repeat="tr in trData">
<nested tr="tr" test="testFunction(tr)"></nested>
</li>
</ul>
When I click on a elem1, I've got in the console:
A
{name : 'elem1',children : []}
B
If I click on 'elem3', I got
A
{name : 'elem3',children : []}
{name : 'elem2',children : [{name : 'elem3',children : []}] }
B
The function testFunction is called twice. I would like to have it called only once with elem3.
I am sure there is a better way to do this.
Fiddle from Banana-In-Black : http://jsfiddle.net/T4uKf/

Update:
Try to click on elem3 : You will see the console.debug output 'Object
{name: "elem3", children: Array[0]' And the console.warn outputs :
Object {name: "elem2", children: Array[1] I though I would had "elem3"
output twice. Instead I got "elem3" and "elem2". Do you know why this
happens?
To anwser this, first we take a look on generated HTML and scope:
<!-- controller scope -->
<li data-ng-repeat=" tr in trData "> <!--1|-child scope -->
<span>elem2 - </span> <!--2 |-isolated scope -->
<ul> <!-- | -->
<li data-ng-repeat="tr0 in tr.children"><!--3 |-child scope -->
<span>elem3</span> <!--4 |-isolated scope -->
</li>
</ul>
</li>
The variables in scope will be like: (|- stands for inheritance)
Controller Scope: { trData: [...] }
|- Scope1 (child): { tr0: elem2 }
Scope2 (isolated): { tr: elem2, test: function() }
|- Scope3 (child): { tr0: elem3 }
Scope4 (isolated): { tr: elem3, test: function() }
Now, according to official api docs, which talks about isolated scope variable:
& or &attr - provides a way to execute an expression in the context of the parent scope.
* This feature is implemented by $parse service.
After you clicked on elem3, the execution order will be: (skip the log part.)
test({ tr: scope.tr }) using Scope4 as its execution context
Equals to $parse('test(tr)')(Scope3, { tr: elem3 }).
It's called in Scope3 because $parse executes in parent scope.
test(tr) using Scope3 as its execution context
Equals to $parse('testFunction(tr)')(Scope1, elem3)
It's called in Scope1 because test() is inherited from Scope2, and $parse executes in parent scope.
testFunction(tr) using Scope1 as its execution context
Now tr will be parsed into Scope1.tr which is elem2 because elem3.tr doesn't exist.
Finally, console.warn(elem2);
This is how that happens...
* About $parse service you can check this.
Origin:
I changed console.log to console.warn, ang log shows it only executed once.
$scope.testFunction = function (tr) {
console.warn(tr);
}
http://jsfiddle.net/T4uKf/
It prints twice
A
{name : 'elem3',children : []}
{name : 'elem2',children : [{name : 'elem3',children : []}] }
B
only because you log it in both scope.setActive and $scope.testFunction()

Related

Passing values from custom angular directive to other page

I've a custom directive that has values stored in an object i want these values from custom directive input and dropdown to be passed as an object to the page where i am referring this directive. this directive values will be passed to the main page when i click apply button which is on my main page, i've tried following but can't get the values from the custom directive in my page where i use this directive. Please suggest how can i pass values from directive to a different page. I need the values from query object in request variable i've declared in a function in my main page controller defined at the last
The custom directive template file metrics-dashboard-configuration.html
<div>
<span>Service Level (SL)</span>
<select ng-model="selection.serviceLevelOption" ng-options="serviceLevelObject.value as serviceLevelObject.text for serviceLevelObject in selection.serviceLevels.values" ng-init="selection.serviceLevelOption='30'"></select>
<br>
<input type="text" ng-model="selection.inputText" />
</div>
Custom directive declaration and controller
function metricsDashboardConfiguration() {
return {
restrict: 'E',
scope: {
query: '='
},
templateUrl: 'metrics-dashboard-configuration.html',
controller: metricsDashboardConfigurationCtrl
};
}
function metricsDashboardConfigurationCtrl($scope) {
$scope.query = {};
$scope.selection = {
serviceLevels: {
values: [
{value : "15" , text : "15"},
{value : "30" , text : "30"},
{value : "45" , text : "45"},
{value : "60" , text : "60"},
{value : "120" , text : "120"}
]
},
inputText: "test"
};
$scope.updateRequest = function() {
$scope.query.inputText = $scope.selection.inputText;
$scope.query.serviceLevels= $scope.selection.serviceLevels;
};
$scope.$watch('selection.inputText', $scope.updateRequest, true);
$scope.$watch('selection.serviceLevels', $scope.updateRequest, true);
The html page where i'm using the directive
<metrics-dashboard-configuration query="queryData" update-Queues-Dashboard="updateQueuesDashboard()"></metrics-dashboard-configuration>
The controller of the page where i need values of the custom directive
$scope.queryData = {
inputText : "",
trailingWindows: []
};
$scope.updateQueuesDashboard = function () {
var request = angular.copy($scope.queryData);
};
The model you have used in your metrics-dashboard-configuration.html file is ng-model="selection.serviceLevelOption" and ng-model="selection.myInput", but in your directive you are watching selection.inputText and selection.trailingWindows
Check this working Plunk and verify what is going wrong in your code.
If you want to use the model bound to the UI elements of the directive in the view where it is being used then, you should make property in isolated scope same way as you have done by passing query in isolated scope.
Something like this:
function metricsDashboardConfiguration() {
return {
restrict: 'E',
scope: {
query: '=',
serviceLevelOption: '=',
inputText: '='
},
templateUrl: 'metrics-dashboard-configuration.html',
controller: metricsDashboardConfigurationCtrl
};
}
HTML
<metrics-dashboard-configuration service-level-option="option" input-text="inText" query="queryData" update-Queues-Dashboard="updateQueuesDashboard()"></metrics-dashboard-configuration>
and then what ever value is being updated from the ui or directive controller it will be reflected in the option and inText variables.

Not able to require a directive from another directive in angularJS

Getting an error while trying to require directive msgpallete from directive itemCounter.
I have removed codes from some functions which i thought is irreelevant in this context.
Do ask for any code which you think is important.Kinda new to angular. So may have many misconceptions.
HTML snippet
<msgpallete msg="message"></msgpallete>
<li ng-repeat = "item in items">
<item-counter
startcounter = 1
resetter = 'reset'
name = {{item.name}} >
{{item.name}}
</item-counter><br><br>
</li>
JS snippet
angular.module('myApp',[])
.controller('MainCtrl', function($scope, $timeout) {
....code irreleveant in this context....
})
.directive('msgpallete',function(){
return{
restrict:"E",
scope:{},
template:"<h4>Added"+""+" "+"</h4>"
}
})
.directive('itemCounter',function(){
return {
restrict:'E',
require:'msgpallete',
scope:{
resetter:"="
},
transclude:true,
link:function(scope,elem,attr){
scope.qty = attr.startcounter
scope.add = function(){}
scope.remove = function(){}
scope.reset = function(){}
scope.$watch();
},
template:"<a ng-transclude></a> &nbsp"+
"<button ng-click='remove();' >less</button>"+
"{{qty}}" +
"<button ng-click='add();'>more</button>&nbsp"+
"<button ng-click='reset();'>submit</button>"
}
});
thanks in advance
require:'^msgpallete'
The require parameter has additional functionality if you look through the docs for it.
someDirective : Require someDirective on same element and pass it to
linking function
?someDirective : Pass someDirective controller if available on same element to linking function. If not, pass null.
^someDirective : Require someDirective on one of the parent elements and pass it to linking function.
?^someDirective : Pass someDirective controller if available on one of parent elements to linking function. If not, pass null.
It has been a little while since I have done this and the directive you are requiring may need to explicitly defined a controller. Just defining and return an empty controller is enough.
.directive('parent', function() {
return: {
controller: function() {return {}}
}
})

Unable to get the children() after compilation within directive

I have a directive which compiles a template with radio buttons.
I'm trying to get the children after compilation and it doesn't show up.
Where am I going wrong ?
<div radiolist></div>
JS
app.directive('radiolist', function() {
var templateHtml = function () {
return '<label ng-repeat="option in options" > <input type="radio" ng-disabled="{{isDisabled}}" name="{{attributename}}" ng-value="option.Value" ng-model="$parent.ngModel" />{{option.Desc}} </label>';
};
return {
template: templateHtml,
link : function(scope, element) {
scope.options = [
{
"Value" : "Y",
"Desc" : "Yes"
},
{
"Value" : "N",
"Desc" : "No"
}
]
console.log(angular.element(element).children())
}
};
});
Demo : http://plnkr.co/edit/SUFXunVLOhYfK1x4jVe2?p=preview
The reason you can't see the children is because when the link function is called the directive has just finished compiling. There are no children because the ng-repeat hasn't had a chance to render anything yet.
I would recommend you restructure your code to not have to get access to the children, I assume because you want to add listeners to the radio buttons. Try explicitly defining each radiobutton instead of using ng-repeat.
Finally, the element is already an angular element, you don't need to wrap it again. Fix that line in your code to continue debugging.
Edit:
This mod of your plunkr might help you with the event bindings, I added ng-click to the rendered elements of ng-repeat, and passed the index of the radiobutton back:
http://plnkr.co/edit/fABRl8OGMywsilmo9m8e?p=preview
app.directive('radiolist', function() {
var templateHtml = function () {
return '<label ng-repeat="option in options" >' +
'<input ng-click="someFunction($index)" type="radio" ng-disabled="{{isDisabled}}" name="{{attributename}}" ' +
'ng-value="option.Value" ng-model="$parent.ngModel" />{{option.Desc}} </label>';
};
return {
template: templateHtml,
link : function(scope, element) {
scope.options = [
{
"Value" : "Y",
"Desc" : "Yes"
},
{
"Value" : "N",
"Desc" : "No"
}
]
scope.someFunction = function(index) {
console.log('clicked:', index);
}
// Don't fix this, but at least don't wrap element again
console.log('children:', element.children());
}
};
});

How should data be passed into a directive when a scope is populated asyncronysly

I have a controller which populates some data asynchronously. That data is used by a directive.
Unfortunately, the directive does not display the data. If I remove the timeout (which is what simulates an asynch process), then things work fine.
I'm wondering what the best solution is. I'm quite certain this is a common scenario-- and that I'm going about things the wrong way. Is there a way to delay the directive from "loading" until the data in the scope is populated.
HTML:
html
head
link(
href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css",rel="stylesheet",type="text/css")
title Hello World
body(ng-app="app")
.container
.well.
How should data be passed into a directive when a scope is populated asyncronysly
div(ng-controller="StoogeCtrl")
.panel.panel-default
.panel-heading
| All
.panel-body
ul
li(ng-repeat="stooge in stooges") {{stooge.name}}
.panel.panel-default
.panel-heading
| Noe Moe
.panel-body
div(filter-stooge except="Moe" stooges="stooges")
JavaScript(CoffeeScript):
app = angular.module 'app', []
app.controller 'StoogeCtrl', ($scope, $q, $timeout)->
stooges = [
{name: "Moe"}
{name: "Larry"}
{name: "Curly"}
]
getPromise = ()->
dfd = $q.defer()
dfd.resolve(bars)
dfd.promise
$timeout ()->
$scope.stooges = stooges
app.directive 'filterStooge', ()->
scope:
stooges: '='
except: '#'
template: "<ul )><li ng-repeat='stooge in filtered'>{{stooge.name}}</li></ul>"
link: (scope)->
filtered = []
for stooge in scope.stooges
filtered.push stooge if stooge.name != scope.except
scope.filtered = filtered
base on answer from ExpertSystem (below). I did this:
app = angular.module 'app', []
app.controller 'StoogeCtrl', ($scope, $q, $timeout)->
stooges = [
{name: "Moe"}
{name: "Larry"}
{name: "Curly"}
]
getPromise = ()->
dfd = $q.defer()
dfd.resolve(bars)
dfd.promise
$timeout ()->
$scope.stooges = stooges
$scope.done = true
,
3000
app.directive 'filterStooge', ()->
scope:
stooges: '='
except: '#'
done: '='
template: "<ul><li ng-repeat='stooge in filtered'>{{stooge.name}}</li></ul>"
link: (scope)->
scope.$watch 'done', (newVal, oldVal)->
if newVal
filtered = []
for stooge in scope.stooges
filtered.push stooge if stooge.name != scope.except
scope.filtered = filtered
The data is not shown, because you show the filtered data and at the time filtered is created there is no data in $scope.stooges (so you are sort-of filtering and empty array, which results in an empty array of course).
You have the following alternatives:
$watch over $scope.stooges and re-build filtered once it changes.
Use filtering in the view (which is probably a better practice). Either create your own filter (if you need advanced filtering capabilities) or use the built-in filter filter.
E.g.:
app.directive('filterStooge', function () {
return {
scope: {
stooges: '=',
except: '#'
},
template:
'<ul>' +
' <li ng-repeat="stooge in stooges | filter:filterSpec">' +
' {{stooge.name}}' +
' </li>' +
'</ul>',
link: function filterStoogePostLink(scope, elem) {
// If `except` is specified, exclude items by name.
// If `except` is not specified, show all items.
scope.filterSpec = scope.except ?
{name: '!' + scope.except} :
'';
}
};
});
See, also, this short demo
BTW, you where ngRepeating the <ul> element (which would result in multiple <ul>s.
I assumed you wanted to ngRepeat the <li> element.

Triggering a function with ngClick within ngTransclude

I have an unordered list loaded with four items from an array while using ngRepeat. The anchor tag in the list item has a function in the ngClick attribute that fires up a message. The function call works well when used like this:
<ul>
<li ng-repeat="n in supsNames">
<a ng-click="myAlert(n.name)">{{n.name}}</a>
</li>
</ul>
I created a simple directive for inserting unordered lists with list items. The list is loaded just fine but the same functioned I previously mentioned does not fire up. The code is as follows:
<div list items="supsNames">
<a ng-click="myAlert({{item.name}})">{{item.name}}</a>
</div>
Here is my javascript and angularjs code:
var app = angular.module('myapp', []);
app.controller('myCtrl', function($scope) {
$scope.title = 'ngClick within ngTransclude';
$scope.supsNames = [
{"name" : "Superman"},
{"name" : "Batman"},
{"name" : "Aquaman"},
{"name" : "Flash"}
];
$scope.myAlert = function(name) {
alert('Hello ' + name + '!');
};
});
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '='
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
console.log(scope);
}
};
});
I also have a plnkr in case you want to see what I tried to do:
http://plnkr.co/edit/ycaAUMggKZEsWaYjeSO9?p=preview
Thanks for any help.
I got the plunkr working. I'm not sure if its exactly what you're looking for. I copied the main code changes below.
Here's the plunkr:
http://plnkr.co/edit/GEiGBIMywkjWAaDMKFNq?p=preview
The modified directive looks like this now:
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '=',
ctrlFn: '&' //this function is defined on controller
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
//directive fn that calls controller defined function
scope.dirFn = function(param) {
if(scope.ctrlFn && typeof scope.ctrlFn == 'function') { //make sure its a defined function
scope.ctrlFn( {'name': param} ); //not sure why param has to be passed this way
}
}
}
};
});
And here's how it's called in the html file that's bound to your controller:
<div list items="supsNames" ctrl-fn="myAlert(name)">
<a ng-click="dirFn(item.name)">{{item.name}}</a>
</div>
I think what was happening before is that you were trying to use a function defined in your controller within the isolated scope of the directive, so it wasn't working--that function was undefined in the directive. So what I did was added another parameter to the directive that accepts method binding (I think that's what its called) with the '&'.
So basically you pass your controller method to the directive, and that method gets invoked however you want by the directive defined method I creatively named "dirFn". I don't know if this is the best way per se, but I've used it in an existing project with good results ;)
you need to pass the function to the directive
scope: {
items: '=', 'myAlert': '='
},
The ng-repeat inside the template of the directive insert a new scope and it require to call transclude funcion manually to work. I suggest remove ng-repeat and make the transclusion manually passing a copy of the controller scope and setting the item on each copy:
for(var i=0,len=scope.items.length;i<len;i++){
var item=scope.items[i];
var itemScope=scope.$parent.$new();
$transcludeFn(itemScope, function (clone,scope) {
// be sure elements are inserted
// into html before linking
scope.item=item;
element.after(clone);
});
};
I edit the pluker and I hope that could be helpfull: http://plnkr.co/edit/97ueb8SFj3Ljyvx1a8U1?p=preview
For more info about transclusion see: Transclusion: $transcludeFn

Resources