Creating Behavioral Components in Angular

Netanel Basal
Netanel Basal
Published in
6 min readSep 17, 2019

--

When working on an enterprise application, or with a company that deals with multiple projects, it’s necessary to develop a solid foundation; Specifically, it’s important to have strong core components which are flexible enough to adapt to any variation or requirement, and can be shared across teams.

One real-world example is an accordion component, aka expansion panel. In our application, there are many pages or sections which contain variations of an accordion, which differ both in styling and in markup.

To make an accordion component that’s flexible enough for the developers, we should provide them with a component which handles the accordion behavior, leaving the styling and markup up to them.

Let’s create an accordion component that provides those requirements. We’ll have four building blocks components. An AccordionHeader, AccordionContent, AccordionGroup, and Accordion. Here’s how developers will use it:

Creating the Components

Let’s start creating them one by one:

Creating the Accordion Header Component

The header component is responsible for two things. First, it exposes a click$ observable so the parent Accordion component can listen to it and toggle the group it belongs to.

Next, it implements an isOpen setter where it updates its local isOpen state. Based on this state, it uses HostBinding to add a specific class to the host so the developers can style it the way they want it.

Creating the Accordion Content Component

The content component responsibility is to toggle the projection content based on the visibility status that it received from the parent Accordion component:

Creating the Accordion Group Component

The group component is a mediator component that’s responsible for communicating between the Accordion component and the AccordionGroupChildren — the AccordionHeader and the AccordionContent.

First, we use the ContentChild decorator to grab a reference to the AccordionHeader and AccordionContent components.

Then, we create a toggle() method that’ll be called by the parent Accordion component when the user clicks on the header. The toggle() method toggles the visibility status both on the header and the content.

Creating the Accordion Component

It’s time to connect all the pieces in the puzzle. You might have already guessed the next phase. We need to obtain a reference to all the AccordionGroup components that exist within the accordion.

We’re using the ContentChildren decorator that returns a QueryList containing a collection of AccordionGroup components.

Now we need to listen to each header click event and toggle the corresponding group. Let’s start by implementing the basic support:

We create a clicks collection in which each item is the header click observable. We also need to know which group it belongs to, so we’re using the map observable to map it to the current group.

Now that we have a collection of observables, it’s as simple as utilizing the merge observable and invoking the group’s toggle method when one of them emits.

We’re still not there. One of the requirements is to support dynamic accordion’s groups. QueryList exposes the changes observable where we can listen to, add, or remove of children. Let’s refactor our code to use it:

The changes observable doesn’t emit the initial value, so we use the startWith operator to provide it with the initial groups value. Now we’re reactive. 😎

Utilizing the ExportAs Feature

We’re still not flexible enough. We can do more. Imagine a case where the developers should use a caret icon for the header that indicates the group’s visibility status.

Currently, they don’t have any way to change the icon based on the status. Earlier, we implemented an isOpen property in the AccordionHeader component that we can expose to our template using the exportAs feature:

The exportAs property takes the name under which the component instance is exported in a template. Put simply, we can expose a directive or a component public API to the template.

Now we can access the AccordionHeader instance in our template:

We create a local variable named header that provides access to the AccordionHeader instance, and we use it to set the proper icon based on the isOpen property.

Supporting Lazy Content

At this point, we have a working accordion, but we can do better. Now, because of the way ng-content works, we have one weakness.

When using ng-content, the host doesn’t have any control over the content. This behavior can lead to unexpected side-effects, as Angular will create the content that is within the AccordionContent without taking into consideration whether it is currently visible or not.

Let’s create a demo component and use it to see this behavior in action:

Open the console, and you’ll see three logs of IconComponent ngOnInit even though each one of the groups is closed.

There’re times when we can benefit from this behavior. For example, we may have a form or any other local state inside the AccordionContent component that we want to persist on toggling.

On the other hand, there are times when we won’t want this behavior. For example, we might have components or directives that perform expensive operations. Another example is side-effects, such as HTTP requests, loading assets such as icons, etc.

In such cases, the right decision is to load it lazily only if the AccordionGroup is open. The way we truly perform the components’ instantiation in a lazy manner is by using ng-template.

Let’s add support for this feature in AccordionContent component. First, we need to create a structural directive whose sole purpose is to expose a reference to its TemplateRef:

Then, we can grab it in the AccordionContent component using the ContentChild decorator:

Now that we have it, we can pass the content property which contains the template we want to create to the ngTemplateOutlet directive.

Let’s refactor the earlier example and use it:

Open the console to see the new lazy behavior. 🦊

Testing it with Spectator

Everything works well, but we won’t release it to developers without writing some unit tests. Testing such components with Angular can be overwhelming. But it’s your lucky day — because Spectator is here to the rescue!

Spectator is a powerful tool to simplify testing in Angular. Spectator helps you get rid of all the boilerplate grunt work, leaving you with readable, sleek, and streamlined unit tests.

Spectator comes with a wide range of features, allow me to show you a taste of it:

Pretty neat, huh? Check out the docs for more information. 😀

Support for Nested Accordions

Let’s finish with a small task for you. Add support for nested accordions. Let me give you a hint about this one:

The accordionChilds property is holding a reference to all the AccordionComponent children for this AccordionComponent — use it.. Pay attention to an existing bug. The accordion itself will be inside this collection.

You can find the complete code in this repo, or see it live in ng-run.

🚀 In Case You Missed It

  • Akita: One of the leading state management libraries, used in countless production environments. Whether it’s entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which all help to manage the data and negate the need for massive amounts of boilerplate code.
  • Spectator: A library that runs as an additional layer on top of the Angular testing framework, that saves you from writing a ton of boilerplate. V4 just came out!
  • And of course, Transloco: The Internationalization library Angular 😀

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.