29

Is there a way to copy text in clipboard (multi-browser) in Angular2 Typescript framework?

I find only sources of using Javascript, e.g.

document.execCommand('copy')
Zeno Rocha
  • 3,226
  • 1
  • 23
  • 27
Andris Krauze
  • 2,092
  • 8
  • 27
  • 39

10 Answers10

38

You could implement an Angular2 directive arount the clipboard.js library.

First configure the library into SystemJS:

<script>
  System.config({
    map: {
      clipboard: 'https://cdn.rawgit.com/zenorocha/clipboard.js/master/dist/clipboard.js'
    },
    packages: {
      'app': {
        defaultExtension: 'js'
      }
    } 
  });
  (...)
</script>

We want to be able to attach clipboard on an element through a directive and provide as parameter the DOM element we want to link with. The value specified into the specified target element will be used to copy its text. Here is a sample of use:

<div>
  <input #foo/>
  <button [clipboard]="foo">Copy</button>
</div>

The implementation of the directive is the following:

import {Directive,ElementRef,Input,Output,EventEmitter} from 'angular2/core';
import Clipboard from 'clipboard';

@Directive({
  selector: '[clipboard]'
})
export class ClipboardDirective {
  clipboard: Clipboard;

  @Input('clipboard')
  elt:ElementRef;

  @Output()
  clipboardSuccess:EventEmitter<any> = new EventEmitter();

  @Output()
  clipboardError:EventEmitter<any> = new EventEmitter();

  constructor(private eltRef:ElementRef) {
  }

  ngOnInit() {
    this.clipboard = new Clipboard(this.eltRef.nativeElement, {
      target: () => {
        return this.elt;
      }
    });

    this.clipboard.on('success', (e) => {
      this.clipboardSuccess.emit();
    });

    this.clipboard.on('error', (e) => {
      this.clipboardError.emit();
    });
  }

  ngOnDestroy() {
    if (this.clipboard) {
      this.clipboard.destroy();
    }
  }
}

See this plunkr for a sample: https://plnkr.co/edit/elyMcP5PX3UP4RkRQUG8?p=preview.

s.alem
  • 12,579
  • 9
  • 44
  • 72
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • that's a very nice approach but there are situations where too many external dependencies are just too many :), and for these situations a plain js + angular solution should be suitable as well https://stackoverflow.com/a/52949299/2583579 – Dan Dohotaru Oct 23 '18 at 14:16
31

i got just one method from https://github.com/pehu71/copy-component/blob/master/src/simple/copy.component.ts works even on android 4.1.2

copy(val) {

    let selBox = document.createElement('textarea');

    selBox.style.position = 'fixed';
    selBox.style.left = '0';
    selBox.style.top = '0';
    selBox.style.opacity = '0';
    selBox.value = val;

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

    document.execCommand('copy');
    document.body.removeChild(selBox);
}
dimson d
  • 1,488
  • 1
  • 13
  • 14
25

Kudos to @ThierryTemplier,

Base on his answer, I put together a directive and sharing on github & npm.

Here is the project on github

UPDATE: 4/30/2017

This library doesn't depends on clipboard.js anymore.

Just Angular!

Quick example (component code):

import { ClipboardService } from 'ngx-clipboard'

...

constructor(private _clipboardService: ClipboardService){
...
}

// not sure, but this should the result of user interaction (e.g. (click) )
copyToClipboard(){
  const text = computeText();
  this._clipboardService.copyFromContent(text)
}
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
maxisam
  • 21,975
  • 9
  • 75
  • 84
  • i installed 'ngx-clipboard' then import it using import { ClipboardModule } from 'ngx-clipboard' and added in @ngmodule [ClipboardModule] in my app module but it is still giving me below error .Uncaught Error: Unexpected value 'ClipboardModule' imported by the module 'AppModule' at http://localhost:9080/vendor.bundle.js:12262:31 at Array.forEach (native) at CompileMetadataResolver._loadNgModuleMetadata (http://localhost:9080/vendor.bundle.js:12247:49)....... – Anagh Verma Feb 02 '17 at 14:50
  • it is hard to tell from that you said. Did you try the demo code on Github ? – maxisam Feb 02 '17 at 20:06
  • @maxisam It doesnot work for me either. Getting error Can't bind to 'ngxClipboard' since it isn't a known property of 'button'. see here for details: http://stackoverflow.com/questions/43062477/angular2-ngx-clipboard-not-working – Peter Mar 28 '17 at 07:30
  • Yours only works for input html elements. If there's a way to make it work for anything (like a span or p) please add an example in your readme. Otherwise, for anyone else who needs it on anything, this example was a copy/paste success [link](https://www.bennadel.com/blog/3235-creating-a-simple-copy-to-clipboard-directive-in-angular-2-4-9.htm?&_=0.22006054253479745#comments_49119) – liquid_diamond May 05 '17 at 20:27
  • @canada11 you should check plunker. it shows you 2 different ways. I think you just missed it. [cbContent]="text" – maxisam May 05 '17 at 23:05
  • @BheshGurung what version of Safari? – maxisam Aug 28 '17 at 16:14
  • @maxisam: Safari 9.1.1. Is it not supported? I saw in [clipboardjs](https://clipboardjs.com/), it only supports Safari 10+, but their sample works on my browser. You have mentioned that it's not using clipboardjs anymore, so just wanted to know about the Safari support. – Bhesh Gurung Aug 28 '17 at 16:26
  • well, I ported clipboardjs to this library, so it should work. I don't have safari at hand to test it now. But you can compare the source code and see what is the difference that stopping it from working. – maxisam Aug 28 '17 at 16:33
  • Unfortunately it fails to compile :/ – xb1itz Nov 27 '17 at 13:10
  • 1
    It works without any problems with the `ClipboardService` service in Angular 9. Also added a quick example to see how easy you can copy an arbitrary text to the clipboard. Thanks. – Alexei - check Codidact Mar 28 '20 at 09:21
4

This is a simple pure Angular2 and javascript solution that doesn't require any libraries and which can be used in an angular component. You could turn it into a service or make it more generic if needed but this will establish the basic idea.

Currently browsers only allow text to be copied to the clipboard from the Selection in an <input> or <textarea>

In the component do something like this:

import {Inject} from "@angular/core";
import {DOCUMENT} from "@angular/platform-browser";

export class SomeComponent {
    private dom: Document;

    constructor(@Inject(DOCUMENT) dom: Document) {        
       this.dom = dom;
    }

    copyElementText(id) {
        var element = null; // Should be <textarea> or <input>
        try {
            element = this.dom.getElementById(id);
            element.select();
            this.dom.execCommand("copy");
        }
        finally {
           this.dom.getSelection().removeAllRanges;
        }
    }
}

Then in the html block associated with the component, do the following:

<div>
   <button (click)="copyElementText('elem1')">Copy</button>
</div>
<textarea id="elem1">Some text</textarea>

That's it! The button calls the copyElementText() function in it's component and passes it the ID of the html element to get text from and copy to the clipboard.

The function uses standard javascript to get the element by it's ID, select it, execute the "Copy" command on the selection and then deselects it.

Cliff Ribaudo
  • 8,932
  • 2
  • 55
  • 78
3

Here is a simple code in case your text is not inside an input or textarea, but a div, or any other HTMLElement and you don't want to use any external libraries:

window.getSelection().selectAllChildren(document.getElementById('yourID'));
document.execCommand("copy");

I was unable to use the select() command because it wasn't recognized by Angular. Hope this helps someone!

Ajay Bhasy
  • 1,920
  • 1
  • 26
  • 38
Pablo Quemé
  • 1,414
  • 4
  • 16
  • 29
  • This is a really useful solution if you need to have the text inside a div and still want to copy it. – Ajay Bhasy Jul 01 '20 at 11:48
  • If you are copying the text for something like an email, the format is also copied. So you can get the HTML formatting if you paste it in a mail client or Gmail. – Ajay Bhasy Jul 04 '20 at 11:51
3

This is a simple pure Angular2 and javascript solution that doesn't require any libraries and which can be used in an angular component. You could turn it into a service or make it more generic if needed but this will establish the basic idea.

Currently browsers only allow text to be copied to the clipboard from the Selection in an or . This can implemented in div

(.html file)
<div id="inputId">Some texts</div>
<button (click)="copyToClipboard()'>click me</button>

//(.ts file)

public copyToClipboard(){
  var el = document.getElementById('inputId');
  el.setAttribute('contenteditable','true');
  el.focus();
  document.execCommand('selectAll');
  document.execCommand('copy');
  el.setAttribute('contenteditable','false');
  el.blur();
}
Shiju Augustine
  • 265
  • 1
  • 4
  • 11
1

Currently only for the most common APIs abstractions are implemented, mostly to be able to pass different implementations when run on the server (server side rendering (https://github.com/angular/universal) in inside a webworker where the API is not available.

I'm pretty sure there is nothing yet for the clipboard API. There are plans to implement more wrappers though.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
1

The code that you mentioned is the right way to do it and it can be done in Angular 2+ too.

I don't know what you accualy need to do, but if you, for example, have an input and a button:

(.html file)

<input id='inputId'></input>
<button (click)="copyToClipboard()'>click me</button>

then all you need to do is:

(.ts file)

public copyToClipboard(): void {
  const inputElement = document.getElementById('inputId');
  (<any>inputElement).select();
  document.execCommand('copy');
  inputElement.blur();
}
pbialy
  • 1,025
  • 14
  • 26
0

Here is a way to achieve this without any external dependency or creating fake elements, only by using Clipboard API:

import { DOCUMENT } from '@angular/common';
import { Directive, EventEmitter, HostListener, Inject, Input, Output } from '@angular/core';

@Directive({
  selector: '[myClipboard]'
})
export class ClipboardDirective {

  @Input() myClipboard: string;
  @Output() myClipboardSuccess = new EventEmitter<ClipboardEvent>();

  constructor(@Inject(DOCUMENT) private document: Document) {}

  @HostListener('click')
  onClick() {
    this.document.addEventListener('copy', this.handler);
    this.document.execCommand('copy');
  }

  private handler = (e: ClipboardEvent) => {
    e.clipboardData.setData('text/plain', this.myClipboard);
    e.preventDefault();
    this.myClipboardSuccess.emit(e);
    this.document.removeEventListener('copy', this.handler);
  }

}

Can I use Clipboard API?

s.alem
  • 12,579
  • 9
  • 44
  • 72
-1

Ben Nadel had a great example that worked for any html element type and doesn't rely on anything to be installed. See Ben's blog post Or see the Git gist

See his blog for more about it and the logging he does, here's the relevant and slightly modified so it fits here better:

Make a directive: clipboard.directive.ts

// Import the core angular services.
import { Directive } from "@angular/core";
import { EventEmitter } from "@angular/core";

// Import the application components and services.
import { ClipboardService } from "./clipboard.service";

// This directive acts as a simple glue layer between the given [clipboard] property
// and the underlying ClipboardService. Upon the (click) event, the [clipboard] value
// will be copied to the ClipboardService and a (clipboardCopy) event will be emitted.
@Directive({
selector: "[clipboard]",
inputs: [ "value: clipboard" ],
outputs: [
    "copyEvent: clipboardCopy",
    "errorEvent: clipboardError"
],
host: {
    "(click)": "copyToClipboard()"
}
})
export class ClipboardDirective {

public copyEvent: EventEmitter<string>;
public errorEvent: EventEmitter<Error>;
public value: string;

private clipboardService: ClipboardService;


// I initialize the clipboard directive.
constructor( clipboardService: ClipboardService ) {

    this.clipboardService = clipboardService;
    this.copyEvent = new EventEmitter();
    this.errorEvent = new EventEmitter();
    this.value = "";
}

// ---
// PUBLIC METODS.
// ---

// I copy the value-input to the Clipboard. Emits success or error event.
public copyToClipboard() : void {
    this.clipboardService
        .copy( this.value )
        .then(
            ( value: string ) : void => {

                this.copyEvent.emit( value );

            }
        )
        .catch(
            ( error: Error ) : void => {

                this.errorEvent.emit( error );
            }
        )
    ;
}
}

And a service clipboard.service.ts

// Import the core angular services.
import { DOCUMENT } from "@angular/platform-browser";
import { Inject } from "@angular/core";
import { Injectable } from "@angular/core";
@Injectable()
export class ClipboardService {

private dom: Document;
// I initialize the Clipboard service.
// --
// CAUTION: This service is tightly couped to the browser DOM (Document Object Model).
// But, by injecting the "document" reference rather than trying to reference it
// globally, we can at least pretend that we are trying to lower the tight coupling.
constructor( @Inject( DOCUMENT ) dom: Document ) {
    this.dom = dom;
}

// ---
// PUBLIC METHODS.
// ---
// I copy the given value to the user's system clipboard. Returns a promise that
// resolves to the given value on success or rejects with the raised Error.
public copy( value: string ) : Promise<string> {
    var promise = new Promise(
        ( resolve, reject ) : void => {
            var textarea = null;
            try {
                // In order to execute the "Copy" command, we actually have to have
                // a "selection" in the currently rendered document. As such, we're
                // going to inject a Textarea element and .select() it in order to
                // force a selection.
                // --
                // NOTE: This Textarea is being rendered off-screen.
                textarea = this.dom.createElement( "textarea" );
                textarea.style.height = "0px";
                textarea.style.left = "-100px";
                textarea.style.opacity = "0";
                textarea.style.position = "fixed";
                textarea.style.top = "-100px";
                textarea.style.width = "0px";
                this.dom.body.appendChild( textarea );

                // Set and select the value (creating an active Selection range).
                textarea.value = value;
                textarea.select();
                // Ask the browser to copy the current selection to the clipboard.
                this.dom.execCommand( "copy" );
                resolve( value );
            } finally {
                // Cleanup - remove the Textarea from the DOM if it was injected.
                if ( textarea && textarea.parentNode ) {

                    textarea.parentNode.removeChild( textarea );
                }
            }
        }
    );
    return( promise );
}
}

Import both in the app.module.ts and then you can reference it in html with something like this:

<p>
        <button [clipboard]="value1.innerHTML.trim()">
            Copy Text
        </button>
        <span #value1>
            Hello World!
        </span>
    </p>
liquid_diamond
  • 429
  • 8
  • 16