PrimeNG 22.1.1 is out. It is a patch on top of 22.1.0, so there are no new components and no API changes, just the fixes that came in after the last release.
npm install [email protected]
Most of these came from users reporting them, so if you hit one of them and opened an issue, thank you. Below is what changed and, where it matters, what you can stop working around.
Table with lazy and stateKey together
If you used lazy and stateKey on the same table, the header came out empty.
No columns at all, which is a strange one to look at because the rows render
fine.
The reason was on our side: the code that assigns the columns was inside a branch that only ran for tables without a state key. A table with a state key went down the other path, and that path expected the columns to already be set. They never were. Both cases now share the same assignment before anything else happens, so this combination works:
<p-table [value]="products" [columns]="cols" [lazy]="true" stateKey="products-table" [paginator]="true" [rows]="10" (onLazyLoad)="load($event)"></p-table>
The other Table fix is related to the paginator. After you cleared a column filter, the paginator kept showing the old record count. We fixed a version of this in 22.1.0, but the same mistake was in two more places that the earlier fix did not cover: the count was only recalculated while the previous count was zero, and after the first load it never is. So every recalculation after that returned the number it already had. It counts properly now, in all three places.
Dialog and appendTo
appendTo takes an element, but Dialog was only really reading it to check
whether it said self. Anything else, including an element you passed, ended up
on document.body.
This works now:
<div #host class="relative">
<p-dialog [(visible)]="visible" [appendTo]="host()" header="Details">
<p>Content</p>
</p-dialog>
</div>
export class DialogDemo {
host = viewChild.required<ElementRef>('host');
visible = signal(false);
}
One thing to check after upgrading. If you were moving the dialog element
yourself because appendTo did not work, remove that code. The component moves
the node now, and if both of you are moving it you will get a fight over the
same element.
ConfirmDialog goes through the same path, so it behaves the same way.
DatePicker marking the control as touched
In a reactive form with a required date, an error message appeared for a moment
while the picker was still open, before you had finished picking anything. The
control was being marked as touched at the point of selection, before the value
was written back, so for that moment the form saw a touched and empty control
and any error styling bound to touched showed up.
It now waits until the overlay closes, which is closer to what people mean by finishing with a field:
form = new FormGroup({
startDate: new FormControl<Date | null>(null, Validators.required)
});
<p-datepicker formControlName="startDate" />
@if (form.controls.startDate.touched && form.controls.startDate.invalid) {
<small class="text-red-500">Start date is required.</small>
}
Inline pickers do not have an overlay, so they still mark the control as touched when you select a date.
Menu with a custom item template
Keyboard navigation stopped working when you used your own item template. You could move the focus with the arrow keys, but Enter did nothing.
Internally the Enter key handler looks for the anchor element that Menu renders for each item and clicks it. When you provide a template, your markup replaces that anchor, so the lookup found nothing and the click went to the row instead, where there is no handler. It now falls back to the item content wrapper, so this works with the keyboard:
<p-menu [model]="items">
<ng-template #item let-item>
<div class="flex items-center gap-2 px-3 py-2">
<i [class]="item.icon"></i>
<span>{{ item.label }}</span>
</div>
</ng-template>
</p-menu>
Carousel page, TreeSelect virtual scroll
Carousel’s page input was not setting the starting page in v22. The code that
reads the initial page was in a lifecycle step that no longer ran before the
first render, so the carousel always started at the first item regardless of
what you passed. It is scheduled explicitly now, so [page]="2" starts where
you asked.
TreeSelect’s overlay was misaligned when virtualScroll was on, usually opening
above the input instead of below it. The panel only had a max height, so its
real height depended on its content, and the overlay was positioned against a
box that then changed size once the scroller measured itself. With virtual
scroll on, the panel now gets a fixed height and the position holds.
A cleanup pass, and what it found
The other half of this release is not a feature. We turned on a set of lint rules that had never been enforced on the codebase and worked through the result, which was about 3,600 errors. Most of it was what you would expect from that kind of pass: formatting, unused parameters, ordering. Boring, and worth doing, but not something you would write about.
A few of them were real bugs though, and that is the part I found interesting.
Three places had a typo in an environment check, comparing typeof against the
value undefined rather than the string "undefined". Since typeof gives you
a string back, that comparison is true every time and the check does nothing.
These were in our documentation site rather than in the library, so no
application was affected. Still, it is a good example of the type of thing that
survives code review: it reads correctly, it does not crash, and it does
nothing.
Two were in the library itself. In one, someone had written ?. on a value to
say it might be missing, then immediately destructured properties off it, which
throws when it is missing. The optional chaining looked like a guard but the
next character undid it. In the other, a helper was declared as taking a
non-null element while both callers passed something that can be null, with a
! added to make the type error go away. At the top of a tree that value is
genuinely null, and the function used it on its first line.
The biggest single change was in DatePicker’s time constraint logic, a
switch (true) with about twenty cases written to fall through on purpose. Six
of them were duplicates that could never be reached on their own, because an
identical condition was checked earlier in the chain. They only existed as
landing points for the fall-through. Rewritten as a normal if/else chain, it is
about half the length.
Behavior changes quietly in a rewrite like that, so before keeping it we ran the old and the new implementation side by side over 419,904 input combinations and compared the return values and the side effects. No differences. I would not have trusted the change without that, and I would suggest the same to anyone touching a block like this: the tests you have were written for the behavior you know about.
We also moved the unit tests from Karma to Vitest in this release. That is internal and does not change anything you install. It is the reason the diff for a small release looks so large.
Upgrading
From any 22.1.x it is just a version bump. Nothing to change in your code:
npm install [email protected]
Two things to look at afterwards. If you had a workaround for the Dialog
appendTo problem, remove it now. And if you were suppressing the DatePicker
validation error with a custom updateOn or your own touched check, you can
drop that too.