Read the rendered element from viewChild and call SnapDOM from the click handler. If the same handler first changes a signal, wait for afterNextRender or you will capture the previous view.
Capture the current Angular view
Install @zumer/snapdom, give the capture root a template reference, and keep the download button outside that root.
npm install @zumer/snapdom
import { Component, ElementRef, viewChild } from '@angular/core'
import { snapdom } from '@zumer/snapdom'
@Component({
selector: 'app-share-card',
template: `
<article #card>
<h2>Quarterly report</h2>
<p>Rendered by Angular.</p>
</article>
<button type="button" (click)="download()">Download PNG</button>
`,
})
export class ShareCardComponent {
readonly card = viewChild.required<ElementRef<HTMLElement>>('card')
async download() {
await snapdom.download(this.card().nativeElement, {
format: 'png',
filename: 'angular-card.png',
scale: 2,
})
}
}
The click happens after the component is mounted, so the required query has a real element. SnapDOM reads Angular’s scoped styles from the computed browser result; it does not need a directive, service, or Angular-specific wrapper.
Capture after changing Angular state
Angular does not update the DOM at the line where a signal changes. Wrap afterNextRender in a promise when one action must expand the card and then capture the expanded view. Passing the component injector matters because the helper is registered from an event method, outside Angular’s creation-time injection context.
import {
afterNextRender, inject, Injector, signal,
} from '@angular/core'
readonly expanded = signal(false)
private readonly injector = inject(Injector)
private waitForNextRender() {
return new Promise<void>((resolve) => {
afterNextRender(resolve, { injector: this.injector })
})
}
async downloadExpanded() {
this.expanded.set(true)
await this.waitForNextRender()
await this.download()
}
afterNextRender waits for Angular’s next render, not for an API request you have not awaited. Resolve the data first, update the component state, then wait for that render.
Conditional views, images, and fonts
viewChild.required fits an element that is always in the template. If the card lives inside @if, use viewChild<ElementRef<HTMLElement>>('card') and return early when the query is empty. Capturing before the branch exists is the usual cause of a missing or zero-size target.
Render completion also does not imply that an image request or web font has finished. If either changes the card’s geometry, await the relevant image’s decode() and document.fonts.ready before capture. Use embedFonts: true when an SVG must carry its font data with it.
SSR and hydration
SnapDOM needs browser layout, computed styles, SVG, Canvas, and Image APIs. Keep the call in a browser event path. Angular render callbacks do not run during server rendering or build-time prerendering, and an explicit download button naturally waits until the page is hydrated.
Frequently asked questions
Does SnapDOM have an Angular-specific package?
No. Import @zumer/snapdom in the component that owns the element reference. SnapDOM works with the browser DOM rather than Angular internals.
When should capture run in Angular?
A button handler can capture the DOM that is already rendered. If that handler first changes a signal or input used by the capture, wait for afterNextRender before calling SnapDOM.
Why is conditional content missing?
The @if branch may not exist yet, or the capture may be reading the previous render. Guard an optional viewChild query and wait for the next render after changing state.
Add capture to an Angular component
Keep the target in a view query and call SnapDOM from the browser event that owns the download.
Open the demoInstall from npm