Content Density Feature Improvements in Fundamental-NGX

Giorgi Cheishvili
Fundamental Library
4 min readJul 26, 2022

The functionality to set Content density has been around in SAP’s @fundamental-ngx library for quite some time already. In fact, it has been one of its core features. In short, it enables you to have multiple content sizes for multiple scenarios for your components. There are three modes you can use in your application:

  • Cozy — displays controls with dimensions large enough to enable fingertip interaction. This option can be used in touch screen devices
  • Compact — reduces the dimensions of the component, focusing more on the information displayed. This option can be used in devices operated by a mouse or keyboard
  • Condensed — reduces the dimensions of the components and the space between letters. This option can be used on small screens

Many components are combinations of multiple other components, for example, shellbar or bar. From the outside, they might seem like one holistic component and it can be looked at that way. Shellbar is a complex component though, used for creating headers and consists of many small moving parts. Its parts consist of Buttons, logos, and inputs for search just to name a few. Additionally, forms are another example, they consist of many input elements, like buttons, select, multi-inputs, comboboxes, checkboxes, and so on. To highlight this, let’s take a look at the simplest and dumbest form:

Every component listens to the globally described content density configuration. @fundamental-ngx/core has a service, creatively called ContentDensityService, which allows you to configure density preference globally and every component will listen to it. What if you want to have locally configured usage? If you want to make every component in this simple example Compact, regardless of global configuration? Well, you’d have to apply local config:

It would work. Everything would be compact, always. This implementation is probably how most of the Angular developers would do, but we have an issue here, although it was pretty easy to add [compact]=”true” here on three items, it would certainly be a lot harder in case of 10s of elements. Additionally, imagine yourself in the place of someone, who creates other reusable components, which are compositions of other components. When you pass [compact]=”true”, they are moving that input to other components and end up creating prop drilling.

With many levels, this becomes annoying fast, very fast, plus human errors are unavoidable.

Wouldn’t it be great, if you were able to declare the whole form(or any element) compact and everything inside of it, at every level would understand that requirement? We thought that too, so came up with some kind of mix of React’s useContext hook and Angular’s directives for declaring local states, which are accessible in DI. So upper example would be written like this:

As you have noticed we just added the fdCompact directive to the form element. So how does everything inside understand the configuration outside of itself? Simply put: Dependency Injection is a great tool! It is not only for depending on services in your components and directives, it gives you way more flexibility that might not be clear at first glance.

So, what does this directive do? It is just a simple directive, with few twists. First of all, it is not an ordinary class, it extends BehaviorSubject<'global' | 'cozy' | 'condensed' | 'compact'>. Also, it has four inputs and four selectors. It is providing itself into DI, so that if anyone Injects this directive, they will have access to the user-defined local content density configuration, reactively!

What is the 'global' in union? It’s for going back to the global definition when you want to return to it.

In the components, which support content density modes and should listen to the changes, we have one very small Observer factory. So what Observer does is that it injects ContentDensityDirective, which is Observable, also injects GlobalContentDensityService and judging from those two instances decides, to which it should listen to and applies appropriate classes to the host(optional). Basically, the consumer is just a combinator of these two observables.

So this directive and consumer gives you, as a user, a way to tap into the ecosystem of components and to us developers, way less code to maintain and easier API to work with.

Let’s say you want to implement some custom component, let’s call it CheckboxButtonComponent. You would do it like this:

We can then use this component in combination with all of the components provided by fundamental-ngx, if the state of the components’ content density changes, it will be reflected in your component too. Also judging from the configuration passed to the contentDensityObserverProviders we can see that it has support for all three modes and it has clearly defined modifier classes in it. So visually from this config, it is easy to visualize the result of it.

More examples and usage can be seen here at the @fundamental-ngx documentation page

Join the community:

LinkedIn: fundamental_lib

Twitter: @fundamental_lib

Slack Channels: Fundamental Library

YouTube: Fundamental Library

--

--