I'm trying to work out where the best place to run a long-running load operation is using Durandal.
From what I can tell, the general recommendation for loading data is in the ViewModel's activate
method, which is what I usually do - something like:
viewModel.activate = function () {
var loadPromise = myService.loadData();
return $.when(loadPromise).then(function (loadedData) {
viewModel.data(data);
});
};
I know that if I don't return the promise here, then there's usually problems with the bindings - as this question and answer indicates.
However, executing a long running load operation in the activate
method makes the app "freeze" while the load operation completes. For example, what if my load was now something like this?
viewModel.activate = function () {
// All loads return a promise
var firstLoad = myService.loadFirstData();
var secondLoad = myService.loadSecondData();
var thirdLoad = myService.loadThirdDataWhichTakesAges();
return $.when(firstLoad, secondLoad, thirdLoad).then(function (one, two, three) {
viewModel.one(one);
viewModel.two(two);
viewModel.three(three);
});
};
In this scenario, the URL is updated to reflect the page which is being loaded, but the page content still shows the previous page (which is what I mean by "freezes").
Ideally, it would be good if the URL should change to the new page, and the page content should show the new page too (even though the data for that page has not yet been returned). Then, as each load operation returns, the relevant part of the page should be updated when the data is bound into the view model.
Is there a recommended way for achieving this inside Durandal?
My current solution is to kick-off the load in the activate
method, and then populate the data in the viewAttached
method:
var loadPromise;
viewModel.activate = function () {
// All loads return a promise
var firstLoad = myService.loadFirstData();
var secondLoad = myService.loadSecondData();
var thirdLoad = myService.loadThirdDataWhichTakesAges();
loadPromise = $.when(firstLoad, secondLoad, thirdLoad);
// Don't return the promise - let activation proceed.
};
viewModel.viewAttached = function () {
$.when(loadPromise).then(function (one, two, three) {
viewModel.one(one);
viewModel.two(two);
viewModel.three(three);
});
};
It seems to work, but I remember reading somewhere that relying on viewAttached
wasn't a good solution. I'm also not sure if there is potential for a race condition since I'm allowing the activate to proceed.
Any other recommendations?
You don't have to return a promise but in that case you must handle this in you knockout bindings so you woun't bind to elements that are undefined. You can try to get rid of that 'return' in activate but add a property indicating if model is still loading. Something like this:
viewModel.isLoading = ko.observable(false);
viewModel.activate = function () {
isLoading(true);
var loadPromise = myService.loadData();
$.when(loadPromise).then(function (loadedData) {
viewModel.data(data);
isLoading(false);
});
};
And then, in your view, you can have a section that shows up when view is still loading and one that shows up when loading is done. Sometinhg like:
<div data-bind:"visible: isLoading()">Loading Data....</div>
<div data-bind:"visible: !isLoading()">Put your regular view with bindings here. Loading is done so bindings will work.</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With