AngularJS Copy to Clipboard Directive

Home / AngularJS Copy to Clipboard Directive

The other day, I was playing around with providing users the ability to easily copy data. This was needed in order to transfer that data to another application. Since this was for an AngularJS application, a reusable directive seemed like a good idea.


Initially, I wanted a simple button. It didn’t take much searching to find a nice template for a button that used an SVG image for a clipboard. Having the SVG makes the button look pretty nice regardless of what size to which it’s scaled. As I progressed through the directive, though, I wanted it to handle ng-model bindings as well as simple attribute binding which would be evaluated.

The directive, then, is really a click-event handler. We only need to check for the presence of the ngModelCtrl and act accordingly; either evaluate the directive’s attribute or use the ngModelCtrl view value.

var copyToClipboard = function (clipboardService) {
	var directive = {
		restrict: 'A',
		require: ['?ngModel'],
		link: function (scope, el, attrs, ctrls) {
			el.on('click', function () {
				var value = undefined;
				if (ctrls && ctrls.length > 0 && ctrls[0]) {
					var ngModelCtrl = ctrls[0];
					value = ngModelCtrl.$viewValue;
				} else {
					value = scope.$eval(attrs.copyToClipboard);
				}

				clipboardService.copy(value);
			});
		}
	};

	return directive;
};

But, wait a tick. Notice that clipboardService that’s being called? This is the service that actually hooks into the copy functionality of the browser through JavaScript. I split this out as a separate service because, you know, maybe I want to copy to clipboard without the directive..

The service utilizes the “document.execCommand” to issue copying of a hidden textarea that we copy the value to be copied into. This is necessary because the copy command works on the focused/selected element. You’ll also notice that in the case of a successful copy, I use my toasterService to let the user know. And, in the case of an exception, my dialogService will let the user know that nothing was copied and that they will have to use Ctrl-C themselves to copy.

var clipboardService = function ($q, $sce, $window, dialogService, toastService) {
	var
		body = angular.element($window.document.body),
		textarea = angular.element('<textarea/>');
	textarea.css({ position: 'fixed', opacity: '0' });
	var
		copy = function (value) {
			textarea.val(value);
			body.append(textarea);
			textarea[0].select();

			try {
				var successful = document.execCommand('copy');
				if (!successful) throw successful;
				toastService.success("Copied to clipboard!");
			} catch (err) {
				var
					errorTitle = "Error copying to clipboard",
					errorBody = "There was an error copying to the clipboard.  Select the text to copy and use Ctrl+C.";

				dialogService.openDialog("modalError.html", ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) {
					$scope.modalHeader = $sce.trustAsHtml(errorTitle);
					$scope.modalBody = $sce.trustAsHtml(dialogService.stringFormat("<p><strong>{0}</strong></p>", errorBody));
					$scope.ok = function () {
						$uibModalInstance.close();
					};
					$scope.hasCancel = false;
				}]);
			}

			textarea.remove();
		};

	return {
		copy: copy
	};
};

A plunk is below. The first clipboard button copies from the textarea and the second clipboard button will copy from an evaluated expression that returns the current Date.

Leave a Reply