Passing Array of Objects into Angular Directive - angularjs

I have an array of objects in my angular controller, e.g.
[{
label: 'label1',
data: [{
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 1, 1])
}, {
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 2, 1])
}]
}, {
label: 'label2',
data: [{
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 1, 11])
}, {
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 1, 15])
}]
}];
Every object has a Date field. How can I pass this array of objects into my directive?
I tried passing by reference:
<div id= "chart" my-directive my-data="data"></div>
$scope: {
myData: '='
},
link: function($scope, $element, $attrs) {
function init() {
var data = $scope.myData;
var timeline = new TimelineChart($element[0], data);
}
$attrs.$observe("myData", init);
}
However, it didn't work. I get undefined.
Passing in as an attribute, i.e. my-data="{{ data }}" and using JSON.parse($attrs.myData) does not work due to the Date fields.

It's very simple to pass the data to directive. I think what you are missing is scope. You are using $scope instead of scope. Directive Definition object takes scope not $scope. So what you need to do it.
scope: { //make it scope
myData: '='
},
link: function($scope, $element, $attrs) {
function init() {
var data = $scope.myData;
var timeline = new TimelineChart($element[0], data);
}
$attrs.$observe("myData", init);
}
Here's the fiddle

I think the problem is your array object is lost in $scope . For confirmation, use console.log($scope.myData);
And see in browser console what it returns, if undefined then simply write console.log($scope); and then run the application . Go to browser console and expand $parent until you find myData object. Count the number of parent scope.
If suppose you found it under 3rd parent ,the use
$scope.$parent.$parent.$parent.myData instead of $scope.myData .
This will work :)

Related

Updating an array in the controller scope from a partial

I am using an isteven-multi-select with a controller (ListController) that is using "$scope.mainCategories" for content that is populated by the "ticked" boolean value.
In the header of the application, I am using a select element to allow the user to select a single category (and then be forwarded to the list page). I am using this select element to toggle the ticked boolean value in $scope.mainCategories.
Both are using the same controller, although references separately through UI-Router (possible issue)
views: {
'header#index': {
templateUrl: 'header.html',
controller: "ListController"
},
'container#index': {
templateUrl: 'search.html',
controller: 'ListController'
},
}
then the isteven-multiselect and the select element are in the same partial - the functionality works - when on separate partials the functionality is broken.
Plunker
x might not be what you expect because you can't look for index of object in this line unless you're passing in the actual object:
var x = $scope.mainCategories.indexOf(item);
I assume you're trying to pass in something like:
{
category: "Adventure",
ticked: false
}
and to get the index, it won't work. You need to loop over the array and match the category, for example.
Your approach for modifying the outside array is fine, though.
See this example to see what I mean:
var people = [
{name: 'Shomz'},
{name: 'John'}
];
alert(people.indexOf({ name: 'John'})); // -1: the copy of object not found
alert(people.indexOf(people[1])); // 1: actual reference found
Scope update
To manually update the scope, either wrap the code in a $timeout callback, or use:
$scope.$apply();
$scope.update = function(item) {
item.ticked = true; // ?
};
$scope.mainCategories = [{
category: "Adventure",
ticked: false
},{category: "Fantasy",
ticked: false}];
Just have your function take the object you want to modify and pass that in from the view.

AngularJS : Scope variable not updating fast enough

I have the following code in my directive.
//Directive Code
var BooleanWidgetController = function ($scope, $filter) {
$scope.booleanOptions = [
{
displayText: '-- ' + $filter('i18n')('Widgets.Generics.Select') + ' --'
},
{
value: 1,
displayText: $filter('i18n')('Widgets.Generics.Yes')
},
{
value: 0,
displayText: $filter('i18n')('Widgets.Generics.No')
}
];
//Added inside watch because query was not being updated if filterUpdated was called using ng-change
$scope.$watch('query', $scope.filterUpdated);
};
app.directive('acxBooleanColumnHeaderFilter', function () {
return {
restrict: 'A',
replace: true,
controller: ['$scope', '$filter', BooleanWidgetController],
scope: {
query: '=',
filterUpdated: '&submit',
columnHeading: '#'
},
templateUrl: 'mailSearch/directives/columnHeaderWidgets/boolean/booleanColumnHeaderWidget.tpl.html'
};
});
//Template
<div class="columnHeaderWidget">
<div class="title pull-left">{{columnHeading}}</div>
<div style="clear:both"></div>
<select ng-model="query" ng-options="option.value as option.displayText for option in booleanOptions">
</select>
The current way is working fine. But when I try to do something like this.
<select ng-model="query" ng-change="filterUpdated" ng-options="option.value as option.displayText for option in booleanOptions">
The $scope.query is not updating fast enough. So the $scope.query is being updated after $scope.filterUpdated is being called. What am I missing here?
This is far more complicated than what it seems, if you want to understand the real problem have a look at this: "Explaining the order of the ngModel pipeline, parsers, formatters, viewChangeListeners, and $watchers".
To summarize, the issue is that: when the ng-change function gets triggered the bound scope properties of your directive (in your case query) have been updated in the scope of the directive, but not in the scope from where they were inherited from.
The workaround that I would suggest would be:
Change your filterUpdated function so that it will take the query from a parameter, rather than taking it from its scope, because its scope hasn't been updated yet.
Create an intermediate function in the scope of your directive in order to catch the ng-change event and the updated scope properties.
Use that intermediate function to call the filterUpdated function and pass the query as a parameter.
Something like this:
var BooleanWidgetController = function ($scope, $filter) {
$scope.booleanOptions = [
{
displayText: '-- ' + $filter('i18n')('Widgets.Generics.Select') + ' --'
},
{
value: 1,
displayText: $filter('i18n')('Widgets.Generics.Yes')
},
{
value: 0,
displayText: $filter('i18n')('Widgets.Generics.No')
}
];
$scope._filterUpdated = function(){ $scope.filterUpdated({query:$scope.query}); };
/** Remove this, you won't need it anymore
** $scope.$watch('query', $scope.filterUpdated);
**/
};
Change your HTML, make it look like this:
<select ng-model="query" ng-change="_filterUpdated" ng-options="option.value as option.displayText for option in booleanOptions">
And remember to change the filterUpdated to use the query as a parameter, like this:
function filterUpdated(query){
...
}

AngularJS - Scope in directive

I'm new to AngularJS.
Can someone explain me why the active class not toggle between tabs in this code: http://jsfiddle.net/eSe2y/1/?
angular.module('myApp', [])
.filter('split', function () {
return function (input, string) {
var temp = string.split('|');
for (var i in temp)
input.push(temp[i]);
return input;
};
})
.directive('myTabs', function () {
return {
restrict: 'E',
scope: { tabs: '#' },
template:
"<div>" +
"<a ng-repeat='e in [] | split:tabs' ng-click='selectedIndex = $index' ng-class='{active:$index==selectedIndex}'>{{e}}</a>" +
"</div>",
replace: true
}
});
If I move the ng-click expression to a method of the controller, the code works as expected: http://jsfiddle.net/g36DY/1/.
angular.module('myApp', [])
.filter('split', function () {
return function (input, string) {
var temp = string.split('|');
for (var i in temp)
input.push(temp[i]);
return input;
};
})
.directive('myTabs', function () {
return {
restrict: 'E',
scope: { tabs: '#' },
template:
"<div>" +
"<a ng-repeat='e in [] | split:tabs' ng-click='onSelect($index)' ng-class='{active:$index==selectedIndex}'>{{e}}</a>" +
"</div>",
replace: true,
controller: ['$scope', function ($scope) {
$scope.onSelect = function (index) {
$scope.selectedIndex = index;
}
}]
}
});
Can someone explain me the difference? And how to modify the first code to make it works without create a method to the controller?
Thanks in advance.
Explanation of the Problem
The problem has to do with javascript inheritance as it relates to scopes and directives in angular. Basically, when in a child scope, all properties of basic types (int, boolean, etc) are copied from the parent.
In your case, the ng-repeat directive creates a child scope for each element, so each one of the links has its own scope. in the first example, selectedIndex is only referenced from within the repeater, each repeater element references its own copy of the selectedIndex. You can investigate this using the
In the second example, you define a selectedIndex object in the controller, which is the parent scope for the repeater. Because the selectedIndex property is undefined initially when it is passed into the controllers, they look to the parent for a definition. When this definition has a value set in the onSelect method, all of the repeater elements "see" this value, and update accordingly.
How to Debug
In the future, you can investigate these types issue using the Angular Batarang.
browse to http://jsfiddle.net/eSe2y/1/show
left-click one of the tabs
right-click the same link
select "inspect element"
open the debug console and type $scope.selectedIndex
repeat the above steps for another tab, and note how the value differs
now go to the elements tab of the debugger, and click on the div
enter $scope.selectedIndex and note that it is undefined
On the second fiddle, try viewing just the $scope on each of the tabs (not $scope.selectedIndex). You will see that selectedIndex is not defined on the repeater elements, so they default to the value from their parent.
Best Practices
The typical angular best practice to avoid this problem is to always reference items that could be change on the scope "after the dot". This takes advantage of the fact that JavaScript objects are inherited by reference, so when the property changes in one place, it changes for all scopes in the parent-child hierarchy. I've posted an updated fiddler that fixes the problem by simply pushing the binding onto an object:
angular.module('myApp', [])
.filter('split', function () {
return function (input, string) {
var temp = string.split('|');
for (var i in temp)
input.push(temp[i]);
return input;
};
})
.directive('myTabs', function () {
return {
restrict: 'E',
scope: { tabs: '#' },
template:
"<div>" +
"<a ng-repeat='e in [] | split:tabs' ng-click='s.selectedIndex = $index' ng-class='{active:$index==s.selectedIndex}'>{{e}}</a>" +
"</div>",
replace: true,
controller: ['$scope', function ($scope) {
$scope.s = {};
}]
}
});
http://jsfiddle.net/g36DY/2/

Angular: How do I dynamically add ng-hide to a template that was loaded via templateUrl?

I'm trying to build a directive that reduces boilerplate for text fields, where the server-side declarations of things like field visibility can be passed in via a model.
I want to load the HTML for a general field from a templateUrl, transform the DOM of that (adding in various attributes and directives to this template) according to the model.
I've got it binding the proper ng-model to the nested input field, but when I try to apply an ng-hide to the top-level element, it shows up in the DOM but does no take effect.
If it were working properly, the code (so far) should be hiding the field, but it is not.
The code is at http://jsbin.com/AHoLAnUg/1/edit, and is reproduced below:
angular.module("directives", []).
directive('tuTextField',
function() {
return {
restrict: 'E',
replace: true,
compile: function(ele, attr) {
var element = jQuery(ele);
var input = jQuery(element.children('input')[0]);
// These work:
element.attr('id', attr.id);
element.attr('class', attr['class']);
// this fails: (I've tried element.attr() as well)
attr.$set('ngHide', attr.model + ".invisible['" + attr.field + "']");
// but this WORKS:
input.attr("ng-model", attr.model + ".fields." + attr.field);
},
templateUrl: '/AHoLAnUg/1.css'
};
}).
controller('v', [ '$scope', function(scope) {
scope.state = {
fields: {
name: "Tony"
},
invisible: {
name: true
},
readonly: {
name: true
},
validations: {
name: {
pattern: "^[a-zA-Z]",
message: "Must begin with a letter"
}
}
};
}]);
You shouldn't manipulate the root element of directive because compile function is called after $compile service finish its job, but you CAN manipulate child elements since they'll be compiled after their parent.
This is an example for directive execution order:
jsFiddle
That's why ngHide in your example won't take effect but ngModel will.
Try wrapping your template with another and manipulate them as you want.

Bind an AngularJS directive to a mapping of an array

I have a directive that takes an array of objects. When declaring the directive in markup, the scope has an array of objects that wrap the ones needed by the directive. So I need to apply a map function on the array. What's the right way to do this so updates made to the original array are reflected inside the directive?
Here's a Plunker with a naive approach (which I was surprised to see mostly "work" except for lots of $digest errors): http://plnkr.co/edit/GUCZ3c
You should avoid calling a function from an Angular expression unless, among other things, that function does some very lightweight work (a quick computation, for instance). This question has more details on that matter.
In your case, you should cache the name list and bind it to the directive. Here's an example:
app.controller('MainCtrl', function($scope) {
$scope.people = [
{ name: 'Alice'},
{ name: 'Bob' },
{ name: 'Chuck' }
];
$scope.addName = function(name) {
$scope.people.push({name: name});
};
$scope.$watch(function() { return $scope.people.length; }, function() {
$scope.names = $scope.people.map(function(p) { return p.name; });
});
});
Your directive code remains the same. Here's a fork of your Plunker.
Update: I've changed the code so it uses a $watch to update the name list automatically, following #KrisBraun advice.
I believe you need to treat expressions a little differently when binding them to your isolated scope. Here is a forked plunker.
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
scope: {
names: '&namesFunc'
},
template: '<div><p>JSON: {{names() | json}}</p><p>Names:</p><ul><li ng-repeat="name in names()">{{name}}</li></ul></div>',
replace: true,
link: function(scope, elem, attr, ctrl) {
}
};
});

Resources