melodic-rx
v0.0.1-alpha.4
Published
## Installation
Readme
Melodic RX
Installation
NOTE: Currently in alpha testing so you will need to put the exact version.
npm install --save [email protected]
Usage
MelodicRX is meant to look very much like NGRX and uses familiar terminology to every state management library out there with Actions, Reducers, Selectors, and Effects.
Here is an example implementation.
In this example, we are working on an online course app for teaching foreign languages to students. Let's say we have a global state and part of that state is called "lessonState". This lessonState has a property called "lessonTypes" which is an array of ILessonType.
First we can define our application state:
Application State
import { createAppState, ActionReducerMap, ActionEffectsMap } from 'melodic-rx';
import { ILessonType } from '@myapp/interfaces/ilesson-type.interface';
import { lessonReducers } from './reducers/lesson.reducers';
import { LessonEffects } from './effects/lesson.effects';
export interface ILessonState {
lessonTypes: ILessonType[];
loading: boolean;
}
export interface IGlobalState {
lessonState: ILessonState;
}
export const initState: IGlobalState = {
lessonState: {
lessonTypes: [],
loading: false
}
};
export const appState = createAppState<IGlobalState>(initState);
// see reducers in docs below
export const actionReducerMap: ActionReducerMap<IGlobalState> = {
lessonState: lessonReducers
};
// see effects in docs below
export const actionEffectsMap: ActionEffectsMap<IGlobalState> = {
lessonState: LessonEffects
};With application state defined, lets create our actions:
Actions
import { ILessonType } from '@myapp/interfaces/ilesson-type.interface';
import { createAction, props } from 'melodic-rx';
export const loadLessonTypes = createAction('[Lessons] load lesson types', props());
export const setLessonTypes = createAction('[Lessons] set loaded lesson types', props<{ lessonTypes: ILessonType[] }>());Now let's define the reducers for these actions:
Reducers
import { createReducer, onAction } from 'melodic-rx';
import { IGlobalState } from '../application-state';
import * as lessonActions from '../actions/lesson.actions';
export const lessonReducers = createReducer<IGlobalState, 'lessonState'>(
onAction(lessonActions.loadLessonTypes(), (state, action) => {
return { ...state, loading: true };
}),
onAction(lessonActions.setLessonTypes(), (state, action) => {
return { ...state, lessonTypes: action.payload.lessonTypes, loading: false };
})
);Now let's define the effects that can fire as side effects of actions that are dispatched:
Effects
import { Injectable, inject } from '@angular/core';
import { EffectsBase } from 'melodic-rx';
import { take } from 'rxjs';
import { loadLessonTypes, setLessonTypes } from '../actions/lesson.actions';
import { LessonService } from '@myap/api/services/lesson.service';
import { ILessonType } from '@myapp/interfaces/ilesson-type.interface';
@Injectable({
providedIn: 'any'
})
export class LessonEffects extends EffectsBase {
private _lessonService: LessonService = inject(LessonService);
constructor() {
super();
this.addEffect([loadLessonTypes()], (_) => {
return new Promise((resolve, reject) => {
this._lessonService
.getLessonTypes()
.pipe(take(1))
.subscribe({
next: (lessonTypes: ILessonType[]) => {
resolve(setLessonTypes({ lessonTypes }));
},
error: (error) => {
console.error('Error loading lesson types', error);
reject(error);
}
});
});
});
}
}With the application state, actions, reducers, and effects defined let's configure our app to use them. In your app.config.ts you can add these lines:
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { appRoutes } from './app.routes';
import { provideMDRX } from 'melodic-rx';
import { actionEffectsMap, actionReducerMap, appState } from './shared/state/application-state';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(appRoutes),
provideHttpClient(),
provideAnimationsAsync(),
provideMDRX(appState, actionReducerMap, actionEffectsMap)
]
};Now that the application is configured to use the different MelodicRX pieces, let's see an example of dispatching actions:
Dispatching Actions
import { Component, inject, OnInit } from '@angular/core';
import { SignalStoreService } from 'melodic-rx';
import { IGlobalState } from './shared/state/application-state';
import { loadLessonTypes } from './shared/state/actions/lesson.actions';
@Component({
standalone: true,
selector: 'instr-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
private _signalStore: SignalStoreService<IGlobalState> = inject(SignalStoreService);
ngOnInit(): void {
this._signalStore.dispatch('lessonState', loadLessonTypes());
}
}...And here is an example of selecting a portion of state...
Selecting State
import { Component, computed, inject, input, output, signal, Signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ILessonType } from '@myapp/interfaces/ilesson-type.interface';
import { IGlobalState } from '../../../../shared/state/application-state';
import { SignalStoreService } from 'melodic-rx';
@Component({
selector: 'instr-lesson-card',
standalone: true,
imports: [CommonModule],
templateUrl: './lesson-card.component.html',
styleUrl: './lesson-card.component.scss'
})
export class LessonCardComponent {
private _signalStore: SignalStoreService<IGlobalState> = inject(SignalStoreService);
lessonTypes: Signal<ILessonType[]> = this._signalStore.select('lessonState', (state) => state.lessonTypes);
}Folder Structure
- app/shared/state
- actions
- lesson.actions.ts
- effects
- lesson.effects.ts
- reducers
- lesson.reducers.ts
- application.state.ts
- actions
More To Come...
Obviously this is a pretty simplistic implementation at the moment. There is more to come.
