The main goal is to gracefully separate the effects logic from the definition of the repository itself. Creating effects should be easy without worrying about managing your subscriptions and unsubscribes. Just mark how you would like to react to certain events in your store. We will do the rest for you.
- π Watch live demo
- π Check our documentation
- π Report issues
- β¨ Medium post
- Separated effects from ngxs store definition
- Application-wide effect / feature-wide effects
- Application-lifetime effects / feature-lifetime effects
- Decorators for for effects logic and effects initialization and termination
- Custom error handlers
- Limit lifetime effects
npm i -S ngxs-effects
First, define your storage. This is a common repository like you used to create
@State<Record<string, PonyInterface>>({
name: 'state',
})
@Injectable()
class PonyState {
@Action(AddPony)
addPony(context: StateContext<Record<string, PonyInterface>>, {payload}: AddPony): void {
context.setState({ ...context.getState(), [payload.id]: payload });
}
@Action(RemovePony)
removePony(context: StateContext<Record<string, PonyInterface>>, {payload}: RemovePony): void {
context.setState({ ...context.getState(), [payload.id]: null });
}
}
Next, we create a separate service dealing with effects. Suppose we would like to output to the console what was transmitted in one of the actions. To do this, we need to mark one of the methods as a decorator @Effect
@Injectable()
class PonyEffectsService {
@Effect(AddPony)
logAddPony({payload}: AddPony): void {
console.log(payload)
}
}
Next, add a core effects module to the imports of our root module. And also declare which service is used to process effects.
@NgModule({
imports: [
// ...
NgxsEffectsModule.forRoot(),
NgxsEffectsModule.forFeature(PonyEffectsService),
],
// ...
})
export class AppModule {}
And it's all! Now we have a dedicated effects service
But what if we add handling actions that should return observable. For example, we want to send a request to the server. It is easy! Just return it from the method marked by the decorator
@Injectable()
class PonyEffectsService {
constructor(private httpClient: HttpClient) {}
@Effect(AddPony)
notifyAboutNewPony$({payload}: AddPony): Observable<PonyInterface> {
return this.httpClient.post<PonyInterface>('my/api/pony', { body: payload })
}
}
By default, effects begin to listen to actions from the moment they are declared in the NgxsEffectsModule module. But suppose we would like to start wiretapping at a specific point in time. For example, when the repository appears in our place lazily in a lazily loaded module or component. In this case, it would be convenient for us to start processing actions along with calling a certain method in the effects service
We can achieve this behavior using a decorator @EffectsStart
@Injectable()
class PonyEffectsService {
constructor(private httpClient: HttpClient) {}
@EffectsStart()
onStart(): void {
console.log('PonyEffects started...');
}
@Effect(AddPony)
notifyAboutNewPony$({payload}: AddPony): Observable<PonyInterface> {
return this.httpClient.post<PonyInterface>('my/api/pony', { body: payload })
}
}
Now in the lazily loaded component we will determine when the effects begin
@Component({
selector: 'app-pony-list',
templateUrl: './pony-list.component.html',
styleUrls: ['./pony-list.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PonyListComponent implements OnInit {
constructor(private ponyEffectsService: PonyEffectsService) {}
ngOnInit(): void {
this.ponyEffectsService.onStart();
}
}
Now processing for events in this service will work only with the moment this component appears. In addition, we can now transfer the definition of our effects for features to the module of this component
@NgModule({
imports: [
// ...
NgxsEffectsModule.forFeature(PonyEffectsService),
],
declarations: [
PonyListComponent,
]
})
export class PonyListModule {}
It is important to note that with repeated appearances of this component we will not duplicate the processing of this effect
Now that we know when the effect sampling begins, we would like to complete it at a certain moment, for example, when the component disappears from view
We can achieve this behavior using a decorator @EffectsTerminate
@Injectable()
class PonyEffectsService {
constructor(private httpClient: HttpClient) {}
@EffectsStart()
onStart(): void {
console.log('PonyEffects started...');
}
@Effect(AddPony)
notifyAboutNewPony$({payload}: AddPony): Observable<PonyInterface> {
return this.httpClient.post<PonyInterface>('my/api/pony', { body: payload })
}
@EffectsTerminate()
onTerminate(): void {
console.log('PonyEffects terminated...');
}
}
Now in the lazily loaded component we will determine when the effects begin
@Component({
selector: 'app-pony-list',
templateUrl: './pony-list.component.html',
styleUrls: ['./pony-list.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PonyListComponent implements OnInit, OnDestroy {
constructor(private ponyEffectsService: PonyEffectsService) {}
ngOnInit(): void {
this.ponyEffectsService.onStart();
}
ngOnDestroy(): void {
this.ponyEffectsService.onTerminate();
}
}
Periodically, effects can lead to errors. An error that can happen during the operation of the effect will stop the processing of actions on the method where it occurred. Other effects in the same service continue to work. In order to correctly handle these errors in the same class where the effect is declared, we need a special tool
For that reasons you can use EffectsCatchError
@Injectable()
class PonyEffectsService {
// ...
@EffectsCatchError()
onError(error: Error): void {
console.log('PonyEffects error', error);
}
}
In order to catch errors occurring in all the effects services you can define your EFFECTS_ERROR_HANDLER
First you need to implement EffectErrorHandlerInterface
@Injectable({
providedIn: 'root',
})
class CustomEffectErrorHandler implements EffectErrorHandlerInterface {
onError(error: Error): void {
console.log(error);
}
}
Next uoy need to provide this services with EFFECTS_ERROR_HANDLER
token
@NgModule({
providers: [
// ...
{ provide: EFFECTS_ERROR_HANDLER, useExisting: CustomEffectErrorHandler }
],
// ...
})
export class AppModule {}
Install project
git clone https://github.com/vladborsh/ngxs-effects.git
npm i
npm run build
Run npm run start
for a dev server. Navigate to http://localhost:4200/
. The app will automatically reload if you change any of the source files.
Run npm run build
to build the project. The build artifacts will be stored in the dist/
directory
Run npm run test
to execute the unit tests via Karma.