How to apply multiple structural directives to the same element
In certain situations, you might want to use more than one structural directive on the same host or for the same element—for example, a combination of *ngIf
and *ngFor
together—which is not something Angular supports out of the box. The reason is that it is hard to identify which directive takes precedence over the other, and even if there was a system, I think the apps would become too complex and hard to manage. In this recipe, we will show a message conditionally using *ngIf
when we have no items in the bucket. Since we’re supposed to show it conditionally and apply the for
loop on the element, this is a perfect example to use for this recipe.
Getting ready
The app that we are going to work with resides in start/apps/chapter02/ng-multi-struc-directives
inside the cloned repository:
- Open the code repository in your code editor.
- Open the terminal, navigate to the code repository directory, and run the following command to serve the project:
npm run serve ng-multi-struc-directives
This should open the app in a new browser tab, and you should see the following:
Figure 2.10: ng-multi-struc-directives app running on http://localhost:4200
Now that we have the app running, let’s see the steps for this recipe in the next section.
How to do it…
- We’ll start by creating a template for the message to be shown when there are no items in the bucket. We’ll modify the
app.component.html
file for this as follows:<div class="fruits"> ... <ng-template #bucketEmptyMessage> <div class="fruits__no-items-msg"> No items in bucket. Add some fruits! </div> </ng-template> </div>
- Now we’ll try to apply the
*ngIf
condition to the element that renders the fruits. Let’s modify the code in the same file, as follows:... <div class="fruits"> <div class="fruits__item" *ngFor="let item of bucket" *ngIf="bucket.length > 0; else bucketEmptyMessage" >...</div> <ng-template #bucketEmptyMessage>...</ng-template> </div>
As soon as you save the preceding code, you’ll see the application breaks, saying that we can’t use multiple template bindings on one element. This means we can’t use multiple structural directives on one element:
Figure 2.11: Angular Language Service explaining we can’t use two structural directives on the same element
- We can fix this by moving one of the structural directives into a
<ng-container>
wrapper, which doesn’t create any additional HTML elements in the DOM. Let’s modify the code as follows:<div class="fruits"> <ng-container *ngIf="bucket.length > 0; else bucketEmptyMessage"> <div class="fruits__item" *ngFor="let item of bucket"> ... </div> </ng-container> <ng-template #bucketEmptyMessage>...</ng-template> </div>
With the change above, you should be able to see the message when there are no items in the bucket, as follows:
Figure 2.12: The final result with *ngIf and *ngFor together
How it works…
Since we can’t use two structural directives on the same element (let’s say a button), we can always use another HTML element as a wrapper (parent) to use one of the structural directive on it, and the other structural directive on the target element (button in our case). However, that adds another element to the DOM and might cause problems for your element hierarchy or other layout behavioral issues, based on your implementation. However, <ng-container>
is a magical element from Angular that is not added to the DOM. Instead, it just wraps the logic/condition that you apply to it, which makes it ideal for us to use in cases like these.
See also
- Grouping sibling elements with the
<ng-container>
documentation: https://angular.io/guide/structural-directives#group-sibling-elements-with-ng-container