Home/Rows/Row Pinning
Rows

Row Pinning

Two kinds of band that never scroll out of the way. gcPinnedTop and gcPinnedBottom project your own markup across the full width; subHeaderTemplate and footerTemplate put a cell under each column instead. Which you want comes down to one question — does the content line up with a column, or not?

Full width — gcPinnedTop and gcPinnedBottom

Your own markup, projected into the grid and spanning it end to end. Scroll the columns sideways: the banner and the status bar do not move with them, because they belong to the grid rather than to any column.

Q2 invoice summary
Paid 4Pending 3Overdue 3
Total 596,200
Invoice #
Client
Project
Owner
Amount
Tax
Total
Status
Due date
1001
Umbrella Co
Cloud migration
Karim F.
12500
1250
13750
paid
2025-01-10
1002
Globex Inc
Mobile app
Ben O.
49500
4950
54450
pending
2025-02-11
1003
Weyland-Yutani
Data warehouse
Tara A.
86500
8650
95150
overdue
2025-03-12
1004
Initech LLC
API integration
Yara N.
27500
2750
30250
paid
2025-04-13
1005
Stark Industries
Dashboard
Elena N.
64500
6450
70950
pending
2025-05-14
1006
Acme Corp
Website redesign
Nadia H.
101500
10150
111650
overdue
2025-06-15
1007
Wayne Enterprises
Security audit
Walid B.
42500
4250
46750
paid
2025-07-16
1008
Cyberdyne
ML pipeline
Alice S.
79500
7950
87450
pending
2025-08-17
1009
Oscorp
E-commerce platform
Petra L.
20500
2050
22550
overdue
2025-09-18
1010
LexCorp
CRM development
Idris S.
57500
5750
63250
paid
2025-01-19
10 invoices
Revenue542,000Grand total596,200
40% collected
1–10 of 10

Put an element inside <gridcraft>, add gcPinnedTop or gcPinnedBottom, and that is the whole API:

<gridcraft […]>
  <div gcPinnedTop class="banner">Q2 invoice summary</div>
  <div gcPinnedBottom class="status-bar">10 invoices</div>
</gridcraft>

It is your markup, with your bindings, your classes and your components inside it — not a config object the grid renders for you. The two directives only decide where it goes.

They span the whole grid and do not scroll with the columns. There are nine columns here and the card is narrower than they are, so the body scrolls sideways — and the banner and the status bar stay exactly where they are. That is the difference from the second example, and it is only visible on a grid that overflows.

They also sit outside the row area, so paging, virtual scrolling and row height leave them alone. The numbers come from (rowsLoaded) — the rows the grid is holding — through ordinary computed()s, which is why they cannot drift from what is on screen.

Column aligned — subHeaderTemplate and footerTemplate

The same numbers, put under the columns they belong to. Scroll sideways and these bands move with the columns, because each one is a cell rather than a strip.

Invoice #
Client
Project
Owner
Amount
Tax
Total
Status
Due date
Avg 54,200
Target 90% paid
1001
Umbrella Co
Cloud migration
Karim F.
12,500
1250
13750
PAID
2025-01-10
1002
Globex Inc
Mobile app
Ben O.
49,500
4950
54450
PENDING
2025-02-11
1003
Weyland-Yutani
Data warehouse
Tara A.
86,500
8650
95150
OVERDUE
2025-03-12
1004
Initech LLC
API integration
Yara N.
27,500
2750
30250
PAID
2025-04-13
1005
Stark Industries
Dashboard
Elena N.
64,500
6450
70950
PENDING
2025-05-14
1006
Acme Corp
Website redesign
Nadia H.
101,500
10150
111650
OVERDUE
2025-06-15
1007
Wayne Enterprises
Security audit
Walid B.
42,500
4250
46750
PAID
2025-07-16
1008
Cyberdyne
ML pipeline
Alice S.
79,500
7950
87450
PENDING
2025-08-17
1009
Oscorp
E-commerce platform
Petra L.
20,500
2050
22550
OVERDUE
2025-09-18
1010
LexCorp
CRM development
Idris S.
57,500
5750
63250
PAID
2025-01-19
1–10 of 10

The same numbers, in cells instead of a strip. subHeaderTemplate renders between the header and the first row; footerTemplate renders under the last one. Both are declared per column, so each band gets a cell exactly as wide as the column it belongs to:

{ key: 'amount', name: 'Amount', prop: 'amount',
  subHeaderTemplate: subAmountTpl(),
  footerTemplate:    footerAmountTpl() }

Scroll sideways and these bands move with the columns — Avg stays under Amount, the paid percentage stays under Status. A full-width band cannot do that, and a column-aligned band cannot hold a toolbar. That is the whole decision.

Declaring either template on any column turns its band on for the whole grid; the columns that declare nothing render an empty cell in it. Both bands stay put while the rows scroll, exactly as the header does.

The template mechanics — the contexts, and getting a TemplateRef onto a column without breaking server-side rendering — are on Custom Cell Templates.

Two bands, one question

Both of these stay on screen while the rows scroll. They differ in what they are made of, and the choice is whether the content belongs to a column or to the grid:

  • gcPinnedTop / gcPinnedBottom — your own element, projected into the grid, spanning it end to end. It does not scroll horizontally with the columns. Banners, bulk-action toolbars, status bars, alerts.
  • subHeaderTemplate / footerTemplate — one cell per column, aligned to the column boundaries and scrolling with them. Per-column totals, averages, units, range hints.

Nothing stops you using both on one grid — a toolbar across the top and totals under the columns is a common pairing. They are separated here so the horizontal-scroll difference is visible; on a grid that fits its container the two look alike and the choice looks arbitrary.

Where the numbers come from

Neither band computes anything. (rowsLoaded) emits the rows the grid is holding, and everything on screen is derived from that with ordinary computed()s — so a sort, a filter or a page change moves the totals with the rows, and a band can never disagree with the body above it.

A total over the whole data set rather than the loaded page is your call: keep it in the same signal and fill it from your API's response. The grid does not know your aggregate, and does not pretend to.

gcPinnedTop and gcPinnedBottom are directives on projected elements, not config properties. Import them alongside GridcraftComponent — a missing import is a silent no-op, because an unknown attribute on a div is just an attribute.

Pinned rows API

PropertyTypeDefaultDescription
gcPinnedTopdirective (projected element)—A full-width element fixed above the column header. Does not scroll horizontally with the columns, and sits outside the row area.
gcPinnedBottomdirective (projected element)—The same, below the scroll area — status bars, bulk-action toolbars, running totals.
column.subHeaderTemplateTemplateRef<SummaryContext<T>>—A column-aligned band between the header and the first row. Declaring it on one column turns the band on for the whole grid.
column.footerTemplateTemplateRef<SummaryContext<T>>—A column-aligned band under the last row. Same context, same whole-grid rule. See Custom Cell Templates.
(rowsLoaded)EventEmitter<T[]>—The rows the grid is currently holding. Both examples derive every figure they show from it, so the bands cannot drift from the body.