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 wich send 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 scannner, the barcode is added but the UI is not refreshed.
I did expect the same behaviour between both mode …
I think it's maybe a NgZone problem but I'm not an expert about it …
I think I missed somethinq, but what …