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')
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')
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.
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 !
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);
}
Here is a simple code in case your text is not inside an input or textarea, but a div, or any other HTMLElement:
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!
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.
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.
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();
}
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?
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>