Re-usable Angular components — Part II

In this part we take a look at structural directives and how we can use them to build even more generic components. In the first part we discussed other approaches to build a re-usable component.

What is a structural directive?

A structural directive is defined like any other directive but with one major difference: In the constructor TemplateRefand/or ViewContainerRef is injected.

Asterix Notation - *directiveName

You might not have used ng-template in your application but you definitely have used structural directives. This is because structural directives usually are used in a different way. As syntactic sugar angular allows to use a structural directive with an asterix prefix. So instead of writing <ng-template appOption></ng-templatethe shorter asterix form can be used <div *appOption></div>.
Under the hood angular translates it back into the <ng-template *appOption></ng-template>form.

Rendering

The template of a structural can be rendered by using the ViewContainerRefwhich offers the method to render the template called createEmbeddedView. The template can also be rendered in the template of a component by using the structural directive ngTemplateOutlet.

Input and Output

Another major difference to a directive is that structural directives can not have any Outputs.

Expression grammar

The expression grammar of a structural direction not only allows you to set parameters of your directive but also to receive input, called context when it is rendered.

Angular specifies the grammar like

1: *appOption="'My Text'"

This is passing a string into our appOtionDirective. It does not have to be necessarily be a string it can also be any other type. *ngIffor example accepts a boolean as expression input. In order to work with the expression in the directive it has to be declared as input. However, all inputs of a structural directive have to be prefixed with the directive selector name.

2: *appOption="let i = index"

As mentioned earlier the expression language not only allows to set input on the structural directive but also to receive content back from it. The content received back is called context.
The context can be either passed as a second parameter when using the createEmbeddedView(templateRef, context) or passed as context input when using *ngTemplateOutlet<ng-content *ngTemplateOutlet="templateRef"; context:{}"></ng-content>
Both allow to pass an optional context object which exposes variables that can be used in the projected content. In this example the structural directive passes the context {index: 1} On the element the context is received and mapped onto the variable ilet i = index .

3: *appOption="let xyz, let i = index"

Angular also allows to receive any content and to map onto a custom variable name, in this case xyz. This is done with the $implicit convention. Content that is set in the context using the name $implicitis mapped on all variables, that are not specified in the context, on the receiving side.

3b: *appOption="let xyz, index as i"

It is basically the same as above only that the as notation is used. index is assigned to iand can be used in the template as i. One has to be careful because this does not work standalone. *appOption="index as i" would result in an error.

4: *appOption="'My text'; color: 'red'; let xyz; let i = index"

In the first example a custom text is passed as input into the directive. This example is extended with a second input which is specified with a key expression. In order to access the color input, it also has to be prefixed with the directive selector name. So colorbecomes available as appOptionColor.

5: *appOption="let xyz setItFrom [1, 2, 3]; let i = index"

This is basically how *ngForworks in a nutshell. setItFrom becomes available by prefixing it with the directive selector name. We iterate over all options and render the projected content that is available via the injected templateRef . The templateRefcan be seen as a blueprint from which a new instance with its own context is generated in each createEmbededView call.

Using the structural directive for our appOption

As we now have a basic understanding of how structural directives, we can use that knowledge to apply it on our original appOptionsButton problem.
A structural directive allows us to access the projected DOM content of the element where it is applied on. We can use this to register the templateRefat our parent appOptionsComponent.

Extend it with everything we learned

We can extend the appOptionsButton with some more inputs in order to apply everything we learned.

Let's assume we got the following requirements from product management:

  • Each entry shall be prefixed with an incremented number
  • We get a list of actions from our backend like Array<{label: string, icon: string}>.

Solution

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