Banishing State Observables in Angular

Building a complex single-page application with Angular often becomes significantly more manageable when you bring a state management library into the mix.

The application store serves as the definitive source of truth, holding all the essential data required for the app to operate correctly.

With the store in place, I can retrieve or modify this data from any smart component acting as a container.

Still, handling the numerous subscriptions to the store can get tedious. In a single component, I often find myself juggling multiple @Select() decorators, each handing back an Observable for its corresponding property in the store.

For every one of those Observables, a subscription must be created and then cleaned up when the component is destroyed—this adds up to a lot of boilerplate code.

Wouldn't it be great to eliminate all these Observables altogether?

In straightforward scenarios, the async pipe comes to the rescue. It handles the subscription for you and automatically extracts the latest value emitted, saving you from manual subscription management.

Imagine we have a @Select() decorator set up like this:

@Select(ListState.SelectAllItems) listItems: Observable<string[]>;

We can then put it to use directly within our template alongside the async pipe:

<ul> 
  <li *ngFor="let item of listItems | async">{{item}}</li>
</ul>

However, there are plenty of situations where subscribing to the Observables within the component is necessary, allowing us to leverage the emitted values inside other methods.

(If you are interested in setting up a store and seeing these examples in a full project, check out my article How to Create a Simple Store in Angular.)


Getting to Know @ngxs-labs/select-snapshot

@ngxs-labs/select-snapshot is an experimental offering from NGXS Labs, developed for the NGXS framework.

Although it has not yet been officially bundled with NGXS, it stands a good chance of gaining official status in the near future. This package introduces @SelectSnapshot() as a substitute for the @Select() decorator.

So, how do these two differ?

While @Select() produces an Observable that demands our subscription, @SelectSnapshot() takes care of the subscription internally and hands back the most recent value. To put it into practice, we need to install the package using this command:

npm install @ngxs-labs/select-snapshot

Next, we need to bring it into our appModule:

@NgModule({
 declarations:
  [AppComponent,
   ListContainerComponent,
   ListItemInputComponent,
   ListComponent
  ],
  imports: 
   [BrowserModule,
   AppRoutingModule,
   FormsModule,
   ReactiveFormsModule,
   NgxsModule.forRoot([ListState]),
   NgxsReduxDevtoolsPluginModule.forRoot(),
   NgxsSelectSnapshotModule.forRoot()
   ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule {}

Now, we have the ability to swap out the @Select() decorator:

@Select(ListState.SelectAllItems) listItems:Observable<string[]>;
@SelectSnapshot(ListState.SelectAllItems) listItems: string[];

And we can start using the value from the store instantly, with no need for a subscription!

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

The @ViewSelectSnapshot Decorator Explained

The select-snapshot library also provides a second selector called @ViewSelectSnapshot(). The choice between this one and the standard @SelectSnapshot() can have implications based on how the component handles change detection.

Consider a presentational component that has change detection configured with OnPush:

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})

export class MyComponent {
  @SelectSnapshot(ListState.SelectAllItems) listItems: string[];
  @ViewSelectSnapshot(ListState.SelectFilteredItems) filteredItems: string[];
}

With this setup, the component won't undergo constant change checks; instead, a check is initiated whenever a user interacts with it.

Here is where @ViewSelectSnapshot differs from its counterpart @SelectSnapshot: it triggers a markForCheck() call on the component whenever the state is updated. This ensures that change detection is kicked off properly.

Given this behavior, the NgXs team advises leaning towards @ViewSelectSnapshot rather than @SelectSnapshot when dealing with template bindings and dynamic rendering.

Wrapping Up

The select-snapshot library delivers handy utilities for avoiding Observable subscriptions entirely in our components. It greatly simplifies the development experience, though you should keep a close eye on how it interacts with change detection!