Component libraries make developers' lives easier as all the components developed are collected in a single library and can be reused in different applications. While creating an Angular Component library, the issue comes when we have to tweak a specific component according to the particular application requirement without touching the component library's styles. After the deprecation of ng:deep
, it was hard to tweak such changes.
I studied how PrimeNg does CSS customizations to its components without ng:deep.
This blog will share my findings and steps to implement these component styling techniques in your component library and its consumer applications.
In this blog, we will explore two powerful concepts used by PrimeNg to customize component styles without affecting other components: CSS Layering and Dynamic Class Management. These techniques allow developers to efficiently tweak the appearance of components within their own consumer application, while maintaining a clean separation of styles in their component library.
One of the key strategies employed by PrimeNg is CSS Layering. All component styles are placed within the @layer primeng
rule. This approach ensures that when styling the components in our component library, the styles defined in our library take precedence over the styles defined in PrimeNg.
To facilitate this, encapsulation in PrimeNg is set to None. By setting encapsulation to none, it allows the PrimeNg components to be styled from our component library, enabling greater customization options.
Another important concept employed by PrimeNg is Dynamic Class Management. Without this concept, any CSS changes made for one component within our component library would inadvertently affect another component in the consumer application if the same component is used.
To address this issue, Dynamic Class Management introduces a new input attribute, let's say, styleClass
, which is bound to the class of the component. The component is then wrapped in a <div>
element with the attributes [class]="styleClass"
and [ngClass]="'parent-component-class'"
. When the styleClass
attribute is provided by the user, it overwrites the styles defined in the parent-component-class
. This allows for the coexistence of both classes, with the styles specified in the styleClass
attribute taking precedence over the ngClass
.
Since ng:deep
is deprecated and no longer recommended, using Dynamic Class Management is a more modern and maintainable approach to styling components without affecting other components in the consumer application.
By utilizing the bStyleClass
attribute with specific styling attributes, it is possible to override attributes defined in the parent class (ngClass
). Both [class]
and [ngClass]
will be applied, but the styles defined in [class]
will take precedence over ngClass
. Any styles not specified in [class]
will be inherited from the class defined in [ngClass]
.
Example: In the case of the button
component, the styles specified in our component library were overridden by the styles in the consumer apps (App 1 and App2). However, other attributes (such as text and size) from our library were applied since there was no override for them in button
.
To implement layering and dynamic class management in our component library, follow these steps:
@layer component-library{}
.bStyleClass
.<div>
Ā element with the following attributes:Ā <div [class]="bStyleClass!" [ngClass]="'parent-component-class'">...</div>
.To implement layering and dynamic class management in the consumer applications, follow these steps:
bStyleClass
Ā attribute to the components and assign a className.parent-component-class
.It is important to note that if we use the @layer, the behavior of @layer changes when !important
is used. The style precedence of attributes with !important
reverses the concept of precedence in layers.
Additionally, when styling consumer applications, it is advisable to focus on application-specific attributes rather than core-specific attributes. Application-specific attributes include padding, margin, color, width, height, etc. By leveraging the use of !important
on core-specific attributes and avoiding it on application-specific attributes, we can maintain better control over style precedence. When mentioning !important
in the component library, it is important to specify if it has been used on a core attribute that should not be changed in the consumer application.
By employing CSS Layering and Dynamic Class Management, developers can effectively customize component styles without impacting other components in their component library. These techniques, as demonstrated in PrimeNg, provide a robust approach to component styling and enable greater flexibility and control over the appearance of applications.
To further enhance component styling, concepts like [style]
and [ngStyle]
can be utilized to programmatically change inline CSS in the consumer application.
Remember, with CSS Layering and Dynamic Class Management, developers can create visually appealing and highly customizable applications while maintaining a clean and organized codebase.