I’m working on implementing an Angular directive using the Web OTP API to auto-populate OTP inputs from an SMS message. Despite the user clicking “Allow” to grant permission, the navigator.credentials.get part doesn’t trigger the expected .then() code to populate the inputs.
Here’s a summary of my directive and HTML setup:
Directive Code:
import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlContainer, FormGroupDirective } from '@angular/forms';
import { ChangeDetectorRef } from '@angular/core';
interface CredentialRequestOptions {
otp: any;
signal: any;
}
@Directive({
selector: '[appWebOtp]',
standalone: true
})
export class WebOtpDirective implements OnInit, OnDestroy {
private ac = new AbortController();
private timer: any;
@Input('timeout') timeout?: number;
constructor(
private el: ElementRef,
private controlContainer: ControlContainer,
private cdRef: ChangeDetectorRef
) { }
ngOnInit(): void {
const options: CredentialRequestOptions = {
otp: { transport: ['sms'] },
signal: this.ac.signal
};
navigator.credentials.get(options).then((otp: any) => {
if (otp && otp.code) {
this.populateOtpInputs(otp.code);
}
}).catch(err => {
console.log(err);
});
if (this.timeout) {
this.timer = setTimeout(() => {
this.ac.abort();
}, this.timeout);
}
}
ngOnDestroy(): void {
this.ac.abort();
if (this.timer) {
clearTimeout(this.timer);
}
}
private populateOtpInputs(code: string): void {
const formGroup = (this.controlContainer as FormGroupDirective).form;
const inputs = this.el.nativeElement.querySelectorAll('input[autocomplete="one-time-code"]');
code.split('').forEach((char, index) => {
const input = inputs[index];
const controlName = input.getAttribute('formControlName');
if (controlName && formGroup.get(controlName)) {
formGroup.get(controlName)?.setValue(char);
input.value = char;
}
});
this.cdRef.detectChanges();
}
}
HTML Template:
<form id="otp-form" appWebOtp [formGroup]="form" class="flex flex-wrap justify-center basis-full" dir="ltr">
<input autocomplete="one-time-code" required type="text" id="digit1" formControlName="digit1" maxlength="1" autofocus>
<input autocomplete="one-time-code" required type="text" id="digit2" formControlName="digit2" maxlength="1">
<input autocomplete="one-time-code" required type="text" id="digit3" formControlName="digit3" maxlength="1">
<input autocomplete="one-time-code" required type="text" id="digit4" formControlName="digit4" maxlength="1">
</form>
Problem: When using this directive, even if the user clicks “Allow” to permit access, navigator.credentials.get(options).then(…) doesn’t trigger. I expect it to call this.populateOtpInputs(otp.code) and populate the input fields with the OTP, but nothing happens after the user approval.
What I Tried:
Adding this.cdRef.detectChanges() after populating inputs to trigger Angular’s change detection.
Setting a timeout with AbortController to limit the waiting period.
Verifying permissions and compatibility with the Web OTP API.
Question:
Why does navigator.credentials.get(options).then(…) not execute after user permission, and what additional steps or changes are needed to make it work?
Any insights into why navigator.credentials.get may not be triggering after the user clicks allow, or potential solutions, would be highly appreciated!