Pass array object from controller to custom directive in AngularJS - 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.

Related

angularjs: Dynamically build a compileable directive with bindings

im currently trying to build a component which gets an object with specifications which directive it should render.
So this is my angular component componentRenderer.js
angular
.module('app.core')
.component('componentRenderer', {
templateUrl: '/component-renderer/component-renderer.tpl.html',
controller: ComponentRendererController,
bindings: {
data: '='
}
});
function ComponentRendererController($scope, $element, $timeout, $compile) {
var vm = this;
// Called when component is ready
vm.$onInit = function () {
if (vm.data.type !== 'plain') {
var html = '<' + vm.data.type + ' ';
if (vm.data.hasOwnProperty('options')) {
angular.forEach(vm.data.options, function (value, key) {
if(typeof value === 'object') {
html += (key+'="value" ');
} else {
html += (key+'="'+value+'" ');
}
});
}
html += '></' + vm.data.type + '>';
var el = $compile(html)($scope);
$element.find('.widget__content').append(el);
}
};
}
/component-renderer/component-renderer.tpl.html:
<div class="widget">
<div class="widget__header">
<h2>{{ $ctrl.data.title }}</h2>
</div>
<div class="widget__content">
<h3>{{ $ctrl.data.subtitle }}</h3>
</div>
A data object can for example look like that:
{
"type": "activity-widget",
"options": {
"type": "simple",
}
"title": "Activity"
}
My componentRenderer should now build up the corresponding html, so that the following result will be possible:
<activity-widget type="simple"></activity-widget>
So the options of that previously shown object should be rendered as attributes of the component. Finally the activity-widget component should render the final html construct.
deviceActivity.js
angular
.module('app.core')
.component('deviceActivity', {
templateUrl: '/device-activity/device-activity.tpl.html',
controller: DeviceActivityController,
bindings: {
data: "="
}
});
Until here everything works as expected! But no i want to be able to use options as object too.
{
"type": "activity-widget",
"options": {
"jsonObject": {
"surveillance": [
{
"name": "location",
"value": 25,
"max": 100
},
{
"name": "reporting",
"value": 58,
"max": 80
},
{
"name": "reporting",
"value": 9,
"max": 120
}
]
}
},
"title": "Activity"
}
My componentRenderer should now build up the corresponding html, so that the following result will be possible:
<activity-widget object="jsonObject"></activity-widget>
Sadly its not working, it is not binding the jsonObject to my component. I don't know what im doing wrong...Any help is greatly appreciated!
Thanks in advance!
Why it doesn't work
So you would like to get this
<activity-widget object="jsonObject"></activity-widget>
Instead, here is what is being generated:
<activity-widget jsonObject="value"></activity-widget>
(which exactly corresponds to what componentRenderer.js should output given the code that it contains)
And the widget that is being generated doesn't work apparently for 2 reasons:
jsonObject="value": the attribute name is camel-cased, though it should be specified like json-object
jsonObject="value": I don't see where you are trying to inject the value of jsonObject into the scope. So it is not surprising that the value is undefined
How to fix it
If you insist that this is indeed what you'd like to get
<activity-widget object="jsonObject"></activity-widget>
given the object that you specified, here is how you should change componentRenderer.js:
angular.forEach(vm.data.options, function (value, key) {
if(typeof value === 'object') {
// providing attribute=value pair correctly
html += ('object="' + key + '" ');
// adding object's value to the scope
// so that it is passed to the widget during compile below
$scope[key] = value;
} else {
html += (key+'="'+value+'" ');
}
});
Notice that this way you can have only one attribute of object type per widget (all other will be overwritten).
Try changing your code like shown above, for me it seem to work fine. If it still doesn't work for you, please create a fiddle (actually, it is what you should have done in the first place for this kind of question)

Angular - Bind directive value to controller object

I'm trying to pass an array from a controller to a directive and for some (probably obvious to you lot!) reason when the array values are updated in the controller it does not reflect in the directive. The controller obtains data from a service into an array and I want to pass that array to the directive to create a bar graph. I've put the key parts of the code below.
Here is my top level HTML
<div dash-progress
graph-data="{{dashCtrl.myProgress}}">
</div>
<div>
Other Stuff
</div>
My template HTML for the directive:
<div class="boxcontent" ng-show="dashCtrl.showProgress">
<div class="chart-holder-lg">
<canvas tc-chartjs-bar
chart-data="progress"
chart-options="options"
height="200"
auto-legend>
</canvas>
</div>
</div>
Controller:
angular
.module('myApp')
.controller('dashCtrl',['mySvc',
function(mySvc) {
var self = this;
this.myProgress = [];
this.getProgress = function() {
//logic must be in the service !
mySvc.getProgress().then(function(success) {
self.myProgress = mySvc.progress;
});
};
}]);
and the directive:
angular
.module('myApp')
.directive('dashProgress', [function() {
return {
restrict: 'AE',
templateUrl: 'components/dashboard/progress.html',
scope: {
graphData: '#'
},
link: function(scope,el,attrs) {
scope.progress = {
labels: ['Duration','Percent'],
datasets: [
{
label: 'Duration',
data: [scope.graphData.duration]
},
{
label: 'Percent',
data: [scope.graphData.percent]
}
]
};
scope.options = { };
}
}
}]);
If I set an initial values of the myProgress object in the controller then these do get reflected in the directive, but I don't get the real values that I need when they are returned to the controller from the service.
In your directive's scope, instead of this:
scope: {
graphData: '#'
}
try using this:
scope: {
graphData: '='
}
Don't use {{ }} when passing array to the directive with =. It will render the array in the view instead of passing a reference to directive's scope.
As far as I know, # is not only one-way binding, but also one-time binding and should be used mostly for string values (e.g. setting an html attribute while initializing directive). If you'd like to use #, you should firstly convert data to JSON, then pass it to directive with {{ }}, then parse it again in directive and after any change - manually recompile the directive. But it would be a little overkill, wouldn't it?
Conclusion
Just remove the curly brackets from the view and use = to bind value to directive's scope.
View
<div dash-progress
graph-data="dashCtrl.myProgress">
</div>
Directive
scope: {
graphData: '='
},
Update
Try one more thing. In dashCtrl, wrap myProgress with an object (you can change names to be more self-explaining - this is just an example):
this.graphData = {
myProgress: []
}
this.getProgress = function() {
mySvc.getProgress().then(function(success) {
self.graphData.myProgress = mySvc.progress;
});
}
Then, pass graphData to directive:
<div dash-progress
graph-data="dashCtrl.graphData">
</div>
Finally, substitute every scope.graphData with scope.graphData.myProgress. This way you make sure that scope.graphData.myProgress always refers to the same data because it's a property of an object.
If this still doesn't work, you will probably have to use a watcher and update properties of scope.progress manually.

Angular directive template unknown scope

I know there is a lot of questions and posts about AngularJS and how directives are supposed to be used. And I got mine working just fine until I got another problem which I don't know how to resolve.
I use a directive on a custom HTML element. Directive transforms this element into a regular html tree as defined in a template. The HTML element has some attributes which are used when building the template. Data for one of the elements is received with HTTP request and is successfully loaded. This is the part which I got working fine.
Now I want to do something more. I've created a plunker which is an example of what I want to achieve. It's a fake one, but illustrates my problem well.
index.html:
<body ng-controller="MainCtrl">
<div id="phones">
<phone brand="SmartBrand" model="xx" comment="blah"></phone>
<phone brand="SmarterBrand" model="abc" comment="other {{dynamic.c1}}"></phone>
</div>
</body>
Angular directive:
app.directive('phone', function() {
return {
restrict: 'E',
replace: true,
scope: {
'comment': '#',
'brand': '#'
},
templateUrl: 'customTpl.html',
controller: function($scope) {
fakeResponse = {
"data": {
"success": true,
"data": "X300",
"dynamic": {
"c1": "12",
"c2": "1"
}
}
}
$scope.model = fakeResponse.data.data;
$scope.dynamic = fakeResponse.data.dynamic;
}
}
});
Template:
<div class="phone">
<header>
<h2>{{brand}} <strong>{{model}}</strong></h2>
</header>
<p>Comment: <strong>{{comment}}</strong></p>
</div>
So I would like to be able to customize one of the tags in the element (phone comment in this example). The trick is that the number of additional info that is going to be in the tag may vary. The only thing I can be sure of is that the names will match the ones received from AJAX request. I can make the entire comment be received with AJAX and that will solve my problem. But I want to separate template from the variables it is built with. Is it possible?
Ok, I got it working. It may not be the state of the art solution (I think #xelilof suggestion to do it with another directive may be more correct), but I'm out of ideas on how to do it (so feel free to help me out).
I've turned the {{comment}} part into a microtemplate which is analysed by a service. I've made a plunk to show you a working sample.
The JS part looks like this now:
app.directive('phone', ['dynamic', function(dynamic) {
return {
restrict: 'E',
replace: true,
scope: {
'comment': '#',
'brand': '#',
'color': '#',
'photo': '#'
},
templateUrl: 'customTpl.html',
controller: function($scope) {
fakeResponse = {
"data": {
"success": true,
"data": "X300",
"dynamic": {
"c1": "12",
"c2": "2"
}
}
}
$scope.model = fakeResponse.data.data;
$scope.comment2 = dynamic($scope.comment, fakeResponse.data.dynamic);
console.log("Comment after 'dynamic' service is: " + $scope.comment);
}
}
}]);
app.factory('dynamic', function() {
return function(template, vars) {
for (var v in vars) {
console.log("Parsing variable " + v + " which value is " + vars[v]);
template = template.replace("::" + v + "::", vars[v]);
}
return template;
}
});

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

Dygraphs not working with ng-repeat

I'm new to AngularJS and building a dashboard with dygraphs.
Tried to put the example code from the dygraphs website in an ng-repeat-list, just to test. Expected the same sample graph for every x in y. Unfortunately the graph doesn't get drawn, just the axes, console doesn't show any errors.
<li ng-repeat="x in y">
<div id="graph">
<script>
new Dygraph(document.getElementById("graph"),
[ [1,10,100], [2,20,80], [3,50,60], [4,70,80] ],
{ labels: [ "x", "A", "B" ] });
</script>
</div>
</li>
If I remove ng-repeat, it works though (single graph) – so the dygraphs-code is valid. Of course it doesn't make sense to draw the graphs directly in the view like I did here, still I wonder why it doesn't work. Am I missing some general point here?
Your problem is that Angular will repeat your <div id="graph"> n times. So you now have n times div with id of 'graph' which are siblings. Therefore, when you call document.getElementById('graph'), that won't work very well.
That said, I don't know how well script tags inside ng-repeat works either, seems like a very strange use case.
The proper way to do this (as with all DOM related operations), is to use a directive. Here's an example:
Javascript:
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function($scope) {
$scope.graphs = [
{
data: [ [1,10,100], [2,20,80], [3,50,60], [4,70,80] ],
opts: { labels: [ "x", "A", "B" ] }
},
{
data: [ [1,10,200], [2,20,42], [3,50,10], [4,70,30] ],
opts: { labels: [ "label1", "C", "D" ] }
}
];
});
myApp.directive('graph', function() {
return {
restrict: 'E', // Use as element
scope: { // Isolate scope
data: '=', // Two-way bind data to local scope
opts: '=?' // '?' means optional
},
template: "<div></div>", // We need a div to attach graph to
link: function(scope, elem, attrs) {
var graph = new Dygraph(elem.children()[0], scope.data, scope.opts );
}
};
});
HTML:
<div ng-controller="MyCtrl">
<graph ng-repeat="graph in graphs" data="graph.data" opts="graph.opts"></graph>
</div>
JSFiddle
Hope this helps!

Resources