Unable to get the children() after compilation within directive - angularjs

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());
}
};
});

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.

In Angular, unable to bind to a radio button in a directive on page load

CURRENT SITUATION: I have form fields that are dynamically created via a directive. One of the types includes radio buttons.
GOAL: On page load, I need to bind the values of these elements to a predefined scope. This works correctly with other field types but not with the radio buttons despite the $parent.model matching the option exactly.As you can see in the JSFiddle, "your_name_0" and "description_1" bind correctly but not the radio buttons.
CONSTRAINTS: Due to code restrictions, the directive's ng-model and ng-value cannot change. I know it is possible to bind the model of a radio button to an object. See this for proof: http://plnkr.co/edit/OnHXect37Phij9VAB0ET?p=preview
The Controller:
function MyCtrl($scope) {
//defines the field types
$scope.formFields = [
{
"fieldName": "Your Name",
"uniqueId": "your_name_0",
"fieldType": "text"
},
{
"fieldName": "Description",
"uniqueId": "description_1",
"fieldType": "textarea"
},
{
"fieldName": "Favorite Color",
"uniqueId": "favorite_color_2",
"fieldType": "radio"
"fieldTypeOptionsArray":[
{
"formFieldId":"favorite_color_2",
"optionId":"favorite_color_2_0",
"optionValue":"yellow"
},
{
"formFieldId":"favorite_color_2",
"optionId":"favorite_color_2_1",
"optionValue":"blue"
}
]
}
];
//values to bind to the model
$scope.output={
"your_name_0":"Bob",
"description_1":"I am a developer",
"favorite_color_2":
{
"optionValue":"yellow",
"formFieldId":"favorite_color_2",
"optionId":"favorite_color_2_0"
}
};
};
The Directive:
myApp.directive("formField",function($compile){
var templates = {
textTemplate:'<div class="form-group"><label for="{{content.uniqueId}}" >{{content.fieldName}}</label><input type="text" ng-model="model" name="{{content.uniqueId}}" class="form-control" id="{{content.uniqueId}}"/> </div><br>',
textareaTemplate:'<div class="form-group"><label for="{{content.uniqueId}}" >{{content.fieldName}}</label> <textarea ng-model="model" name="{{content.uniqueId}}" id="{{content.uniqueId}}" class="form-control"></textarea> </div>',
radioTemplate : '<div class="form-group"><label>{{content.fieldName}}</label><div class=""><div class="radio" ng-repeat="option in content.fieldTypeOptionsArray"><label><input type="radio" name="{{content.uniqueId}}" ng-value="option" id="{{option.optionId}}" ng-model="$parent.model">{{option.optionValue}}</label></div></div></div>',
};
var getTemplate = function(content, attrs){
var template = {};
template = templates[content.fieldType+"Template"];
if(typeof template != 'undefined' && template != null) {
return template;
}
else {
return '';
}
};
var linker = function(scope, element, attrs){
element.html(getTemplate(scope.content, attrs)).show();
$compile(element.contents())(scope);
}
return {
restrict:"E",
replace:true,
link:linker,
scope:{
content:'=',
model:'=?'
}
};
});
Here is a JSFiddle:
http://jsfiddle.net/scheedalla/d2jzmmd7/
I have been stuck for a while and I cannot understand the root of the problem. It has something to do with it being a nested object or because it is within a directive. Any help will be useful and please let me know if you need further clarification! Thanks!!
Seems to work when I change the ng-value and ng-model to be the option's value as opposed to the entire option object.
Before and after:
<input type="radio" name="{{content.uniqueId}}" ng-value="option" id="{{option.optionId}}" ng-model="$parent.model" >
<input type="radio" name="{{content.uniqueId}}" ng-value="option.optionValue" id="{{option.optionId}}" ng-model="model.optionValue"
http://jsfiddle.net/txbtpLgd/1/
I found the solution. I added this to my controller. If I mapped the $scope.output[favorite_color_2] to the equivalent value in $scope.formFields it worked. The reason behind this is that the item in the ng-repeat can only be mapped to something within its parent scope. In this case, $scope.formFields.
angular.forEach($scope.output, function(value,key){
for(var f=0;f<$scope.formFields.length;f++){
if(key == $scope.formFields[f].uniqueId){
if($scope.formFields[f].fieldType == 'radio'){
console.log($scope.formFields[f].fieldTypeOptionsArray.length);
for (var z=0;z<$scope.formFields[f].fieldTypeOptionsArray.length;z++){
if($scope.formFields[f].fieldTypeOptionsArray[z].optionValue == value.optionValue){
$scope.output[key]=$scope.formFields[f].fieldTypeOptionsArray[z];
}
}
}
}
}
});
Updated JSFiddle: http://jsfiddle.net/scheedalla/d2jzmmd7/17/

Pass array object from controller to custom directive in AngularJS

Im trying to pass a array of objects from a angular controller to a custom directive element and iterate the object with ng-repeat, but appears the following error: [ngRepeat:dupes]
home.js:
home.controller("homeController", function ($scope) {
$scope.points =[
{
"url": '../assets/images/concert.jpg',
"id":1
},
{
"url": '../assets/images/dance.jpg',
"id":2
},
{
"url": '../assets/images/music.jpg',
"id":3
},
{
"url": '../assets/images/jazz.jpg',
"id":4
},
{
"url": '../assets/images/violin.jpg',
"id":5
},
{
"url": '../assets/images/music.jpg',
"id":6
}
];
});
Shareddirectives.js:
var sharedDirectives = angular.module("sharedDirectives", []);
sharedDirectives.directive("interestPoints", function () {
function link($scope, element, attributes, controller ) {
$(element).find('#interest-points').owlCarousel({
items : 4, //4 items above 1200px browser width
itemsDesktop : [1200,3], //3 items between 1200px and 992px
itemsDesktopSmall : [992,3], // betweem 992px and 768px
itemsTablet: [850,2], //1 items between 768 and 0
itemsMobile : [600,1] // itemsMobile disabled - inherit from itemsTablet option
});
}
return {
restrict: "E",
templateUrl : "../html/views/interest-points.html",
link: link,
scope: {
interestPoints: '#'
}
};
});
interest-points.html:
<div id="interest-points" class="owl-carousel">
<div ng-repeat="point in interestPoints" class="item">
<img ng-src="{{point.url}}" alt="Owl Image"><h4>27<br>JUL</h4>
</div>
</div>
home.html:
<div ng-controller='homeController'>
<interest-points interest-points="{{points}}""></interest-points>
</div>
I tried with track by $index but the error don't appear and it don't iterate
You are using interestPoints: '#' as the method of binding interestPoints to the scope. That actually binds only the string {{points}} to interestPoints instead of actually evaluating that expression in the parent's scope.
Use the interestPoints: '=' as the binding method and then interest-points="points" to get the desired behaviour.
Related docs under the heading Directive definition object.

How to stop propagation of & functions into nested directives

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()

unable to make custom html with angular tags work with select2

I am using angular-ui's ui-select2. I want to add custom html formatting to the selections. Select2 allows this by specifying the formatSelection in its config.
I have html with angular tags as below that I want to use for formatting the selection-
var format_code = $compile('<div ng-click="showHide=!showHide" class="help-inline"><div style="cursor: pointer;" ng-show="!!showHide" ng-model="workflow.select" class="label">ANY</div><div style="cursor: pointer;" ng-hide="!!showHide" ng-model="workflow.select" class="label">ALL</div></div>')( $scope );
var format_html = "<span>" + data.n + ' : ' + data.v +' ng-bind-html-unsafe=format_code'+ "</span>"
$scope.select_config = {
formatSelection: format_html
}
If I compile the html as in above and assign it, I just see an [object,object] rendered in the browser. If I dont compile it, I see the html rendered properly, but the angular bindings dont happen, ie the clicks dont work.
Any ideas what is wrong?
I had the same problem, select2 loading in a jquery dialog and not using the options object I would give it.
What I ended up doing is isolating the element in a directive as following:
define(['./module'], function (module) {
return module.directive('dialogDirective', [function () {
return {
restrict: 'A',
controller: function ($scope) {
console.log('controller gets executed first');
$scope.select2Options = {
allowClear: true,
formatResult: function () { return 'blah' },
formatSelection: function () { return 'my selection' },
};
},
link: function (scope, element, attrs) {
console.log('link');
scope.someStuff = Session.someStuff();
element.bind('dialogopen', function (event) {
scope.select2content = MyResource.query();
});
},
}
}]);
and the markup
<div dialog-directive>
{{select2Options}}
<select ui-select2="select2Options" style="width: 350px;">
<option></option>
<option ng-repeat="item in select2content">{{item.name}}</option>
</select>
{{select2content | json}}
</div>
What is important here:
'controller' function gets executed before html is rendered. That means when the select2 directive gets executed, it will already have the select2Options object initialized.
'link' function populates the select2content variable asynchronously using the MyResource $resource.
Go on and try it, you should see all elements in the dropdown as "blah" and selected element as "my selection".
hope this helps, that was my first post to SO ever.

Resources