@d4h/ion-ngx-storage
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.
Installation
npm install --save @d4h/ion-ngx-storage
Configuration
interface StorageConfig<T extends object = {}> {
keys?: Array<string>;
name: string;
storage?: {
description?: string;
driver?: string | Array<string>;
name?: string;
size?: number;
version?: number;
}
}
-
keys: Array<string>
: Optional array of keys. When given, corresponding values are picked from the top-level state before write and after read. -
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.
Default Configuration
const defaultConfig: StorageConfig = {
keys: [],
name: 'ion_ngx_storage',
storage: {
name: 'ion_ngx_storage'
}
};
Use
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.
Without Extra Configuration
import { StorageModule } from '@d4h/ion-ngx-storage';
@NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
EffectsModule.forRoot(effects),
StorageModule
]
})
export class AppModule {}
With Extra Configuration
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.
Waiting for Hydration
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
.
ReadSuccess Action
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(/* ... */) {}
}
Inject Configuration
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 }
]
};
}
}
Selecting Storage Status
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.
Defer Store Access
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.
AccountFacade
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>
) {}
}
AuthenticatedGuard
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');
})
);
}
}
Logout/Reinitialization
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'] })
])
));
}
Support and Feedback
Feel free to email support@d4h.org, open an issue or tweet @d4h.
License
Copyright (C) 2019 D4H
Licensed under the MIT license.