Flexibile decorator, an alternative for the
@Select
but selects a snapshot of the state.
This package is compatible only with Ivy!
To install @ngxs-labs/select-snapshot-ivy
run the following command:
yarn add @ngxs-labs/select-snapshot-ivy
Import the module into your root application module:
import { NgModule } from '@angular/core';
import { NgxsModule } from '@ngxs/store';
import { NgxsSelectSnapshotIvyModule } from '@ngxs-labs/select-snapshot-ivy';
@NgModule({
imports: [
NgxsModule.forRoot(states),
NgxsSelectSnapshotIvyModule.forRoot()
]
})
export class AppModule {}
There are 2 decorators exposed publicly. These are @SelectSnapshot
and @ViewSelectSnapshot
. They can decorate class properties. Given the following example:
@Injectable()
export class TokenInterceptor {
@SelectSnapshot(AuthState.token) token: string | null;
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.token) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${this.token}`
}
});
}
return next.handle(req);
}
}
We assume that the AuthState
has static selector token
:
export class AuthState {
@Selector()
static token(state: AuthStateModel): string | null {
return state.token;
}
}
As you mentioned we don't have to inject the Store
class and call the selectSnapshot
. This simplifies business logic. What about the @ViewSelectSnapshot
decorator? This decorator must be used only inside components and directives. Why? They are able to inject an instance of the ChangeDetectorRef
. The @ViewSelectSnapshot
decorator retrieves ChangeDetectorRef
instance and invokes markForCheck
under the hood thus your view gets updated. Let's look at the below example:
import {
Component,
ChangeDetectionStrategy,
ViewChild,
ElementRef,
OnInit,
NgZone,
Renderer2,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot-ivy';
import { CounterState, CounterStateModel, Increment } from './counter.state';
@Component({
selector: 'app-root',
template: `
<pre>Counter state: {{ counter | json }}</pre>
<button #button>Increment outside of Angular's zone</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements OnInit {
@ViewSelectSnapshot(CounterState) counter: CounterStateModel;
@ViewChild('button', { static: true }) button: ElementRef<HTMLButtonElement>;
constructor(private zone: NgZone, private renderer: Renderer2, private store: Store) {}
ngOnInit() {
this.zone.runOutsideAngular(() =>
this.renderer.listen(this.button.nativeElement, 'click', () => {
this.store.dispatch(new Increment());
})
);
}
}
We intentionally use the runOutsideAngular
to add an event listener outside of the Angular's zone, thus it will not cause ApplicationRef.tick
to be invoked. But if you try this example you will mention that the view gets updated still reacting on the state changes.
We have looked at several examples of using the @SelectSnapshot
and the @ViewSelectSnapshot
. Consider to use the @SelectSnapshot
in non-component classes only! Use the @ViewSelectSnapshot
in components and directives only!