Issue
I want to implement an alert dialog which asks the user for a passcode. If the passcode is equal to a saved password, the dialog should dismiss. Otherwise, the dialog should present itself again until the user enter the correct passcode. For this task I'm using Ionic and in particular ion-alert.
The following works as expected.
askForPasscode() {
const alertOpts = {
backdropDismiss: false,
header: this.transloco.translate('passcodeEnter'),
message: this.failedAttempts ? this.transloco.translate('passcodeFailedAttempts', { failedAttempts: this.failedAttempts }) : '',
inputs: [ {
name: 'passcode',
placeholder: this.transloco.translate('passcodeEnter'),
attributes: { maxlength: passcodeLength, type: 'password' },
} ],
buttons: [ {
text: this.transloco.translate('confirm'),
handler: this.verifyPasscode.bind(this),
} ],
};
return fromPromise(this.alertCtrl.create(alertOpts)).pipe(
map(alert => fromPromise(alert.present())));
}
verifyPasscode({ passcode }: any ) {
const digest = SHA1(passcode).toString();
this.storage.get('passcode').subscribe(stored => {
const isFailedAttempt = digest !== stored;
this.failedAttempts = isFailedAttempt ? this.failedAttempts + 1 : 0;
if (isFailedAttempt)
this.askForPasscode().subscribe();
});
}
this.askForPasscode().subscribe();
As refinement, I'd like to move the retry portion outside of the askForPasscode
method, and manage it with the RxJS operators.
So I have refactored the code in the following way.
askForPasscode() {
const alertOpts = {
backdropDismiss: false,
header: this.transloco.translate('passcodeEnter'),
message: this.failedAttempts ? this.transloco.translate('passcodeFailedAttempts', { failedAttempts: this.failedAttempts }) : '',
inputs: [ {
name: 'passcode',
placeholder: this.transloco.translate('passcodeEnter'),
attributes: { maxlength: PasscodeLength, type: 'password' },
} ],
buttons: [ {
text: this.transloco.translate('confirm'),
// handler: this.verifyPasscode.bind(this),
} ],
};
return fromPromise(this.alertCtrl.create(alertOpts)).pipe(
// map(alert => fromPromise(alert.present())));
switchMap(alert => {
alert.present();
return fromPromise(alert.onDidDismiss());
}));
}
verifyPasscode(passcode: string) {
const digest = SHA1(passcode).toString();
// this.storage.get('passcode').subscribe(stored => {
return this.storage.get('passcode').pipe(map(stored => {
const isFailedAttempt = digest !== stored;
this.failedAttempts = isFailedAttempt ? this.failedAttempts + 1 : 0;
// if (isFailedAttempt)
// this.askForPasscode().subscribe();
return !isFailedAttempt;
}));
}
// this.askForPasscode().subscribe();
this.askForPasscode().pipe(
map(result => result.data.values.passcode),
switchMap(passcode => this.passcode.verifyPasscode(passcode)),
map(isSuccessful => { if (!isSuccessful) throw 'error' }),
retry({count: 5}),
).subscribe();
verifyPasscode
now just verify the correctness of the passcode, and returns a flag.
askForPasscode
now have a bunch of pipeable operators which (1) read the flag, (2) throw an error if the verification failed, and (3) resubscribe if an error was thrown.
However, the dialog is not presented again if the passcode is wrong.
It looks like the askForPasscode()
is not invoked again no matter what.
What am I missing?
Solution
I refactored your stackblitz-example to make it work as intended:
What were the issues with your code?
- When
retry
is triggered,fromPromise(this.alertCtrl.create(alertOpts))
is not executed, while the subsequentswitchMap
actually is. Thereforealert.present()
refers to an alert that was already dismissed. - Because you placed the
alertOpts
inside the function, its properties would not be updated on failed login-attempts and therefore the${this.failedAttempts} failed attempts
message would not have been displayed ever.
How did I resolve the issues?
- In the
askForPasscode()
method, I wrapped allalert
promises in adefer
operator which creates a new observable each time it is subscribed to. - I've moved the alert options out into a separate function so that they are reassembled from scratch each time a login fails, thus showing the correct number of failed login attempts.
failedAttempts = 0;
constructor(private alertCtrl: AlertController) {}
ngOnInit() {
this.askForPasscode()
.pipe(
map((result: any) => result.data.values.passcode),
switchMap((passcode) => this.verifyPasscode(passcode)),
map((isSuccessful) => {
if (!isSuccessful) throw 'error';
}),
retry({ count: 5 })
)
.subscribe();
}
askForPasscode(): Observable<any> {
return defer(async () => {
const alert = await this.alertCtrl.create(this.getLoginModal());
await alert.present();
return alert.onDidDismiss();
});
}
verifyPasscode(passcode: string) {
return of('passcode').pipe(
map((stored) => {
const isFailedAttempt = passcode !== stored;
this.failedAttempts = isFailedAttempt ? this.failedAttempts + 1 : 0;
return !isFailedAttempt;
})
);
}
getLoginModal() {
return {
backdropDismiss: false,
header: 'passcodeEnter',
message: this.failedAttempts
? `${this.failedAttempts} failed attempts`
: '',
inputs: [
{
name: 'passcode',
placeholder: 'enter passcode',
attributes: { type: 'password' },
},
],
buttons: [
{
text: 'confirm',
},
],
};
}
Answered By - kellermat
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.