I'm having som issues with a button in my Ionic app. My button have a click event "loginEvent()" that performs a login for the user. I have two span tags that is supposed to display a loading symbol while the login is performed.
The problem is that the button is not updated with the loading symbol until the loginEvent() returns. The click animation on the button does also not run until the loginEvent() returns.
When the button is clicked both "login" and "/login" is printed before the button updates to a spinner, giving the impression that the GUI freezes. The desired behaviour is that the login-button get the spinner while the login call is being performed.
Any idea why my button behaves like this?
Login button
<button ion-button block (click)="loginEvent();">
<span *ngIf="!isLoading">Logga in</span>
<span *ngIf="isLoading"><ion-spinner></ion-spinner></span>
</button>
loginEvent()
loginEvent() {
console.log("login");
this.isLoading = true;
this.userHandler.login(this.username, this.password, this); //Performa authentication to Amazon Cognito and returns the result via callback function
console.log("/login");
}
EDIT
Adding the code for my UserHandler and AuthenticationService.
UserHandler.ts
#Injectable()
export class UserHandler {
constructor(public authenticationService: AuthenticationService) {
this.observers = new Array<Observer>();
}
public login(username: string, password: string, callback: any) {
this.authenticationService.performLogin(username, password, callback);
}
}
AuthenticationService.ts
public performLogin(username: string, password: string, callback: CognitoCallback) {
username = username .toLowerCase();
var authenticationData = {
Username: username,
Password: password,
};
var authenticationDetails = new AWSCognito.AuthenticationDetails(authenticationData);
var userData = {
Username: username,
Pool: this.getUserPool()
};
let cognitoUser = new AWSCognito.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result: any) {
var loginKey = 'cognito-idp.' + environment.region + '.amazonaws.com/' + environment.userPoolId;
var loginProvider = {};
loginProvider[loginKey] = result.getIdToken().getJwtToken();
var credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: environment.identityPoolId,
Logins: loginProvider
});
credentials.refresh((error: any) => {
if (error) {
console.error(error);
}
});
credentials.get(function (err: any) {
if (!err) {
callback.cognitoCallback(null, result, "FAILED");
} else {
callback.cognitoCallback(err.message, null, "SUCCESS");
}
});
},
onFailure: function (err: any) {
callback.cognitoCallback(err.message, null, "FAILURE");
},
newPasswordRequired: function (userAttributes: any, requiredAttributes: any) {
callback.cognitoCallback("Please set a new password", null, "newPasswordRequired");
}
});
}
I guess 'userHandler.login' is used to call the API for user authentication. As the HTTP requests are asynchronous it returns promise immediately you call them.
Instead of the spinner, you can use ionic loading component, where you can show the loader until the API returns the response.
Please share your userHandler code.
Related
I build a simple page and it renders a list of some items. These items have a href element to open it in browser. The trouble is that when user comes back all the class used to hold local variable gets reinitialized and breaks the app functionality. What is the correct way to handle this. I want to retain the data set. Below is my code for list and the variables in a service class
<ion-content>
<div class="full-screen-bg">
<div *ngFor="let alb of core.albums">
<ion-row><ion-col no-padding text-center><img src="{{alb.image}}"></ion-col></ion-row>
<ion-row><ion-col class="sg-title-text">{{alb.name}}</ion-col></ion-row>
</div>
the service class that has variables and gets reset is
#Injectable({
providedIn: 'root'
})
export class CoreService {
loggedIn:boolean
name:string
constructor(public admob: Admob,
public sgSvc:DataServiceService) {
this.loggedIn = false
console.log("album constructor called:::" + this.loggedIn + " name:" + this.name)
}
}
The simple way is save login user (username, token and expiration time) by StorageService, and for your CoreService should be try get it from StorageService and confirm it, if token is none or already over exporation time then it is mean user should be login again.
export class CoreService {
//...
public isAdmin = false;
private token = false;
public userSubject: Subject<Object> = new Subject<Object>();
//...
constructor(/*...*/) {
this.userSubject.subscribe(user => {
if (user && user['role']) {
if (user['role'] == 'admin') {
this.isAdmin = true;
} else {
this.isAdmin = false;
}
}
});
this.getLocal('user').then(val => {
this.userSubject.next(val);
});
}
//...
}
//And for other component also subscribe same subject like
export class AppComponent /*...*/ {
constructor(coreService: CoreService /*...*/) {
this.coreService.userSubject.subscribe(user => {
// your logic there
});
}
}
When the user login I want to display the username of that user at the navbar. I have set the token and username to the localStorage after user succesfully login. My issue is username is not displayed at the navbar unless I refresh the page.
I am not sure how can I fix this problem.
Can anybody help me
Thank You.
login component
onSubmit = function () {
this.userService.loginUser(this.loginUserData).subscribe(
res => {
this.tokenService.handle(res);
this.authService.changeAuthStatus(true);
},
error => console.log(error)
);
}
auth service
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(this._tokenService.loggedIn());
authStatus = this.loggedIn.asObservable();
user = this.tokenService.getUser();
changeAuthStatus(value: boolean) {
this.loggedIn.next(value);
}
constructor(private tokenService: TokenService) {}
}
token service
handle(res) {
this.setToken(res);
}
setToken(res) {
localStorage.setItem('token', res.access_token);
localStorage.setItem('user', res.user);
}
getToken() {
return localStorage.getItem('token');
}
getUser() {
return localStorage.getItem('user');
}
}
navbar component
ngOnInit() {
this.authService.authStatus
.subscribe(
value => {
this.loggedIn = value
}
);
//set the username on navbar
this.user = this.tokenService.getUser();
}
You auth service function is a callback that will fire success or failure event when all operations are complete hence the code this.user = this.tokenService.getUser(); executed before the localstorage is populated. Try moving this code inside subscribe method of authService.authStatus.
ngOnInit() {
this.authService.authStatus
.subscribe(
value => {
this.loggedIn = value
}
);
//set the username on navbar
this.user = this.tokenService.getUser();
}
like this.
ngOnInit() {
this.authService.authStatus
.subscribe(
value => {
this.loggedIn = value
this.user = this.tokenService.getUser();
}
);
}
Try making the call
this.user = this.tokenService.getIser()
inside the subscribe.
I am making a ionic 3 app. I want notifications to appear even when app is in foreground. I have tried using FCM Plugin I'm getting notifications only when app is in background.
Home.ts
import { AngularFireDatabase } from 'angularfire2/database';
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import firebase from 'firebase';
declare var FCMPlugin;
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
firestore = firebase.database().ref('/pushtokens');
firemsg = firebase.database().ref('/messages');
constructor(public navCtrl: NavController,public afd:AngularFireDatabase) {
this.tokensetup().then((token)=>{
this.storeToken(token);
})
}
ionViewDidLoad() {
FCMPlugin.onNotification(function (data) {
if (data.wasTapped) {
//Notification was received on device tray and tapped by the user.
alert(JSON.stringify(data));
} else {
//Notification was received in foreground. Maybe the user needs to be notified.
alert(JSON.stringify(data));
}
});
FCMPlugin.onTokenRefresh(function (token) {
alert(token);
});
}
tokensetup(){
var promise = new Promise((resolve,reject)=>{
FCMPlugin.getToken(function(token){
resolve(token);
},(err)=>{
reject(err);
});
})
return promise;
}
storeToken(token){
this.afd.list(this.firestore).push({
uid: firebase.auth().currentUser.uid,
devtoken: token
}).then(()=>{
alert('Token stored')
}).catch(()=>{
alert('Token not stored');
})
// this.afd.list(this.firemsg).push({
// sendername:'adirzoari',
// message: 'hello for checking'
// }).then(()=>{
// alert('Message stored');
// }).catch(()=>{
// alert('message not stored');
// })
}
}
the function cloud for notifications
var functions = require('firebase-functions');
var admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var wrotedata;
exports.Pushtrigger = functions.database.ref('/messages/{messageId}').onWrite((event) => {
wrotedata = event.data.val();
admin.database().ref('/pushtokens').orderByChild('uid').once('value').then((alltokens) => {
var rawtokens = alltokens.val();
var tokens = [];
processtokens(rawtokens).then((processedtokens) => {
for (var token of processedtokens) {
tokens.push(token.devtoken);
}
var payload = {
"notification":{
"title":"From" + wrotedata.sendername,
"body":"Msg" + wrotedata.message,
"sound":"default",
},
"data":{
"sendername":wrotedata.sendername,
"message":wrotedata.message
}
}
return admin.messaging().sendToDevice(tokens, payload).then((response) => {
console.log('Pushed notifications');
}).catch((err) => {
console.log(err);
})
})
})
})
function processtokens(rawtokens) {
var promise = new Promise((resolve, reject) => {
var processedtokens = []
for (var token in rawtokens) {
processedtokens.push(rawtokens[token]);
}
resolve(processedtokens);
})
return promise;
}
it works only when the app in the background. but when i exit from the app and it's not in the background I don't get any notification.
You need to edit the FCM Plugin files. I found the solution only for android now.
I use https://github.com/fechanique/cordova-plugin-fcm this FCM plugin for android and ios in cordova.
You need to edit file MyFirebaseMessagingService.java line 53(line no be may be differ).
In this file there is a method onMessageReceived at the end of the method there is a line which is commented, this line calling an another method i.e. sendNotification(....).
sendNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody(), data);
You have to uncomment this line and change last parameter from remoteMessage.getData() to data (data variable is already there in the code).
And comment this line FCMPlugin.sendPushPayload( data );
Now you are good to go. Now you are able to receive notification even when app is opened (foreground), you will receive the banner (floating) notifications.
If you found anything for IOS please let me know!!!
I am using firebase plugin for ionic 3.
There is a check if notification data contain "notification_foreground" or not and save it in variable foregroundNotification.
if(data.containsKey("notification_foreground")){
foregroundNotification = true;
}
then it create showNotification variable which decide if we need to show notification or not and pass this to the sendMessage (show notification function).
if (!TextUtils.isEmpty(body) || !TextUtils.isEmpty(title) || (data != null && !data.isEmpty())) {
boolean showNotification = (FirebasePlugin.inBackground() || !FirebasePlugin.hasNotificationsCallback() || foregroundNotification) && (!TextUtils.isEmpty(body) || !TextUtils.isEmpty(title));
sendMessage(data, messageType, id, title, body, showNotification, sound, vibrate, light, color, icon, channelId, priority, visibility);
}
your payload should contain notification_foreground, notification_title and notification_body.
I'm programatically validating an email and password inputs for simple login, here is the function that call other function that validate the email.
handleLogin(event) {
this.validateEmail();
this.validatePassword();
if (this.state.emailValid === 'error' || this.state.passwordValid === 'error') {
alert('invalid form');
return;
};
const email = ReactDOM.findDOMNode(this.refs.email).value;
const password = ReactDOM.findDOMNode(this.refs.password).value;
const creds = { email: email, password: password }
this.props.onLoginClick(creds)
}
Notice that first than all I'm calling the validateEmail() function which modifies the store that indicates if the input is correct, here's the validateEmail() source code:
validateEmail() {
const email = ReactDOM.findDOMNode(this.refs.email).value;
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
this.setState({
emailValid: 'error'
});
return;
}
this.setState({
emailValid: 'success'
});
}
But in the if statement the state.emailValid has not been yet updated, this is a delay in the state modifying, so the alert() is not displayed. How to get the updated state correctly?
Thanks
The thing to note here is that setState is asynchronous. It will not update the state until everything else that is synchronous in your handleLogin method has completed.
With React I like to use state as a single source of truth as often as I can. In the example above you have the html element as a source of truth and state. By changing your components to be controlled by the react state, you can validate your forms on each keystroke.
Forms and Controlled Components
Start by keeping the state of your input in state
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
emailValid: true,
};
// we bind the function in case we want to
// control text in child component
this.emailChange = this.handleEmailChange.bind(this);
}
emailChange(event) {
this.setState({email: event.target.value});
}
render() {
<textarea value={this.state.email} onChange={this.emailChange} />
}
}
Now whenever you type the state of your html input is handled in react. This will enable you to more easily check its validity. We can do this by adding another method to our class:
class LoginForm extends React.Component {
// ...all the stuff from above
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
// Object.assign just ensures immutability
this.setState(Object.assign({}, this.state, {
emailValid: false
}))
} else {
// If using babel, this is ensure immutable also
this.setState({
...state,
emailValid: true
})
}
}
// or....
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
this.setState({...state, emailValid: validEmail})
}
// ...render method
}
The validation will now occur on each keystroke. When you need to submit your form all you need to do is check the state if the data is valid and dont need to reference the dom. You can send the data from state.
I am trying to implement auth0 in my Vue.js 2 application.
I followed this link to implement the auth0 lock:
https://github.com/auth0-samples/auth0-vue-samples/tree/master/01-Login
This is my application in Login.vue:
HTML:
<div v-show="authenticated">
<button #click="logout()">Logout</button>
</div>
<div v-show="!authenticated">
<button #click="login()">Login</button>
</div>
Javascript:
function checkAuth() {
return !!localStorage.getItem('id_token');
}
export default {
name: 'login',
data() {
return {
localStorage,
authenticated: false,
secretThing: '',
lock: new Auth0Lock('clientId', 'domain')
}
},
events: {
'logout': function() {
this.logout();
}
},
mounted() {
console.log('mounted');
var self = this;
Vue.nextTick(function() {
self.authenticated = checkAuth();
self.lock.on('authenticated', (authResult) => {
console.log(authResult);
console.log('authenticated');
localStorage.setItem('id_token', authResult.idToken);
self.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
console.log(error);
return;
} else {
console.log('no error');
}
localStorage.setItem('profile', JSON.stringify(profile));
self.authenticated = true;
});
});
self.lock.on('authorization_error', (error) => {
console.log(error);
});
});
},
methods: {
login() {
this.lock.show();
},
logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
this.authenticated = false;
}
}
}
I am pretty sure that it already worked, but suddenly it doesnt work anymore.
My callbacks defined in auth0: http://127.0.0.1:8080/#/backend/login
That is also how I open the login in my browser.
When I login it I only get this in my localStorage:
Key: com.auth0.auth.14BK0_jsJtUZMxjiy~3HBYNg27H4Xyp
Value: {"nonce":"eKGLcD14uEduBS-3MUIQdupDrRWLkKuv"}
I also get redirected to http://127.0.0.1:8080/#/ so I do not see any network requests.
Does someone know where the problem is?
I ran the demo from auth0 with my Domain/Client and it worked without any problem.
Obviously I do not get any errors back in my console.
Atfer research I finally found the answer to my problem.
The reason, why it is not working is because my vue-router does not use the HTML5 History Mode (http://router.vuejs.org/en/essentials/history-mode.html).
To have it working without the history mode, I had to disable the redirect in my lock options and to disable auto parsing the hash:
lock: new Auth0Lock(
'clientId',
'domain', {
auth: {
autoParseHash: false,
redirect: false
}
}
)
Reference: https://github.com/auth0/lock