@DataAction() - This decorator emulates the execution of asynchronous or synchronous actions. Actions can either be thought of as a command which should trigger something to happen.
setState - this is a public method of the NgxsDataRepository class, it is annotated by the action decorator. This means that when it is called, an action will be registered into Store and will be called dispatch from Store. Thus you will see the state of the changed state through the logger or devtools plugins. When you call setState then it calls the ctx.setState method from state context.
State Context provides a way to pass data through the global states tree without having to pass new state manually at every level.
However, there is an unpleasant moment here, if you call the context directly, then you may not see anything in the devtools, because the context will be called immediately without dispatching states:
@StateRepository()@State<string[]>({ name:'todo', defaults: []})@Injectable()exportclassTodoStateextendsNgxsDataRepository<string[]> {publicaddTodo(todo:string):void {this.ctx.setState(todo); }}@Component({/* ... */})classTodoComponent {constructor(private todoState:TodoState) {}publicaddTodo(todo:string):void {// When you make a call, the state will be changed directly,// without notifying the NGXS lifecycle services// This is a bad approach!this.todoState.addTodo(todo); }}
Therefore, the context should only be called inside the action.
// GOOD@StateRepository()@State<string[]>({ name:'todo', defaults: []})@Injectable()exportclassTodoStateextendsNgxsDataRepository<string[]> { @DataAction()publicaddTodo(@Payload('todo') todo:string):void {// call context from under the actionthis.ctx.setState(todo); }}// GOOD@StateRepository()@State<string[]>({ name:'todo', defaults: []})@Injectable()exportclassTodoStateextendsNgxsDataRepository<string[]> {publicaddTodo(@Payload('todo') todo:string):void {// call context inside another actionthis.setState(todo); }}
However, there are very difficult situations in which you yourself need to determine which method should be an action and which should not:
Why didn’t we see anything? why didn’t we see a new state?
Everything is very simple, we made our method an action, this method is asynchronous and when the request ends, the action ends. But then we manually change the state directly through the context, but the previous action has already completed and we will not get the difference in states in the devtools.
Now everything works, but in this case, we understand that the getContent method should be an ordinary method, not an action, because during its execution the state does not change, the state changes only after the request is completed. Therefore, it would be more correct to write such code:
The most common use of action is to optimize performance when starting a work consisting of one or more asynchronous or synchronous tasks that don't require UI updates or error handling to be handled by Angular. Such tasks can be kicked off via runOutsideAngular and if needed, these tasks can reenter the Angular zone via run.
By default, all action methods are called outside the Angular zone. But if you want to change this, you can define a parameter insideZone:
@StateRepository()@State({ name:'counter', defaults:0})@Injectable()classCounterStateextendsNgxsDataRepository<number> { @DataAction({ insideZone:true })publicincrementInZone():void {console.log('expect is in Angular Zone',NgZone.isInAngularZone());this.ctx.setState((state) =>++state); }}
Use @DataAction without subscription
With Data-plugin in case, @DataAction returns an Observable, you have to subscribe to the @DataAction function to fire an action itself. Without a subscribe, action will not be fired, and you will not see updates in the store. This can be confusing and unexpected for developers that are used to pure NGXS. Also, it can provide issues while integrating Data-plugin into the projects that were developed a long time without it.
To achieve an origin behavior of Actions you can pass option subscribeRequired with false value to the @DataAction decorator.
@StateRepository()@State<PersonModel>({ name:'person', defaults: { title:null!, description:null! }})@Injectable()exportclassPersonStateextendsNgxsImmutableDataRepository<PersonModel> {constructor(privatereadonly personService:PersonService) {super(); }// Note: Also can be configured globally by providing custom NGXS_DATA_CONFIG @DataAction({ subscribeRequired:false })publicgetContent():Observable<PersonModel> {returnthis.personService.fetchAll().pipe(tap((content:PersonModel):void=>this.setState(content))); }}
The same behavior can be achieved globally for all @DataAction in the app by providing a global config property.