I'm trying to update data of my chart using chartJS and its wrapper angular-chart. When a button is clicked, it adds data on the chart, no problem with this.
I have another event, which update the chart after a scroll event. The call works great, and it calls exactly the same function as previous. But my $scope.valuesData isn't updated . Then I added a $scope.$apply() after the function, and it redraws the whole chart with the right values.
Question are :
Why does my $scope.chartValues is not updated when I call the function AddData from the controller (and why is $scope.chartValues is updated when I call the function from the DOM). It is probably a data-binding issue ?
Is it a function that update only a specific scope variable, instead of refreshing the whole scope ?
My code :
HTML :
<ion-scroll zooming="false" direction="x" delegate-handle="scroller" on-scroll="getScrollPosition()" has-bouncing="true" scroll-event-interval="30000">
<div class="chart_wrapper" ng-style="{ 'width': myWidth + '%' }">
<canvas class="chart-bar" chart-data="data2" chart-labels="labels" chart-dataset-override="datasetOverride2" chart-options="options2" ng-click="goToMetrics()" ">
</canvas>
</div>
</ion-scroll>
<button class="button button-small button-calm" ng-click="addValues()">Add Data </button>
JS (controller) :
$scope.getScrollPosition = function(){
var pos = $ionicScrollDelegate.$getByHandle('scroller').getScrollPosition().left ;
if (pos == 0 && $scope.flag == -1){
$scope.flag = 0;
//setTimeout($scope.addValues(), 1000);
$scope.addValues();
}
if (pos < 0){
$scope.flag = -1;
}
};
$scope.addValues = function(){
console.log('add');
// Retrieve values
var week = Graph.getData();
var pp = $scope.chartData;
var mm = $scope.labels;
// Add values to the chart and update the chart width
var tmp = Graph.addBegin(pp, mm, week);
$scope.valuesData = tmp.values;
$scope.myWidth = $scope.myWidth + tmp.length * 4 ;
};
Any help appreciated
Because setTimeout runs outside angular context, use $timeout version.
Nope. You can use $scope.$digest(), to check changes from current scope, not all $scope tree;
Related
I'm calculating containers heights as per viewport by ng-style through my custom method.
Everything works well, but it keeps calling the method even if the element is styled. I have a big amount of DOM elements that are need to be styled. That's why, I can't allow continuous execution for all elements. Please note, I can't use ng-class because each element contains different content. And can't use controller scope variable due to unlimited numbers of elements.
HTML:
<div class="myElement" ng-style="styleElement('myElement')">
...
...
</div>
Function:
$scope.styleElement = function (elementCls) {
var elementToSet = $('.'+elementCls+':visible');
if(elementToSet.length){
var winHeight = $( window ).height();
var eTop = elementToSet.offset().top;
if(eTop == 0){
var elemChilds = elementToSet;
var elemChildsLen = elemChilds.length;
for(var i=0;i<elemChildsLen;i++){
var elem = elemChilds[i];
var r = elem.getBoundingClientRect();
if(r.top != 0){
eTop = r.top;
i= elemChildsLen;
}
}
}
var nScrollHeight = winHeight - eTop - 20;
return {
'height': nScrollHeight + 'px',
'overflow-x': 'hidden',
'overflow-y': 'auto'
};
}
};
I've tried using a custom directive but binding DOM or writing a watcher isn't a preferable solution for me due to performance. Thanks in advance!
Use one time binding, which will only call styleElement once.
<div class="myElement" ng-style="::styleElement('myElement')">
...
...
</div>
In my project, i am using ng-repeat with limitto filter and track by $index
<button ng-disabled="currentPage == 0" ng-click="currentPage=currentPage-1">Previous</button>
<button ng-disabled="currentPage >= cil.cilChemicals.length/pageSize - 1" ng-click="currentPage=currentPage+1">Next</button>
<div ng-repeat="chemical in Chemicals | startFrom: currentPage * pageSize | limitTo:pageSize track by $index">
<span ng-init="initDetails(chemical)"> {{chemical.details}} </span>
</div>
Controller code :
$scope.currentPage = 0;
$scope.pageSize = 50;
Module.filter('startFrom', function() {
return function(input, start) {
start = +start;
return input.slice(start);
}
});
With this setup, initDetails(chemical) is called for all chemicals during first time ng-repeat rendering thus not called for each page which is rendered perfectly for the first time but when we repeat the page then ng-repeat starts re rendering from in between the array.
Please suggest why it starts from in between and when we use trackby chemical.chemicalIdthen angular js calls initDetails(chemical)for each page.
The way you have ng-init, runs in each iteration.
Except (enters track by):
when you use trackby $index the first element has always the $index 0 regardless of page number,
so angular doesn't re-render the html
but when you change the trackby to chemical.chemicalIdthen angular re-renders the html in evey iteration.
Actually angular will re-render only the items that have diff in the track by expression, either it is an index or id, than the previous items.
Also I agree with #ste2425's comment. that is not the intended use of ng-init
just a note:
LimitTo filter accepts 2 parameters, limit and begin,
limit is the page size, and begin is the index to start counting, so there is no need to create a custom filter for that.
You can essentially do this:
<button ng-disabled="currentPage == 0" ng-click="previousPage()">Previous</button>
<button ng-disabled="currentPage >= cil.cilChemicals.length/pageSize - 1" ng-click="nextPage()">Next</button>
<div ng-repeat="chemical in Chemicals | limitTo:pageSize:startFrom track by $index">
<span ng-init="initDetails(chemical)"> {{chemical.details}} </span>
</div>
And your controller
$scope.currentPage = 0;
$scope.pageSize = 50;
scope.startFrom = 0
$scope.nextPage = function() {
$scope.currentPage++;
$scope.startFrom = $scope.currentPage * $scope.pageSize;
};
$scope.previousPage = function() {
$scope.currentPage--;
$scope.startFrom = $scope.currentPage * $scope.pageSize;
};
I know I've had this happen before but not in exactly this way and I'm having trouble making the necessary changes.
I have a div that starts out empty and hidden as part of a gallery. When I click an image, it populates the div and shows it. Along with the image/content, there's some navigation elements as well. Thing is, those are also dynamically generated and not working since the angular needs to recompile. Here's the function that populates:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
pic = match[0].image;
title = match[0].title;
desc = match[0].desc;
closediv = "<div id=\"divClose\" class=\"floatRight\" ng-click=\"closeParent();\">Close</div>";
navDiv = "";
if(picID > 1){
prev = picID - 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + prev + ")\">Previous</div>";
}
if(picID < $scope.picCount){
next = picID + 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + next + ");\">Next</div>";
}
$('#innerPictureDisplay').html(closediv + "<br/><br/><img src=\"images/paintings/" + pic + "\" /><p><b>" + title + "</b><\p><p>" + desc + "</p>"+ navDiv);
$('#outerPictureDisplay').css('display','block');
};
How do I make that code "recompile" so that the "CLOSE" and navigational items work?
UPDATE
So, I changed my approach but I'm clearly still missing something:
my new HTML:
<div id="outerPictureDisplay" ng-show="picID > 0">
<div id="innerPictureDisplay">
<div id="divClose" class="floatRight" ng-click="picID = 0;">Close</div>
<div id="picNav" ng-click="picturePop({{prevID}});" ng-show="picID > 1">Previous</div>
<div id="picNav" ng-click="picturePop({{nextID}});" ng-show="picID < picCount">Next</div>
<img src="images/paintings/{{thisPic.image}}" />
<p>
<b>{{thisPic.title}}</b>
</p>
<p>{{thisPic.desc}}</p>
</div>
</div>
my new function:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
$scope.thisPic = match[0];
$scope.picID = picID;
$scope.nextID = picID + 1;
$scope.prevID = picID - 1;
var $content = $('#innerPictureDisplay');
var scope = $content.scope();
$compile($content.contents())(scope);
};
When I click on a pic, it all loads up fine but when I click on a Previous or Next, it closes since it's registering picID as null. I checked and the numbers are indeed getting printed in the code, but angular doesn't see them on the click. They're coming through as undefined - seemingly the compile issue. I tried implementing it all as a directive, even using the suggested link, but I must've done something off since it didn't help.
You can use the angular service $compile like so:
$compile("<a ng-click='myFunction()'>my html</a>")(scope);
And of course you must inject him wherever you are.
Anyway, for God sake, don't use DOM manipulation this way. You should use a directive and feed the content from a model instead.
UPDATE 1
I sugest use incuna's bindHtmlCompile directive that you can found here.
I am trying to get a ng-click directive to function within an ng-repeat and ng-bind-html. The ng-click code is added to a string of html from data pulled from the server (hence the ng-bind-html). The setup has a controller, a base template that is put onto the page with Drupal, and a partial that is loaded via the template from Drupal.
The controller looks like this at the moment:
var guideListController = angular.module('app')
.controller('guideListController', [
'$scope',
'$sce',
'$compile',
'ViewService',
'ToolKit',
function($scope, $sce, $compile, ViewService, ToolKit) {
// Storage for which rows need more/less links
this.rowIndex = [];
this.showFull = false;
this.showFullClick = function() {
alert('Showfull');
};
this.trustIntro = function(code) {
return $sce.trustAsHtml(code);
};
// Fetch the guide list view from services
var data = ViewService.get({view_endpoint:'guide-service', view_path: 'guide-list'}, function(data) {
//console.log(data);
// Update/process results
for (var row in data.results) {
// Create short intro w/ truncate.js
data.results[row].Intro_short = $sce.trustAsHtml($scope.guideList.getShortIntro(data.results[row], row));
//data.results[row].Intro_short = $scope.guideList.getShortIntro(data.results[row], row);
// Update intro
data.results[row].Introduction = $sce.trustAsHtml($scope.guideList.updateIntro(data.results[row], row));
//data.results[row].Introduction = $scope.guideList.updateIntro(data.results[row], row);
}
$scope.guideList.guides = data.results;
});
// Add a read less anchor tag at the end of the main intro
this.updateIntro = function(row, row_index) {
var intro = row['Introduction'].trim();
if ($scope.guideList.rowIndex[row_index]) { // only apply Less link if needed
var index = intro.length - 1;
var tag = [];
if (intro.charAt(index) === '>') { // we have a tag at the end
index--;
do {
tag.push(intro.charAt(index));
index--;
} while (intro.charAt(index) != '/'); // the closing tag
index--; // we move the index one more for the "<"
tag.reverse(); // Reverse
tag = tag.join('');
}
var inserts = ['div', 'p']; // we insert the Less link here.
if (jQuery.inArray(tag, inserts) >= 0) { // insert into the tag
intro = intro.substr(0, index) + ' <a class="less" ng-click="$parent.guideList.showFull = false">Less</a>' + intro.substr(index);
}
else { // insert at the end of the html
intro = intro + '<a class="less" ng-click="this.showFull = false">Less</a>';
}
}
return intro;
};
// Truncate the long intro into a shorter length blurb
this.getShortIntro = function(row, row_index) {
// Truncate if necc.
var short_intro = jQuery.truncate(row['Introduction'], {
length: 250,
words: true,
ellipsis: '\u2026 <a class="more moreish" attr-ng-click="guideList.showFullClick()">Read on</a>'
});
var more = jQuery('.more', short_intro); // select more link
if (more.length) { // do we have a more link
$scope.guideList.rowIndex[row_index] = true;
}
else { // no more link
$scope.guideList.rowIndex[row_index] = false;
}
$compile(short_intro)($scope);
return short_intro;
};
}]);
As you can see in the ViewService.get() call, data is fetched and then processed. The processing simply involves putting a link at the end of the "Intro" field that is intended to be clickable.
For a while I was having a tough time to even get the ng-click directive to even show (it was being filtered out w/out $sce.trustAsHtml). Now it is there but clicking it has no effect.
The main template (from Drupal) currently looks like:
<div class="guide-listing" ng-controller="guideListController as guideList">
<a ng-click="guideList.showFullClick()">Click me</a>
<div class="guide-teaser"
ng-repeat="guide in guideList.guides"
ng-include src="'/sites/all/themes/ngTheme/ngApp/partials/guide_teaser.html'">
<!-- See guide_teaser.html partial for guide teasers -->
</div>
</div>
The ng-click as placed in the Drupal template above works as expected.
And for the partial that is used in the ng-repeat, it looks like so:
<div ng-controller="guideListController as guideList">
<h2 class="guide-teaser-title">{{guide.node_title}}</h2>
<div class="guide-teaser-intro" ng-bind-html="guide.Introduction" ng-show="guide.showFull">
{{guide.Introduction}}
</div>
<div class="guide-teaser-intro-short" ng-bind-html="guide.Intro_short" ng-show="!guide.showFull">
{{guide.Intro_short}}
</div>
</div>
So far I have only been working on getting the ng-click to work on the short_intro and have had no success so far. Any ideas as to what I am doing wrong would be greatly appreciated!
Ok, So I did get some traction! I used the ngHtmlCompile (http://ngmodules.org/modules/ng-html-compile) directive that was created by https://github.com/francisbouvier (thanks!)
The issue was that the new (dynamic) html wasn't being compiled.
At first it didn't work. I had two issues that prevented it from firing:
A: I stopped using $sce.trustAsHtml. Using this in conjunction with the directive caused the content to disappear!
B: The other issue was one of scope. After I changed the directive such that transclude was set to false it worked just fine!
I am having an issue where a value in an object in a list is changing, but the change is not being reflected in ng-href.
I have the following HTML:
<div class='tabrow docRow'>
<span data-ng-repeat='f in dp.source.files'>
<span class='linkedDoc' >
<a class='attachedCaption' target='_blank'
data-ng-href='{{f.vpath}}'>{{f.caption}}
<img class='attachedSrc' data-ng-src='{{ f.vimg }}'/>
</a>
</span>
</span>
</div>
I have a watch on dp.source, and when it changes a call a function that loops through each f in dp.source.files and adds v.img and v.path.
vimg is updated before the callback in the watch exits. vpath is updated asynchronously, maybe a second or two after the callback completes. Since it is updated in an asynch call, I use $apply.
$scope.$watch('dp.source', function() {
for (var idx = 0; idx < $scope.dp.source.files.length; idx++) {
var f = $scope.dp.source.files[idx];
f.vimg = _ev.imgMapper(dataProvider.imgMap, f.path); <--- immediate
f.vpath = '#';
attachService.getURL(f); <-- asynch call that updates f.vpath
}
}
attachService:
myObj.getURL = function(obj) {
syncService.getURL(obj, function(url) { <-- asynch return
$rootScope.$apply(function() {
obj.vPath = url;
});
});
};
vimg is present on the rendered page immediately, while the change in vpath is NEVER reflected on the page.
What am I missing?
To be clear, I have verified that the variable f.vpath IS getting updated.
OMG is was a spelling error!
the HTML expects f.vpath and attachService updated obj.vPath
It was that simple