This comparison evaluates five key libraries for displaying PDF documents in modern web applications. pdfjs-dist is the core rendering engine maintained by Mozilla, serving as the foundation for the other wrappers. react-pdf is the standard React component wrapper around this engine. For Angular developers, ng2-pdf-viewer, ng2-pdfjs-viewer, and ngx-extended-pdf-viewer offer varying levels of abstraction and feature completeness, with ngx-extended-pdf-viewer currently standing as the most robust and maintained Angular-specific solution.
Displaying PDF files in a web browser is a common requirement for dashboards, document management systems, and reporting tools. The ecosystem offers several options ranging from low-level rendering engines to high-level UI components. This analysis compares pdfjs-dist, react-pdf, and three Angular-specific wrappers (ng2-pdf-viewer, ng2-pdfjs-viewer, ngx-extended-pdf-viewer) to help you make the right architectural choice.
Understanding the relationship between these packages is critical. pdfjs-dist is the core engine. The others are wrappers that make it easier to use within specific frameworks.
pdfjs-dist is the official Mozilla library. It handles the heavy lifting of parsing and rendering. You manage the DOM and state yourself.
// pdfjs-dist: Manual rendering setup
import * as pdfjsLib from 'pdfjs-dist';
const loadingTask = pdfjsLib.getDocument('file.pdf');
loadingTask.promise.then(function(pdf) {
pdf.getPage(1).then(function(page) {
const scale = 1.5;
const viewport = page.getViewport({ scale: scale });
const canvas = document.getElementById('the-canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({ canvasContext: context, viewport: viewport });
});
});
react-pdf wraps this engine in React components. It handles the lifecycle for you.
// react-pdf: Component-based usage
import { Document, Page } from 'react-pdf';
function MyApp() {
return (
<Document file="file.pdf">
<Page pageNumber={1} />
</Document>
);
}
ngx-extended-pdf-viewer wraps the engine for Angular with a full UI toolbar.
<!-- ngx-extended-pdf-viewer: Full UI component -->
<ngx-extended-pdf-viewer
[src]="'file.pdf'"
[useBrowserLocale]="true"
height="90vh">
</ngx-extended-pdf-viewer>
ng2-pdf-viewer is an older Angular wrapper that renders pages as canvas elements without the full toolbar UI by default.
<!-- ng2-pdf-viewer: Basic canvas rendering -->
<pdf-viewer
[src]="pdfSrc"
[render-text]="true"
[show-all]="true"
style="display: block;">
</pdf-viewer>
ng2-pdfjs-viewer is another Angular wrapper that aims to expose more of the underlying PDF.js viewer functionality than the basic canvas renderer.
<!-- ng2-pdfjs-viewer: Exposing viewer options -->
<pdfjs-viewer
[src]="pdfSrc"
[show-toolbar]="true"
style="width: 100%; height: 100%;">
</pdfjs-viewer>
The biggest difference lies in how much UI work you want to do yourself.
ngx-extended-pdf-viewer provides a complete interface. It includes zoom controls, page navigation, search, and printing buttons out of the box. This saves weeks of development time if you need a standard reader experience.
<!-- ngx-extended-pdf-viewer: Built-in search and print -->
<ngx-extended-pdf-viewer
[src]="documentUrl"
[show-search-button]="true"
[show-print-button]="true">
</ngx-extended-pdf-viewer>
react-pdf gives you the rendering but not the controls. You must build your own toolbar for zooming or page turning. This offers maximum design flexibility but requires more code.
// react-pdf: Custom controls required
function Reader({ file }) {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
return (
<div>
<button onClick={() => setPageNumber(prev => prev - 1)}>Prev</button>
<Document file={file} onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} />
</Document>
<button onClick={() => setPageNumber(prev => prev + 1)}>Next</button>
</div>
);
}
pdfjs-dist requires you to build everything. There is no UI. You are responsible for the canvas, the buttons, and the state management.
// pdfjs-dist: Completely custom UI implementation
const canvas = document.createElement('canvas');
// ... rendering logic ...
document.getElementById('controls').appendChild(canvas);
// You must write event listeners for zoom/pan manually
ng2-pdf-viewer focuses on rendering pages in the flow. It does not include the Adobe-style toolbar by default. You build the navigation logic in your Angular component.
<!-- ng2-pdf-viewer: Custom navigation logic -->
<pdf-viewer
[src]="src"
[(page)]="page"
[zoom]="zoom">
</pdf-viewer>
<button (click)="page = page - 1">Previous</button>
<button (click)="page = page + 1">Next</button>
ng2-pdfjs-viewer attempts to bridge the gap by including some viewer options, but it often requires more configuration to match the polish of ngx-extended-pdf-viewer.
<!-- ng2-pdfjs-viewer: Configuring viewer options -->
<pdfjs-viewer
[src]="src"
[options]="{ showToolbar: true }">
</pdfjs-viewer>
Setting up the worker file is a common pain point with PDF.js based libraries. The worker is a separate script that handles parsing to keep the UI thread free.
react-pdf requires you to set the worker source explicitly to match the version you are using. Mismatched versions cause runtime errors.
// react-pdf: Setting the worker source
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
pdfjs-dist requires similar manual configuration of the GlobalWorkerOptions.
// pdfjs-dist: Manual worker configuration
pdfjsLib.GlobalWorkerOptions.workerSrc = 'path/to/pdf.worker.js';
ngx-extended-pdf-viewer simplifies this for Angular. It often handles the worker path automatically or provides clear Angular CLI configuration steps to copy the assets.
// ngx-extended-pdf-viewer: Angular module config
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
@NgModule({
imports: [
NgxExtendedPdfViewerModule.useDefaults()
]
})
export class AppModule {}
ng2-pdf-viewer requires you to configure the worker source in your root component or module setup, similar to the raw library.
// ng2-pdf-viewer: Worker setup in component
import { PdfViewerModule } from 'ng2-pdf-viewer';
import * as pdfjsLib from 'pdfjs-dist';
pdfjsLib.GlobalWorkerOptions.workerSrc = 'path/to/pdf.worker.js';
ng2-pdfjs-viewer also relies on the underlying pdfjs-dist worker configuration, meaning you must ensure the paths are correct in your build process.
// ng2-pdfjs-viewer: Ensuring worker path is valid
// Typically configured in angular.json assets or component init
Performance depends on how the library handles large documents and viewport changes.
react-pdf renders pages as canvas elements. It supports lazy loading pages, which is crucial for large files. You control when pages render.
// react-pdf: Lazy loading pages
<Document file={largeFile}>
<Page pageNumber={1} renderAnnotationLayer={false} />
<Page pageNumber={2} renderAnnotationLayer={false} />
</Document>
ngx-extended-pdf-viewer uses the standard PDF.js viewer which includes virtualization (only rendering visible pages). This handles large files well without extra code from you.
<!-- ngx-extended-pdf-viewer: Automatic virtualization -->
<ngx-extended-pdf-viewer
[src]="largeFile"
[show-all]="false">
</ngx-extended-pdf-viewer>
pdfjs-dist gives you raw control. You must implement virtualization logic yourself if you want to support 100+ page documents without freezing the browser.
// pdfjs-dist: Manual virtualization logic required
// Listen to scroll events and render only visible pages
container.addEventListener('scroll', () => {
// Calculate visible range and render pages
});
ng2-pdf-viewer renders all pages by default if show-all is true, which can cause performance issues with large files. You should paginate manually for better performance.
<!-- ng2-pdf-viewer: Pagination for performance -->
<pdf-viewer
[src]="src"
[show-all]="false"
[page]="currentPage">
</pdf-viewer>
ng2-pdfjs-viewer inherits the performance characteristics of the PDF.js viewer it wraps, generally handling virtualization better than basic canvas wrappers.
<!-- ng2-pdfjs-viewer: Viewer mode performance -->
<pdfjs-viewer
[src]="src"
[view]="'fit-width'">
</pdfjs-viewer>
This is a critical factor for long-term projects.
ng2-pdf-viewer is effectively in maintenance mode or deprecated for new features. The community has largely moved to ngx-extended-pdf-viewer. Using this for a new project introduces technical debt.
// ng2-pdf-viewer: Legacy implementation
// Avoid for new projects
import { PdfViewerModule } from 'ng2-pdf-viewer';
ngx-extended-pdf-viewer is actively maintained. It tracks updates to the underlying PDF.js library and fixes Angular-specific integration issues regularly.
// ngx-extended-pdf-viewer: Active maintenance
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
react-pdf is the standard for React. It is actively maintained and updated to support recent React versions and PDF.js updates.
// react-pdf: Standard React implementation
import { Document } from 'react-pdf';
pdfjs-dist is maintained by Mozilla. It is stable but changes occasionally in breaking ways, which is why wrappers exist.
// pdfjs-dist: Core engine
import * as pdfjsLib from 'pdfjs-dist';
ng2-pdfjs-viewer has lower adoption and update frequency compared to ngx-extended-pdf-viewer. Verify the latest commit history before relying on it for critical features.
<!-- ng2-pdfjs-viewer: Check maintenance status -->
<pdfjs-viewer [src]="src"></pdfjs-viewer>
| Feature | pdfjs-dist | react-pdf | ngx-extended-pdf-viewer | ng2-pdf-viewer |
|---|---|---|---|---|
| Framework | Vanilla JS | React | Angular | Angular |
| UI Components | None | Canvas Only | Full Toolbar | Canvas Only |
| Setup Complexity | High | Medium | Low | Medium |
| Maintenance | Active (Mozilla) | Active | Active | Low / Legacy |
| Best For | Custom Engines | React Apps | Angular Enterprise Apps | Legacy Angular Apps |
For React Developers: Use react-pdf. It is the community standard. You will find the most support and examples online. Be prepared to build your own toolbar.
For Angular Developers: Use ngx-extended-pdf-viewer. It saves significant development time by providing a complete UI. Avoid ng2-pdf-viewer for new work as it is older technology.
For Custom Needs: Use pdfjs-dist only if you are building your own framework wrapper or have very specific rendering requirements that the existing wrappers cannot satisfy.
Choose ng2-pdf-viewer only if you are maintaining a legacy Angular application that already depends on it. It is largely considered deprecated in favor of newer wrappers. Do not start new projects with this package as it lacks recent updates and modern feature support compared to alternatives.
Choose ng2-pdfjs-viewer if you need a lightweight Angular wrapper that stays very close to the raw pdfjs-dist API without extra UI features. It is suitable for simple use cases where you want to build custom controls from scratch, but verify its maintenance status before committing as it sees less activity than extended versions.
Choose ngx-extended-pdf-viewer for new Angular projects requiring a full-featured PDF viewer. It includes a toolbar, search, and printing capabilities out of the box. This is the recommended choice for Angular teams who want a 'batteries-included' experience similar to Adobe Acrobat Reader within their app.
Choose pdfjs-dist if you are building a custom framework wrapper or need complete control over the rendering pipeline without any UI opinions. It is the underlying engine for the other libraries. Use this if you are comfortable managing the viewer lifecycle, rendering contexts, and event listeners manually in vanilla JavaScript or a custom framework setup.
Choose react-pdf if you are building an application with React. It provides a clean component-based API to render PDF pages as canvas elements. It is the standard choice for React developers who need to display PDF content while managing state and props within the React ecosystem.
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 :)