In my angularjs application, I need to get some data from remote, and during the time, I showed a "loading data ..." text on the page.
The page is basically like this:
<div ng-show="remoteData">
{{remoteData}}
</div>
<div id="loading" ng-hide="remoteData">
Loading data, wait ...
</div>
And in the angularjs code:
function SomeCtrl($scope, $http) {
$http.get("/...").success(function(data) {
$scope.remoteData = data;
});
}
But how can I test it with protractor?
Since we use mocking files provided in local server in e2e test, the $http.get(...) part will run really fast, and when I try to check the "loading" div, it always be hidden since the data has already loaded.
If there any way to test it?
In my application, I decided to implement the following scheme:
backend, when run in development mode, adds an additional middleware which is looking at the value of the cookie header
if the cookie header named __km_delay is present, it contains a number with the delay set in ms. This middleware will sleep for the given amount of time before handling control to the next middleware
At the same time, I use the following with Protractor:
browser.manage().addCookie('__km_delay', millis, '/')
And when I'm done with the test, I invoke:
browser.manage().deleteCookie('__km_delay')
This has the advantage of not having to modify the Angular app logic in any way for testing.
If you're wondering why I chose cookie and not another HTTP header, is because I found no way of setting an extra header with Protractor. And cookie is a header anyway ;)
BTW, I use similar thing to test 5xx server responses.
Related
I have some services that call for data while loading.
so my tests fail because there are unexpected calls that I need to specify in $httpBackend.
this causes a lot of duplicate code in my tests.
part of my attempts to reduce the duplicated code, I decided to add $rootScope.test flag, and if this flag is on those services do not load the data but still I need to duplicate $rootScope.test=true all over the tests.
Is there a way to do this properly in angular tests?
Here is some code
$httpBackend.expectGET('/backend/system/translations/en.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/backend/system/translations/he.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/backend/system/translations/ru.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/backend/system/translations/ar.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/translations/general.json').respond({'angularjs': 'cool'});
i18n = $filter('i18n');
Every directive I have with some translation support require these statements per language.
To handle every url in the same way with just one statement try sth like this:
$httpBackend.expectGET(function(url){
return true;
}).respond({'angularjs': 'cool'});
From documentation first argument of expectGET is:
HTTP url or function that receives the url and returns true if the url
match the current definition.
If you want to define response to just translation urls, try sth like this:
$httpBackend.expectGET(function(url){
return url.lastIndexOf("/backend/system/translations/", 0) === 0;
}).respond({'angularjs': 'cool'});
Also, are you sure that you need to use expectGET() instead of whenGET()? If you don't care whether given urls were called or not, how many times and in which order and you just want to define responses, then when... methods are the way to go:
See: "Request Expectations vs Backend Definitions" section of the mentioned doc page.
Backend definitions allow you to define a fake backend for your
application which doesn't assert if a particular request was made or
not, it just returns a trained response if a request is made. The test
will pass whether or not the request gets made during testing.
When trying to retrieve the history message in the on event , the loading time is too long.
The spinner show and hides too fast. But the message is not yet loaded.
How can we calculate or get the exact time to make the history load?
$scope.limit = 100
PubNub.ngHistory( {
channel : $scope.channel,
limit : $scope.limit
});
$rootScope.$on(PubNub.ngMsgEv($scope.channel), function(ngEvent, payload) {
**ActivityIndicator.showSpinner();**
$scope.$apply(function(){
$scope.messages.push(payload.message);
});
$(".messages-wrap").scrollTop($(".messages-wrap")[0].scrollHeight);
**ActivityIndicator.hideSpinner();**
});
Thank you so much for trying out the PubNub AngularJS API! I'll try
to provide some help. There is a little bit of a difference between
the PubNub JS API and the PubNub AngularJS API in this case.
Background
Behind the scenes, The PubNub JS API history() method returns instantly,
and invokes the callback when the given "page" of history is retrieved.
The AngularJS API, in its quest for simplifying this interaction, does not
take a callback - instead, it calls $rootScope.$broadcast() for each message
in the returned history payload.
In this version of the AngularJS API, it's not currently possible to "get inside"
the 'ngHistory' method to provide a callback. However, there are 2 solutions
available to you: one has always been there, and the second one I just added
based on your feedback.
Solutions
1) See codepen here (http://codepen.io/sunnygleason/pen/afqmh). There is an "escape hatch" in the PubNub AngularJS API that lets you call the JS API directly for advanced use cases, called jsapi. You can call PubNub.jsapi.history({channel:theChannel,limit:theLimit,callback:theCallback}). The only thing to keep in mind is that this will not fire message events into the $rootScope, and you will need to call $rootScope.$apply() or $scope.$apply() to make sure any changes you make to $scope within the callback function are propagated properly to the view.
2) See codepen here (http://codepen.io/sunnygleason/pen/JIsek). If you prefer a promise-based approach, I just added an ngHistoryQ() function to version 1.2.0-beta.4 of the PubNub AngularJS API. This will let you write code like:
PubNub.ngHistoryQ({channel:theChannel,limit:theLimit}).then(function(payload) {
payload[0].forEach(function(message) {
$scope.messages.push(message);
}
});
You can install the latest version of the AngularJS SDK using 'bower install pubnub-angular'.
With either of these solutions, you should be able to display and hide your spinner
accordingly. The only difference is in #2, you'll want to write code like this:
var historyPromise = PubNub.ngHistoryQ({channel:theChannel,limit:theLimit});
showSpinner();
historyPromise.then(function(payload) {
// process messages from payload[0] array
hideSpinner();
});
Does this help? Let me know what you think. Thank you again so much for trying this out.
Can you programmatically turn the spinner on at history call time, and programmatically disable it at callback time?
I wrote an AngularJS directive for vimeo videos with built in play/pause functionality using their froogaloop library.
It's works great! The only issue is that I get the following error when the page first loads.
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://player.vimeo.com') does not match the recipient
window's origin
Am I initializing the froogaloop object wrong in the directive?
Any suggestions would be most appreciated.
You can check it out the plunker here: http://plnkr.co/edit/GKWNk3LhX0MR3lhpfqyA
I recommend to execute the code in the onLoad event from <iframe>. Then you are ensured that the code will execute when iframe is ready for receiving messages.
There are plenty ways to do it:
You can use jQuery if you already have it in your project: $('iframe').load(callback) or
write an EventListener: iframe_element.addEventListener('load', callback) or
use plain onload callback: iframe_element.onload = callback.
Where callback is the method which uses Froogaloop.
But you have to know that some of those solutions might have some drawbacks on some old/MS browsers browsers.
For me it looks like angularjs triggers Player API before actually rendering the iframe on the page. At least if I postpone scope.$watch it works fine:
$timeout(function() {
scope.$watch('controlBoolean', function() {/* your code goes here */});
});
My problem
I am using Angular to create a Phonegap application. Most of my pages are fairly small and the transition/responsiveness is quick and smooth. However I have one page that is fairly large that I am having an issue with.
The method for changing to this page is straightforward:
<button ng-click="$location.url('/page2')"></button>
When you "tap" the button above it takes about 1-2s to respond and change pages. I have double checked all areas for improvement on this page and determined that the delay is caused by Angular compiling and parsing the DOM of this page prior to changing the page. Please note that I am testing this on a real device so it is not due to emulator speeds.
The question
Is there a way to automatically or manually intercept page changes and put them in a sort of "loading" page so the response to the button click is immediate and page change is visible but the page content loads in a second or 2 later onto this "loading" page.
Its only an issue cause it is very awkward to click something and have nothing happen. I am having a very hard time finding any resources on this matter so if someone can even point me in the right direction to look I would be grateful.
Edit:
A super hacky solution I found was to use an ng-include on wrapper page and delay the include for a little bit.
myBigPageWrapper.html:
<div ng-include="page"></div>
Controller:
$scope.page = '';
setTimeout(function() { $scope.page='/pages/myBigPage.html'; $scope.$apply(); }, 1000);
Then navigate to your wrapper page instead: $location.url('/myBigPageWrapper')
This is obviously not ideal... But I hope this helps clarify what I am attempting to do.
Page2.html
This is the section that causes the page to slow down, commenting this out makes the page load very quickly. There are 13 pages in the "auditPages" array each containing about 50 lines of html mostly containing form input elements. Quite a bit of logic however it runs great once it is loaded. I am not going to include all the pages as it would be overload.
<div class="page-contents">
<form name="auditPageForm">
<div ng-repeat="(pageKey, pageData) in auditPages " ng-show="currentAuditPage.name==pageData.name">
<audit-form page="pageData">
<ng-include src=" 'partials/audit/auditSections/'+pageData.name+'.html'" onload="isFormValid(pageKey)"></ng-include>
</audit-form>
</div>
</form>
</div>
To sum up my comments above:
Your question was:
Is there a way to automatically or manually intercept page changes and
put them in a sort of "loading" page?
A lot of people asks for this question since Angular doesn't seem to provide a nice handling of a loading transition.
Indeed, the possible nicest solution would have been to "play" with the resolve property of angular's module configuration.
As we know, resolve allows to run some logic before the targeted page is rendered, dealing with a promise. The ideal would be to be able to put a loading page on this targeted page, while the resolve code is running.
So some people have nice ideas like this one:
Nice way to handle loading icon while route is changing
He uses $routeChangeStart event, so the loading icon would happen on the SOURCE page.
I use it and it works well.
Also, there is another way: make use of $http interceptor (like #oori answer above), to have a common code allowing to put a loading icon but...I imagine you don't want the same icon on every kind of http request the page does, it's up to you.
Maybe in the future, a solution would come directly associated to the resolve property.
Angular has $httpProvider.responseInterceptors
// Original by zdam: http://jsfiddle.net/zdam/dBR2r/
angular.module('LoadingService', [])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
angular.element(document.getElementById('waiting')).css('display','block');
return data;
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
}])
// register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', ['$q','$window', function ($q, $window) {
return function (promise) {
return promise.then(function (response) {
angular.element(document.getElementById('waiting')).css('display','none');
return response;
}, function (response) {
angular.element(document.getElementById('waiting')).css('display','none');
return $q.reject(response);
});
};
}])
I want to intercept console log message from AngularJS and display them in a div on the page. I need this in order to debug ajax traffic in a PhoneGap app.
This is an example of the kind of errors I want to capture:
I tried this Showing console errors and alerts in a div inside the page but that does not intercept Angular error messages.
I also tried the solution gameover suggested in the answers. No luck with that either. Apparently $http is handling error logging differently.
I guess the answer you tried has the right idea but you're overriding the wrong methods. Reading here I can see angularJs uses $log instead of console.log, so to intercept you can try to override those.
Something like this:
$scope.$log = {
error: function(msg){document.getElementById("logger").innerHTML(msg)},
info: function(msg){document.getElementById("logger").innerHTML(msg)},
log: function(msg){document.getElementById("logger").innerHTML(msg)},
warn: function(msg){document.getElementById("logger").innerHTML(msg)}
}
Make sure to run that after importing angular.js.
EDIT
Second guess, override the consoleLog method on the LogProvider inner class on angular.js file:
function consoleLog(type) {
var output ="";
//arguments array, you'll need to change this accordingly if you want to
//log arrays, objects etc
forEach(arguments, function(arg) {
output+= arg +" ";
});
document.getElementById("logger").innerHTML(output);
}
I've used log4javascript for this purpose. I create the log object via
var log = log4javascript.getLogger('myApp')
log.addAppender(new log4javascript.InPageAppender());
I then use this in a value dependency, and hook into it where needed (e.g. http interceptor).
A more lightweight approach might be to use $rootScope.emit and then have a component on your main page which prepends these log messages to a visible div, but this will require you to change all your calls to console.log (or redefine the function in your js).
I think that this message is not even displayed from AngularJS. It looks like an exception which has not been caught in any JavaScript (angular.js just appears on top of your stack because that's the actual location where the HTTP request is being sent).
Take a look at ng.$exceptionHandler. That should be the code you seem to be interested in. If not, take a quick web search for „JavaScript onerror“ which should tell you how to watch for these kinds of errors.
I would rather user an $http interceptor.
Inside the responseError function, you can set a property on a service that will be exposed to the div's scope.