Re-usable Angular Components—Part I

This blogpost demonstrates different approaches how to build re-usable components with a simple example. This is a two part blogpost. In the second part it is getting more advanced and we have a look how to utilise structural directives to build a re-usable options button component with a simple interface.

Alexander Zarges
Nov 21, 2020 · 5 min read

An important part of building scaleable web applications, is creating your own toolbox of re-usable components.
However, building re-usable components is not always easy because they might have to fit different requirements.

The more flexible a component is, the more requirements it can support. Of course, before extending an existing component with new functionality, one has to always think about whether the functionality makes sense for that component or whether it shall rather be a new component for itself.

For a better understanding let us have a look at the following example.

Product management is asking us to replace the native browser button with custom designed one.
So we go ahead and create a button component which wraps the native button and overrides its style.
We provide a component input for the button text so it can be configured from the outside.

This is how product management want the button to look like

Now product management comes back to us and asks us to style the button for one particular view in red.
For all the other views it shall stay blue. To fulfil this requirement we go back to our component and extend it with an optional component input that configures the color of our button.

A few days later a new request from the design team is waiting on our desk: They want to have icons for the buttons. Again, we are going to our button component and extend it with a new input to configure an icon.
The only problem that is arising now, is that the interface is getting bloated more and more with each new requirement.

<app-button text="Say hello" 
icon="sun"
color="blue">
</app-button>

In order to clean up the interface of our button component we use ng-content for the button label-ing instead of having an input for the text and the icon.

Not only have we cleaned up the interface of our component, but we also provided more flexibility how the component can be used.
Imagine we have not only fontawesome as icon provider but also a second icon provider like icomoon.
With ng-contentit's fairly easy to switch providers, we just replace <i class=fas fa-sun</i>with any icon we want. This would not be possible in the previous approach.

ng-contentis used for content projection and renders the content which is projected from the outside into the component. Thus, it allows more flexibility how the component can be used.

<app-button>
Projected content that is rendered by ng-content
</app-button>

The only negative side is that ng-contentleads to lose of control of the kind of content that is passed into the component.
The input options specify what can be passed in and one is under full control how it is displayed.
With ng-contentit could be anything, also things that might not be supported e.g. another button.
In order to gain back the control ng-contentallows to specify a selector. The selector attribute is basically a CSS selector. Only elements that match the selector will be rendered.

So we are gaining back control because:

  • we specify which elements are supported in our component
  • we specify the order in which the elements are placed (icon always before text)

Yet, product management never sleeps and has a new feature request.

Product management wants to have an options-button with different actions. The options shall be used in different places with different actions.

Options button with multiple options

We start by creating a new component called OptionsButton. To simplify things we use matMenuprovided by Angular material design.

In the first version we provide hard coded options.

This would result in a button that opens a popup on click with the options Option 1 and Option 2. The component itself is quite useless because we can neither define the texts for the options nor can we define what shall happen when the user clicks on an item.

So in the next step we make those buttons configurable. We add an Inputfield called options.
In the template we iterate over the array with ngFor in order to render the options.

The component would be then used like this:

The array approach is quite inflexible and also one has to be careful how to specify the callback. If the callback is specified like {text: ‘Option 1’, callback: alert()the callback will be invoked immediately instead of when the user clicks the option.

If one wants to show an action only when a condition is true, the options array has to be modified. A more preferred approach would be to handle that in the template with *ngIf.

The Angular Dependency Injection allows injecting controllers of parent components into a component. This allows us to access a parent component from a child component. We can utilise this and provide our options as a new component, which then registers itself in the appOptionsComponent. The template would look something like this:

We create a new component called appOption. The component allows the configuration of a text and it also provides an executeoutput, which shall be triggered when the user clicks on an option.

In the constructor we inject the OptionsParentDirective.
Angular resolves it by traversing all parent elements of the DOM tree until it finds the OptionsButtoncomponent. If it is found it will become available in the AppOptioncomponentotherwise an error will be thrown.

In the ngOnInitmethod we then register the option. As callback we
wrap our EventEmitter thus whenever the callback is invoked it will be delegated to the event emitter. In the ngOnDestroy we make sure to unregister the option from the parent.

We extend the OptionsButtonComponent with the registerand unRegistermethod. One nice thing about this approach is, that we still can register options by using the component options input. So we created an add-on instead of a replacement.

As our AppOptionButton neither has a template nor any associated styles and the pure purpose is registering itself at the parent component, it makes more sense to use it as directive instead of a component. So we adjust our AppOptionComponentand refactor it into an AppOptionDirective.

Also, we have to adjust the part were we use it so instead of <app-option></appOption> we have to use it like <div appOption></div>.

As with the very first button example the input approach becomes quite inflexible at some point, and we would rather use the ng-contentapproach. We would like to achieve something like this:

Using ng-content in the appOptionsComponent for all the options would not work because each option has to be wrapped with the structure of matMenuItem.

Fortunately, Angular provides a solution for that problem and calls it structural directives.

This will be discussed in the second part of this blog post which you can find here: https://zarlex.medium.com/re-usable-angular-components-part-ii-c4db1da57244

The Startup

Get smarter at building your thing. Join The Startup’s +729K followers.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +729K followers.

Alexander Zarges

Written by

I'm a PWA developer with 7+ years of Angular experience. During the day I develop the WebApp for an IOT business application, during the night I work on aux.app

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +729K followers.