Skip to main content

RxIf

RxIf is a reactive suspense / error / complete template state machine driven from an Observable (or Signal, or static value). Bind an async condition directly and render loading, error, and complete UI from the source's notifications: no manual @if boilerplate, no extra async pipes to wire the states together.

For a plain boolean toggle, prefer native @if: it is synchronous and needs no import. Reach for RxIf when you want the full reactive context (suspense / error / complete) of an async source expressed declaratively, together with per-directive render scheduling.

Import

import { RxIf } from '@rx-angular/template/if';

@Component({
standalone: true,
imports: [RxIf],
template: `...`,
})
export class AnyComponent {}

Inputs

Value

InputTypeDescription
rxIfboolean or ObservableInput<boolean> or Signal<boolean>The observable, signal, or value bound to the context of the template.

Contextual state

InputTypeDescription
errorTemplateRef<RxIfViewContext>Template for the error state.
completeTemplateRef<RxIfViewContext>Template for the complete state.
suspenseTemplateRef<RxIfViewContext>Template for the suspense state.
nextTriggerObservable<unknown>Trigger to show the next template.
errorTriggerObservable<unknown>Trigger to show the error template.
completeTriggerObservable<unknown>Trigger to show the complete template.
suspenseTriggerObservable<unknown>Trigger to show the suspense template.
contextTriggerObservable<RxNotificationKind>Trigger to show any template, based on the given RxNotificationKind.

Rendering

InputTypeDescription
thenTemplateRef<RxIfViewContext>Template for when the bound condition is true.
elseTemplateRef<RxIfViewContext>Template for when the bound condition is false.
patchZonebooleandefault: true if false, RxIf operates outside NgZone. Zoneful-only concern; see How to tune rendering with strategies.
parent (deprecated)booleandefault: false if true, RxIf informs its host component about template changes so @ViewChild / @ContentChild queries update. Deprecated: not needed with signal queries.
strategyObservable<RxStrategyNames> or RxStrategyNamesdefault: normal the RxStrategyRenderStrategy used to detect changes. See How to tune rendering with strategies.
renderCallbackNextObserver<boolean>Emits the latest value causing the view to update, giving the exact timing when RxIf created or removed its template.

Features

DX

  • Context variables (error, complete, suspense).
  • Context templates (error, complete, suspense).
  • Context triggers to switch context manually.
  • Reduces boilerplate (removes multiple async pipes).
  • Works with static values (*rxIf="true").

Performance

  • Value binding is always present.
  • Lazy template creation, driven by render strategies.
  • Change detection scheduled at the EmbeddedView level.
  • Distinct-until-changed on repeated values.

Usage

RxIf ships as a structural directive; use its *rxIf microsyntax.

src/some.component.html
<app-item *rxIf="show">
<app-item-child />
</app-item>
src/some.component.ts
import { RxIf } from '@rx-angular/template/if';
import { Component, signal } from '@angular/core';

@Component({
standalone: true,
imports: [RxIf],
templateUrl: './some.component.html',
})
export class SomeComponent {
show = signal(true);
}

The bound value may be a Signal, an Observable, or a static value:

<!-- Observable -->
<app-item *rxIf="show$"><app-item-child /></app-item>

<!-- Signal -->
<app-item *rxIf="showSignal()"><app-item-child /></app-item>

<!-- static -->
<app-item *rxIf="true"><app-item-child /></app-item>
note

The full reactive context (suspense, error, complete) can only be derived from Observable sources. A Signal derives suspense and error only. Static values have no reactive context.

Context

RxIf exposes the reactive context of its source in two ways: context variables and context templates.

Context variables

Available on both the then and else templates, based on the last valid value:

  • $implicit: T, the default variable accessed via let val
  • error: boolean | Error
  • complete: boolean
  • suspense: boolean
<ng-container *rxIf="customer$; let customer; let s = suspense; let e = error; let c = complete">
@if (s) { <loader /> } @if (e) { <error /> } @if (c) { <complete /> }
<app-customer [customer]="customer" />
</ng-container>

With an else template:

<ng-container *rxIf="show$; else: nope; let s = suspense; let e = error; let c = complete">
@if (s) { <loader /> } @if (e) { <error /> } @if (c) { <complete /> }
<app-item />
</ng-container>
<ng-template #nope let-s="suspense" let-e="error" let-c="complete">
@if (s) { <loader /> } @if (e) { <error /> } @if (c) { <complete /> }
<nope />
</ng-template>

Context variables with then/else templates on initial rendering

valuereactive contexttemplate (both defined)template (only then)
undefinedsuspenseno renderno render
truthy primitive value (number, string, boolean, ..)nextthenthen
falsy primitive value (number, string, boolean, ..)nextelseno render
Observable emitting undefinedsuspenseelseno render
Observable or Promise not yet emitted a value (e.g Subject)suspenseno renderno render
Observable emitting truthynextthenthen
Observable emitting falsy value !== undefinednextelseno render
Observable completing after truthy value (e.g of(true))completethenthen
Observable completing after falsy (incl. undefined) value (e.g of(undefined))completeelseno render
Promise emitting truthy valuecompletethenthen
Promise emitting falsy (incl. undefined) valuecompleteelseno render
Observable throwing an error after truthy valueerrorthenthen
Observable throwing an error after falsy value (incl. undefined)errorelseno render

Context templates

Template anchors render the reactive context declaratively:

<ng-container *rxIf="show$; error: error; complete: complete; suspense: suspense">
<app-item />
</ng-container>

<ng-template #suspense><loader /></ng-template>
<ng-template #error><error /></ng-template>
<ng-template #complete><completed /></ng-template>

Context templates with then/else templates on initial rendering

valuereactive contexttemplate (both defined)template (only then)
undefinedsuspensesuspensesuspense
truthy primitive value (number, string, boolean, ..)nextthenthen
falsy primitive value (number, string, boolean, ..)nextelseno render
Observable emitting undefinedsuspensesuspensesuspense
Observable or Promise not yet emitted a value (e.g Subject)suspensesuspensesuspense
Observable emitting truthynextthenthen
Observable emitting falsy value !== undefinednextelseno render
Observable completing after truthy value (e.g of(true))completecompletecomplete
Observable completing after falsy (incl. undefined) value (e.g of(undefined))completecompletecomplete
Promise emitting truthy valuecompletecompletecomplete
Promise emitting falsy (incl. undefined) valuecompletecompletecomplete
Observable throwing an error after truthy valueerrorerrorerror
Observable throwing an error after falsy value (incl. undefined)errorerrorerror

Triggers

Besides deriving the reactive context from the source, RxIf can switch context manually. Each trigger applies its context state and updates the context variables (or switches to the registered template).

TriggerTypeEffect
nextTriggerObservable<unknown>Switch back from any template to the actual value (next).
errorTriggerObservable<unknown>Switch to the error template.
completeTriggerObservable<unknown>Switch to the complete template.
suspenseTriggerObservable<unknown>Switch to the suspense template.
contextTriggerObservable<RxNotificationKind>Set any context; combines suspenseTrigger, completeTrigger, and errorTrigger in one.
@Component({
standalone: true,
imports: [RxIf],
template: `
<button (click)="nextTrigger$.next()">show value</button>
<ng-container *rxIf="show$; complete: complete; nextTrigger: nextTrigger$">
<item />
</ng-container>
<ng-template #complete>✔</ng-template>
`,
})
export class AppComponent {
private readonly state = inject(GlobalState);
nextTrigger$ = new Subject<void>();
show$ = this.state.show$;
}

Using contextTrigger to set an arbitrary context:

@Component({
standalone: true,
imports: [RxIf],
template: `
<input (input)="search($event.target.value)" />
<ng-container *rxIf="show$; suspense: suspense; contextTrigger: contextTrigger$">
<item />
</ng-container>
<ng-template #suspense>loading...</ng-template>
`,
})
export class AppComponent {
private readonly state = inject(GlobalState);
show$ = this.state.show$;
contextTrigger$ = new Subject<RxNotificationKind>();

search(str: string) {
this.state.search(str);
this.contextTrigger$.next(RxNotificationKind.Suspense);
}
}

See also

Example applications: a demo is available on GitHub.