Guide to Structural Directives in Angular

    Apr 24, 20259 min read38 viewsUpdated:Apr 25, 2025
    Guide to Structural Directives in Angular

    What is a Directive in Angular

    In Angular, a directive is a special type of class or keyword that adds additional behavior or functionality to HTML elements. Directives act as helpers, making HTML templates more dynamic and interactive by extending their behavior.

    An Example of Understanding Directives

    Imagine you're decorating a hall for a party:

    Without Directives:

    You stick plain balloons on the wall, just like regular HTML tags. These balloons are static, but they remain the same regardless of the party's theme or mood.

    With Directives:

    You use smart balloons that light up when it gets dark or change color based on the party's vibe. These dynamic balloons are like directives they add extra behavior to your basic HTML elements.

    Types of Directives

    Directives in Angular can be categorized into three main types: Component Directives, Structural Directive, and Attribute Directive. Let's explore each type in detail:

    1. Component Directive

    What it is: Component directives are special directives in Angular that come with a template or template URL. They are the foundation of Angular applications and control a section of the DOM.

    Purpose: They display content in the DOM and typically contain logic for user interaction.

    Example: Every Angular component (@Component) is essentially a component directive.

    @Component({
      selector: "app-hello",
      template: `<h1>Hello, Angular!</h1>`,
    })
    export class HelloComponent {}

    2. Structural Directive

    What it is: Structural directives are used to change the structure of the DOM by adding, removing, or manipulating elements.

    Purpose: These directives control the layout and structure of the view.

    Examples:

    *ngIf: Conditionally adds or removes elements.

    <div *ngIf="isVisible">This element is visible!</div>

    *ngFor: Iterates over a collection and displays elements for each item.

    <ul>
      <li *ngFor="let item of items">{{ item }}</li>
    </ul>

    3. Attribute Directive

    What it is: Attribute directives modify the appearance or behavior of an existing DOM element without changing its structure.

    Purpose: These directives enhance or style existing elements.

    Examples:

    ngClass: Dynamically applies one or more CSS classes.

    <div [ngClass]="{'highlight': isActive}">
      Highlight me!
    </div>

    ngStyle: Dynamically applies inline styles.

    <div [ngStyle]="{'color': textColor, 'font-size': fontSize}">
      Style me!
    </div>

    A Deep Dive into Structural Directives

    Structural directives in Angular are used to alter the structure of the DOM by adding, removing, or modifying elements dynamically. These directives manipulate the layout of the HTML page based on certain conditions or data. They are recognized by the * prefix in templates, which is shorthand for the more complex logic that Angular internally generates.

    Structural directives allow developers to control the DOM elements and structure of the page, without needing to directly interact with the underlying host element. They are especially useful for rendering or hiding content conditionally, or iterating over lists of data.

    Key Features:

    1. Dynamic changes: Structural directives like ngIf, ngFor, and *ngSwitch help in removing DOM elements or instantiating templates based on specific conditions.

    2. Template context: They provide their own context object, including properties like index, first, last, even, and odd to manage elements during iteration or conditional rendering.

    3. Template input variables and reference variables: Structural directives make use of template input variables and template reference variables to enhance the flexibility and dynamic nature of templates.

    4. Optimized rendering: Angular optimizes rendering using the template type checker to ensure that structural directives behave as expected at runtime.

    Built-in Structural Directives

    Angular provides several built-in structural directives that enable developers to control the DOM layout dynamically. These directives manipulate DOM elements based on conditions or iterated data, simplifying the process of creating responsive and interactive UIs.

    1. *ngIf Directive

    Let’s dive into how the *ngIf directive can help us control the visibility of elements based on a condition. This is useful when you want to show or hide content dynamically in response to user actions or application state.

    • Basic Example: Showing and Hiding Based on a Boolean Expression
      Suppose we have a profile page, and we want to display the user's first name only if they are logged in. We can use the *ngIf directive to show or hide the name based on the isLoggedIn property.
      HTML

      <div>
        <h2 *ngIf="isLoggedIn">John</h2>
      </div>

      TS

      export class ProfileComponent {
        public isLoggedIn: boolean = true;
      }

      Output

      A UI showing "John" based on a conditional check for logged-in status.

      In this scenario:

      • Since isLoggedIn is initially true, the text 'John' will be displayed.

      • If isLoggedIn is set to false, the name 'John' will not appear in the browser.

      • This is a very common use case in web applications, where certain content (like a user profile) should only be visible when the user is authenticated.

    Using ngIf with else Clause

    Now let’s say that we want to show the user's first name if they are logged in and their last name if they are not logged in. We can use the else clause in the *ngIf directive to display alternative content.

    • HTML Template with ngIf Else Clause:

      <div *ngIf="isLoggedIn; else showLastName">
        <h2>John</h2>
      </div>
      
      <ng-template #showLastName>
        <h2>Doe</h2>
      </ng-template>

      TS

      export class ProfileComponent {
        public isLoggedIn: boolean = false;
      }

      Output

      A UI displaying the name "John" when logged in and "Doe" when not logged in.

      In this case:

      • If isLoggedIn is true, the first name "John" will be displayed.

      • If isLoggedIn is false, the ng-template with the last name "Doe" will be displayed.

      • This pattern is useful when showing different pieces of content based on a condition, like showing a welcome message or a login prompt.

    Using Multiple Templates with ngIf

    If you have more complex conditions and want to use different templates for different situations, you can use the then and else clauses of the *ngIf directive together.

    Let’s imagine we want to display different templates for:

    • Showing the user’s first name when logged in.

    • Showing the user's last name when they are logged out.

    • Showing a “guest” message when the user is neither logged in nor has a name.

      HTML

      <div *ngIf="isLoggedIn === null; then showGuest; else loggedInStatus"></div>
      
      <ng-template #loggedInStatus>
        <div *ngIf="isLoggedIn; then showFirstName; else showLastName"></div>
      </ng-template>
      
      <ng-template #showFirstName>
        <h2>John</h2>
      </ng-template>
      
      <ng-template #showLastName>
        <h2>Doe</h2>
      </ng-template>
      
      <ng-template #showGuest>
        <h2>Guest</h2>
      </ng-template>

      TS

      export class ProfileComponent {
        public isLoggedIn: Boolean | null = null;
      }

      Output

      A dynamic Angular UI that displays "Guest" when isLoggedIn is null, "John" when isLoggedIn is true, and "Doe" when isLoggedIn is false.

      In this case:

      • If isLoggedIn is true, the showFirstName template will be displayed.

      • If isLoggedIn is false, the showLastName template will be shown.

      • If neither condition matches, we could display a showGuest template (you can modify the logic to fit your needs).

    When to Use ngIf: Dynamic Content Rendering

    The ngIf directive is an essential tool in Angular for dynamically rendering content based on a boolean condition. It allows developers to control the visibility of elements in the DOM, making it ideal for scenarios where content needs to be displayed or hidden based on user interactions, data states, or application conditions.

    Here are some real-world examples of when to use ngIf for dynamic content rendering:

    1. User Authentication: In a login system, to show different content based on whether the user is logged in or not. For example, you could show a login form if the user is not authenticated, and a profile page or logout button if they are logged in.

    2. Conditional UI Rendering: In a shopping cart, to display a "Cart Empty" message or the list of items in the cart based on whether there are products in the cart or not.

    3. Loading Spinner: To display a loading spinner while data is being fetched from the server. Once the data has been loaded, the content will be displayed, and the spinner will be hidden.

    4. Error Handling: In a form, to conditionally display error messages when a user submits invalid data or a form submission fails.

    5. Content Toggle: In a dashboard application, you can toggle between different views based on the selected option. For example, you can show a detailed report or a summary report based on the user’s choice.

    6. Access Control: For different user roles (Admin, User, Guest), to show specific components or sections of the page based on the user's role.

    7. Feature Flags: You can conditionally enable or disable features in your application based on a configuration or feature flag. For instance, you might only want to display certain features to a subset of users.

    8. Form Steps: In a multi-step form, show one step at a time based on the user's progress. For example, show a "Review" step only after the user has completed the "Personal Details" and "Address" steps.

    2. *ngFor Directive

    The ngFor directive in Angular is used to iterate over a collection of items and render a template for each element in that collection. This directive is extremely useful for displaying lists or any set of items in your user interface.

    • Basic Example: Iterating Over an Array of Makes
      Imagine you have a list of car makes, such as Chevy, Ford, GMC, and Dodge, and you want to display each of these names in an HTML page. Here's how you can do it using the *ngFor directive.
      TS

      export class CarComponent {
        public makes = ['Chevy', 'Ford', 'GMC', 'Dodge'];
      }

      HTML

      <div *ngFor="let make of makes">
        <h2>{{ make }}</h2>
      </div>

      Output

      A list of car makes displayed as "Chevy", " Ford", "GMC", and "Dodge."

      In this case, the *ngFor directive will loop through the makes array, and for each make, it will create a new <h2> tag displaying the make's name. You’ll see a list of the car makes rendered on the page, one per line.

    Advanced Usage: Accessing Helper Properties

    The *ngFor directive also provides several helper properties to give you more control and insights about the current iteration in the loop. Here are a few of them:

    1. Using Index as i to Access the Current Index

      Sometimes, you might want to know the index of the current item in the list, which can be useful for displaying numbers or applying logic. Here's how to get the index:
      HTML

      <div *ngFor="let make of makes; index as i">
        <h2>{{ i }} - {{ make }}</h2>
      </div>

      Output

      A list of car makes displayed with their respective indices, showing "0 - Chevy", "1 - Ford", etc.

      In this example, i will represent the index of each item in the makes array, so the browser will display the index followed by the car make.

    2. Using first as f to Identify the First Item
      If you need to apply special styling or logic to the first item in the collection, you can use the first property.
      HTML

      <div *ngFor="let make of makes; first as f">
        <h2>{{ f }} - {{ make }}</h2>
      </div>

      Output

      A list displaying car makes with the first item labeled "true" followed by the make name, such as "true - Chevy", "false - Ford", etc.

      Here, f will be true for the first item in the list, and false for all other items. This is useful when you want to highlight or apply different styles to the first item.

    3. Using last as l to Identify the Last Item

      Similar to the first, the last property helps you identify the last item in the collection.
      HTML

      <div *ngFor="let make of makes; last as l">
        <h2>{{ l }} - {{ make }}</h2>
      </div>

      Output

      A list displaying car makes with the last item labeled "true" followed by the make name.

      In this case, l will be true for the last item in the list, and false for all others. This can be used to display something special for the last item, like a "Thank you" message.

    4. Using odd as o and even as e to Identify Odd or Even Items
      If you need to distinguish between odd and even items, the odd and even properties are perfect. For example, you might want to style odd items differently from even items in a list.
      HTML Template for Odd Items:

      <div *ngFor="let make of makes; odd as o">
        <h2>{{ o }} - {{ make }}</h2>
      </div>

      Output

      A list of car makes with odd-indexed items marked "true" and even-indexed items marked "false."

      HTML Template for Even Items:

      <div *ngFor="let make of makes; even as e">
        <h2>{{ e }} - {{ make }}</h2>
      </div>

      Output

      A list of car makes with even-indexed items marked "true" and odd-indexed items marked "false."

      In both of these cases, o and e will be true for odd and even items, respectively, allowing you to apply different styles or logic based on the position of the item.

    When to Use ngFor: Real-Life Examples

    The ngFor directive in Angular is ideal for rendering lists or arrays of data. It allows you to dynamically create elements based on the content of an array or iterable.

    Here are some real-life examples of when to use ngFor in your Angular applications:

    1. Displaying a List of Products: In an e-commerce application, to display a list of products on a product page. Each product, such as its name, image, and price, can be dynamically rendered from an array of product data.

    2. Rendering User Comments: On a blog or social media platform, to display a list of user comments under a post. Each comment will be rendered as part of the comment section based on an array of comment data.

    3. Building a Navigation Menu: In a website, to generate a dynamic navigation menu from a list of menu items. To iterate over the list and render each menu option in a navigation bar.

    4. Displaying a List of Tasks: In a task management application, to display a list of tasks that a user has created. Each task would be displayed with information such as task name, due date, and status.

    5. Rendering Data in a Table: To render rows in a table from an array of data, such as a list of employees or customers. Each row represents a data item with multiple columns (e.g., name, position, salary).

    6. Displaying a List of Images: On an image gallery page, to dynamically render a grid of images from an array. Each image in the array will be shown in its respective place within the gallery layout.

    7. Iterating Over Notifications: In a notification center, display a list of notifications. Each notification could represent different types, like message alerts, system updates, or reminders.

    3. *ngSwitch Directive

    The *ngSwitch directive in Angular allows you to conditionally display templates based on the value of an expression. This is very similar to the switch statement in programming languages like C# or JavaScript, where you compare an expression against several cases and execute code based on which case matches.

    The ngSwitch directive is useful when you want to render different templates or DOM elements depending on the value of an expression. Let’s take a closer look at how to use *ngSwitch in a real-world Angular application.

    Basic Example: Displaying Fruit Based on User Choice
    TS

    export class FruitComponent {
      public selectedFruit = 'Apple'; 
    }

    HTML

    <div [ngSwitch]="selectedFruit">
      <h2 *ngSwitchCase="'Apple'">You selected Apple!</h2>
      <h2 *ngSwitchCase="'Banana'">You selected Banana!</h2>
      <h2 *ngSwitchCase="'Cherry'">You selected Cherry!</h2>
      <h2 *ngSwitchDefault>Select a valid fruit.</h2>
    </div>

    Output

    A UI displaying a message based on the selected fruit, showing "You selected Apple!" initially.

    Explanation:

    1. We use [ngSwitch]="selectedFruit" to bind the value of the selectedFruit property to the ngSwitch directive.

    2. Each fruit (Apple, Banana, Cherry) has its own *ngSwitchCase directive to display a message when that fruit is selected.

    3. If none of the cases match the selectedFruit value, the *ngSwitchDefault will be displayed, showing the message "Select a valid fruit."

    How It Works

    1. If selectedFruit is "Apple", the message "You selected Apple!" will be shown.

    2. If selectedFruit is "Banana", the message "You selected Banana!" will appear.

    3. If selectedFruit is "Cherry", the message "You selected Cherry!" will be displayed.

    4. If the selectedFruit value is something else (like "Grapes"), the default message "Select a valid fruit." will be shown.

    Real-World Use Cases for ngSwitch in Angular

    The ngSwitch directive in Angular is designed to conditionally display different templates based on a single expression. This is especially useful when you need to render multiple possibilities for the same value, but only one should be displayed at a time. Here are some real-world use cases for ngSwitch in Angular applications:

    1. Displaying User Roles: In an admin panel, where content needs to be displayed differently based on whether the user is an Admin, User, or Guest. Use ngSwitch to show different sections or templates depending on the user's role.

    2. Multi-step Form: In an application like an online survey or checkout process, where users have to fill out forms in multiple steps. ngSwitch can be used to display each form section based on the current step, such as Personal Info, Address, and Review.

    3. Theme Selection: In apps where users can toggle between dark, light, or custom themes, ngSwitch can be used to switch the layout and apply the correct styles for each theme selected.

    4. File Preview: In a file upload feature, where users upload images, PDFs, or videos. ngSwitch can be used to display previews based on the file type, such as showing an image preview for pictures or a document viewer for PDFs.

    5. Language Translation: In a multilingual website or app, ngSwitch can be used to show content in different languages like English, French, or Spanish, depending on the user's language selection.

    6. Alert Messages: In a notification system, where users may see success, error, or warning messages based on different actions. ngSwitch can conditionally show different types of alerts to the user, such as success notifications after a successful form submission or error messages when something goes wrong.

    7. Payment Method Form: In an e-commerce checkout process, where users choose their preferred payment method, like Credit Card or PayPal. ngSwitch can be used to display the specific form elements needed for each payment method.

    Creating Own Structural Directive

    To create your structural directive, you need to define a directive class and specify the CSS selector that identifies the directive in an Angular template.

    To create a custom directive using Angular CLI, you can use the following command:

    ng generate directive directive-name

    or the shorthand version:

    ng g d directive-name

    Steps to Create Your Structural Directive

    1. Define a Directive Class

      • Use the @Directive decorator to define the directive.

      • Specify a CSS selector to identify your structural directive in the template uniquely.

    2. Import Required Modules

      • Input: Enables passing dynamic input values to the directive.

      • TemplateRef: Provides access to the structure and content of the Angular <ng-template>.

      • ViewContainerRef: Acts as a container to dynamically manage embedded views in the DOM.

    3. Inject TemplateRef and ViewContainerRef

      • The TemplateRef is injected into the directive constructor to interact with the content of the <ng-template>.

      • The ViewContainerRef is injected to manage the insertion or removal of views adjacent to the directive’s host element.

    4. Define an @Input() Property with a Setter

      • The @Input() property receives the condition or value for the directive.

      • Use a setter to evaluate the condition dynamically and manipulate the DOM by creating or clearing views.

    Detailed Example of a Custom Structural Directive

    TS

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    
    @Directive({
      selector: '[appIf]', // CSS selector for the directive
    })
    export class AppIfDirective {
      private hasView = false; // Tracks the current state of the view (rendered or not)
    
      constructor(
        private templateRef: TemplateRef<any>, // Accesses the template content
    
        private viewContainer: ViewContainerRef // Manages rendering in the DOM
      ) {}
    
      @Input() set appIf(condition: boolean) {
        if (condition && !this.hasView) {
          // If the condition is true and view is not yet displayed, create the view
    
          this.viewContainer.createEmbeddedView(this.templateRef);
    
          this.hasView = true;
        } else if (!condition && this.hasView) {
          // If the condition is false and view is displayed, clear the view
    
          this.viewContainer.clear();
    
          this.hasView = false;
        }
      }
    }

    In the above example, we created a structural directive named appIf, which works similarly to Angular’s ngIf directive.

    How It Works

    1. CSS Selector: The directive is identified in the template using the [appIf] selector.

    2. Condition: The @Input() property evaluates the condition passed to appIf.

    3. Dynamic View Management:

      • When the condition is true, createEmbeddedView() dynamically adds the content of <ng-template> to the DOM.

      • When the condition is false, clear() removes the content from the DOM.

    Using the Custom Structural Directive

    You can use the *appIf directive in your Angular templates like this:

    <div *appIf="showContent">
      This content is displayed only when 'showContent' is true.
    </div>

    Here:

    • showContent is a boolean variable defined in your component.

    • The directive dynamically displays or removes the <div> element based on the condition.

    Benefits of Creating a Custom Structural Directive

    • Provides enhanced flexibility for DOM manipulation.

    • Enables reusable logic for dynamic display of content based on conditions.

    • Leverages TemplateRef and ViewContainerRef for efficient DOM rendering.

    Improving Template Type Checking

    Template type checking in Angular ensures that the templates in your application are validated at compile time, reducing the risk of runtime errors. You can enhance template type checking for your custom structural directives by implementing template guard properties in your directive definition.

    Steps to Improve Template Type-Checking

    Add Template Guard Properties

    • Template guard properties allow the Angular template type checker to validate the directive’s usage in the template.

    • These properties ensure that only valid inputs and contexts are passed to the directive.

    Use the ngTemplateContextGuard Type Assertion Function

    • Angular provides the ngTemplateContextGuard utility to define and enforce the type of the directive's context.

    • This function ensures the directive is compatible with the structure of the context expected in the template.

    Define Context Type

    • Specify a dedicated context type for your directive.

    • The context type allows you to reference and access specific properties inside the <ng-template> without errors.

    Example: Improving Type Checking for a Structural Directive

    Below is an example of how to implement template guard properties in a custom directive named appLet:

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    
    interface AppLetContext<T> {
      $implicit: T; // Default value accessible via "let-"
    
      appLet: T; // Value explicitly passed to the directive
    }
    
    @Directive({
      selector: '[appLet]',
    })
    export class AppLetDirective<T> {
      private context: AppLetContext<T> = { $implicit: null!, appLet: null! };
    
      constructor(
        private viewContainer: ViewContainerRef,
    
        private templateRef: TemplateRef<AppLetContext<T>>
      ) {}
    
      @Input()
      set appLet(value: T) {
        this.context.$implicit = value;
    
        this.context.appLet = value;
    
        this.updateView();
      }
    
      static ngTemplateContextGuard<T>(
        dir: AppLetDirective<T>,
    
        ctx: any
      ): ctx is AppLetContext<T> {
        return true; // Ensures the context matches the defined type
      }
    
      private updateView() {
        this.viewContainer.clear();
    
        this.viewContainer.createEmbeddedView(this.templateRef, this.context);
      }
    }

    How This Works

    1. Custom Context:

      • The AppLetContext interface defines the structure of the context passed to the directive.

      • $implicit enables shorthand syntax (let-variable) within <ng-template>.

      • appLet explicitly refers to the passed input.

    2. Type Assertion:

      • The ngTemplateContextGuard function ensures the context type is valid when used in the template.

    3. Dynamic Update:

      • The updateView() method dynamically updates the content of the <ng-template> when the directive input changes.

    Using the appLet Directive

    You can use the appLet directive with enhanced type checking as follows:

    <ng-container *appLet="user as currentUser">
      <p>User: {{ currentUser.name }}</p>
    </ng-container>

    Here:

    • The user object is passed to the appLet directive.

    • Inside the <ng-template>, currentUser is strongly typed based on the context definition.

    Benefits of Template Type Checking

    • Compile-Time Error Detection: Catches potential template issues before runtime.

    • Enhanced Developer Experience: Provides IntelliSense and type suggestions in IDEs.

    • Consistency and Safety: Ensures templates align with the directive's context and structure.

    Using Multiple Structural Directives

    In Angular, structural directives dynamically manipulate the DOM by adding or removing elements. However, when using multiple structural directives in the same element, there are specific rules and best practices to follow.

    Restriction of One Structural Directive per Element

    • Angular only allows only one structural directive per element when using the shorthand syntax (e.g., ngIf or ngFor).

    • This restriction exists because the shorthand syntax (*) unwraps into a single <ng-template> element behind the scenes.

    • If multiple structural directives are applied to the same element, Angular cannot generate multiple <ng-template> elements for the same host element.

    • Example of Invalid Usage

      <div ngIf="isLoggedIn" ngFor="let item of items">
        {{ item }}
      </div>

      This will result in an error, as Angular does not allow multiple structural directives on the same element.

    Solution: Nesting <ng-template> Elements

    To apply multiple structural directives, use explicitly nested <ng-template> elements. Each <ng-template> can host one structural directive.

    Correct Usage

    <ng-template *ngIf="isLoggedIn">
      <ng-template *ngFor="let item of items">
        <div>{{ item }}</div>
      </ng-template>
    </ng-template>

    In this example:

    • The outer <ng-template> handles the *ngIf condition (isLoggedIn).

    • The inner <ng-template> loops through the items array using *ngFor.

    Using Container Elements for Simplicity

    An alternative to nested <ng-template> elements is to group content using a container element, such as a <div>. Each directive can then be applied to separate sibling elements.

    Example: Using a Container Element

    <div *ngIf="isLoggedIn">
      <div *ngFor="let item of items">
        {{ item }}
      </div>
    </div>

    The outer <div> manages the *ngIf condition.

    The inner <div> iterates over the items array.

    This approach improves readability and simplifies template structure while avoiding deeply nested <ng-template> elements.

    Combining Multiple Directives Using Logical Expressions

    In some cases, you can use logical expressions within a single structural directive to combine conditions.

    Example: Combining *ngIf Conditions

    <div *ngIf="isLoggedIn && items.length > 0">
      <div *ngFor="let item of items">
        {{ item }}
      </div>
    </div>

    The *ngIf evaluates both isLoggedIn and items.length > 0 before rendering the element.

    Summary

    Structural directives are essential in Angular for dynamically manipulating the DOM. They enable conditional rendering of elements or groups of elements. Developers can create custom structural directives using the @Directive decorator, leveraging Angular's shorthand syntax and micro syntax parser for concise implementation. Additionally, the ngTemplateContextGuard type assertion function ensures proper type checking for directive contexts, enhancing template safety and developer experience.

    24

    Related articles

    This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.