Guide
Pagination
How ngpdf breaks content into pages - pagination units, table rows and row views taller than a page, text that breaks between lines with orphans and widows, repeated table headers, breakBefore, breakAfter, keeping a view or table together and pdf-page-number formats.
You do not place page breaks by hand. ngpdf lays out each pdf-page at its content width with unbounded height, then cuts the result into sheets of the page's size. This page explains where it cuts.
Units
ngpdf moves content between pages in whole units. A unit is never split, but a long pdf-text is cut into several units, so it can break between its lines (see Text across pages), and a table row or a row pdf-view taller than a page splits between the lines of its cells or children (see Rows taller than a page and Row views taller than a page).
Starting from the children of the pdf-page, ngpdf looks inside some elements and treats others as units:
| Element | Treated as |
|---|---|
pdf-view or block pdf-link with flex-direction: column (the default) and children |
a wrapper: its children are looked at in turn |
pdf-view or block pdf-link with flex-direction: row and flex-wrap: wrap or wrap-reverse |
a wrapper: each of its lines is one unit (see Wrapping rows) |
pdf-view with a transform |
one unit, with everything inside it, so it is never cut across sheets |
pdf-view with overflow: hidden, clip, scroll or auto |
one unit, with everything inside it, so its clip is never split |
pdf-table |
a wrapper: each of its rows is a unit; a row taller than a page splits instead (see Rows taller than a page) |
pdf-view or block pdf-link with flex-direction: row that does not wrap |
one unit, with everything inside it; one taller than a page splits instead (see Row views taller than a page) |
column pdf-view with flex-wrap whose children went into more than one column |
one unit, with everything inside it |
pdf-text |
its lines, grouped by orphans and widows; one unit if it is short, has break-inside: avoid, a fixed height, a transform or an overflow other than visible |
pdf-row, pdf-image, an empty pdf-view or one that holds only absolute elements and notes |
one unit |
an element with position: absolute |
never a unit; see Positioned elements |
pdf-note |
never a unit; see Notes |
Wrappers can nest to any depth, so a table inside a column pdf-view inside another column pdf-view still yields its rows as units. A table inside a cell or inside a row pdf-view is part of that unit.
When the next unit does not fit below the previous one, it moves to the top of a new sheet. The vertical space between units (margins, gaps, the padding of wrappers) is kept as it was in the layout, except that a unit moved to a new sheet starts at the top of the content area. The bottom padding and border of wrappers that close right after a unit move with that unit. An auto margin counts as the space Yoga gave it in that single layout: a box with margin-top: auto or margin-bottom: auto in a column split across sheets keeps that position, and the free space is not shared out again on each sheet.
If the page's own flex-direction is row, its whole content is one unit, unless the page wraps: then each of its lines is a unit.
Wrapping rows
A row with flex-wrap: wrap or wrap-reverse breaks between its lines. Each line is a unit with every child on it, so a line that fits on an empty sheet is never cut, and a card on it never leaves the rest of the line. Only a line taller than a sheet splits, as below.
- A line goes to the next sheet whole when it does not fit. The row gap (or
align-contentspace) before it is dropped there, and the line starts at the top of the content area, as a child of a column does. - A line taller than the content area splits across sheets like a row view, with the children on the line as its columns. A line with nothing that can break, such as a tall image or a box with a fixed height, goes on a sheet of its own and is clipped, as any unit taller than a page. On a wrapping
pdf-pagethe warning names the line:ngpdf: a wrapped line of pdf-page is taller than a page and is cut at the bottom. - The row's background and border are sliced per sheet like a wrapper's, and
break-inside: avoid,break-before,break-afterwork on it as on a columnpdf-view. - With
wrap-reverse, the lines go on the sheets top to bottom as drawn, so the first children end up last. align-contentis not worked out again per sheet: lines keep the places Yoga gave them in the single layout of the page, apart from the space dropped before a line that moves to a new sheet. It only matters when the row has free space on the cross axis, such as a fixedheighttaller than its lines.- A wrapping row inside a table cell or inside a row
pdf-viewis part of that unit and does not break.
Keep units small
A pdf-view with a transform or an overflow other than visible is never split, with everything inside it, text included. A table row or a row pdf-view that does not wrap is split only when it is taller than a page; one that fits moves to the next sheet whole. A row pdf-view with flex-wrap: wrap breaks between its lines.
Text across pages
A pdf-text that is a unit by the rules above, as a child of the page or of a column pdf-view, can break between two of its lines, as a paragraph does in CSS:
<pdf-text style="orphans: 3; widows: 2">{{ longParagraph() }}</pdf-text>orphansis the fewest lines left at the bottom of a page before the break, andwidowsthe fewest carried to the top of the next. Both are integers of at least 1, are inherited, and default to 2.- A text cut by
max-linessplits the same way, over the lines it keeps. - A text with fewer than
orphans + widowslines moves to the next page whole, as do texts withbreak-inside: avoid, a fixedheight, amax-height, atransformor anoverflowother thanvisible. - A text longer than a page continues over as many pages as it needs. The first
orphansand lastwidowslines always stay together, and a page in the middle keeps at least as many lines as the larger of the two. Each page takes as many lines as fit while the rest can still be laid out that way, so with lines of very different heights, such as a very largepdf-span, a page can end early or the text can start on the next page. - When no layout keeps those limits, because a few lines are taller than most of a page, lines are placed one by one between the first and last groups, and a page in the middle can have fewer lines.
- Its top padding and border stay with its first lines, the bottom ones with its last lines. A background or border is sliced like a wrapper's.
- A
pdf-textinside a rowpdf-viewor a table row is part of that unit. It breaks only when the row is taller than a page, as described below.
Rows taller than a page
A table row that fits on an empty page (below its repeated header, if the table has one) moves to the next page whole, as any unit. A row taller than that splits across sheets instead of being cut:
- It starts on the current sheet when at least one line of each non-empty cell fits there, and otherwise on the next sheet.
- A row that fits on an empty page only without its header splits only when every piece fits below the header. When one can't, because content that can't break is taller than the space under the header, the row goes whole on a sheet of its own, without the header (see Tables).
- Each cell breaks on its own: between the lines of its texts, or between the children of a column
pdf-viewinside it, at the last break that fits. The next sheet carries on from there, under the repeated header. orphansandwidowsdon't apply inside a split cell: any line can end a piece.- Content that can't break, such as an image, a row
pdf-view, a table or a box with a fixed height, moves whole to the next piece. One taller than a page is clipped, with the warning below. - Every piece of the row is as tall as its tallest cell piece on that sheet, so the borders of the cells line up. Cell and row backgrounds and borders are sliced like a wrapper's: no border is drawn at a cut, and the top padding stays with the first piece.
- Every cell of a split row is drawn from the top of each piece and stretched to its height:
align-itemson the row,align-selfon a cell, and the space thatjustify-contentadds above a cell's content are ignored. - An absolute element inside a split row is drawn on the sheet where its top lands, and cut at the bottom of that sheet.
break-inside: avoid on the row keeps it one unit, clipped as below. So does a fixed height or max-height, a transform or an overflow other than visible on the row. Header rows never split.
Row views taller than a page
A pdf-view or block pdf-link with flex-direction: row that is taller than an empty sheet splits the same way as a table row, with its children as the cells. This is how a two-column layout with a long main column runs over several pages:
<pdf-view style="flex-direction: row; column-gap: 24">
<pdf-view style="flex: 1">
@for (paragraph of article(); track $index) {
<pdf-text style="margin-bottom: 8">{{ paragraph }}</pdf-text>
}
</pdf-view>
<pdf-view style="width: 150; background-color: #f2f2f2; padding: 8">
<pdf-text>{{ sidebar() }}</pdf-text>
</pdf-view>
</pdf-view>- A row view that fits on an empty sheet moves whole, as before. Only a taller one splits, so documents that already fit keep their pages.
- It starts on the current sheet when at least one line of each non-empty child fits there, and otherwise on the next sheet.
- Each child breaks on its own: a
pdf-textbetween its lines, a columnpdf-viewbetween its children (and inside them, the same way).orphansandwidowsdon't apply. Content that can't break, such as an image, a nested rowpdf-viewor a box with a fixed height, moves whole to the next piece, or is clipped when it is taller than a page. - Every piece is as tall as its tallest child piece on that sheet.
- The row's background and border are sliced per piece, with no border drawn at a cut. A child the row stretched to its full height (the default
align-items: stretch) fills every piece too, so a sidebar's background runs down each sheet. A child that is shorter, such as withalign-items: flex-start, is drawn only as far as its content goes. - On the first sheet each child keeps its place in the row, its margins included. On the next sheets, each child's content carries on from the top of the piece.
- An absolute element of the row is drawn on the sheet where its top lands.
min-presence-aheadandbreak-after: avoidtreat a split row view as they treat a split table row: neither moves it, since it is taller than a page. Amin-presence-aheadon a child counts for the whole row when the row fits a page.
break-inside: avoid, a fixed height or max-height, a transform or an overflow other than visible on the row view keep it one unit, clipped with the warning below. There is no separate switch: break-inside: avoid keeps a view whole, and break-inside: auto (the default) lets it split.
Which views split
| View | Fits on an empty sheet | Taller than an empty sheet |
|---|---|---|
column pdf-view |
breaks between its children | breaks between its children |
column pdf-view with break-inside: avoid |
moves whole | breaks between its children |
row pdf-view |
moves whole | splits, each child on its own |
row pdf-view with break-inside: avoid, a fixed height or a max-height |
moves whole | one unit, clipped |
row pdf-view with flex-wrap: wrap |
breaks between its lines | breaks between its lines; a line taller than a sheet splits like a row view |
pdf-view with a transform or an overflow other than visible |
moves whole | one unit, clipped |
column pdf-view with flex-wrap whose children went into more than one column |
moves whole | one unit, clipped |
Units taller than a page
A unit taller than the content area goes on a sheet of its own and is clipped at the bottom of the content area. In development mode ngpdf logs a warning such as:
ngpdf: pdf-view is taller than a page and is cut at the bottom
Text lines that would cross the cut are not drawn, and images, backgrounds and side borders are cut at it. A pdf-text that can break between lines, and a table row or row pdf-view whose content can, continue on the next page instead. Only a group of lines that is itself taller than the content area, such as one line in a very large pdf-span, is still clipped.
Wrapper backgrounds and borders
A column pdf-view or a pdf-table with a background or a border that runs across several sheets is sliced: each sheet draws the part that holds its units. The bottom border is not drawn at the cut, and the top border is not drawn on the continued part, so the box reads as one box across pages.
A block pdf-link is sliced the same way: each sheet gets one clickable area over the part of the link on it.
Repeated table headers
A pdf-row with the header attribute repeats at the top of every sheet its table runs onto. See Tables for the cases where it is not repeated.
breakBefore
breakBefore on a pdf-view or pdf-text starts a new sheet before that element. On any element but a pdf-span, the style break-before: page does the same. Inside a unit, a break starts the new sheet before that whole unit, since a unit is never split: break-before: page on a pdf-cell in a table row, or breakBefore on a pdf-text inside a row pdf-view.
@for (chapter of chapters(); track chapter.title; let first = $first) {
<pdf-view [breakBefore]="!first">
<pdf-text style="font-size: 18; font-weight: bold; margin-bottom: 8">
{{ chapter.title }}
</pdf-text>
@for (paragraph of chapter.paragraphs; track $index) {
<pdf-text style="margin-bottom: 6">{{ paragraph }}</pdf-text>
}
</pdf-view>
}On a wrapper, the break happens before its first unit. On a repeated table header row, it happens before the table's first row, once (see Tables). A break on a sheet that has no units yet is ignored, so you never get a blank page.
breakAfter
breakAfter on a pdf-view or pdf-text, or the style break-after: page on any element but a pdf-span, starts a new sheet after that element. It works like breakBefore turned around:
- On a wrapper, the next sheet starts after its last unit. A split
pdf-textbreaks after its last line, never between its lines. - Inside a unit, the break comes after the whole unit:
break-after: pageon apdf-cellbreaks after its row. - When nothing follows on that
pdf-page, it does nothing, so the last element never adds a blank sheet. break-after: avoidoravoid-pagekeeps the element with what follows instead; see Keeping a heading with what follows.autodoes nothing, and any other value throws.
Keeping a view or table on one sheet
break-inside: avoid on a column pdf-view or a pdf-table keeps it on one sheet when it can:
<pdf-view style="break-inside: avoid">
<pdf-text style="font-weight: bold">Totals</pdf-text>
<pdf-table [columns]="['1fr', 80]">…</pdf-table>
</pdf-view>- If it does not fit in the space left on the sheet but fits on a whole sheet, it moves to the next sheet in one piece. A table's header is drawn once, above its first row.
- If it is taller than a sheet, it splits as it would without
avoid: it is not clipped and there is no warning. A table's header repeats as usual. - Wrappers that close with it count: the bottom padding and border of a view that ends with it must fit too.
- When kept views nest, the outermost one that fits on a sheet is kept. If the outer one is too tall, a view inside it can still be kept on its own.
- A
pdf-vieworpdf-tableinside a row is already part of that row's unit, soavoidchanges nothing there. On apdf-text,avoidkeeps the text as one unit, as described in Text across pages.
Keeping a heading with what follows
break-after: avoid (or avoid-page) keeps an element on the same sheet as what comes after it, so a heading does not end a sheet alone, except in the cases listed below:
<pdf-text style="font-size: 14; font-weight: bold; break-after: avoid">Totals</pdf-text>
<pdf-text>The figures below cover the whole year…</pdf-text>- When the unit after it does not fit in the space left, the element moves to the next sheet with it.
- When that unit is a
pdf-textsplit across sheets, only its first lines need to fit: the firstorphanslines. When it is a table, its first row and its header. - On a column
pdf-view, it applies to the view's last unit, and on a splitpdf-textto its last lines. Inside a unit, such as a cell in a row, it applies to the whole row. - Elements in a row that all have
avoid, such as a heading and a subheading, move together with the unit after the last one. - When the whole group would not fit on an empty sheet,
avoiddoes nothing and the sheet breaks as usual, so it never pushes a sheet's worth of content forward. - A page break asked for between them wins, and an element already at the top of a sheet stays there.
- The last lines of a
pdf-textthat began on an earlier sheet stay where the text's own breaks put them, soorphansandwidowsstill hold. What follows moves alone. - A view or table with
break-inside: avoidafter it counts with its whole height. - It is a style only; there is no input for it, and it is not inherited.
Leaving room after an element
min-presence-ahead asks for room below an element. When the element would end a sheet with less than that much space left under it, it moves to the next sheet, so a section title never sits at the very bottom with its first paragraph on the next sheet:
<pdf-text style="font-size: 14; font-weight: bold; min-presence-ahead: 80pt">Totals</pdf-text>
<pdf-text>The figures below cover the whole year…</pdf-text>- It takes
0(the default, which turns it off) or a length in any unit ngpdf reads. A percentage, a keyword or a negative length throws. It is a style only, and it is not inherited. - The space left is measured to the bottom of the content area, above the page number and any fixed footer, which keep their own space.
- An element already at the top of a sheet stays there. So does the last element of a
pdf-page, and one a page break follows (its ownbreak-after: pageorbreakAfter, or abreak-before: pageorbreakBeforeon what comes next), since nothing follows it on that sheet. - It works on any element that is a unit, such as a
pdf-text, apdf-imageor a row in a table, and on a columnpdf-view, apdf-textsplit into line groups or apdf-table, which then move whole. A value on a cell counts for its whole row. A row that moves gets its table's header repeated above it, as usual. - It applies only when the element fits whole in what is left of the sheet. One that does not moves or splits as it would without it, and the last part of an element split across sheets stays where it lands.
- A
headerrow's own value is ignored, whether the header repeats or not. - It works on its own next to
break-after: avoid. When an element moves because of it, the elements before it joined to it bybreak-after: avoidmove with it. A joined group that would not fit on an empty sheet is left where it is, asbreak-after: avoidleaves it.
Positioned elements
An element with position: absolute is never a unit. It never moves, splits or pushes other units, and page breaks asked for on it or inside it do nothing.
- When its containing block is one unit (a row
pdf-view, or a view with atransformor anoverflowother thanvisible), it is drawn with that unit, on the same sheet. - When its containing block is split across sheets (a column
pdf-view, apdf-tableor the page itself), it goes on the sheet where its own top lands, moved like the units around it. One whose top falls in the space a page break skips goes to the top of the next sheet. One whose top falls below the last unit's sheet goes on a new sheet, at its top, and the others below that sheet go on the new one too, moved with it, until one's top falls below it and starts the next. It is never drawn above the top of the content area. - It is never split: one taller than what is left of its sheet is cut at the bottom, with a dev-mode warning.
- Against the page,
bottomand percentages of the height are measured on the page's whole content, all sheets together, not on one sheet. - Inside a fixed view it is drawn with the view on every sheet, and the view is its containing block.
- A
position: relativeelement is placed like any other: pagination ignores its offsets, which only move it when it is drawn. A relative columnpdf-viewthat is split moves each of its pieces by its offsets.
Notes
A pdf-note takes no space and is never a unit of its own. A note in the flow never adds a sheet: it goes on a sheet that other content already needs. A page with nothing but notes still gets its one sheet, as an empty page does.
- It goes on the sheet of the content right after it, at the top of where that content starts. When that content moves to the next sheet, or a
breakBeforeorbreakAftersends it there, the note goes with it. - When it is the last child of its parent, it goes on the sheet of the content before it, at its bottom.
- Inside a unit, such as a row
pdf-view, it is drawn with that unit. - Inside a table row split across sheets, it goes on the piece where its point falls: with the content after it at a break, or on the last piece of its cell when it is that cell's last child. A note right after a split row goes on the row's last piece, at its bottom.
- Inside a fixed view it is written on every sheet.
- With
position: absoluteit follows the rules of positioned elements instead, so one placed below the sheet of the last content goes on a new sheet.
Fixed headers and footers
Give a pdf-view that is a direct child of pdf-page the fixed input to draw it on every sheet:
<pdf-page style="padding: 36">
<pdf-view fixed="top" style="flex-direction: row; margin-bottom: 12">
<pdf-text style="flex: 1; font-weight: bold">Stock count</pdf-text>
<pdf-text>Warehouse 7</pdf-text>
</pdf-view>
…
<pdf-view fixed="bottom" style="margin-top: 8">
<pdf-text style="font-size: 8">Made-up data</pdf-text>
</pdf-view>
<pdf-page-number />
</pdf-page>- A fixed view is laid out once and drawn the same on every sheet that
pdf-pageproduces. It is taken out of the flow wherever you write it among the page's children. - A fixed view is as wide as its
width(a percentage is of the content width), or else the content width less its margins. Left and rightautomargins place it as in CSS, sowidth: 200; margin: 0 autocentres it. Top and bottomautomargins on a fixed view count as 0. fixed="top"views stack at the top of the content area, andfixed="bottom"views at the bottom, in template order, with their margins. The flow gets the space between them on every sheet.- Bottom views sit above the
pdf-page-number, which keeps the very bottom. - A fixed view is never a pagination unit: it is not split, moved or clipped, and page breaks on it or inside it (
breakBefore,breakAfter,break-before,break-after) do nothing. - A fixed view can have a
transform, such as a header turned along the page edge. It is drawn transformed on every sheet, and the flow keeps the space of its untransformed box. - Fixed views that leave no room for content fail the render with
ngpdf: fixed views leave no room for content on pdf-page. - A
fixedview nested deeper than a direct child ofpdf-pageis laid out as an ordinary view where it is, and logsngpdf: a fixed pdf-view must be a direct child of pdf-pagein development mode: inside the flow it stays in the flow, and inside a fixed view it repeats with that view. - The content of a fixed view is the same on every sheet, except a
pdf-page-numberinside it, which shows each sheet's number (see Page numbers), and apdfPerSheettemplate (see Content that changes per sheet).
Content that changes per sheet
Put an ng-template with the pdfPerSheet directive inside a fixed view to render different content on each sheet, such as "Continued on next page" on every sheet but the last:
<pdf-view fixed="bottom" style="height: 20; flex-direction: row">
<ng-template pdfPerSheet let-sheet let-total="total">
<pdf-text style="flex: 1">Sheet {{ sheet }} of {{ total }}</pdf-text>
@if (sheet < total) {
<pdf-text>Continued on next page</pdf-text>
} @else {
<pdf-text>End of report</pdf-text>
}
</ng-template>
</pdf-view>pdfPerSheet comes with PDF_ELEMENTS, so there is nothing else to import. The template gets these values:
| Name | Type | Value |
|---|---|---|
$implicit, sheet |
number |
The sheet's number, from 1, across all pdf-page elements |
total |
number |
The number of sheets in the whole document |
first |
boolean |
Whether it is the first sheet of the document |
last |
boolean |
Whether it is the last sheet of the document |
page |
number |
Which pdf-page the sheet comes from, from 1, in template order |
- The template renders like the rest of the document: pipes, inputs, services and your own components work in it. Change detection runs on the template alone, once per sheet. Each template destroys its previous sheet's content before it renders the next one, and its last one when the document is destroyed, so a long document keeps at most one sheet's content per template in memory.
- The rest of the fixed view's content stays the same on every sheet. The template's content is drawn where the
ng-templatesits in the view. - A
pdf-page-numberinside the template shows the same numbers assheetandtotal. - A
pdf-imagein the template loads like any other, under the samemaxImageBytesand serverfetchImage. Each sheet loads only the sources no earlier sheet loaded, so an image over the cap that only a later sheet shows still rejects the render. pdfPerSheetworks only inside a fixed view (at any depth inside it). Anywhere else, the render fails withngpdf: pdfPerSheet must be inside a fixed pdf-view. Content in the flow that changed per sheet could move a page break, which would change the number of sheets and so the content again.- An
anchororbookmarkinside the template is ignored, since its element would be on every sheet. Put it on the fixed view itself.
Height
The fixed view reserves its height before the sheets are known. To measure it, the template is rendered once as if the document had one sheet: sheet and total are 1, and first and last are true. The flow gets the space that leaves, as with any fixed view.
On each sheet, the template's content is laid out in that reserved height. Content taller than the reservation is cut at the bottom of the view, with one development-mode warning per render: ngpdf: pdfPerSheet content is taller on sheet <n> than on the first; set a height on the fixed view.
When the content's height varies from sheet to sheet, give the fixed view a height that fits the tallest version, as in the example above.
Page numbers
A pdf-page-number shows the sheet's number. Put it directly inside a pdf-page to reserve the bottom of every sheet for it:
<pdf-page style="padding: 48">
…
<pdf-page-number format="Page {page} of {total}" style="font-size: 8; color: #777777" />
</pdf-page>formatreplaces{page}with the sheet's number and{total}with the number of sheets in the whole document, across allpdf-pageelements. Each token can appear any number of times. The default format is{page} / {total}.- It sits at the bottom of the content area, inside the page padding, across the content width, on every sheet that
pdf-pageproduces. It is taken out of the flow wherever you write it among the page's children, sobreak-beforeandbreak-afteron it do nothing. - It reserves its height on every sheet: one line of its font (
font-size × line-height) plus its own vertical padding and borders, or itsheightif you set one. Content and bottom fixed views stop above it. Its text sits in the middle of that line, as in apdf-text. - It is right-aligned unless it, or an element above it, sets
text-align. - Text styles apply:
font-size,color,font-weightand the rest. Other position styles are ignored. - The text is drawn on one line and does not wrap. Content you put inside the element is ignored.
- Only the first
pdf-page-numberamong the direct children of apdf-pagereserves the bottom; other direct children are ignored. Page numbers placed anywhere else are drawn too.
Anywhere else
A pdf-page-number that is not a direct child of pdf-page is laid out where you write it, like a one-line pdf-text, and shows the numbers of the sheet it is drawn on. In a fixed header, that is every sheet:
<pdf-view fixed="top" style="flex-direction: row">
<pdf-text style="flex: 1; font-weight: bold">Stock count</pdf-text>
<pdf-page-number format="Sheet {page} of {total}" />
</pdf-view>- The layout is done once for all sheets, so its box is measured with every
{page}and{total}taken as three digits. The drawn text is aligned inside that box bytext-align, so a number that grows from 9 to 10 does not move what is next to it. - In the flow, it shows the number of the sheet its unit lands on.
{total}is always the number of sheets in the whole document. - Inside a
pdf-textit is flattened like any other element there; put it next to the text instead, for example in a rowpdf-view.
Pages with an auto height
A page with an auto height ([size]="[226, 'auto']", see Pages that grow with their content) is cut the same way, on a sheet 14400pt tall. Each sheet then ends below its content: bottom fixed views and the page number move up to sit right after it. A page break you ask for (break-before, break-after) starts a sheet sized to its own content. As in CSS, a bottom margin just before such a break is dropped; the padding and border of the wrappers that end there are kept. Content taller than 14400pt breaks onto further sheets, with one warning.
Warnings
All of these warnings are printed with console.warn at the end of the render, once each, and only in development mode (when Angular's ngDevMode is on). Production builds print nothing.