Dygraphs not working with ng-repeat - angularjs

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!

Related

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

Angular Expressive Directive Design

I'm working on a open-source project for a AngularJS Data Table directive ( still WIP ). When you look at components like Angular Grid or UI Grid they all describe their columns and attributes in a object in the parent controller like:
$scope.gridOptions = {
enableSorting: true,
enableCellEditOnFocus: true,
columnDefs: [
{ name: 'field1', enableSorting: false, enableCellEdit: false },
{ name: 'field2' },
{ name: 'field3', visible: false }
]
};
which works fine, however, I don't think this is really the 'angular way'. It feels more like a jQuery widget. If you look at projects like Angular Material, they are much more expressive in the HTML template vs object driven.
For my implementation, I originally wanted to make it very expressive and expose each one of the inner directives I use, however, that ended up a mess to just create a basic table. So I did some research on other frameworks and found that react had a nice architecture where you just define the columns like:
React.render(
<Table
rowHeight={50}
rowGetter={rowGetter}
rowsCount={rows.length}
width={5000}
height={5000}
headerHeight={50}>
<Column
label="Col 1"
width={3000}
dataKey={0}
/>
<Column
label="Col 2"
width={2000}
dataKey={1}
/>
</Table>,
document.getElementById('example')
);
I feel in love with this approach, its simple and is expressive all at the same time. 90% of the time you only want to customize the column template anyways. So instead of this:
$scope.gridOptions = {
enableFiltering: true,
rowTemplate: rowTemplate(),
data: 'data',
columnDefs: [
{ name: 'name' },
{ name: 'gender' },
{ name: 'company' },
{ name: 'widgets' },
{
name: 'cumulativeWidgets',
field: 'widgets',
cellTemplate: '<div class="ui-grid-cell-contents" title="TOOLTIP">{{grid.appScope.cumulative(grid, row)}}</div>'
}
]
};
with the cell template, you could do something like this:
<dt options="options" rows="data" class="material">
<Column name="name" width="300"></Column>
<Column name="Gender">
<strong>{{value}}</strong>
</Column>
<Column name="Company"></Column>
</dt>
notice how I took the React concept and paired it with a more Angular concept where I would include the ability to have a template inside the column.
Ok now for the problem. I want the columns on init, but not after that. I want to replace it with the actual table. Problem is I can never get that HTML when I need it.
So on this line I tried to do something like:
compile: function(tElem, tAttrs){
var tags = z.getElementsByTagName('column');
console.log(tags) // equals = []
return {
pre: function($scope, $elm, $attrs, ctrl){
}
};
}
but the columns are never there til later when I try to transclude them ( not really what I wanna do ). I need a way to get ahold of them before the controller is initialized and the template replaces the inner contents. Here is a plunkr!
Additionally, since my directive is scoped ( which I def wanna do for perf reasons ) I have no way to get access to the parent scope to compile the inner template with the outer contents.
Also, any suggestions / thoughts on this design paradigm? Thanks!
Try to use the template as a function, which returns the actual template string. The first parameter is the original html:
var tags;
return {
template: function(element) {
tags = element[0].getElementsByTagName('column');
return "<div>table</div>"
},
compile: ...
}
I only found a weird way to achieve what you want.
Before someone give a better solution, here is mine working in this plunker.
Adding the $transclude service to the controller and transclude:'element' to the directive
app.directive("simple", function(){
return {
restrict: "EA",
replace:true,
transclude:'element',
template:"<div>table</div>",
compile: function(element, attributes, transclude){
var tags = element[0].getElementsByTagName('column');
console.log("compile", tags);
console.log("transclude", transclude);
return {
pre: function(scope, element, attributes, controller, transcludeFn){
var tags = element[0].getElementsByTagName('column');
console.log("pre", tags);
},
post: function(scope, element, attributes, controller, transcludeFn){
var tags = element[0].getElementsByTagName('column');
console.log("post", tags);
}
}
},
controller: function($scope, $element, $transclude){
$transclude(function(clone,scope){
/* for demo am converting to html string*/
console.log("From controller",angular.element(clone).find('column'));
});
}
};
});
I tested some other things. But i'm only able to get the column with this and only into the controller.

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.

Angularjs create directive dynamically with ng-repeat

I'd like to know is there any way to create dynamic directive with ng-repeat. Something, like this:
<div ng-repeat="document in documents">
<div document.name></div>
</div>
You could use attributes to do that. For example:
<div ng-repeat="document in documents">
<document-viewer name="document.name"></document-viewer>
</div>
and
var app = angular.module("documents", []);
app.controller("AppCtrl", function($scope) {
$scope.documents = [
{id:1, name:'document1'},
{id:2, name:'document2'}
];
});
app.directive("documentViewer", function() {
return {
scope: {
//attribute name will match the name passed from your view,
//and you will be able to use it on your template or logic
name: "="
},
//Add your template here:
template: '<div><button ng-click="openDocument(name);"></div>',
//Add your logic here:
link: function(scope, element, attrs){ ... }
}
});
I don't know if that answers your question, but you are not making clear what is it you are trying to achieve.
Have a look at the following videos as they might come handy for you when dealing with directives and isolate scope:
http://egghead.io/lessons/angularjs-directive-to-directive-communication
http://egghead.io/lessons/angularjs-isolate-scope-expression-binding
http://egghead.io/lessons/angularjs-isolate-scope-attribute-binding
http://egghead.io/lessons/angularjs-isolate-scope-two-way-binding

How to get jquery.Datatable's data to respect angular 2-way binding

I have a json object that may be modified by local conditions (not async ajax calls). I want this json object to be output on a datatables grid.
I have the table displaying the data just fine, however if the data changes, the grid does not. Do I have to call the constructor datatables every time? I would prefer to just use 2-way binding, but I am not sure. I have gotten it to update using a $watch, but I'd like to avoid using a $watch on this large object if I can. It could have 1000's of rows.
Here is a fiddle showing the datatable load, and the data it runs on changing after 3000 millis (simple $timeout). Is there a way to get the table to detect the change in the json object and refresh itself? Or do I need to do something different in the Angular code?
I am using a directive to load the table:
.directive('myTable', function() {
return {
restrict: 'E',
scope: {
data: '=',
options: '=',
columns: '='
},
template:
'<table id="balancesTable"></table>',
replace: true,
link: function(scope, elem, attrs) {
scope.options["aaData"] = scope.data;
scope.options["aoColumnDefs"] = scope.columns;
elem.dataTable(scope.options);
}
};
})
My Controller (edited for brevity... see fiddle for full code):
var myApp = angular.module('myApp', [])
.controller('MyAppCtrl', function ($scope, $timeout) {
$("#data-preview").css("color","green");
$scope.dataObj = [ { "balanceType": "Available Funds", ... } ];
$timeout(function(){
$scope.dataObj = [ { "balanceType": "Available Funds", ... different data ...} ];
$("#data-preview").css("color","red");
},
3000);
// columns settings for data table
$scope.columnDefs = [
{
"mData": "balanceType",
"sTitle": "Balance Type",
"aTargets":[0],
"sWidth": "200px"
},
{
...
}
];
// data table settings for table to not show search, pagination and allow column resize
$scope.overrideOptions = {
"bSort": true,
"bPaginate": false,
"sDom": "Rlfrtip",
"bFilter": false,
"bAutoWidth": false,
"bInfo": false
};
})
HTML:
<pre id="data-preview">{{dataObj}} | json}}</pre>
<my-table options="overrideOptions" data="dataObj" columns="columnDefs"><my-table>
Thanks in Advance!!

Resources