Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Sara Dunnack
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Sara Dunnack

Pasting Images Into Your App Using File Blobs And URL.createObjectURL() In Angular 7.2.15

By
Published in Comments (13)

In the past couple of months, I've been playing around a lot more with File handling in Angular. Things like reading a drag-and-drop text File, uploading a single File with HttpClient and, uploading multiple File objects as a single Form Post all turn out to be somewhat simple in Angular. As another fun experiment in file handling, I waned to see if I could allow the user to Paste a copied Image File from their computer's clipboard right into my Angular 7.2.15 app.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

A few years ago, I learned about the ability to render image previews using "Object URLs". This approach uses the URL.createObjectURL() method to convert a Blob (binary object) into an addressable URL like:

blob:http://127.0.0.1:56809/92ece87b-9242-4cf5-b027-90bdb7939dbb

This URL can then be treated just like any other URL; and, in particular, can be used as the src attribute for an image (or the href attribute of an anchor tag). This allows us to navigate to dynamic data without having to worry about Base64-encoding complexities and URL length limitations.

Given this functionality - which has been supported since Internet Explorer 10 (IE10) - I wanted to see if I could capture the File object attached to a Window paste event, turn that File object into an "Object URL", and then render it in my Angular application.

To explore this idea, I created an App component that provides a few demo images (of Wildlings from Game of Thrones). These images can be right-clicked for "Copy Image" functionality. Then, if the user pastes (Cmd+V) anywhere in the Browser window, I'm going to intercept the event, extract the File, and then render it in a list of img elements:

// Import the core angular services.
import { Component } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { SafeUrl } from "@angular/platform-browser";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "my-app",
	host: {
		"(window:paste)": "handlePaste( $event )"
	},
	styleUrls: [ "./app.component.less" ],
	template:
	`
		<h2>
			Sample Images (That You Can Right-Click And Copy)
		</h2>

		<p class="sample-images">
			<img src="./img/image-1.jpg" class="sample-image" />
			<img src="./img/image-2.jpg" class="sample-image" />
			<img src="./img/image-3.png" class="sample-image" />
			<img src="./img/image-4.jpg" class="sample-image" />
			<img src="./img/image-5.jpg" class="sample-image" />
		</p>

		<h2>
			Pasted Images
		</h2>

		<p class="images">
			<ng-template ngFor let-imageUrl [ngForOf]="imageUrls">

				<img [src]="imageUrl" class="image" />

			</ng-template>
		</p>
	`
})
export class AppComponent {

	public imageUrls: SafeUrl[];

	private lastObjectUrl: string;
	private sanitizer: DomSanitizer;

	// I initialize the app component.
	constructor( sanitizer: DomSanitizer ) {

		this.sanitizer = sanitizer;

		this.imageUrls = [];
		this.lastObjectUrl = "";

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I handle the paste event on the Window (see host bindings).
	public handlePaste( event: ClipboardEvent ) : void {

		var pastedImage = this.getPastedImage( event );

		if ( ! pastedImage ) {

			return;

		}

		// When we create Object URLs, the browser will keep them in memory until the
		// document is unloaded or until the URL is explicitly released. Since we are
		// going to create a new URL every time the user pastes an image into the app (in
		// this particular demo), we need to be sure to release the previous Object URL
		// before we create the new one.
		// --
		// NOTE: One the Image is rendered in the DOM, releasing the Object URL will not
		// affect the rendering.
		if ( this.lastObjectUrl ) {

			URL.revokeObjectURL( this.lastObjectUrl );

		}

		// At this point, the "pastedImage" is a File object, which is a specialized type
		// of "Blob". We can now generate a "blob:" URL using the given File.
		this.lastObjectUrl = URL.createObjectURL( pastedImage );

		// By default, Angular WILL NOT TRUST this "blob:" style URLs. However, since we
		// know these are going to be expected, we can use the DOM Sanitizer to bypass
		// the security checks on these images.
		// --
		// NOTE: The sanitizer doesn't return Strings - it returns SafeUrls. 
		this.imageUrls.unshift(
			this.sanitizer.bypassSecurityTrustUrl( this.lastObjectUrl )
		);

	}

	// ---
	// PRIVATE METHODS.
	// ---

	// I return the first Image File from the given paste event (or null).
	private getPastedImage( event: ClipboardEvent ) : File | null {

		// NOTE: I am not very familiar with the Paste Event. As such, I am probably
		// being more cautious here than I need to be. However, in an abundance of
		// caution, I am checking each part of the targeted object path.
		if (
			event.clipboardData && 
			event.clipboardData.files && 
			event.clipboardData.files.length &&
			this.isImageFile( event.clipboardData.files[ 0 ] )
			) {

			return( event.clipboardData.files[ 0 ] );

		}

		return( null );

	}


	// I determine if the given File is an Image (according do its Mime-Type).
	private isImageFile( file: File ) : boolean {

		return( file.type.search( /^image\//i ) === 0 );

	}

}

As you can see, this turns out to be a fairly simple workflow. The ClipboardEvent contains a list of File objects. I grab the first File in the list, check to make sure its mime-type matches image/*, and then I generate an Object URL which I store in my view-model.

If I run this Angular app in the browser and copy-paste a few of the images, we get the following output:

Copy and pasting images into an Angular 7.2.15 app using the paste event and Object URLs.

The one hoop that we have to jump through is Security. By default, Angular tries to protect us from potentially malicious behavior. And, it categorizes blob: URLs as potentially malicious. So, in order for us to be able to consume the generated Object URL in our Image src attribute, we have to pass it through the DOMSanitizer, which will return an instance of SafeUrl (this is not a String).

The more I play with File management in my Angular applications, the more I am delighted to see that modern Browser APIs make things rather simple. I think this opens up a whole world of interesting interaction workflows, like being able to paste Images directly into an application. This is definitely something I'm going to start thinking about at InVision.

Want to use code from this post? Check out the license.

Reader Comments

2 Comments

Hi this is great idea i liked to and i have doubt here how to upload pasted (attached) images in to the serve.Can i upload blob URL Directly ?
Could you help me please.

Thank you

15,902 Comments

@Thejesh,

I'm glad you find this stuff interesting. Yes, you can definitely upload a pasted-image. The paste event actually contains a File object. And, you should be able to take that File object and just upload it using an HTTP client. For example, in this post:

www.bennadel.com/blog/3593-uploading-files-with-httpclient-in-angular-7-2-11.htm

.... I am uploading a File object via the HttpClient in Angular. In that post, I was selecting the File using the File Input element; but, you could just as easily -- I am pretty sure -- use the paste event to do the same thing.

Let me know if that helps. Otherwise, I can try to put together a demo.

15,902 Comments

@Thejesh,

I am not sure. I've been using Markdown as my editing approach (which is nice because it doesn't require much client-side functionality). But, I see there is a Ticket here about adding a rich text editor to Angular Material:

https://github.com/angular/material/issues/8487

... while there isn't one currently, it looks like there are several mentioned in the ticket comments.

1 Comments

Hi,
I love this approach. Anyhow, when I tried copying an image with transparent background from windows application such as powerpoint, and pasting it in web app, the transparency is not preserved. Image is being displayed with white background.
Could you provide some idea on how to maintain the transparency?
Thanks in advance

15,902 Comments

@Ganesh,

I am not sure what in this process would be responsible for stripping the transparency from the image. After all, we're not modifying the image - we're just pasting the binary data into the Angular app and then uploading it via AJAX.

As a sanity check, I would try right-clicking the image in the source page and do "Save As" to your local computer. And then, confirm that the saved image has transparency. It could be that there's something confusing about the image?? I'm not really sure.

15,902 Comments

@Akhil,

Hmmm, very interesting. I just did some Googling and it looks like the new approach is to use HTMLMediaElement and srcObject. But, according to MDN, the support for that is not great:

https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject

MDN suggests falling back to the old URL.createObjectURL for most browsers. Since I've only just learned about this (from you), I don't have any good insights. I'll have to dig a little deeper.

@Werner,

Awesome! I'm glad you found this helpful :D

15,902 Comments

@Akhil,

Actually, looking closer at the deprecation notes, I do not think that it is fully deprecating all uses of URL.createObjectURL(). It seems to only say that it is being deprecated specifically when the argument is of type MediaStream. In my demos, the arguments are generally of type File or Blob. As such, I am not sure this deprecation applies to us.

In fact, if you run this Angular demo in either Firefox or Chrome, you will see that no deprecation warning shows up in the logs. So, I think we may be safe!

15,902 Comments

@Saad,

Awesome! I'm happy to hear this helped you out. I love the interaction we get with File objects and Blob data.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel