I make an Ionic app with Angular and I need to read barcodes with a physical barcode reader.
This reader is typically like a physical keyboard which sends keyboard event after it read a barcode.
So I made a script to capture Keyboard event on the window object because the user can read a barcode outside inputs, and I transform keyboard events to observable which will "emit" the complete barcode.
This script work as expected but I talk about it because I think there is a link with my problem…
In the TypeScript file of my page (Ionic Page, an angular component), I subscribe to my observable (the one I talked about previously…).
The subscription is quiet simple, I just add the barcode into a Set<string> after I made some checking steps. Or, these steps return a Promise, and I add the barcode when the promise is resolved …
The Set of barcodes is shown in the html file in a ngFor loop.
When the barcode reader read the barcode, it is added to the set but the UI is not refreshed…
I'm pretty sure I missed something, and maybe it's about NgZone which I do not really know…
If I add the barcode without calling checking steps (no async code called) the UI is refreshed.
I also tried to call checking steps with a hard coded button which simulate the barcode scanner and it worked…
So the problem is when the barcode is added after the promise is resolved AND the barcode is from the Observer made by window Keyboard Events…
To observe a codebar reader:
export namespace LecteurCodebarrePhysique {
// L'évènement est-il dans un input ?
const inInput = (event) => {return event.target instanceof Element && event.target.nodeName.toLowerCase() === 'input'};
// La touche relachée est-elle un caractère ?
const isTextKey = (event) => {return !inInput(event) && event.key.length === 1};
// La touche relachée est-elle la touche entrée ?
const isEnter = (event) => {return !inInput(event) && event.keyCode === 13};
/**
* Observable émettant le codebarre lu par un lecteur physique
*/
export function codebarreLu(): Observable<{text: string, format: string}> {
// Observable initiale : évèrement clavier
const keyup: Observable<KeyboardEvent> = fromEvent(window, 'keyup');
return keyup.pipe(
// On ne garde que les touches représentant un caractère
filter(ev => isTextKey(ev)),
// On ne garde que la valeur du caractère
map(ev => ev.key),
// On «bufferise» en attendant la touche entrée
buffer(keyup.pipe(filter(ev => {
const enter = isEnter(ev);
if (enter) {
ev.preventDefault();
ev.stopPropagation();
}
return enter;
}))),
// Quand la touche entrée est relachée, on concatène les caractères
// Et on essaye de déterminer si c'es un EAN13 (13 caractères numériques)
map(chars => {
const codebarre = chars.reduce((code, char) => code + char, '');
const isEan13 = /\d{13}/.test(codebarre);
return {text: codebarre, format: isEan13 ? 'EAN_13' : 'INCONNU'};
})
);
}
}
The TypeScript file of the page (MArticle is a service with different methods for Article objects. In this class, I use it to check if a barcode is already known on an Article object in DB):
export class ArticlesNouveauPage {
codebarres = new Set<string>();
codebarreLuSub: Subscription;
article = new Article();
constructor(private mArticle: MArticle) {}
ionViewWillEnter() {
// On souscrit à la letcure de codebarre physique
this.codebarreLuSub = LecteurCodebarrePhysique.codebarreLu().subscribe(resultat => this.ajouterCodebarre(resultat));
}
ionViewWillLeave() {
// Quand on quitte l'écran on ne souscrit plus à la lecture des codebarres physiques
this.codebarreLuSub.unsubscribe();
}
/**
* Ajout d'un codebarre
* @param resultat
*/
private ajouterCodebarre(resultat: {text: string}) {
// If an «Article» object is found with the barcode, we show an error message
return this.mArticle.getInstanceByGtin(resultat.text)
.then(article => {
this.tools.afficherMessage(`Le codebarre ${resultat.text} est déjà assigné à l'article "${article.libelle}" !`);
})
.catch(() => {
// If the promise is rejected, the barcode is unknown, we can add it to the list
this.addCodebarreToList(resultat.text);
});
}
private addCodebarreToList(codebarre: string) {
this.codebarres.add(codebarre);
}
testAddBarcode() {
this.ajouterCodebarre({text: `1234567890123`});
}
}
The HTML code of the page:
<ion-content >
<form #f="ngForm">
<ion-item-group>
<ion-item-divider color="secondary">Article</ion-item-divider>
<ion-item>
<ion-label color="primary" fixed>Libellé</ion-label>
<ion-input type="text" [(ngModel)]="article.libelle" name="libelle" required></ion-input>
</ion-item>
<ion-item>
<ion-label color="primary" fixed>Prix</ion-label>
<ion-input type="number" [(ngModel)]="article.prix" name="prix" required></ion-input>
</ion-item>
<ion-item>
<ion-label color="primary" fixed>Code</ion-label>
<ion-input type="text" [(ngModel)]="article.code" name="code"></ion-input>
</ion-item>
</ion-item-group>
</form>
<ion-item-group>
<ion-item-divider color="secondary">
Codebarres associés
</ion-item-divider>
<ion-item *ngFor="let codebarre of codebarres">
<ion-icon name="barcode" item-start color="secondary"></ion-icon>
<h2>{{codebarre}}</h2>
</ion-item>
</ion-item-group>
<ion-fab left bottom>
<button ion-fab color="danger" (click)="testAddBarcode()"><ion-icon name="add"></ion-icon></button>
</ion-fab>
</ion-content>
When I click on the «plus» button, the barcode is added to the list and UI is refreshed.
When I scan a barcode with the physical barcode scanner, the barcode is added but the UI is not refreshed.
I did expect the same behavior between both mode…
I think it's maybe a NgZone problem but I'm not an expert about it…
I think I missed something, but what…