ng2-pdf-viewer, ng2-pdfjs-viewer, ngx-extended-pdf-viewer, pdfjs-dist, react-pdf, and vue-pdf-embed are npm packages that enable PDF rendering in web applications. They all leverage Mozilla's PDF.js library under the hood but differ significantly in their level of abstraction, framework integration, feature completeness, and maintenance status. These libraries range from low-level access (pdfjs-dist) to high-level, framework-specific components that provide ready-to-use UI elements for viewing, navigating, and interacting with PDF documents.
Displaying PDFs in web applications is a common but surprisingly nuanced task. The six packages under review — ng2-pdf-viewer, ng2-pdfjs-viewer, ngx-extended-pdf-viewer, pdfjs-dist, react-pdf, and vue-pdf-embed — represent different approaches: from thin wrappers around Mozilla’s PDF.js to full-featured, framework-native components. Let’s break down how they work, where they excel, and what trade-offs you’ll face.
All these libraries ultimately rely on Mozilla’s PDF.js for rendering, but they differ in how tightly they integrate it with their target frameworks.
pdfjs-dist is the raw, official PDF.js distribution. It gives you full control but requires manual DOM management and event wiring.
// pdfjs-dist: Manual setup
import * as pdfjsLib from 'pdfjs-dist';
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.min.js';
const loadingTask = pdfjsLib.getDocument('document.pdf');
loadingTask.promise.then(pdf => {
pdf.getPage(1).then(page => {
const canvas = document.getElementById('pdf-canvas');
const viewport = page.getViewport({ scale: 1.5 });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = { canvasContext: canvas.getContext('2d'), viewport };
page.render(renderContext);
});
});
ng2-pdf-viewer, ng2-pdfjs-viewer, and ngx-extended-pdf-viewer are Angular wrappers. They encapsulate this boilerplate into components, but with varying degrees of feature completeness.
<!-- ng2-pdf-viewer: Minimal Angular component -->
<pdf-viewer [src]="'document.pdf'" [page]="1" [original-size]="true"></pdf-viewer>
<!-- ngx-extended-pdf-viewer: Feature-rich Angular component -->
<ngx-extended-pdf-viewer
[src]="'document.pdf'"
[showHandTool]="true"
[showFindButton]="true"
[filenameForDownload]="'custom-name.pdf'">
</ngx-extended-pdf-viewer>
react-pdf provides React hooks and components that abstract PDF.js while embracing React’s declarative model.
// react-pdf: React component with hooks
import { Document, Page } from 'react-pdf';
function PDFViewer() {
return (
<Document file="document.pdf">
<Page pageNumber={1} />
</Document>
);
}
vue-pdf-embed follows a similar pattern for Vue, offering a single component that handles rendering.
<!-- vue-pdf-embed: Vue component -->
<template>
<vue-pdf-embed :src="'document.pdf'" page="1" />
</template>
<script>
import { VuePdfEmbed } from 'vue-pdf-embed';
export default { components: { VuePdfEmbed } };
</script>
If you only need to display a static page, most libraries will suffice. But real-world apps often require text selection, zoom controls, search, printing, or form filling.
ngx-extended-pdf-viewer stands out by bundling a near-complete replica of the official PDF.js viewer UI, including toolbar, sidebar, and annotation support. It even supports printing and downloading with custom filenames.
// ngx-extended-pdf-viewer: Full UI with customization
<ngx-extended-pdf-viewer
[src]="pdfSrc"
[useBrowserLocale]="true"
[showBorders]="false"
[showPresentationModeButton]="true">
</ngx-extended-pdf-viewer>
react-pdf focuses on core rendering and leaves UI controls to the developer. You’ll need to build your own toolbar for zoom, page navigation, etc.
// react-pdf: Manual page navigation
import { useState } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
function PDFViewer() {
const [pageNumber, setPageNumber] = useState(1);
return (
<div>
<button onClick={() => setPageNumber(p => p - 1)} disabled={pageNumber <= 1}>Prev</button>
<Document file="document.pdf">
<Page pageNumber={pageNumber} />
</Document>
<button onClick={() => setPageNumber(p => p + 1)}>Next</button>
</div>
);
}
ng2-pdf-viewer and ng2-pdfjs-viewer offer basic rendering but lack advanced features like text layer support or form interaction. ng2-pdfjs-viewer appears to be less actively maintained and has fewer configuration options.
vue-pdf-embed provides solid rendering but minimal UI — similar to react-pdf in scope.
<!-- vue-pdf-embed: Basic usage only -->
<vue-pdf-embed :src="pdfUrl" :page="currentPage" @loaded="onLoaded" />
When you need fine-grained control over rendering (e.g., custom overlays, dynamic annotations, or integration with drawing tools), lower-level access matters.
pdfjs-dist gives you direct access to every PDF.js API — page objects, text layers, annotation layers, and more. This is essential for complex use cases like collaborative document editing.
// pdfjs-dist: Access text content
const page = await pdf.getPage(1);
const textContent = await page.getTextContent();
console.log(textContent.items); // Array of text items with positions
react-pdf exposes some internals via props like customTextRenderer and onGetAnnotations, but it’s still an abstraction layer.
// react-pdf: Custom text rendering
<Page
pageNumber={1}
customTextRenderer={({ str }) => <mark>{str}</mark>}
/>
ngx-extended-pdf-viewer provides extensive Angular-specific inputs and outputs to customize behavior without dropping down to PDF.js APIs directly.
// ngx-extended-pdf-viewer: Event handling
<ngx-extended-pdf-viewer
(pageChange)="onPageChange($event)"
(textLayerRendered)="onTextRendered($event)">
</ngx-extended-pdf-viewer>
The other Angular and Vue packages offer limited hooks for customization, often requiring DOM manipulation or workarounds.
PDF.js is large (~1MB minified). All these packages inherit that weight, but they differ in how they handle code splitting and worker loading.
pdfjs-dist requires you to manually configure the worker path, which is critical for performance.
// Must set workerSrc before using PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = '/assets/pdf.worker.min.js';
react-pdf and ngx-extended-pdf-viewer provide utilities to simplify worker loading, often supporting CDN or dynamic imports.
// react-pdf: Worker via CDN
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
ng2-pdf-viewer and vue-pdf-embed typically bundle the worker or require manual setup, which can lead to larger initial bundles if not handled carefully.
As of the latest package metadata:
ng2-pdfjs-viewer shows signs of reduced maintenance. Its documentation is sparse, and recent issues suggest compatibility problems with newer Angular versions. Avoid for new projects.ng2-pdf-viewer is still maintained but has a narrower feature set compared to ngx-extended-pdf-viewer.ngx-extended-pdf-viewer, react-pdf, and vue-pdf-embed are actively updated and support modern framework versions.pdfjs-dist is the canonical PDF.js package, maintained by Mozilla, and should always be used when direct access is needed.ng2-pdf-viewerngx-extended-pdf-viewerng2-pdfjs-viewer due to maintenance concerns.react-pdf for rendering within React’s lifecycle.pdfjs-dist only if you need low-level text/annotation manipulation.vue-pdf-embed is sufficient for simple use cases.pdfjs-dist directly.| Package | Framework | Feature Completeness | Customization | Maintenance Status |
|---|---|---|---|---|
ng2-pdf-viewer | Angular | Basic | Low | Active |
ng2-pdfjs-viewer | Angular | Basic | Low | Not recommended |
ngx-extended-pdf-viewer | Angular | Full viewer UI | High | Active |
pdfjs-dist | None (vanilla) | Full API access | Highest | Active (Mozilla) |
react-pdf | React | Core rendering | Medium | Active |
vue-pdf-embed | Vue | Basic | Low | Active |
ngx-extended-pdf-viewerreact-pdfvue-pdf-embedpdfjs-distng2-pdfjs-viewer for new projects due to unclear maintenance and limited features.Choose based on your framework, required features, and how much control you need over the underlying PDF.js engine.
Choose ng2-pdf-viewer if you're working in an Angular application and need a lightweight, straightforward PDF viewer for basic rendering without advanced features like text selection, search, or form filling. It's suitable for simple use cases where you control the PDF content and don't require a full toolbar or sidebar functionality.
Avoid ng2-pdfjs-viewer for new projects. The package shows signs of reduced maintenance, has sparse documentation, and lacks the feature set and active support found in alternatives like ngx-extended-pdf-viewer. If you encounter it in legacy code, plan a migration to a more robust solution.
Choose ngx-extended-pdf-viewer if you're building an Angular application that requires a full-featured PDF viewer with built-in toolbar, sidebar, search, printing, form support, and extensive customization options. It provides the most complete PDF.js experience in the Angular ecosystem with active maintenance and rich configuration.
Choose pdfjs-dist when you need direct, low-level access to Mozilla's PDF.js library for maximum control over rendering, text extraction, annotation handling, or custom integrations. This is ideal for framework-agnostic solutions, complex document editing scenarios, or when you must build a highly customized viewer from scratch.
Choose react-pdf if you're developing a React application and want a well-maintained, idiomatic way to render PDFs using React components and hooks. It handles core rendering efficiently but requires you to build additional UI controls like navigation or zoom yourself, making it best for projects where you need React integration without a full pre-built toolbar.
Choose vue-pdf-embed if you're working with Vue and need a simple, component-based approach to embed PDF pages with minimal setup. It's appropriate for basic viewing needs in Vue applications but lacks advanced features and deep customization, so consider pdfjs-dist directly if you require more control.
PDF Viewer Component for Angular 5+
https://vadimdez.github.io/ng2-pdf-viewer/
https://stackblitz.com/edit/ng2-pdf-viewer
https://medium.com/@vadimdez/render-pdf-in-angular-4-927e31da9c76
npm install ng2-pdf-viewer
Partial Ivy compilated library bundles.
npm install ng2-pdf-viewer@^7.0.0
npm install ng2-pdf-viewer@~3.0.8
In case you're using systemjs see configuration here.
Add PdfViewerModule to your module's imports
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { PdfViewerModule } from 'ng2-pdf-viewer';
@NgModule({
imports: [BrowserModule, PdfViewerModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);
And then use it in your component
import { Component } from '@angular/core';
@Component({
selector: 'example-app',
template: `
<pdf-viewer [src]="pdfSrc"
[render-text]="true"
[original-size]="false"
style="width: 400px; height: 500px"
></pdf-viewer>
`
})
export class AppComponent {
pdfSrc = "https://vadimdez.github.io/ng2-pdf-viewer/assets/pdf-test.pdf";
}
| Property | Type | Required |
|---|---|---|
| [src] | string, object, UInt8Array | Required |
Pass pdf location
[src]="'https://vadimdez.github.io/ng2-pdf-viewer/assets/pdf-test.pdf'"
For more control you can pass options object to [src]. See other attributes for the object here.
Options object for loading protected PDF would be:
{
url: 'https://vadimdez.github.io/ng2-pdf-viewer/assets/pdf-test.pdf',
withCredentials: true
}
| Property | Type | Required |
|---|---|---|
| [page] or [(page)] | number | Required with [show-all]="false" or Optional with [show-all]="true" |
Page number
[page]="1"
supports two way data binding as well
[(page)]="pageVariable"
If you want that the two way data binding actually updates your page variable on page change/scroll - you have to be sure that you define the height of the container, for example:
pdf-viewer {
height: 100vh;
}
| Property | Type | Required |
|---|---|---|
| [stick-to-page] | boolean | Optional |
Sticks view to the page. Works in combination with [show-all]="true" and page.
[stick-to-page]="true"
| Property | Type | Required |
|---|---|---|
| [render-text] | boolean | Optional |
Enable text rendering, allows to select text
[render-text]="true"
| Property | Type | Required |
|---|---|---|
| [render-text-mode] | RenderTextMode | Optional |
Used in combination with [render-text]="true"
Controls if the text layer is enabled, and the selection mode that is used.
0 = RenderTextMode.DISABLED - disable the text selection layer
1 = RenderTextMode.ENABLED - enables the text selection layer
2 = RenderTextMode.ENHANCED - enables enhanced text selection
[render-text-mode]="1"
| Property | Type | Required |
|---|---|---|
| [external-link-target] | string | Optional |
Used in combination with [render-text]="true"
Link target
blanknoneselfparenttop[external-link-target]="'blank'"
| Property | Type | Required |
|---|---|---|
| [rotation] | number | Optional |
Rotate PDF
Allowed step is 90 degree, ex. 0, 90, 180
[rotation]="90"
| Property | Type | Required |
|---|---|---|
| [zoom] | number | Optional |
Zoom pdf
[zoom]="0.5"
| Property | Type | Required |
|---|---|---|
| [zoom-scale] | 'page-width'|'page-fit'|'page-height' | Optional |
Defines how the Zoom scale is computed when [original-size]="false", by default set to 'page-width'.
'page-width' with zoom of 1 will display a page width that take all the possible horizontal space in the container
'page-height' with zoom of 1 will display a page height that take all the possible vertical space in the container
'page-fit' with zoom of 1 will display a page that will be scaled to either width or height to fit completely in the container
[zoom-scale]="'page-width'"
| Property | Type | Required |
|---|---|---|
| [original-size] | boolean | Optional |
[original-size]="true"
| Property | Type | Required |
|---|---|---|
| [fit-to-page] | boolean | Optional |
Works in combination with [original-size]="true". You can show your document in original size, and make sure that it's not bigger then container block.
[fit-to-page]="false"
| Property | Type | Required |
|---|---|---|
| [show-all] | boolean | Optional |
Show single or all pages altogether
[show-all]="true"
| Property | Type | Required |
|---|---|---|
| [autoresize] | boolean | Optional |
Turn on or off auto resize.
!Important To make [autoresize] work - make sure that [original-size]="false" and pdf-viewer tag has max-width or display are set.
[autoresize]="true"
| Property | Type | Required |
|---|---|---|
| [c-maps-url] | string | Optional |
Url for non-latin characters source maps.
[c-maps-url]="'assets/cmaps/'"
Default url is: https://unpkg.com/pdfjs-dist@2.0.550/cmaps/
To serve cmaps on your own you need to copy node_modules/pdfjs-dist/cmaps to assets/cmaps.
| Property | Type | Required |
|---|---|---|
| [show-borders] | boolean | Optional |
Show page borders
[show-borders]="true"
| Property | Type | Required |
|---|---|---|
| (after-load-complete) | callback | Optional |
Get PDF information with callback
First define callback function "callBackFn" in your controller,
callBackFn(pdf: PDFDocumentProxy) {
// do anything with "pdf"
}
And then use it in your template:
(after-load-complete)="callBackFn($event)"
| Property | Type | Required |
|---|---|---|
| (page-rendered) | callback | Optional |
Get event when a page is rendered. Called for every page rendered.
Define callback in your component:
pageRendered(e: CustomEvent) {
console.log('(page-rendered)', e);
}
And then bind it to <pdf-viewer>:
(page-rendered)="pageRendered($event)"
| Property | Type | Required |
|---|---|---|
| (pages-initialized) | callback | Optional |
Get event when the pages are initialized.
Define callback in your component:
pageInitialized(e: CustomEvent) {
console.log('(pages-initialized)', e);
}
And then bind it to <pdf-viewer>:
(pages-initialized)="pageInitialized($event)"
| Property | Type | Required |
|---|---|---|
| (text-layer-rendered) | callback | Optional |
Get event when a text layer is rendered.
Define callback in your component:
textLayerRendered(e: CustomEvent) {
console.log('(text-layer-rendered)', e);
}
And then bind it to <pdf-viewer>:
(text-layer-rendered)="textLayerRendered($event)"
| Property | Type | Required |
|---|---|---|
| (error) | callback | Optional |
Error handling callback
Define callback in your component's class
onError(error: any) {
// do anything
}
Then add it to pdf-component in component's template
(error)="onError($event)"
| Property | Type | Required |
|---|---|---|
| (on-progress) | callback | Optional |
Loading progress callback - provides progress information total and loaded bytes. Is called several times during pdf loading phase.
Define callback in your component's class
onProgress(progressData: PDFProgressData) {
// do anything with progress data. For example progress indicator
}
Then add it to pdf-component in component's template
(on-progress)="onProgress($event)"
In your html template add input:
<input (change)="onFileSelected()" type="file" id="file">
and then add onFileSelected method to your component:
onFileSelected() {
let $img: any = document.querySelector('#file');
if (typeof (FileReader) !== 'undefined') {
let reader = new FileReader();
reader.onload = (e: any) => {
this.pdfSrc = e.target.result;
};
reader.readAsArrayBuffer($img.files[0]);
}
}
By default the worker is loaded from cdn.jsdelivr.net.
In your code update path to the worker to be for example /pdf.worker.mjs
(window as any).pdfWorkerSrc = '/pdf.worker.mjs';
This should be set before pdf-viewer component is rendered.
If you ever have a (super rare) edge case where you run in an environment that multiple components are somehow loaded within the same web page, sharing the same window, but using different versions of pdf.worker, support has been added. You can do the above, except that you can append the specific version of pdfjs required and override the custom path just for that version. This way setting the global window var won't conflict.
(window as any)["pdfWorkerSrc2.14.305"] = '/pdf.worker.mjs';
Use eventBus for the search functionality.
In your component's ts file:
pdf-viewer component,search() like this:@ViewChild(PdfViewerComponent) private pdfComponent: PdfViewerComponent;
search(stringToSearch: string) {
this.pdfComponent.eventBus.dispatch('find', {
query: stringToSearch, type: 'again', caseSensitive: false, findPrevious: undefined, highlightAll: true, phraseSearch: true
});
}
If this project help you reduce time to develop, you can give me a cup of tea :)