I'm quite new to angular, now I was able to show an alert message when someone requests a new password, from our app:
Usermodel:
.service('userModel', ['$q', '$http', 'authorizedTracker', function($q, $http, authorizedTracker) {
this.passwordreset = function(token) {
var deferred = $q.defer();
$http({
method: 'GET',
url: '/reset/'+token
})
.then(function(response) {
if (!_(response.data).has('user')) {
deferred.reject('unkown user id');
}
model.resetted = true;
}, function(error) {
deferred.reject(error.data.message);
});
authorizedTracker.addPromise( deferred.promise)
return deferred.promise;
};
So if resetted is true, the message will show up, see below:
html:
<!-- Succes-->
<div class="alert alert-success animated fadeInDown" ng-cloak ng-show="userModel.resetted">
<strong><i class="icon-attention"></i>Success!</strong> New password is sent to your e-mail
</div>
But now I want to hide the alert after x amount of seconds, or if the user clicks to another page. How is that possible? Any solution?
you should use the $timeout service (symilar to the window.setTimeout in native javascript).
In your code you should add the following code
...
model.resetted = true;
$timeout(function() {
model.resetted = false;
}, xxx)//replace xx by the amount of milisecond you want
here is an example, hope it will help you
http://plnkr.co/edit/Qt39x5Xo5JhP61QHYLwO?p=preview
To close after X seconds use the $timeout service.
model.resetted = true;
$timeout(function() {
model.resetted = false;
}, X); // X in milliseconds
For angular2+ users:
You should add timeout to the method witch is showing alert:
setTimeout(() => this.hideAlert(), X);
where hideAlert makes a logic needed to hide an alert and X is number of miliseconds
the idea behind this is to start counting X miliseconds until this.hideAlert is called
arrow function is needed to keep original context (be able to use this as we are used to - pointing to parent class for example)
Related
My workmate is working with Angular Material and he's using the mdToast this way: (in the else statements)
$scope.login = () => {
$scope.loading = true;
authService.login($scope.username, $scope.password, (result) => {
if (result) {
if (result === true) {
$location.path('/');
} else {
$mdToast.show($mdToast.simple().textContent($filter('capitalize')($filter('translate')('passwordNotValid'))).position('top right').hideDelay(3000));
$scope.loading = false;
$("#input_username_login").focus();
}
} else {
//error callback from server
$mdToast.show($mdToast.simple().textContent($filter('capitalize')($filter('translate')('passwordNotValid'))).position('top right').hideDelay(3000));
$scope.loading = false;
$("#input_username_login").focus();
}
});
};
I need to test the text of the resulted toast but it seems like Angular Material's mdToast uses ng-if. My workmate hasn't any HTML code for the toast (he's just using the controller above to generate it) even so when the toast is triggered the next code appears for a few seconds in the DOM:
screenshot of the generated md-toast
I've tried, among other things, slowing down the toast's disappearance with browser.wait, waitForAngular and so on, but didn't work. I'm stuck with:
it("should show error toast in English when user fails login", function(){
login.email_address_field.sendKeys('random_user#correo.com');
login.password_field.sendKeys('hardest_pass_123');
login.login_button.click();
expect(element(by.tagName('md-toast')).element(by.tagName('span')).getText()).toBe('Incorrect username and/or password');
});
I found a solution using this answer as an example. My spec is:
it("should show error toast in English when user fails login", function(){
login.email_address_field.sendKeys('random_user#correo.com');
login.password_field.sendKeys('hardest_pass_123');
login.login_button.click();
browser.ignoreSynchronization = true;
browser.sleep(1000);
expect(element(by.tagName('md-toast')).element(by.tagName('span')).getText()).toBe('Incorrect username and/or password');
browser.ignoreSynchronization = false;
});
You can use ExpectedConditions to make your script wait till the toaster message is displayed.Once the toaster is displayed then you can validate the message as well.
var EC = protractor.ExpectedConditions;
browser.wait(EC.visibilityOf(element(by.tagName('md-toast'))),5000);
expect(element(by.tagName('md-toast')).element(by.tagName('span')).getText()).toEqual('Incorrect username and/or password');
This spec worked for me and it does not use sleeps. The important thing to note here is that the browser.ignoreSynchronization flag must be set while the browser is waiting. Due to the asynchronous nature of the browser.wait, changing ignoreSynchronization must be done after the browser.wait promise resolves or else it could have no effect.
// a close button that appears on the md-toast template
const closeToastButton = $('[data-automation="toast-close"]')
const cond = protractor.ExpectedConditions
function waitToastShow() {
return browser.wait(cond.elementToBeClickable(closeToastButton), 5000)
}
function waitToastHide() {
return browser.wait(cond.invisibilityOf(closeToastButton), 5000)
}
screenshot = name => browser.takeScreenshot().then(/* save fn */)
describe('a suite ... ', () => {
it('takes screenshots of an md-toast once shown and after hidden', function () {
// ... actions that launch an md-toast using $mdToast.show({ ... })
browser.ignoreSynchronization = true
waitToastShow().then(() => {
screenshot('toast-showing.png')
waitToastHide().then(() => {
screenshot('toast-hidden.png')
browser.ignoreSynchronization = false;
})
})
});
}
I want to show a loading animation (ideally that shows % of how much is loaded) whilst content loads from my $http get.
I have made an attempt, but it does not seem to hide the content I am trying to hide.
I set a time length- but I do not want it to show the loading overlay for a set time. I want it to show the loading overlay (possibly until a minimum of 3 images are loaded?) until the element is loaded.
Here is my attempt in a plunker:
http://plnkr.co/edit/7ScnGyy2eAmGwcJ7XZ2Z?p=preview
.factory('cardsApi', ['$http', '$ionicLoading', '$timeout', function ($http, $ionicLoading, $timeout) {
var apiUrl = 'http://mypage.com/1/';
$ionicLoading.show({
duration: 3000,
noBackdrop: true,
template: '<p class="item-icon-left">Loading stuff...<ion-spinner icon="lines"/></p>'
});
var getApiData = function () {
return $http.get(apiUrl).then($ionicLoading.hide, $ionicLoading.hide);
};
return {
getApiData: getApiData,
};
}])
.controller('CardsCtrl', ['$scope', 'TDCardDelegate', 'cardsApi', '$http',
function ($scope, TDCardDelegate, cardsApi, $http) {
$scope.cards = [];
cardsApi.getApiData()
.then(function (result) {
console.log(result.data) //Shows log of API incoming
$scope.cards = result.data;
$scope.product_id = result.data.product_id;
})
.catch(function (err) {
//$log.error(err);
})
Remove the duration line from your $ionicLoading.show declaration.
duration: 3000,
So that it looks like:
$ionicLoading.show({
noBackdrop: true,
template: '<p class="item-icon-left">Loading stuff...<ion-spinner icon="lines"/></p>'
});
And that should work (at least it does in the plunker). The duration property specifies when to close the ionicLoading instance and does not wait for ionicLoading.hide().
You want to wait until the image is actually loaded and rendered, but you are hiding the loading messages as soon as the API call returns. From your code it looks as though the API returns the image URL, not the image data itself?
In which case you could do it using the element.onload(), however the problem with this is that it's no longer a generic API which works for loading anything but I'll let you decide whether that's OK for your use case.
var imagesLoaded = 0;
var loadImage = function(result) {
var image = new Image();
image.onload = function () {
imagesLoaded++;
if (imagesLoaded >= 3)
$ionicLoading.hide();
};
// We still want to hide the loading message if the image fails to load - 404, 401 or network timeout etc
image.onerror = function () {
imagesLoaded++;
if (imagesLoaded >= 3)
$ionicLoading.hide();
};
image.src = result.image_url;
};
// We still want to hide the loading message if the API call fails - 404, 401 or network timeout etc
var handleError = function() {
imagesLoaded++;
if (imagesLoaded >= 3)
$ionicLoading.hide();
};
var getApiData = function () {
return $http.get(apiUrl).then(loadImage, handleError);
};
I've read the docs but it's 3am and I'm at the end of me teather. Here's my controller:
controller('makeDashCtrl', function ($scope, $rootScope, $cookies, $location, $http, $interval) {
var userId = $cookies.get('user_id');
var orgId = $cookies.get('org_id');
$http.get('/api/consolidateProfile/').then(function(data){
console.log(data);
}, function(res){
console.log(res + " - Eh, consolidateProfile probably timed out, no biggie")
})
var testStart = $interval(function(){
$http.get('/api/testProgress/' + userId).then(function(obj){
$scope.message = obj.data.message;
$scope.sub = obj.data.sub;
if(obj.data.nextPhase){
console.log("Should be cancelling...");
nextScrape();
$interval.cancel(testStart); // This one cancels fine
}
}, function(err){
$interval.cancel(testStart);
});
}, 1000);
function nextScrape(){
console.log("In checkScraperadsfsdfads!")
var checkScraper = $interval(function(){
console.log("In checkScraper REAL!")
$http.get('/api/checkScraper/' + userId).then(function(obj){
var msg = JSON.parse(obj.data);
console.log("Got some data!")
console.log(obj);
if(msg.doneFbs){
$scope.fbMsg = "We've finished gathering " + msg.doneFbs + " Facebook accounts";
}
if(msg.doneTwits){
$scope.twitMsg = "We've finished gathering " + msg.doneTwits + " Twitter accounts";
}
$scope.message = msg.message;
$scope.sub = msg.sub;
if(msg.finished){
$interval.cancel(checkScraper); // This does NOT cancel
console.log("Scraping Done!")
$location.path('/dashboard') // This successfully redirects to the next page
}
},function(err){
console.log("There was an error in Checkscraper ")
console.log(err)
$interval.cancel(checkScraper); // This does NOT cancel when there's an error
});
}, 3000)
}
})
See comments in the code above. Perhaps it's an issue within the nextScrape() function, but I cannot get that $interval to cancel. Even when the $location.path() changes to the next page, the interval is still running.
What am I doing wrong here?
You might have multiple ajax requests at a time if your call takes more than a second to get back. Instead of using $interval, why don't you make one call and use $tiemout to schedule a second call if you need to when the first call is done?
function checkProgress() {
$http.get('/api/testProgress/' + userId).then(function(obj){
$scope.message = obj.data.message;
$scope.sub = obj.data.sub;
if(obj.data.nextPhase){
// move on to next phase
nextScrape();
} else {
// not done, schedule another check
$timeout(checkProgress, 1000);
}
}, function(err){
// error, you cancel, but maybe schedule another progress check?
});
}
I suspect the code is making multiple call before receiving response from the Ajax. That means your ajax is taking more than one second to respond. This is happening because you have mentioned very less amount of time to pool the data from server again.
To deactivate the $interval after page redirection you could take a use of $destroy event on scope. Events needs to be clear manually, because they won't get vanished until we detach them. You could stop interval while leaving the page. Destroy the event on scope event
Code
$scope.$on('$destroy', function(){
$interval.cancel(testStart);
})
Requirement : To Show and Hide a div.
HTML
<div ng-show="IsSuccess">
My Div Content
</div>
HTML after page load
<div class="ng-hide" ng-show="false">
HTML after updated from controller (http post call)
<div class="ng-hide" ng-show="true">
ng-show is true but still class contains ng-hide
How to resolve this issue ?
For reference, below is my controller
myController.controller('AuthenticationController',
function AuthenticationController($scope, $location, authDataService, loginDuration) {
$scope.Login = {};
$scope.IsSuccess= false;
$scope.login = function () {
authDataService.authenticateUser($scope.Login, $scope).then(
function (status) {
if (status === 200) {
if ($scope.message == 'Login failed') {
$scope.IsSuccess= true;
}
else {
$scope.IsSuccess= false;
}
}
},
function (data) {
$scope.ErrorMessage = data.Message;
}
);
}
});
Because authDataService.authenticateUser is returning a promise that looks like it's outside of the angular context, angular doesn't know when the scope changes. In that situation, you need to add $scope.$apply()
if ($scope.message == 'Login failed') {
scope.IsSuccess= true;
}
else {
$scope.IsSuccess= false;
}
$scope.$apply();
** Edit: Extended Explanation **
Because you asked for more details about this, I'll try to explain a little further.
$scope.$apply() needs to be called when outside of the angular context. Here's what I mean by outside of the angular context:
$scope.login = function() {
// inside angular context
console.log('a');
setTimeout(function() {
// outside angular context
console.log('b');
$scope.hello = 'b';
// $scope.$apply() needs to be called
$scope.$apply();
}, 1000);
// inside angular context
console.log('c');
$scope.hello = 'c';
};
In this example, here's the output to the log:
a
c
// $scope.$apply() is assumed at this point
b
Angular knows it needs to adjust its bindings after the last line of $scope.login() is processed, and so $scope.$apply() is assumed then, but Angular doesn't know if you have any other callback functions that might be called later on through another context, another context being setTimeout or jQuery's $.ajax or $.Deferred, etc. If that different context modifies the $scope, then you need to call $scope.$apply() to manually update the Angular bindings.
If I am understanding your question correctly, I would change your HTML to show
<div ng-hide="IsSuccess">
My Div Content
</div>
and then in your angular file
$scope.login = function () {
if(<!-- logic to hide or show-->){
$scope.IsSuccess = false;
}else{
$scope.IsSuccess = true;
}
}
Hopefully this helps.
HTML code:
<button class="show-more-btn ng-hide" ng-show="hasMoreItemsToShow()" ng-click="showMoreItems()"">Show More</button>
Javascript code:
setTimeout(function(){ $('.show-more-btn').removeClass('ng-hide');
}, 3000);
I am (sometimes) getting a weird $apply already in progress error when opening a confirm dialog box in the following and innocent looking situation :
var mod = angular.module('app', []);
mod.controller('ctrl', function($scope, $interval, $http) {
$scope.started = false;
$scope.counter = 0;
// some business function that is called repeatedly
// (here: a simple counter increase)
$interval(function() {
$scope.counter++;
}, 1000);
// this function starts some service on the backend
$scope.start = function() {
if(confirm('Are you sure ?')) {
return $http.post('start.do').then(function (res) {
$scope.started = true;
return res.data;
});
};
};
// this function stops some service on the backend
$scope.stop = function() {
if(confirm('Are you sure ?')) {
return $http.post('stop.do').then(function (res) {
$scope.started = false;
return res.data;
});
};
};
});
// mock of the $http to cope with snipset sandbox (irrelevant, please ignore)
mod.factory('$http', function ($q) {
return {
post: function() {
return $q.when({data:null});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
<button ng-disabled="started" ng-click="start()">Start</button>
<button ng-disabled="!started" ng-click="stop()">Stop</button>
<br/><br/>seconds elapsed : {{counter}}
</div>
</div>
The error message is :
$rootScope:inprog] $apply already in progress http://errors.angularjs.org/1.2.23/$rootScope/inprog?p0=%24apply
And the callstack is :
minErr/<#angular.js:78:12
beginPhase#angular.js:12981:1
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12770:11
tick#angular.js:9040:25
$scope.start#controller.js:153:8
Parser.prototype.functionCall/<#angular.js:10836:15
ngEventHandler/</<#angular.js:19094:17
$RootScopeProvider/this.$get</Scope.prototype.$eval#angular.js:12673:16
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12771:18
ngEventHandler/<#angular.js:19093:15
jQuery.event.dispatch#lib/jquery/jquery-1.11.2.js:4664:15
jQuery.event.add/elemData.handle#lib/jquery/jquery-1.11.2.js:4333:6
To reproduce :
use Firefox (I could not reproduce it with Chrome or IE)
open the javascript console
click alternatively the start and stop buttons (and confirm the dialogs)
try multiple times (10-20x), it does not occur easily
The problem goes away if I remove the confirm dialog box.
I have read AngularJS documentation about this error (as well as other stackoverflow questions), but I do not see how this situation applies as I do not call $apply nor do I interact directly with the DOM.
After some analysis, it seems to be a surprising interaction between the $interval and modal dialog in Firefox.
What is the problem ?
The callstack shows something strange : an AngularJS function tick is called within the controller's start function. How is that possible ?
Well, it seems that Firefox does not suspend the timeout/interval functions when displaying a modal dialog box : this allows configured timeout and intervals callback to be called on the top of the currently executing javascript code.
In the above situation, the start function is called with an $apply sequence (initiated by AngularJS when the button was clicked) and when the $interval callback is executed on the top the start function, a second $apply sequence is initiated (by AngularJS) => boom, an $apply already in progress error is raised.
A possible solution
Define a new confirm service (adapted from this and that blog posts) :
// This factory defines an asynchronous wrapper to the native confirm() method. It returns a
// promise that will be "resolved" if the user agrees to the confirmation; or
// will be "rejected" if the user cancels the confirmation.
mod.factory("confirm", function ($window, $q, $timeout) {
// Define promise-based confirm() method.
function confirm(message) {
var defer = $q.defer();
$timeout(function () {
if ($window.confirm(message)) {
defer.resolve(true);
}
else {
defer.reject(false);
}
}, 0, false);
return defer.promise;
}
return confirm;
});
... and use it the following way :
// this function starts some service on the backend
$scope.start = function() {
confirm('Are you sure ?').then(function () {
$http.post('start.do').then(function (res) {
$scope.started = true;
});
});
};
// this function stops some service on the backend
$scope.stop = function() {
confirm('Are you sure ?').then(function () {
$http.post('stop.do').then(function (res) {
$scope.started = false;
});
});
};
This solution works because the modal dialog box is opened within an interval's callback execution, and (I believe) interval/timeout execution are serialized by the javascript VM.
I was having the same problem in Firefox. Using window.confirm rather than just confirm fixed it for me.