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>
Related
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.
}
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
}
};
I can't seem to work out how to redraw my Angular-Datatable after I delete a record from my database. I don't get any errors, but the table never seems to redraw unless I manually refresh the page. I have been trying to work with many examples from the website documentation.
I have my datatable:
$scope.dtInstance = {};
$scope.selectedItems = [];
$scope.toggleItem = toggleItem;
$scope.reloadData = reloadData;
// Build the User table
$scope.dtOptions = DTOptionsBuilder
.fromFnPromise(function() {
var deferred = $q.defer();
deferred.resolve(users);
return deferred.promise;
})
.withBootstrap() // Style with Bootstrap
.withOption('responsive', true)
.withDisplayLength(15) // Show 15 items initially
.withOption('order', [0, 'asc']) // Sort by the first column
.withOption('lengthMenu', [15, 50, 100]) // Set the length menu items
.withOption('createdRow', function(row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
})
.withOption('headerCallback', function(header) {
if (!$scope.headerCompiled) {
// Use this headerCompiled field to only compile header once
$scope.headerCompiled = true;
$compile(angular.element(header).contents())($scope);
}
})
.withOption('fnRowCallback', formatCell);
$scope.dtColumns = [
DTColumnBuilder.newColumn(null).withTitle('Username').withClass('col-md-2').renderWith(createUsernameHyperlink),
DTColumnBuilder.newColumn('Email').withTitle('Email'),
DTColumnBuilder.newColumn('Level').withTitle('Role').withClass('col-md-2'),
DTColumnBuilder.newColumn('LastConnected').withTitle('Last Accessed'),
DTColumnBuilder.newColumn('Verified').withTitle('Account Verified').withClass('col-md-2'),
DTColumnBuilder.newColumn(null).withTitle('')
.notSortable()
.renderWith(function(data, type, full, meta) {
return '<input type="checkbox" ng-click="toggleItem(' + data.Id + ')" />';
}).withClass("text-center")
];
// Reload the datatable
function reloadData() {
var resetPaging = false;
$scope.dtInstance.reloadData(callback, resetPaging);
};
function callback(json) {
console.log(json);
};
And then I have my delete function that sits in the same controller. Calling reloadData() on a successful response from the service. I can see from the console.log that it is calling the function correctly, but nothing happens.
$scope.deleteUser = function( selectedItems ) {
swal({
title: 'Are you sure?',
text: 'Are you sure you want to delete the selected account profile(s)? This process cannot be undone...',
type: 'warning',
showCancelButton: true,
confirmButtonText: 'Delete',
confirmButtonColor: "#DD6B55",
closeOnConfirm: false,
allowEscapeKey: true,
showLoaderOnConfirm: true
}, function() {
setTimeout( function() {
// Delete user
UsersService.deleteUser( selectedItems.toString() )
.then(function( data ) {
// Show a success modal
swal({
title: 'Success',
text: 'User has been deleted!',
type: 'success',
confirmButtonText: 'Close',
allowEscapeKey: false
}, function() {
reloadData(); //<== Calls the function but doesn't do anything
//$state.go('users');
});
}, function() {
// Show an error modal
swal({
title: 'Oops',
text: 'Something went wrong!',
type: 'error',
confirmButtonText: 'Close',
allowEscapeKey: true
});
});
}, 1000);
});
};
Just wondering if I have missed some step?
As suggested by #davidkonrad in a previous comment and more so from the Angular-Datatable's author, I was not reloading my content when attempting to redraw my table. Even though I was referencing my data (users) from an injected service, it was never getting updated within the controller and so my table content was never differing.
The author suggested that it is preferable to load the data from a promise that makes a HTTP request, thus allowing further calls to the promise each time the table redraws.
So instead of this:
// Build the User table
$scope.dtOptions = DTOptionsBuilder
.fromFnPromise(function() {
var deferred = $q.defer();
deferred.resolve(users);
return deferred.promise;
})
.withBootstrap() // Style with Bootstrap
I changed it to this:
// Build the User table
$scope.dtOptions = DTOptionsBuilder
.fromFnPromise(function() {
return UsersService.getUsers();
})
.withBootstrap() // Style with Bootstrap
Which now updates my table fine upon each redraw event with a call to $scope.dtInstance.reloadData();
My Github post can be found here
setTimeout function works from outside of the angular digest cycle since it's async. If you want actions you take inside a timeout to apply to the angular digest cycle you should use $timeout instead.
Another option is to use $scope.apply(), but this will just mimic the $timeout function.
Please note that you'll need to inject $timeout to your controller.
I'm trying to integrate Angular Bootstrap Calendar to my Laravel 5 project. Right now, the calendar works using the provided pre-populated demo list of events.
vm.events = [
{
title: 'An event',
type: 'warning',
startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(),
endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate(),
draggable: true,
resizable: true
}, {
title: 'Event 2',
type: 'info',
startsAt: moment().subtract(1, 'day').toDate(),
endsAt: moment().add(5, 'days').toDate(),
draggable: true,
resizable: true
}, {
title: 'This is a really long event title that occurs on every year',
type: 'important',
startsAt: moment().startOf('day').add(7, 'hours').toDate(),
endsAt: moment().startOf('day').add(19, 'hours').toDate(),
recursOn: 'year',
draggable: true,
resizable: true
}
];
I would like to retrieve and format the events from my database like the example above, but I'm not sure how to tackle this from my controller.
On the Angular Calendar side, I've read that I can use the angular $http service to load the events, like this:
$http.get('/events').success(function(events) {
//TODO - format your array of events to match the format described in the docs
$scope.events = events; //Once formatted correctly add them to the scope variable and the calendar will update
});
Any help would be greatly appreciated
What you would want to do is create a service that takes care of all the HTTP request/response handling and have your controller consume it to get/save/update data. Something like:
// assuming that you have a REST service endpoint at /events
// create your service that will handle all HTTP interaction for the events resource
app.factory('EventsService', ['$http', function($http) {
return {
getAll: function() {
// fetch all events asynchronously
return $http.get('/events').success(function(response) {
var events = response.data;
// if you need to do any pre-processing of the events first, do it here
// pass your events to the next function in the promise chain.
return events;
}, function(err) {
// handle errors here
// pass your error object down the chain in case other error callbacks are added later on to the promise.
return err;
});
}
};
}]);
app.controller('YourController', ['$scope', 'EventsService', function($scope, EventsService) {
// call the asynchronous service method and add your promise success callback that returns your array of events to be bound to your context.
EventsService.getAll().then(function(evts) {
$scope.events = evts;
}, function(err) {
// do any additional error handling here
});
});
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