Reader Stacks

Angular Structural Directives: *ngFor, *ngIf, and Combining Them

The two most-used built-in structural directives, the if-else-elseif pattern, looping over a plain object, and the workaround for needing both on the same element.

*ngFor and *ngIf are the two most-used built-in Angular structural directives, and understanding their individual behavior — plus the one genuine restriction on combining them directly — covers the majority of conditional and repeated rendering in a typical Angular template.

Basic *ngFor

  • {{ product.name }}
  • *ngFor with the index

  • {{ i + 1 }}. {{ product.name }}
  • Basic *ngIf

    Welcome back!

    *ngIf with if/else

    Welcome back!
    Please log in

    *ngIf with if/else-if/else

    Loading...
    Something went wrong.
    Content loaded successfully.

    Angular has no native *ngIf/*ngElseIf/*ngElse chain — nesting templates this way is the standard workaround for an if/else-if/else chain using only *ngIf and named ng-template references.

    Why you can't put *ngFor and *ngIf on the same element

    
    
  • {{ product.name }}
  • Two structural directives can't coexist on the same element, since each one desugars to its own wrapping behind the scenes — having both would mean two conflicting template wrappers on one element.

    The fix: wrap with ng-container

    
        
  • {{ product.name }}
  • Following the ng-container pattern, this splits the two directives across the container and the actual element without adding any extra wrapping DOM node to the rendered output.

    Looping over a plain object's keys and values

    {{ entry.key }}: {{ entry.value }}
    objectEntries(obj: Record) {
        return Object.entries(obj).map(([key, value]) => ({ key, value }));
    }

    *ngFor only iterates arrays and other genuinely iterable values natively — for a plain object, converting it to an array of key/value pairs first (via Object.entries(), as shown here, or Angular's built-in KeyValue pipe) is necessary before *ngFor can loop over it.

    Using the built-in KeyValue pipe instead

    {{ entry.key }}: {{ entry.value }}

    The keyvalue pipe does the same object-to-array conversion directly in the template, without needing a helper method in the component class — generally the more convenient choice for a simple case, while a component method offers more control if the conversion needs custom sorting or filtering logic.

    The modern alternative: Angular's built-in control flow syntax (Angular 17+)

    @for (product of products; track product.id) {
        
  • {{ product.name }}
  • } @if (isLoggedIn) {
    Welcome back!
    } @else { Please log in }

    Newer Angular versions introduced this built-in @for/@if syntax as a more concise, JavaScript-like alternative to the *ngFor/*ngIf directives — both approaches remain valid and supported, with the newer syntax becoming increasingly common in projects built on Angular 17 and later.