Scope variables not loading on page load AngularJS - angularjs

I have a chart on one of my views in my one page app using angular-chart.js http://jtblin.github.io/angular-chart.js/. The issue is that the chart won't load when the view initializes because the data hasn't yet loaded via the API in the factory. No matter how long I stay on that view, the data won't populate. However, if I go to a different view on the one page app, and come back, the data is there, since it has already loaded in the factory.
It's my understanding that once the data is loaded from factory, that the chart should update immediately because of data bindingl, but why won't it do this on the first view?
Factory (where the data is loaded via service.getChartData())
app.factory('chartFactory', ['$http', function($http) {
var service = {
height_chart: window.innerHeight * 0.4,
labels: [],
series: ['GDAX Value'],
data: [],
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
id: 'y-axis-1',
// type: 'linear',
display: true,
position: 'left',
ticks: {
beginAtZero: false,
callback: function(value, index, values) {
if (parseInt(value) >= 1000) {
return '$' + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return '$' + value;
}
}
}
}],
xAxes: [{
display: false
}]
}
},
getChartData: function() {
$http.get('/portfolio/get-chart-data')
.then(function(response) {
service.labels = response.data[0]
service.data = response.data[1]
})
}
}
return service
}])
Controller
app.controller('chartCtrl', chartCtrl)
chartCtrl.$inject = ['$scope', '$interval', 'chartFactory']
function chartCtrl($scope, $interval, chartFactory) {
var vmChart = this
vmChart.height_chart = chartFactory.height_chart
vmChart.labels = chartFactory.labels
vmChart.series = chartFactory.series
//this data hasn't loaded yet.
vmChart.data = [chartFactory.data]
vmChart.onClick = function(points, evt) {
console.log(points, evt)
}
vmChart.datasetOverride = [{
yAxisID: 'y-axis-1'
}]
vmChart.options = chartFactory.options
chartFactory.getChartData()
//the chart never shows up on the first view, even after the function loads the data in the factory.
}
<div ng-controller="chartCtrl as vmChart" class="chart-container">
<canvas id="line" class="chart chart-line" chart-data="vmChart.data" chart-labels="vmChart.labels" chart-series="vmChart.series" chart-options="vmChart.options" chart-dataset-override="vmChart.datasetOverride" chart-click="vmChart.onClick" style="width:100%;">
</canvas>
</div>
Just to clarify - the chart does load, but not until I switch to a different view then back again. How do I get the chart to load on. the first view once the data is loaded in the factory?

A http request takes around 200 ms to fetch data but the DOM gets loaded before the request is completed.So the first time when you land on the page ,the chart data gets populated but only after the whole page has been loaded.
Try this:
Make the getChartData function 'async' and add 'await' to http request to tell DOM to wait till the request has been completed.
In Chart Factory :
getChartData: async function() {
await $http.get('/portfolio/get-chart-data')
.then(function(response) {
service.labels = response.data[0]
service.data = response.data[1]
})
}

Please add chartFactory.getChartData() after vm declaration, like below-
app.controller('chartCtrl', chartCtrl)
chartCtrl.$inject = ['$scope', '$interval', 'chartFactory']
function chartCtrl($scope, $interval, chartFactory) {
var vmChart = this
chartFactory.getChartData()
vmChart.height_chart = chartFactory.height_chart
vmChart.labels = chartFactory.labels
vmChart.series = chartFactory.series
//this data hasn't loaded yet.
vmChart.data = [chartFactory.data]
vmChart.onClick = function(points, evt) {
console.log(points, evt)
}
vmChart.datasetOverride = [{
yAxisID: 'y-axis-1'
}]
vmChart.options = chartFactory.options
//the chart never shows up on the first view, even after the function loads the data in the factory.
}

Related

Highchart is not working when I am fetching the axis values from database

I am using ASP .Net web api to fetch the data from the database, And trying to make a graph of the derived data , using Highchart. When I am giving the axis values in the form:
xAxis: {
categories:["Sabab","Sunjare","Ety"]
},
series: [{
data: [20,30,40]
}]
it is working fine. But when I am initializing the categories of xAxis and data of series , by the data , derived from the database, no graph is formed then.
Here is the angular controller code.
app.controller('chartController', function ($scope,chartService) {
loadStudentName();
function loadStudentName() {
var promiseget = chartService.getStudentName();
promiseget.then(function (p1) { $scope.StudentNames = p1.data },
function (errorP1) {
alert("Error Occurred");
});
}
loadTotalMarks();
function loadTotalMarks() {
var promiseget = chartService.getTotalMarks();
promiseget.then(function (p1) { $scope.TotalMarks = p1.data },
function (errorP1) {
alert("Error occured");
}
);
}
Highcharts.chart('container', {
chart: {
type: 'column'
},
xAxis: {
categories:$scope.StudentNames
},
yAxis: {
title: {
text:"Marks"
},
},
series: [{
name: "Marks",
data: $scope.TotalMarks
}]
});
});
The $scope.StudentNames is populated with ["Ety","Sabab","Sunjare","Sadia"] from the database and $scope.TotalMarks is populated with [393,390,394,341] from the database, but yet the graph is not formed.
You need to refresh your highcharts after data is updated, either:
put a watch on both of your data objects and do chart.redraw(); if
it changes
call redraw method after after you update your data
inside of promise.
I recommend creating a directive that takes highchart config object and watches that for changes and then redraws the chart, that way you can reuse it.

ui-angular fullcalendar refresh issue

I'm using ui-calendar to display events. To fill out the calendar model, the controller fetches the events from a Factory. The wierd part (which I can't figure out), is that when the Factory fetches the data from the API, the calendar shows the events just fine. However, in order to "speed things up" a little, the Factory saves the api fetched data in a local variable. If the Factory returns the data from the local variable, the calendar does not display the events. However if the Factory returns data from the API, the events are displayed just fine (so there must be something wrong with the way I am returning the local variable data from the Factory).
The Factory method is as follows:
function getAll() {
if (!_allEventsRequiresUpdate && _allEvents) {
var deferred = $q.defer();
deferred.resolve(_allEvents);
return deferred.promise;
}
else {
var request = $http({
method: "Get",
url: baseUrl
});
return request.then(function (response) {
_allEvents = response.data;
_allEventsRequiresUpdate = false;
return response.data;
}, handleError);
}
}
The _allEvents variable get filled when the data is fetched from the API. The data in both cases (returned from the API or the local variable), is exactly the same (at least to my knowledge), however, as stated previously, only the data fetched from the API gets rendered in ui-calendar/fullcalendar.
Any ideas? Is there something wrong as to how I am returning the local variable from the Factory?
BTW, in both cases, the controller resolves the promise.
UPDATE
The following is the method in the Angular controller that fetches the data from the Factory:
function getAllEvents() {
serviceAppointmentsServices.getAll()
.then(function (data) {
vm.events = angular.copy(data);
vm.fullCalendarEvents = [];
for (var i = 0; i < vm.events.length; i++) {
var event = {
id: vm.events[i].xrmId,
title: vm.events[i].xrmName,
start: moment(vm.events[i].startDate).tz('America/Santiago'),
end: moment(vm.events[i].endDate).tz('America/Santiago'),
stick: true
}
if (vm.events[i].xrmStatus.value == 1)
event.color = '#D2A15D';
vm.fullCalendarEvents.push(event);
}
uiCalendarConfig.calendars["calendar"].fullCalendar('removeEventSources');
uiCalendarConfig.calendars["calendar"].fullCalendar('addEventSource', vm.fullCalendarEvents);
}, function (mesage) {
toastr.error(mesage, "error!");
});
}
Here is the calendar config:
vm.uiConfig = {
calendar: {
height: 450,
editable: true,
eventClick: editEvent,
dayClick: addEvent,
eventDrop: $scope.alertOnDrop,
eventResize: $scope.alertOnResize,
eventAfterAllRender: documentReady,
locale: 'es',
timezone: 'America/Santiago',
customButtons: {
addEvents: {
text: 'nuevo',
click: function () {
vm.fx.addEvent();
$scope.$apply()
}
}
},
header: {
left: 'month basicWeek basicDay agendaWeek agendaDay',
center: 'title',
right: 'addEvents today prev,next'
},
eventRender: eventRender
}
};
I'm posting the answer in case anyone else out there gets into the same issue.
Thanks to #Javarome (https://stackoverflow.com/users/650104/javarome) in the post: Directive is being rendered before promise is resolved. I followed his suggestion and everything worked like a charm.
Summary: the issue was that the directive was getting fired before the promise resolved in the controller. So I followed his suggestion to wrap the directive in an ng-if (with the variable needed to be resolved as the trigger, and voila!!! Something like this:
<div class="container" ng-if="vm.fullCalendarEvents">
<div class="calendar" ng-model="eventSources" calendar="calendar" config="uiConfig.calendar" ng-disabled="waitingForHttp" ui-calendar="vm.uiConfig.calendar" ></div>
</div>

AngularJS Include Refresh

I have a html page which I am including as follows.
<ng-include src="lowerTabURL"></ng-include>
This page contains a devextreme control which loads a datasource via ajax.
html:
<div class="tab-container" style="height:100%; width:100%">
<div dx-tabs="lowerTabOptions" dx-item-alias="lowerTab">
</div>
</div>
controller:
DemoApp.controller('NavigationController', function DemoController($scope, $templateCache) {
$scope.lowerTabURL = "LowerPanelTest";
$scope.currentSidebarId = 10100;
$scope.lowerTabOptions = {
dataSource: new DevExpress.data.CustomStore({
load: function (loadTabOptions) {
console.log('get tabs');
var d = $.Deferred();
$.ajax({
url: 'GetLowerTabs',
data: { currentSidebarId: $scope.currentSidebarId },
type: 'GET',
success: function (result) { console.log(result); d.resolve(result); }
});
return d.promise();
}
}),
animationEnabled: true,
swipeEnabled: true,
itemTitleTemplate: 'title',
height: '100%'
};
$scope.navBarClicked = function (sidebarId) {
console.log(sidebarId);
$scope.currentSidebarId = sidebarId;
}
});
This works correctly however I have a navbar which when clicked, should change the tab control.
Currently I am changing the sidebarId which gets passed to the ajax call but I need a way to reload the include page so that this is called again. I have tried changing the lowerTabUrl and then changing it back again but this doesnt refresh the page. What is the best way to do this?
It depends on your angular version, you will need to watch after changes of value for param sidebarId, # angular 1 this is achieved by scope.watch
scope.$watch('sidebarId', function(newValue, oldValue) {
// ..call refresh here
});
at angular 1.5 and later you can override ngOnChages
this.$onChanges = function (changesObj) {
if (changesObj.sidebarId) {
// changesObj.sidebarId.currentValue
}
};

angularjs - scope changes but function is not fired

I'm working on an angular app which needs to display some data as a chart.
The library i'm using in nvd3.js.
The chart is a directive, and the code that creates the chart is the following:
angular.module('charts', []).factory('d3', [
function() {
return d3;
}
]).factory('nv', [
function() {
return nv;
}
]).directive('hgCharts', [
'$timeout', 'd3', 'nv', function($timeout, d3, nv) {
var linkFunction;
linkFunction = function(scope, element, attr) {
var chart, chartCreated, containerHeight, containerWidth, createChart, date, svg, xScale;
containerWidth = element[0].offsetWidth;
containerHeight = element[0].offsetHeight;
date = new Date;
svg = element.find('svg');
chart = void 0;
xScale = d3.time.scale().domain([date, date]).nice(d3.time.day).range([0, containerWidth - 50]);
createChart = function() {
debugger;
chart = nv.models.multiChart();
chart.options({
showLegend: true,
duration: 0,
margin: {
'left': 20,
'right': 30,
'top': 50,
'bottom': 50
},
yDomain1: [0, 30],
showXAxis: false,
showMinMax: false,
useVoronoi: true
});
chart.xAxis.scale(xScale).orient('bottom').ticks(3).duration(0).tickFormat(d3.time.format('%H:%M'));
chart.yAxis1.orient('left').ticks(7).duration(0);
chart.tooltip.enabled(true);
nv.utils.windowResize(function() {
containerWidth = element[0].offsetWidth;
containerHeight = element[0].offsetHeight;
chart.xAxis.range([0, containerWidth - 50]);
chart.update();
return d3.selectAll(".nv-axisMaxMin-x")[0][0].remove();
});
return chart;
};
chartCreated = function() {
console.log("Scope.data is: ", scope.data);
d3.select(svg[0]).attr('width', '100%').attr('height', '100%').datum(scope.data).call(chart);
return d3.selectAll(".nv-axisMaxMin-x")[0][0].remove();
};
scope.$watch(function() {
return scope.data;
}, function() {
if (scope.data) {
console.log("Scope data changed...");
return nv.addGraph(createChart, chartCreated);
}
});
};
return {
restrict: 'E',
scope: {
data: '='
},
template: '<svg></svg>',
link: linkFunction
};
}
]);
In my UI, I have a prev and next button.
Each of the buttons above have a function in the controller to get the right start and end time period using moment.js, and once they have the right times, the call a function.
This it the code for the funciton they call:
var loadChartData;
loadChartData = function(start, end) {
if (!start) {
start = 1467331200000;
}
if (!end) {
end = 1467417599000;
}
return hgChartData.loadData(start, end).then(function(res) {
return $scope.chartData = res;
});
};
The function calls a service to load my data, and returns them as a promise (res), and then creates (or modify) $scope.chartData with the new data.
Debugging this process a lot, I noticed that the problem I've got, is that even if $scope.chartData changes (I have the right result printed on my console), the debugger inside createChart function is never called, and so the chart with my new data is not created.
Am I doing something wrong with the watcher?
Strangely, it works only the first time I click on a button (it doesn't matter if it prev or next), but then it stops (as I said above, scope.chartData changes everytime according to the console messages I print.) The code for next and prev buttons are below:
var requiredDay;
requiredDay = '';
$scope.prevDay = function() {
if (!requiredDay) {
requiredDay = moment(1467377134000);
}
requiredDay = requiredDay.clone().subtract(1, 'day');
console.log("Required data period is: ", requiredDay);
displayRequestedData(requiredDay);
return requiredDay;
};
$scope.nextDay = function() {
if (!requiredDay) {
requiredDay = moment(1467377134000);
}
requiredDay = requiredDay.clone().add(1, 'day');
console.log("Required data period is: ", requiredDay);
displayRequestedData(requiredDay);
return requiredDay;
};
I think the problem is with my watcher, but I can't figure out what i'm doing wrong.
Thanks for any help
EDIT
After debugging this a bit more, I found out that the scope is actually watched, but as soon as I changes to empty array, it's not watched anymore.
I'll explain it a bit better:
scope.data (the scope i'm watching in my directive) is equal to an array of object, and each object has this structure (taken directly from console):
Object{
color:"red"
duration:0
key:"Temperature"
originalKey:"Temperature"
type:"line"
values:Array[10]
yAxis:1
}
Each array inside the previous object look like this:
Object{
series:0
x:1467330307000
y:20
}
what I found is that if my array changes its values (array values are the only thing that changes inside that scope), the scope is updated and the chart re-rendered, but as soon as I have no data for the requested time period, the number of arrays inside my object goes to 0 and then the watcher doesn't work anymore.
I hope this helps a bit more. I'm still having issues try to understand why and how to solve it.
Thanks again

How to resolve a promise for a vector map datasource in Angularjs

Right so I'm fairly new to angular and really enjoying the experience and I'm slowly but successfully running through a few gotchas that keep cropping up, however this one has be stumped.
I'm loading a version of the Jquery Vector map and everything is working a treat. I'm creating the empty object and populating it from my datasource in a format that the map can use to colour code but here is where the problem crops up.
When the map is instantiated, it gets the contents of the object 'ratingobj' however the resource hasn't populated the object by the time its rendered. I can see this in the console log as ratingobj is always empty.
I understand the concept that the resource is a promise and when the data is retrieved it will be populated however what I can't work out is how to get the resource to resolve the resource and get the data prior to the map being loaded!
Please help, any pointers would be great!
Thanks
Here is my resource query in my services:
.factory('CountryData', ['$resource',
function($resource){
return $resource('http://mydatasource/datafeed', {}, {
query: {
method:'GET',
isArray:false,
}
})
}])
Here's the controller
.controller('jqvmapCtrl', ['$scope' ,'CountryData', 'greeting',
function($scope, CountryData, greeting) {
var ratingobj = {};
$scope.rating = CountryData.query(function (response){
angular.forEach(response.record, function(value,key) {
ratingobj[value.countryISO] = value.countryRating;
});
});
console.log(ratingobj);
$scope.worldMap = {
map: 'world_en',
backgroundColor: null,
color: '#ffffff',
hoverOpacity: 0,
hoverColor: '#C2C2C2',
selectedColor: '#666666',
enableZoom: true,
showTooltip: true,
values: ratingobj,
scaleColors: ['#C4FFFF', '#07C0BB'],
normalizeFunction: 'polynomial',
};
}
]);
This is my main app file with the route
.when('/countries/map', {
templateUrl: 'views/countries/map.html',
controller: 'jqvmapCtrl',
})
how to get the resource to resolve the resource and get the data prior to the map being loaded
There are two ways how to accomplish that.
1. Delay rendering of the map widget until the data is loaded. Add ng-if="worldMap" to the element holding your map, for example
<div id="vmap" ng-if="worldMap"></div>
See the following SO answer for more details: https://stackoverflow.com/a/22510508/69868
2. Delay rendering of the whole view until the data is loaded.
Extend the route definition with a resolve block.
.when('/countries/map', {
templateUrl: 'views/countries/map.html',
controller: 'jqvmapCtrl',
resolve: {
ratingobj: function(CountryData) {
return CountryData.query().$promise
.then(function(response) {
var ratingobj = {};
angular.forEach(response.record, function(value,key) {
ratingobj[value.countryISO] = value.countryRating;
});
return ratingobj;
});
}
}
})
Modify the controller to get ratingObj injected instead of CountryData:
.controller('jqvmapCtrl', ['$scope' ,'ratingobj', 'greeting',
function($scope, ratingobj, greeting) {
console.log(ratingobj);
$scope.worldMap = {
map: 'world_en',
backgroundColor: null,
color: '#ffffff',
hoverOpacity: 0,
hoverColor: '#C2C2C2',
selectedColor: '#666666',
enableZoom: true,
showTooltip: true,
values: ratingobj,
scaleColors: ['#C4FFFF', '#07C0BB'],
normalizeFunction: 'polynomial',
};
}
]);
See the following SO answer for more details: https://stackoverflow.com/a/16288468/69868

Resources