Skip to content

Developer Insights

Join millions of viewers! Our engineers craft human-written articles solving real-world problems weekly. Enjoy fresh technical content and numerous interviews featuring modern web advancements with industry leaders and open-source authors.

Newest First
Tags:Directive
Introduction to Directives Composition API in Angular cover image

Introduction to Directives Composition API in Angular

In version 15, Angular introduced a new directives composition API that allows developers to compose existing directives into new more complex directives or components. This allows us to encapsulate behaviors into smaller directives and reuse them across the application more easily. In this article, we will explore the new API, and see how we can use it in our own components. All the examples from this article (and more) can be found on Stackblitz here. Starting point In the article, I will use two simple directives as an example * HighlightDirective - a directive borrowed from Angular's Getting started guide. This directive will change an element's background color whenever the element hovers. ` Fig. 1 * BorderDirective - a similar directive that will apply a border of a specified color to the element whenever it hovers ` Fig. 2 We can now easily apply our directives to any element we want ie. a paragraph: ` Fig. 3 However, if we wanted to apply both highlighting and border on hover we would need to add both directives explicitly: ` Fig. 4 With the new directives composition API, we can easily create another directive that composes behaviors of our 2 directives. Host Directives Angular 15 added a new property to the @Directive and @Component decorators. In this property, we can specify an array of different directives that we want our new component or directive to apply on a host element. We can do it as follows: ` As you can see in the above example, by just defining the hostDirectives property containing our highlight and border directives, we created a new directive that composes both behaviors into one directive. We can now achieve the same result as in Fig. 4 by using just a single directive: ` Fig. 5 Passing inputs and outputs Our newly composed directive works nicely already, but there is a problem. How do we pass properties to the directives that are defined in the hostDirectives array? They are not passed by default, but we can configure them to do so pretty easily by using an extended syntax: ` Fig. 6 This syntax takes exposes the "inner" directives\ color input from the HighlightAndBorderDirective`, and passes them down to both highlight and border directives. ` Fig. 7 This works, but we ended up with a border and highlight color both being blue. Luckily Angular's API allows us to easily redefine the properties' names using the : syntax. So let's remap out properties to highlightColor and borderColor so that the names don't collide with each other: ` Fig. 8 Now we can control both colors individually: ` Fig. 9 We could apply the same approach to mapping directive's outputs eg. ` Fig. 10 or ` Fig. 11 Adding host directives to a component Similarly to composing a directive out of other directives, we can apply the same approach to adding behavior to components using hostDirectives API. This way, we could, for example, create a more specialized component or just apply the behavior of the directive to a whole host element: ` Fig. 12 This component will render the paragraph, and apply both directives' behavior to the host element: ` Fig. 13 Just like we did for the directive, we can also expose and remap the directives inputs using the extended syntax. But if we would like to access and modify the directives inputs from within our component, we can also do that. This is where Angular's dependency injection comes in handy. We can inject the host directives via a constructor just like we would do for a service. After we have the directives instances available, we can modify them ie. in the ngOnInit lifecycle hook: ` Fig. 14 With this change, the code from Fig. 13 will use lightcoral as a background color and red as a border color. Performance Note While this API gives us a powerful tool-set for reusing behaviors across different components, it can impact the performance of our application if used excessively. For each instance of a given composed component Angular will create objects of the component class itself as well as an instance of each directive that it is composed of. If the component appears only a couple of times in the application. then it won't make a significant difference. However, if we create, for example, a composed checkbox component that appears hundreds of times in the app, this may have a noticeable performance impact. Please make sure you use this pattern with caution, and profile your application in order to find the right composition pattern for your application. Summary As I have shown in the above examples, the directives composition API can be a quite useful but easy-to-use tool for extracting behaviors into smaller directives and combining them into more complex behaviors. In case you have any questions, you can always tweet or DM me at @ktrz. I'm always happy to help!...

How to Write a Custom Structural Directive in Angular - Part 2 cover image

How to Write a Custom Structural Directive in Angular - Part 2

How to write a custom structural directive in Angular - part 2 In the previous article I've shown how you can implement a custom structural directive in Angular. We've covered a simple custom structural directive that implements interface similar to Angular's NgIf directive. If you don't know what structural directives are, or are interested in basic concepts behind writing custom one, please read the previous articlefirst. In this article, I will show how to create a more complex structural directive that: - passes properties into the rendered template - enables strict type checking for the template variables Starting point I am basing this article on the example implemented in the part 1 article. You can use example on Stackblitz as a starting point if you wish to follow along with the code examples. Custom NgForOf directive This time, I would like to use Angular's NgForOf directive as an example to re-implement as a custom CsdFor directive. Let's start off by using Angular CLI to create a new module, and directive files: ` First, we need to follow similar steps as with the CsdIf directive. - add constructor with TemplateRef, and ViewContainerRef injected - add an @Input property to hold the array of items that we want to display ` Then, in the ngOnInit hook we can render all the items using the provided template: ` Now, we can verify that it displays the items properly by adding the following template code to our AppComponent. ` It displays the items correctly, but doesn't allow for changing the displayed collection yet. To implement that, we can modify the csdForOf property to be a setter and rerender items then: ` Now, our custom directive will render the fresh items every time the collection changes (its reference). Accessing item property The above example works nice already, but it doesn't allow us to display the item's content yet. The following code will display "no content" for each template rendered. ` To resolve this, we need to provide a value of each item into a template that we are rendering. We can do this by providing second param to createEmbeddedView method of ViewContainerRef. ` The question is what key do we provide to assign it under item variable in the template. In our case, the item is a default param, and Angular uses a reserved $implicit key to pass that variable. With that knowledge, we can finish the renderItems method: ` Now, the content of the item is properly displayed: Adding more variables to the template's context Original NgForOf directives allows developers to access a set of useful properties on an item's template: - index - the index of the current item in the collection. - count - the length of collection - first - true when the item is the first item in the collection - last - true when the item is the last item in the collection - even - true when the item has an even index in the collection - odd - true when the item has an odd index in the collection We can pass those as well when creating a view for a given element along with the $implicit parameter: ` And now, we can use those properties in our template. ` Improve template type checking Lastly, as a developer using the directive it improves, the experience if I can have type checking in the template used by csdFor directive. This is very useful as it will make sure we don't mistype the property name as well as we only use the item, and additional properties properly. Angular's compiler allows us to define a static ngTemplateContextGuard methods on a directive that it will use to type-check the variables defined in the template. The method has a following shape: ` This makes sure that the properties of template rendered by our DirectiveClass will need to conform to DirectiveContext. In our case, this can be the following: ` Now, if we eg. try to access item's property that doesn't exist on the item's interface, we will get a compilation error: ` The same would happen if we made a typo in any of the context property names: ` Summary In this article, we've created a clone of Angular's built-in NgForOf directive. The same approach can be used to create any other custom directive that your project might need. As you can see, implementing a custom directive with additional template properties and great type checking experience is not very hard. If something was not clear, or you want to play with the example directive, please visit the example on Stackblitz. In case you have any questions, you can always tweet or DM me at @ktrz. I'm always happy to help!...

Getting Started with Custom Structural Directives in Angular cover image

Getting Started with Custom Structural Directives in Angular

Introduction Angular comes with many built-in directives. Some of them (eg. NgIf, NgModel or FormControlDirective) are used daily by Angular developers. Those directives can be split into 2 categories: - Attribute directives They can be used to modify the appearance of behavior of Angular components and DOM elements. For example: - RouterLink - NgModel - FormControlDirective - Structural directives They can be used to manipulate the HTML structure in the DOM. Using them, we can change the structure of part of the DOM that they control. For example: - NgIf - NgForOf - NgSwitch In this article, I will focus on the latter. Creating a custom structural directive As I've mentioned above, there are a couple of built-in structural directives in Angular. However, we might come across a case that the ones provided with the framework don't solve. This is where a custom structural directive might help us resolve the issue. But how do we write one? --- All the code examples in this article use the Angular CLI or Nx CLI generated project as a starting point. You can generate a project using the following command, or use Stackblitz starter project. ` --- NgIf directive clone Let's learn the basic concepts by reimplementing the basic features of the NgIf directive. We will call it CsdIf (CSR prefix stands for Custom Structural Directive :)) The structural directive is actually just a regular directive (with some additional syntactic sugars provided by Angular). So we can start with creating a module and empty directive using AngularCLI: ` our new directive should look like this: ` Let's implement the basic functionality of displaying the content if passed value is true. ` To achieve that, we need a couple of elements: - an input that will determine whether to show or hide the content (@Input) - a reference to the template that we want to conditionally display (TemplateRef) - a container that will provide us with access to Angular's view (ViewContainerRef) The input can be just a regular class property with Angular's @Input decorator. The important thing is to use a proper naming convention. For it to work as it does in the example code shown above, we need to name the property the same as the attribute's selector: ` Now our directive has the information whether to display the content or not but we need to also gain access to the TemplateRef and ViewContainerRef instances. We can do that by injecting them via a constructor: ` Now we have all the necessary tools and information to display or hide the content. We can use ViewContainerRef's createEmbeddedView method to display and clear method to remove the content. Important note: To make sure the csdIf property is assigned already, we need to use ngOnInit lifecycle hook. ` With this implementation, the following example already works as expected. ` There is still a problem with this implementation. Let's try to use the following example: ` The "My conditional header" is displayed correctly when the page renders but as soon as we uncheck the showInput, our header doesn't disappear as we would expect. This is because we only check the csdIf input value inside of ngOnInit, but we do not react to the input's changes. To resolve this, we can either use ngOnChanges lifecycle hook or modify the csdIf to be a setter rather than just a property. I will show you the later solution but implementing it using ngOnChanges should be very similar. As a first step, let's modify the csdIf to be a setter, and store its value in a private property show. ` Secondly, when the new csdIf value is set, we need to perform the same logic as we do in ngOnInit. We need to make sure though that we don't render the template twice so we can clear the view first in all cases. ` As a final step, let's refactor to remove the code duplication by extracting the common logic into a method. ` Now, our second example works as expected: ` Handling additional parameters - else template The CsdIf directive shows and hides the content based on the boolean input correctly. But the original NgIf directive allows for specifying an alternative template via the "else" property as well. How do we achieve this behavior in our custom directive? This is where understanding the "syntactic sugar" that stands behind the structural directives is crucial. The following NgIf syntax: ` is actually equivalent to the following syntax: ` This means that the else property is actually becoming ngIfElse input parameter. In general, we can construct the property name by concatenating the attribute following * and the capitalized property name (eg. "ngIf" + "Else" = "ngIfElse""). In case of our custom directive it will become "csdIf" + "Else" = "csdIfElse ` is equivalent to ` By analyzing the "unwrapped" syntax we can notice the the reference to an alternative template is passed via the csdIfElse property. Let's add and handle that property in the custom directive implementation: ` This addition makes our directive much more useful, and allows for displaying content for cases when the condition is true or false. If something is not clear, or you want to play with the example directive please visit the example on Stackblitz. Real life example The above example is very simple, but it gives you tools to create your own custom directive when you need it. If you want to have a look at some real-life custom directive example that we've found useful at This Dot Labs, I suggest checking out our route-config open source library. You can read more about it in one of our articles: - Introducing @this-dot/route-config - What's new in @this-dot@route-config v1.2 Summary In this article, we've learnt how to write a simple custom structural directive that handles additional inputs. We've covered the syntactic sugar that stands behind the structural directive, and how it translates into directive's inputs. In the second part, I will show you how to add some additional functionalities to the custom structural directive and present ways to improve type checking experience for the custom directive's templates. In case you have any questions, you can always tweet or DM me at @ktrz. I'm always happy to help!...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co