Sharing (Ngrx-based) logic between Angular8 web app and Ionic4 mobile app

Benoit Hediard
Stories by Agorapulse
5 min readJan 8, 2018

This is a follow up article to Hybrid mobile apps: sharing logic between Angular2 and Ionic2 apps. In 2016, it was our first attempt to share Ngrx-based business logic in between an Angular2 app and an Ionic2 mobile app, but it used a pretty unstable npm link “hack”, with three separate npm Typescript projects.

With Angular8 and Ionic4 latest CLIs, there is now a much elegant way to achieve this, thanks to a mono-repo project structure.

The goals remains the same:

  • encapsulate all the business logic (based on @ngrx/store) in a core module, and
  • keep specific view layout, markup and navigation logic in the mobile and web app projects.

Note: @ngrx/store is a RxJS powered state management inspired by Redux for Angular apps. It’s currently the most popular way to structure complex business logic in Angular apps.

Source: https://www.smashingmagazine.com/2016/06/an-introduction-to-redux/

Demo app and Github repo

Here is the repo of our proof of concept:

The repo use a mono-repo Nx Workspace structure (with custom Ionic integration detailed later):

  • /, project root based on Angular (version 8.1.2) and @ngrx/store (version 8.1.0), managed by Angular CLI
  • /libs/core, a shared module with state, reducers and actions logic,
  • /apps/web, a web app
  • /apps/mobile, a mobile app, based on Ionic (version 4.7.1), managed by Ionic CLI

For the demo, we are using the counter example code (actions and reducers) from @ngrx/store.

The core module provides:

  • CounterState interface, counter state model
  • counterReducer reducer, counter state management based on dispatched actions,
  • Increment, decrement and reset counter actions

In the mobile or web app, we use those assets to build the app state model.

import {CounterState} from ‘@ngrx-demo/core’;export interface AppState {
counter: CounterState,
// Add other states here
}

And, provide actions, services and reducers during app bootstrap.

import {ActionReducerMap} from '@ngrx/store';
import {counterReducer} from '@ngrx-demo/core';
import {AppState} from './app.state';
const reducers: ActionReducerMap<AppState> = {
counter: counterReducer
};
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot(reducers)
]
bootstrap: [
AppComponent
],
declarations: [
AppComponent
],

})
export class AppModule { }

We can now use those assets in any view component of the app.

// HTML component template
<h2>
{{counter$ | async}}
</h2>
<p>
<button (click)=”increment()”> +1 </button>
<button (click)=”decrement()”> -1 </button>
</p>
<p>
<button (click)=”reset()”> Reset </button>
</p>

With the corresponding component:

// Typescript angular component
import {CounterActions} from '@ngrx-demo/core';
import {AppState} from './app.state';
@Component({
templateUrl: 'some.component.html'
})
export class SomeComponent {
counter$: Observable<number>; constructor(private store: Store<AppState>) {
this.counter$ = this.store.select(s => s.counter.total);
}
decrement() {
this.store.dispatch(new CounterActions.DecrementAction());
}
increment() {
this.store.dispatch(new CounterActions.IncrementAction());
}
reset() {
this.store.dispatch(new CounterActions.ResetAction());
}
}

All the business logic is completely encapsulated in the shared module. For example, we could add @ngrx/effects to the mix, in order to handle API async calls.

The view only “knows” about the available actions to dispatch and the state model it can subscribe to.

Ionic app integration into a Nx Workspace

By default, Nx Workspaces don’t support Ionic apps. Since Ionic uses its own CLI and dependencies to build, test and run an app, /app/mobile has its own /app/mobile/package.json (which is independant from the main workspace /package.json), and therefore it’s own /app/mobile/node_modules.

Ionic custom config

In package.json, Ionic provides several hooks to customize default config. We are using the ionic_watch and ionic_webpack config options and 2 custom config files stored in /app/mobile/config.

// app/mobile/package.json
{
...
"description": “An Ionic project”,
"config": {
"ionic_watch": "./config/watch.config.js",
"ionic_webpack": "./config/webpack.config.js"

},
...
}

Typescript module resolution

In order to be able to use our shared modules and get correct Typescript module resolution, we use a custom paths mapping property in /app/mobile/tsconfig.json. In our app, we use @ngrx-demo, but you could use @local for instance.

// app/mobile/tsconfig.json
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@ngrx-demo/*": ["../../libs/*"]
}
...
}

And TSConfig paths webpack plugin in our custom Webpack config, so that Webpack is aware of those custom path mappings.

// app/mobile/config/webpack.config.js
const TsconfigPathsPlugin = require(‘tsconfig-paths-webpack-plugin’);
const resolveConfig = {
extensions: [‘.ts’, ‘.js’, ‘.json’],
modules: [path.resolve(‘node_modules’)],
plugins: [
new TsconfigPathsPlugin({})
]

};
// Default config update
const webpackConfig = require(‘../node_modules/@ionic/app-scripts/config/webpack.config’);
webpackConfig.dev.resolve = resolveConfig;
webpackConfig.prod.resolve = resolveConfig;

Live reload

It’s great to be able to benefit from live reload during development: when running ionic serve, if a file is updated in /libs/*, the changes are detected and the mobile app is automatically reloaded in your browser. We use the custom watch config for that:

// app/mobile/config/watch.config.js
// Default config update
const watchConfig = require(‘../node_modules/@ionic/app-scripts/config/watch.config’);
watchConfig.srcFiles.paths = [
‘{{SRC}}/**/*.(ts|html|s(c|a)ss)’,
‘../../libs/**/*.(ts|html|s(c|a)ss)’
];

Ngrx Store Dev Tools

And last be not least, during development, the Ngrx Store Dev Tools is a must to debug your app, but it must disabled in production environment. We use custom @app/env path mappings for that (to mimic environment variables in Angular apps), thanks to https://github.com/gshigeto/ionic-environment-variables.

// app/mobile/tsconfig.json
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@app/env": ["src/environments/environment"],
"@ngrx-demo/*": ["../../libs/*"]
}
...
}

With alias mapping in our custom Webpack config.

// app/mobile/config/webpack/config.json...
webpackConfig.dev.resolve.alias = {
"@app/env": path.resolve('../src/environments/environment.ts')
};
webpackConfig.prod.resolve.alias = {
"@app/env": path.resolve('../src/environments/environment.prod.ts')
};

Then, we can simply import our environment variables in our Ionic app!

import {StoreModule} from '@ngrx/store';
import {StoreDevtoolsModule} from '@ngrx/store-devtools';
...
import {environment} from '
@app/env';
@NgModule({
...
imports: [
BrowserModule,
IonicModule.forRoot(AppComponent),
DemoCoreModule,
StoreModule.forRoot(reducers),
EffectsModule.forRoot([]),
!environment.production ? StoreDevtoolsModule.instrument({maxAge: 25}) : []
],

...
})
export class AppModule { }

Workspace and mono-repo advantages

That’s it! As you can see, this new project structure is much cleaner than the 2016 initial solution.

It benefits from all the advantages described in Nx documentation, Why a Workspace (except for the unified dependency management since Ionic app requires it’s own dependencies):

Unified versioning

Everything at that current commit works together

A label or branch can capture the same

Promotes code sharing and reuse

Easy to split code into lib modules

Easy to consume/implement that code and the latest changes to it

Refactoring benefits

Code editors and IDEs are “workspace” aware

Can have a single commit for a refactor that spans applications in the domain

Consistent developer experience

Ensures all necessary dependant code is available

If you have any questions or suggestions to improve the demo app, don’t hesitate to submit an issue or a pull request!

Note: we’re hiring! Are you kick-ass fullstack or front-end dev that want to work on Angular, Java or Groovy? You must contact me to join our dream team in Paris!

If you liked this article, please hit the ❤ button to recommend it. This will make it easier for other Medium users to discover this.

--

--

Benoit Hediard
Stories by Agorapulse

GenAI Enthusiast | Ex-CTO & Co-Founder @Agorapulse | Angel Investor