'Angular 6 + jsQR - How to capture QR code on the first success?

I use jsQR (https://www.npmjs.com/package/jsqr) with my Angular app to decode QR. Generally I copy the code from jsQR into my app, and it works right away. However, jsQR only reads when I hold a QR code in webcam area, when moving the code away, the previous result is not captured. So I want to capture the first QR code if success and bind it into a form to submit the result. How can I do it properly?

Here is my code so far:

ngOnInit() {
    this.getQR();
}

  getQR() {
    const video = document.createElement('video');
    const canvasElement: any = document.getElementById('canvas');
    const canvas = canvasElement.getContext('2d');
    const outputContainer = document.getElementById('output');
    const outputMessage = document.getElementById('outputMessage');
    const outputData = document.getElementById('outputData');

    function drawLine(begin, end, color) {
      canvas.beginPath();
      canvas.moveTo(begin.x, begin.y);
      canvas.lineTo(end.x, end.y);
      canvas.lineWidth = 4;
      canvas.strokeStyle = color;
      canvas.stroke();
    }

    // Use facingMode: environment to attemt to get the front camera on phones
    navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }).then(function(stream) {
      video.srcObject = stream;
      // video.setAttribute('playsinline', true); // required to tell iOS safari we don't want fullscreen
      video.play();
      requestAnimationFrame(tick);
    });

    function tick() {
      if (video.readyState === video.HAVE_ENOUGH_DATA) {
        canvasElement.hidden = false;
        outputContainer.hidden = false;

        canvasElement.height = video.videoHeight;
        canvasElement.width = video.videoWidth;
        canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
        const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
        const code = jsQR(imageData.data, imageData.width, imageData.height);
        if (code) {
          drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#009688');
          drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#009688');
          drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#009688');
          drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#009688');
          outputMessage.hidden = true;
          outputData.parentElement.hidden = false;
          outputData.innerText = code.data;
        } else {
          outputMessage.hidden = false;
          outputData.parentElement.hidden = true;
        }
      }
      requestAnimationFrame(tick);
    }
  }


Solution 1:[1]

I have wrapped javascript pure code into typescript; this is my template:

<canvas id="scan-canvas"></canvas>
<div id="output">
  <div id="outputMessage">No QR code detected.</div>
  <div hidden=""><b>Data:</b> <span id="outputData">{{ qrcodeDetected }}</span></div>
</div>

Here the TS controller:

ngOnInit() {
    this.canvasElement = <HTMLCanvasElement> document.getElementById('scan-canvas');
    this.canvasContext = this.canvasElement.getContext('2d');
    this.outputContainer = <HTMLDivElement>document.getElementById('output');
    this.outputMessage = <HTMLDivElement>document.getElementById('outputMessage');
    this.outputData = <HTMLDivElement>document.getElementById('outputData');

    this.video = <HTMLVideoElement>document.createElement('video');

    navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }).then(async (stream: MediaStream) => {
        this.video.srcObject = stream;
        this.video.setAttribute('playsinline', 'true'); // required to tell iOS safari we don't want fullscreen
        await this.video.play();
        requestAnimationFrame(this.tick.bind(this));
    });
}

drawLine(begin, end, color): void {
    this.canvasContext.beginPath();
    this.canvasContext.moveTo(begin.x, begin.y);
    this.canvasContext.lineTo(end.x, end.y);
    this.canvasContext.lineWidth = 4;
    this.canvasContext.strokeStyle = color;
    this.canvasContext.stroke();
}

tick(): void {
    if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
        this.canvasElement.hidden = false;
        this.outputContainer.hidden = false;

        this.canvasElement.height = this.video.videoHeight;
        this.canvasElement.width = this.video.videoWidth;
        this.canvasContext.drawImage(this.video, 0, 0, this.canvasElement.width, this.canvasElement.height);
        const imageData: ImageData = this.canvasContext.getImageData(0, 0, this.canvasElement.width, this.canvasElement.height);
        const code: QRCode = jsQR(imageData.data, imageData.width, imageData.height);
        if (code) {
            this.drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#FF3B58');
            this.drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#FF3B58');
            this.drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#FF3B58');
            this.drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#FF3B58');
            this.outputMessage.hidden = true;
            this.outputData.parentElement.hidden = false;
            this.qrcodeDetected = code.data;
        } else {
            this.outputMessage.hidden = false;
            this.outputData.parentElement.hidden = true;
        }
    }
    requestAnimationFrame(this.tick.bind(this));
}

Solution 2:[2]

To completely answer this question, Fabio Mercorillo's answer works great. Only missing thing to have a completely working example is adding the variable declarations to the component:

...
import jsQR, { QRCode } from 'jsqr';
...

export class MyComponent implements OnInit {

  canvasElement: HTMLCanvasElement;
  canvasContext: CanvasRenderingContext2D;
  outputContainer: HTMLDivElement;
  outputMessage: HTMLDivElement;
  outputData: HTMLDivElement;
  video: HTMLVideoElement;

  qrcodeDetected: string;

  constructor() { }

...

To answer the initial question, the last scanned code is saved to the qrcodeDetected: string variable, the result is simply css-hidden by the else statement of the next scan/tick this.outputData.parentElement.hidden = true;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Patrick
Solution 2 denu5