The copy function is not performed correctly in my angular component

Viewed 888

I have built a password generator component in angular 5. It suppose to work like this: When I click on generate button it will also copy the text in output textbox. It has an odd behavior because I have to click the Generator button twice before it copies the string that was before. I want it to copy the text string that has just been generated.

I can not figure out why it behaves like this.

I have built a example here: https://stackblitz.com/edit/angular-password-generator?file=app%2Fpassword.component.ts

import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-password',
  template: `
  <form>
    <input id="passwordOutput" #passwordOutput value="{{newPassword}}" />
    <input type="range" (change)="updatePasswordLength($event)" min="4" max="32" step="1" value="{{passwordLenght}}" />
    <input type="text" value="{{passwordLenght}}" />

    <div class="flex">
      <span *ngFor="let checkbox of checkboxes">
        <input type="checkbox" (change)="updateCheckboxValue($event)" id="{{checkbox.id}}" [checked]="checkbox.checked" />
        <label for="{{checkbox.id}}">{{checkbox.label}}</label>
      </span>
    </div>

    <button type="button" (click)="generatePassword()">{{buttonLabel}}</button>
  </form>
`
})
export class PasswordComponent {
  // Alternative for checkboxes
  checkboxes = [
    {
      "id": "lowercase",
      "label": "a-z",
      "library": "abcdefghijklmnopqrstuvwxyz",
      "checked": true
    }, {
      "id": "uppercase",
      "label": "A-Z",
      "library": "ABCDEFGHIJKLMNOPWRSTUVWXYZ",
      "checked": true
    }, {
      "id": "numbers",
      "label": "0-9",
      "library": "0123456789",
      "checked": true
    }, {
      "id": "symbols",
      "label": "!-?",
      "library": "!@#$%^&*-_=+\\|:;',.\<>/?~",
      "checked": false
    }
  ]

  // Declarations
  dictionary: Array<String>;

  lowercase: Boolean = this.checkboxes[0].checked;
  uppercase: Boolean = this.checkboxes[1].checked;
  numbers: Boolean = this.checkboxes[2].checked;
  symbols: Boolean = this.checkboxes[3].checked;

  passwordLenght: Number = 4;
  buttonLabel: String = "Generate";
  newPassword: String;

  // Password length
  private updatePasswordLength(event) {
    this.passwordLenght = event.target.value;
  }

  // Checkbox value
  private updateCheckboxValue(event) {
    if (event.target.id == "lowercase")
      this.lowercase = event.target.checked;

    if (event.target.id == "uppercase")
      this.uppercase = event.target.checked;

    if (event.target.id == "numbers")
      this.numbers = event.target.checked;

    if (event.target.id == "symbols")
      this.symbols = event.target.checked;
  }

  // Copy password to clipboard
  @ViewChild('passwordOutput') password: ElementRef;
  private copyPassword() {
    const inputElement = <HTMLInputElement>this.password.nativeElement;
    inputElement.select();
    document.execCommand("copy");
  }

  // Generate password
  private generatePassword() {
    if (this.lowercase === false && this.uppercase === false && this.numbers === false && this.symbols === false) {
      return this.newPassword = "...";
    }

    // Create array from chosen checkboxes
    this.dictionary = [].concat(
      this.lowercase ? this.checkboxes[0].library.split("") : [],
      this.uppercase ? this.checkboxes[1].library.split("") : [],
      this.numbers ? this.checkboxes[2].library.split("") : [],
      this.symbols ? this.checkboxes[3].library.split("") : []
    );

    // Generate random password from array
    var newPassword = "";
    for (var i = 0; i < this.passwordLenght; i++) {
    newPassword += this.dictionary[Math.floor(Math.random() * this.dictionary.length)];
    }
    this.newPassword = newPassword;

    // Call copy function
    this.copyPassword();

    // Change text on button when clicked
    this.buttonLabel = "Copied!";
    setTimeout(() => { this.buttonLabel = "Generate" }, 1500);
  }
}
1 Answers

There is not enough time between when the generate button is clicked the copyPassword method is called, you can fix this by using setTimeout to push your copy method to the end of the process queue:

setTimeout(() => this.copyPassword());

Updated Stackblitz


Update:

Change you copy method to the following:

private copyPassword() {
  let input = document.createElement('input');

  input.style.position = 'fixed';
  input.style.left = '0';
  input.style.top = '0';
  input.style.opacity = '0';
  input.value = this.newPassword;

  document.body.appendChild(input);
  input.focus();
  input.select();

  document.execCommand('copy');
  document.body.removeChild(input);
}

Updated stackblitz

Related