With the Angular community developing rapidly there are lots of new libraries and plugins for the popular Google-supported framework. Today I am going to elaborate on PrimeNG, a UI components library developed by PrimeTek Informatics.
I have used PrimeNG as the primary source for UI components in a recent project at work, and have also looked thoroughly inside it’s source code, even contributed a little (though my pull request still has not been accepted or rejected on their GitHub), so I have lots of stuff to share based on my (often negative) experience.
So, let’s get started.
I don’t want to sound too biased, so I want to start with mentioning some real advantages that this library has. First of all, it is well composed and pretty much understandable (though I’ll talk on some consistency issues later), it’s source code is clear and for the most part, well organized, and it can be used easily with Angular. It has been a real pleasure diving into it (I was very excited on commiting my first contribution), and it really boosts ones knowledge of how Angular components do really interact.
So, now let’s dive into the issues I have found in this library.
If you are reading this article, I assume that you have knowledge of how Angular components work and interact.
We do know that components can be nested, and that they create separate “scopes” (as we would have used this word when talking about angular.js, the first version of the Angular framework), and can communicate with the outside world using Inputs and Outputs. PrimeNG is in no way different and uses the same approach.
The Inputs are basically more simple, than Outputs, just being a one way binding to a variable from the parent scope, in which the component is nested.
But Outputs may be a little trickier. Components usually have selectors and templates defined as its metadata, and whenever we use a component’s selectors inside another components template, we actually populate that custom HTML tag with the nested component’s template and logic. Here is a small example (omitted imports and module declarations for the sake of brevity):
Notice how the ParentComponent’s template uses the ‘nested-component’ selector to create and input inside it. Notice one thing about the Parent too: it has an onBlur method defined, which we are going to invoke when the user leaves the input in the nested component… but how? We don’t have access to the internals of the nested component, and we can’t directly bind a method from ParentComponent to an input’s (blur) event inside the nested one. The workaround to this problem is an Output:
Yes, now we do manually emit an event from the nested component, for a method to bind to it in the parent one. This is how Angular components deliver data with events from inner scopes to the outer world.
The problem with PrimeNG here is that it consists of components, but fails to provide all (or at least basic) events to bind from your code to. Take, for example, the AutoComplete component. This particular issue is related to a previous version and is fixed by now, but there are lots of other similar missing API points. The ‘p-AutoComplete’ component provided an (onFocus) output, which emitted the (focus) event of the original input element inside that component, but it did not provide an (onBlur) output! The client needed to show an info label whenever user focuses on the autocomplete input, which was easily done with the (onFocus) output, and, logically, the message would have been removed as soon as the user navigated away from the input, which was, on the contrary, impossible, because there was no event to bind a method to.
Another great example is the GrowlMessage component, which provides an (onClose) output, to which I can bind a method to be invoked whenever the user closes a particular message, but it fails to provide such a basic event as a click! I can’t invoke a method when the user clicks on one of the messages, because there is no such event emitted from the component! (Which resulted in us making a dirty hack by catching the click event with jQuery, finding the respective message in the array of our messages and only then invoking the method on it). By the way, the pull request I have submitted to PrimeNG was about adding the click event on this component, hope it will be accepted as soon as possible.
Data inputs need customization. If I have a component that renders a dropdown, I need to provide data for it. This is how it works in PrimeNG:
I really, REALLY have to pass an array of objects that look like this. This is how it works in the ideal world of PrimeNG. The reality is, I don’t always have such an array. Usually I would render this dropdown using a list retrieved from a server, which provides me with the full-blown user data, where no ‘value’-s and ‘label’-s are present:
So, if I want the user’s ‘id’ property to be my dropdown value, and the ‘fullName’ to be the label, I have to map my users to a new Array:
You know what would have been nice? A ‘dataKey’ and ‘labelKey’ attributes on the dropdown component to tell it which attributes in an array of objects to look for, when rendering the dropdown! Just like this:
Irony is, the Autocomplete component (which also renders a dropdown) has a ‘dataKey’ attribute to resolve the property which is going to be used as the value for the dropdown, and a ‘field’ attribute to be used as label. Which leaves one wondering: why is this kind of feature present one component, but completely absent on another? Inconsistent API it is!
Another awesome thing that Angular makes heavy use of is templates — building blocks for repeating pieces of UI code. PrimeNG uses it too for customizing elements of UI like the dropdowns:
Pretty nice, yeah? The problem is, PrimeNG does not provide templating on every component you’d expect. Consider the Lightbox component (it renders a gallery of images, which can bee zoomed in and viewed as a slideshow). Here is a simple usage:
It’s extremely easy to use, unless you want something more specific, for example, a button on each image thumbnail to delete it from the list. It won’t work the way you think: there is no templating for Lightbox. There is ‘type=”content” ’ attribute, but it leaves the entire rendering process to the developer, and is usually meant to display one item like an iframe.
Dialog boxes are a pretty much common part of a modern web application today, and you can easily open up one with PrimeNG:
This is really not nice. It happens I just have a hidden element inside my parent component and make it visible on button click. This is not how such things are meant to work in Angular: in Material, for example, one can render any dialog component using a service provided by the library, not polluting the original HTML.
And yes, to be more precise, multiple dialog boxes in PrimeNG are okay, but multiple ConfirmDialog-s are not — they cause weird layer behavior, and no dialog box becomes visible
Well, sometimes PrimeNG just assumes it knows what you want. Consider the FileUpload component:
Where the ‘url’ attribute where the files will be uploaded. It renders a nice box with image thumbnails like this:
Well, nice, right? But what if I don’t want to upload the images rightaway from this component, but they are rather a part of a larger form with lots of other data, which are to be transported to the server simultaneously? It appears, you can’t do it easily: FileUpload does not support binding to an Angular Reactive Form or ngModel. If you want to bind, you have to do it manually (FileUpload fires an event when the user adds new files, one can bind to it and manually add the files from the event object to the form/ngModel).
Sometimes the documentation can be misguiding: events missing, sometimes even attributes, and yes, I had a major problem understanding whenever an ‘attribute’, (as it is listed in the docs), is really just an attribute or rather a bindable property (‘attr=”someValue” ’ vs ‘[property]=”someVariable” ’).
Should you still consider PrimeNG for your upcoming projects? Definitely yes, but:
- Be careful: if you already have an idea that you’ll be going to need more customization and event bindings, like mentioned examples above, maybe there’s something better for you.
- Be prepared to dive into source code more often then expected, as sometimes the docs are a little behind the actual situation
- Never, NEVER make an assumption like ‘I will just make the same thing with this component that I made with the other one’, as mentioned above, inconsistency is not rare in this API.
PrimeNG is young, as is Angular. I hope sooner or later it will become more stable and consistent.