Hi All I have tried the var loadTime = window.performance.timing.domContentLoadedEventEnd- window.performance.timing.navigationStart; but value seems to be much smaller than actual time it takes to load the page.
Ideally I want to have a stopwatch that stop counting when the page is fully loaded. Any suggestion?
I won't say that this method is not without its flaws, but if you are measuring the loading of data, here is one method you can use to achieve a loading counter.
Create a bool to evaluate against, isLoading. Hoist it to true.
Create your int, loadTime.
Use a SetInterval to increment the int
Load some data
after you have processed the data, set the isLoading condition to false.
Example:
https://plnkr.co/edit/ARVpFd9NmlFNURlyS9SZ
app.controller("myCtrl", function($scope, $http) {
$scope.isLoading = true;
$scope.loadTime = 0;
$scope.msg = '';
var tmr = setInterval(function() {
if(!$scope.isLoading || $scope.isLoading === false) {
clearInterval(tmr);
}
$scope.loadTime += 0.01;
}, 100);
$http.get('https://unsplash.com')
.then(function(res) {
$scope.msg = 'Data is loaded';
$scope.isLoading = false;
});
Related
I have an Umbraco project with an Area section configured with Angular.
I use the Plugins to integrate the Area with the use of package.manifest like this:
Into edit.controller.js, I have this script:
'use strict';
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
$http.get('/umbraco/backoffice/administration/CustomSection/GetUrl/?node=' + $scope.id)
.success(function (data) {
$scope.url = JSON.parse(data);
$scope.canShow = $scope.url;
});
});
When I run the project and click on any node in this area, I receive most of the time a 404 error like if the page was not exist. I say "most of the time" because 1 out of 10, it works and the page is displayed.
However, if I put a breakpoint in the javascript function below and I click on any node and resume the javascript after the breakpoint was hitting, the node related html page is displayed correctly.
Anybody know why when I put a breakpoint, Umbraco or Angular are able to resolve 100% of the time the page but not when I don't have any breakpoint in this function?
Thanks
I really hate to answer my own questions but after 2 weeks without answers and a lot of reflections on my side, I finally found a solution to this problem.
What was causing the problem of out synching between Umbraco and Angular was due to the $http.get query which is asynchronous with Angular (no other choice I think) and after a response from the server to get a valid URL, the $scope object was not able to talk to Umbraco to redirect to the valid URL.
On my asp.net MVC controller, the GetUrl function was trying to get a valid URL doing a query to the database where I keep a structure of nodes which correspond to a tree displayed to the user. This is a slow process and the time required to respond to the HTTP get request was too long the vast majority of the time.
Here is my solution to this problem:
'use strict';
var myCompany = myCompany || {};
myCompany.myProject = myCompany.myProject || {};
myCompany.myProject.controller = (function (parent){
parent.urls = {};
function loadUrls () {
$.get('/umbraco/backoffice/administration/CustomSection/GetUrls')
.success(function (data) {
parent.urls = data;
});
};
loadUrls();
return parent;
})({});
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
var url = myCompany.myProject.controller.urls.find(function (element) {
return element.Key == $scope.id;
});
if (url) $scope.url = url.Value;
$scope.canShow = $scope.url;
});
Note in this case that I have an iffe function which query the server to build an array of all my URLs from the backoffice and then when Angular need a redirection, I search directly from the array.
The iffe function is calling only once when the user enters in the backoffice section which I think is nice because the structure behind rarely changes.
I'm not sure if it's a hack or the valid way to do the thing due to my lack of experience with Angular but it works like a charm.
function (position) {
mysrclat = position.coords.latitude;
mysrclong = position.coords.longitude;
console.log(mysrclat);
console.log(mysrclong);
});
how to pass this mysrclat and mysrclong value to html page or service file?
my controller:
var lat = 0;
var lan = 0;
var mysrclat = 0;
var mysrclong = 0;
$scope.nearme = function($scope) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
mysrclat = position.coords.latitude;
mysrclong = position.coords.longitude;
console.log(mysrclat);
console.log(mysrclong);
$scope.lat = mysrclat;
$scope.lan = mysrclong;
});
}
}
When you change a $scope value outside of angularJS you should manually trigger the digest cycle.
One way to do that is using $scope.$evalAsync
Change your nearme function definition as below:
$scope.nearme = function($scope) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
$scope.$evalAsync(function() {
$scope.lat = position.coords.latitude;
$scope.lan = position.coords.longitude;
})
});
}
}
To achieve your expected results, you can $scope.$apply() or $scope.evalAsync
$scope.$apply : $apply() is responsible for executing entire list of watchers of available scopes.
From Angular documentation:
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.
Use below code snippet to get expected result
$scope.$apply(function() {
$scope.lat = mysrclat;
$scope.lan = mysrclong;
})
$scope.evalAsync:
In some scenarios , you might get below error on using $scope.$apply heavily
Error: $digest already in progress
To avoid that , use $scope.evalAsync which will evaluate expression in current or next digest cycle
$scope.$evalAsync(function() {
$scope.elat = mysrclat;
$scope.elan = mysrclong;
})
codepen url for reference- https://codepen.io/divyar34/pen/dvJrba
Wrap it all in a $timeout(function(){//logic here}) . It will work as expected as $timeout internally calls the $apply & Everythin'll be fine .
I have a complex page (lots of ng-repeats nested) so the digest takes a while to finish. I want to give the user some feedback, so they don't think the browser is hung.
Below is a sample fiddle, when you click HIT ME the $watch hangs for 2 seconds. I want the "Working" message to show up, but it does not.
https://jsfiddle.net/jdhenckel/c7edvdt1/
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.n = 0;
$scope.test = function() {
$scope.msg = 'Working...';
$scope.n += 1;
};
$scope.$watch(function(scope) {
return $scope.n;
}, function() {
var x = Date.now() + 2000;
while (x > Date.now()) {}
$scope.msg = 'Done.';
});
});
I also tried to use JQuery to directly change the DOM before the digest, but that also didn't work. Seems like my only option is to move all the long running stuff a future digest using a $timeout, but that seems like a hack!
Is there an elegant way to notify the user that the digest is running?
EDIT: Here is a possibly more realistic example.
https://jsfiddle.net/jdhenckel/9vcLq0k3/
$scope.n = 0;
$scope.msg = 'Ready';
$scope.test = function() {
$scope.msg = 'Working...';
$timeout(function() {
doStuff();
$scope.msg = 'Done';
}, 100);
}
This works because I moved all the expensive changes into doStuff.
I was hoping that Angular would provide a simpler way to do this (such as ng-cloak for initialization.) If not, then I'll keep using $timeout.
I am making a little diagnose web page and want to make it look like the system is working hard in background by showing loading image for a while.
I am not a programmer. so I basically look all over for a chunk of codes that works and this is how I tried... but justDelaying function never delays the later process. It seems all the process is running simultaneously.
app.controller('HardworkCtrl', function($scope, $timeout) {
$scope.hardWork = function() {
// start showing hard-working image
$scope.loading = true;
$scope.justDelaying = function() {
$timeout(function() {
// do nothing...
}, 5000);
}
$scope.justDelaying();
$scope.theAnswer = "42."
// stop showing hard-working image
$scope.loading = false;
};
};
Any idea?
Anything that happens inside "$timeout" is asynchronous, so it's just scheduled for later, without blocking the main execution. In other words, the instructions after you call $scope.justDelaying() happen immediately, while the instructions inside justDelaying get delayed for 5 seconds. To make those instructions execute later, you need to move them inside the $timeout, like this:
app.controller('HardworkCtrl', function($scope, $timeout) {
$scope.hardWork = function() {
// start showing hard-working image
$scope.loading = true;
$scope.delayThenDoStuff = function() {
$timeout(function() {
$scope.theAnswer = "42."
// stop showing hard-working image
$scope.loading = false;
}, 5000);
}
$scope.delayThenDoStuff();
};
};
I built an infinite scroll for a mobile web app built with AngularJS with the following extras features:
I built it to be bidirectional
This is for a mobile web app so I wanted it to unload out-of-view contents to avoid memory issues
Here is the jsfiddle link.
Now, I have a few questions and I also needs a small code review:
I am not familiar with promises, but then() seems to be executed before $digest. Thus, I need to delay my codes with $timeout. For me, it's a sign that something is wrong. I would like to remove the $timeout on lines 85 and 98. The $timeout on line 85 is a bit "hacky", I need to make sure it is executed ms after then() otherwise, it won't work and I don't know why.
I would like to know if it's considered a "good practice" to call a $scope method from a directive. In my code, I am calling $scope.init(value) from my directive.
Including jQuery for a position() is quite funny. Should I be using a services with a function that does what $.position() does?
I know those could be seperate questions but they are really related to my piece of code.
For those who do not want to click on the jsfiddle link, here is the code:
HTML:
<div id="fixed" scroll-watch="4" scroll-up="loadTop()" scroll-down="loadBottom()">
<ul>
<li data-id="{{i.id}}" ng-repeat="i in items" ng-class="calculateType(i.id)">{{i.id}}</li>
</ul>
</div>
JS:
function Main($scope, $timeout, $q) {
var cleanup = 5;
$scope.items = [];
//This is called from the scrollWatch directive. IMO, this shouldn't be a good idea
$scope.init = function(value) {
var deferred = $q.defer();
//This $timeout is used to simulate an Ajax call so I will keep it there
$timeout(function() {
$scope.items = [{id: +value}];
$scope.loadTop();
$scope.loadBottom();
deferred.resolve();
}, 200);
return deferred.promise;
};
//This is only used to simulate different content's heights
$scope.calculateType = function(type) {
return 'type-' + Math.abs(type) % 4;
};
$scope.loadBottom = function() {
var deferred = $q.defer(),
counter;
if ($scope.items.length > 1) {
$scope.items.splice(0, cleanup);
}
//This $timeout is used to simulate an Ajax call so I will keep it there
$timeout(function() {
counter = (($scope.items[$scope.items.length - 1]) || {id: 0}).id;
for (var i = 1; i < 6; i++) {
$scope.items.push({id: counter + i});
}
deferred.resolve();
}, 200);
return deferred.promise;
};
$scope.loadTop = function() {
var deferred = $q.defer(),
counter;
//Why can't I use this here?
//$scope.items.splice($scope.items.length-cleanup, $scope.items.length);
//This $timeout is used to simulate an Ajax call so I will keep it there
$timeout(function() {
counter = (($scope.items[0]) || {id: 0}).id;
for (var i = 1; i < 6; i++) {
$scope.items.unshift({id: counter - i});
}
deferred.resolve();
}, 200);
return deferred.promise;
};
//Why is this method needs to be delayed inside the directive? I would like to call it in loadTop()
$scope.removeBottom = function() {
$scope.items.splice($scope.items.length-cleanup, $scope.items.length);
};
}
angular.module('scroll', []).directive('scrollWatch', ['$timeout', function($timeout) {
var lastScrollTop = 0;
return function($scope, elm, attr) {
var raw = elm[0];
$scope.init(attr.scrollWatch).then(function() {
//Why do I need this? It looks like the resolve is called before the $digest cycle
$timeout(function() {
raw.scrollTop = $('li[data-id="' + attr.scrollWatch + '"]').position().top;
}, 300); //This value needs to be great enough so it is executed after the $scope.loadTop()'s resolve, for now, I know that I can set it to 300 but in real life app?
});
elm.bind('scroll', function() {
if (raw.scrollTop > lastScrollTop && raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
$scope.$apply(attr.scrollDown);
} else if (raw.scrollTop < lastScrollTop && raw.scrollTop === 0) {
var scrollHeight = raw.scrollHeight;
$scope.$apply(attr.scrollUp).then(function() {
//Why do I need this? It looks like the resolve is called before the $digest cycle
$timeout(function() {
raw.scrollTop = raw.scrollHeight - scrollHeight;
//I would like to move this in the $scope.loadTop()
$scope.removeBottom();
});
});
}
lastScrollTop = raw.scrollTop;
});
};
}]);
Thank you
http://www.youtube.com/watch?v=o84ryzNp36Q
Is a great video on Promises, how to write them and how they work.
https://github.com/stackfull/angular-virtual-scroll
Is a directive replacement for ng-repeat that doesn't load anything not on screen It does from what I can tell exactly what your looking for.
I would have put this as a comment but you need 50 cred or reputation or whatever they call it.