ion-ngx-storage is a module for Ionic 4/Angular applications which copies an application's NgRx root state to the native device or browser through Ionic/Storage.
npm install --save @d4h/ion-ngx-storage
interface StorageConfig<T extends object = {}> {
name: string;
storage?: {
description?: string;
driver?: string | Array<string>;
name?: string;
size?: number;
version?: number;
}
}
name: string
: The name of your application. Used internally as an Ionic Storage table key. All data is stored per application as a single object.storage?: StorageConfig
: Ionic Storage configuration.
const defaultConfig: StorageConfig = {
name: 'ion_ngx_storage',
storage: {
name: 'ion_ngx_storage'
}
};
Import StoreModule
or call StoreModule.forRoot()
with a configuration. ion-ngx-storage read and write the application state without any additional configuration. After effects initialization, ion-ngx-storage writes a serialized copy of the root state to the device after each action dispatch.
import { StorageModule } from '@d4h/ion-ngx-storage';
@NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
EffectsModule.forRoot(effects),
StorageModule
]
})
export class AppModule {}
import { StorageConfig, StorageModule } from '@d4h/ion-ngx-storage';
// Optional configuration
const storageConfig: StorageConfig<AppState> = {
name: 'my_application_name'
};
@NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
EffectsModule.forRoot(effects),
StorageModule.forRoot(storageConfig)
]
})
export class AppModule {}
Your application must import StoreModule.forRoot
and EffectsModule.forRoot
in order for ion-ngx-storage to function.
Internally, ion-ngx-storage operates in the following manner:
- Register
StorageEffects
andHydrateEffects
. - Dispatch
Read
fromHydrateEffects
. - Read state from storage and dispatch
ReadResult
. - Merge the result into the application state via meta-reducer.
- If
{ hydrated: true }
then dispatchReadSuccess
.
ion-ngx-storage makes the ReadSuccess
action public for use in NgRx effects.
import { ReadSuccess } from '@d4h/ion-ngx-storage';
@Injectable()
export class AppEffects {
// Keep up splash screen until after hydration.
init$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(ReadSuccess),
tap(() => {
this.platform.ready().then(() => {
this.statusBar.styleLightContent();
this.splashScreen.hide();
});
})
),
{ dispatch: false }
);
constructor(/* ... */) {}
}
The public STORAGE_CONFIG
token allows injection of the configuration in cases of module composition.
import { STORAGE_CONFIG, StorageConfig, StorageModule } from '@d4h/ion-ngx-storage';
@NgModule({
imports: [
StorageModule
]
})
export class AppFeatureModule {
static forFeature(config: FeatureConfig): ModuleWithProviders {
return {
ngModule: AppFeatureModule,
providers: [
{ provide: STORAGE_CONFIG, useValue: config.storage }
]
};
}
}
ion-ngx-storage makes StorageState
available for cases where you need to select or extend the state:
import { StorageState } from '@d4h/ion-ngx-storage';
export interface AppState extends StorageState {
// ...
}
After this you can employ the getHydrated
and getStorageState
selectors.
Although ion-ngx-storage hydrates data from storage once NgRx Effects dispatches ROOT_EFFECTS_INIT
, the asynchronous nature of Angular and NgRx make it likely your application will attempts to read from the state it is ready. Applications which rely on the NgRx store to determine i.e. authentication status must be modified in a way which defers assessment until after hydration.
In both cases below:
filter(Boolean)
leads to only truthy values emitting.- Once this happens,
switchMap
replaces the prior observable with a new one that contains the actual assessment of authentication status.
import { StorageFacade } from '@d4h/ion-ngx-storage';
@Injectable({ providedIn: 'root' })
export class AccountFacade {
readonly authenticated$: Observable<boolean> = this.storage.hydrated$.pipe(
filter(Boolean),
switchMap(() => this.store.select(getAuthenticated))
);
constructor(
private readonly storage: StorageFacade,
private readonly store: Store<AppState>
) {}
}
import { AccountFacade } from '@app/store/account';
@Injectable({ providedIn: 'root' })
export class AuthenticatedGuard implements CanActivate {
private readonly authenticated$: Observable<boolean>;
constructor(
private readonly accounts: AccountFacade,
private readonly router: Router
) {
this.authenticated$ = this.store.pipe(
select(getHydrated),
filter(Boolean),
switchMap(() => this.store.select(getAuthentication))
);
}
canActivate(): Observable<boolean | UrlTree> {
return this.authenticated$.pipe(
map((authenticated: boolean): boolean | UrlTree => {
return authenticated || this.router.parseUrl('/login');
})
);
}
}
Many applications have some kind of logout action which resets the application to its in initial state. In these cases ion-ngx-storage resets to { hydrated: false }
, meaning it will no longer write device state to storage. In these cases you have to dispatch one Clear
or Read
:
Clear
: Wipe the stored application state and triggersRead
with an initial empty value.Read
: Reads the logged-out state and triggers reducer setting{ hydrated: true }
.
The difference in practice is whether you want to remove all content stored on the device.
import { Read } from '@d4h/ion-ngx-storage';
class LoginEffects {
logout$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(Logout),
switchMap(() => [
Read(),
Navigate({ path: ['/login', 'username'] })
])
));
}
Feel free to email [email protected], open an issue or tweet @d4h.
Copyright (C) 2019 D4H
Licensed under the MIT license.