Card 1
Columns balance themselves as content settles.
Add, prepend, remove and shuffle. There is nothing to call afterwards.
Every button below mutates the source array and nothing else. Item order is derived from the DOM at layout time, so a prepend really lands first — the ordering bug that makes other masonry wrappers expose a manual reloadItems() cannot occur here.
<!-- There is no reloadItems(). Change the array and the grid
follows â prepends land first, because item order is read
from the DOM on every pass. -->
<masonry-grid columnWidth="200" gutter="14" (removeComplete)="onRemoved($event)">
@for (card of cards(); track card.id) {
<article masonryGridItem (click)="remove(card.id)">
{{ card.title }}
</article>
}
</masonry-grid>
<!-- component -->
add() { this.cards.update((c) => [...c, makeCard(this.next++)]); }
prepend() { this.cards.update((c) => [makeCard(this.next++), ...c]); }
remove(id: number) { this.cards.update((c) => c.filter((x) => x.id !== id)); }Columns balance themselves as content settles.
Positioning runs on transforms, off the layout path.
A single ResizeObserver watches the whole grid.
Order comes from the DOM, so nothing shuffles.
Options are validated in development and compiled out of production.
Items hold their place until their images decode.
Repeated passes with identical input do no work.
Columns balance themselves as content settles.
Positioning runs on transforms, off the layout path.
A single ResizeObserver watches the whole grid.
Order comes from the DOM, so nothing shuffles.
Options are validated in development and compiled out of production.
Items hold their place until their images decode.
Repeated passes with identical input do no work.
Columns balance themselves as content settles.