Best way to share components etc. between multiple apps?

So far I’ve been copy-pasting common components between Apps, but it is time for a better way to do it.

I started off with symlinks, but they are hard to manage and Ionic doesn’t pick up on the changes so well so you end up restarting Ionic quite a lot so I abandoned this way.

I’m now using Git submodules. Even though I have to commit/push/pull to get stuff back and forth I think it works well.

I’m now looking into ngModule to group common components together to be reused. For example login/signup, settings, about, etc. It looks over-complicated to me but probably the way to do it.

Will it be possible to customize an imported ngModule to tailor to specifics of the App?

What do you do with the assets that belong to an ngModule? Say you have an “About” ngModule that has some logos and icons in it. Do I have to copy those manually to the App or is there a better way, like include them in the ngModule somehow?

Looking forward, what does it involve to create an NPM package so it can be published and used by other people? I suppose the components have to be compiled before packaging. And the same question about the assets apply to this as well. Seems like this is quite complicated. Is this even possible?

I like Ionic a lot, but from my point of view, Angular seems to over-complicate things. I wonder if that is really necessary. So far I’ve bit the bullet and gone on with the steep learning process to get simple things done. At this point I’m kind of hitting the wall.

What have you guys done and what works well for you?

2 Likes

Sharing a component or module across Ionic 2 App projects

My setup.

  • I have a folder 'ionic’
    with a subFolder for each App.
    One special subFolder is named ‘shared’
  • the Folder 'shared’
    can initially be set up as a regular App Folder
    but it is only meant to hold sources of components and/or modules
    that must be shared across the real Apps
  • for that reason I created shared/src/components
    (for sources of shared components) and shared/src/modules
    (for sources of modules; each module together with the components that are part of it)

Now you can import a module or component from the shared folder into an App.
F.i. like this:
import {MyModule} from ‘…/…/…/shared/src/modules/my-module/my-module.module’;

Note 1. When you change something in a shared module or component
and want to test it, you need a rebuild of an App that’s using it.
When you use ‘ionic serve’ on this App it can easily be done
by making a fake edit in one of its sources that uses the changed component or module and save it.

Note 2. You can delete all App-like stuff from the ‘shared’ folder
You minimally need to keep this:
node_modules (for imports of @angular/core and alike in a shared component or module)
src/components
src/modules

Appendix: Module setup example.
This applies to either an app specific module or a ‘my way’ shared module.

  • put the module in a folder modules/my-module

  • sources:
    my-module.module.ts
    my-module.component1.component.ts
    my-module.component2.component.ts

  • my-module.module.ts

    import { NgModule } from ‘@angular/core’;
    import { IonicModule } from ‘ionic-angular’;

    import {Component1} from ‘./my-module.component1.component’;
    import {Component2} from ‘./my-module.component2.component’;

    @NgModule({
    imports: [
    IonicModule,
    ],
    declarations: [
    Component1,
    Component2,
    ],
    entryComponents: [
    Component1,
    Component2,
    ],

    exports: [
      Component1,
      Component2,
    ],
    

    })
    export class MyModule {}

  • in app.module.ts of an App

    import {MyModule} from ‘…/modules/my-module/my-module.module’;

    @NgModule({

    imports: [
    MyModule,
    IonicModule.forRoot(MyApp)
    ],

    })
    export class AppModule {}

When you start asking yourself these questions it usually means it’s time to step up your architecture. You could probably just follow the angular style guide and have all your questions answered: https://angular.io/styleguide#!#application-structure-and-angular-modules

The whole folder-by-type approach quickly leads to spaghetti code, makes it hard to locate the proper files, and makes it harder to re-use features. It’s a fairly old fashioned approach at this point, but for whatever reason the Ionic starter projects still encourage it.

You should move to a folder-by-feature approach, where each feature area is it’s own Angular module and it’s own folder. Everything related to that module should live in that folder.

At that point sharing features is trivial. Copy the folder from one project and paste it into the other. Import it into your main module and provide any shared services at the highest component they should be shared from.

If even that isn’t sufficient, the folder-by-feature approach makes it pretty easy to promote your feature to it’s own npm module. You can then develop the module independently and just npm install it in every project you use it in. That may or may not be overkill depending on the size, complexity, and how often you reuse it.

Ok thanks.
Cause I am a newbee to all these technologies I cannot oversee
what you exactly tell me. Will try to study it.
What I propose to the starter of this topic works for me, but I can imagine
that there are some pitfalls that I will understand later when they come in to bother me.

Some explanation.
I’m an old-hand retired project leader/developer with lots of conceptual knowledge,
helping a Dutch startup to develop a ‘one shot app for all’.
Ionic2/Angular 2/cordova is my final choice and I’m trying to climb the steep learning curve
in order to test the feasibility of it all.
So far so good. I’m happy with what I tested. What comes into my mind early is: how to share across apps.
This is not a requirement, but it attracts my interest as a developer.

That App Structure helped me a lot. It was a tough concept to grasp, but when I worked through it, it made a lot of sense. I wish Ionic had adopted that in the starter App, it would have saved me a lot of work.

I found some examples of making an NPM module, like here. It’s not very straightforward and I haven’t tried to do it myself yet, but I think it’s doable. Not sure about the overkill part though.

I’m using a simpler approach for the moment. I put the shared feature in a separate git repo and add it to the root of my project with git submodule <repo>. I then hard-link the directory as a feature directory under src/app/feature. I only have to import it in my app.module and it’s ready to be used. On macOS I’m using ‘hln’ to make the link. So far it works well.

Once the feature is stabilized, it should be possible to turn it into an npm package. It’s wise to install Verdaccio to have a private npm registry.

@HanHermsen @richie765 @rlouie

We had the same problem to solve. There are lots of common components that are shared among multiple apps. We also wanted to keep same linting, build, test scripts for all apps. That’s why we decided to keep them on a single repo to keep it more maintainable. But as you said, there’s a watch issue for common components folder which is very annoying.

Our solution:

  • Run a setup script once to set an environment value for src dir and create symlinks for config files for an app.
  • Keep different applications on different folders and set env value IONIC_SRC_DIR once on setup to point out which app to work on for ionic-cli.
    • /src/apps/appA/src/config.xml
    • /src/apps/appA/src/{{ionic_app_a_src}}
    • /src/apps/appB/src/config.xml
    • /src/apps/appB/src/{{ionic_app_b_src}}
    • /src/common/{{common_components_src}}
  • Symlink app specific config files config.xml, .io-config.json, ionic.config.json to the root once on setup.

setup.js

const appName = argv.app;
const rootDir = process.cwd();

console.log('Setting up .env and symlinks for ', rootDir);

fs.writeFileSync(path.join(rootDir, '.env'), `IONIC_SRC_DIR=${path.join('src/apps', appName, 'src')}`);
[
    '.io-config.json',
    'config.xml',
    'ionic.config.json'
].forEach((file) => {
    fs.removeSync(path.join(rootDir, file));
    fs.symlinkSync(path.join('src/apps', appName, file), path.join(rootDir, file));
});

  • Extend ionic app-scripts’ default watch.config.js to point out common components path to watcher.

/config/watch.config.js

const watchConfig = require('@ionic/app-scripts/config/watch.config');

// Since we build multiple apps on the same repo and share a common component module among them,
// we need to add path of common components to make ionic-cli watch for changes of it
watchConfig.srcFiles.paths.push('{{SRC}}/../../../common/app/**/*.(ts|html|s(c|a)ss)');

package.json

"config": { 
   "ionic_watch": "./config/watch.config.js"
}

We built the architecture above to build multiple ionic apps on the same repo and share common components with them and it works fine for us so far. I hope it helps for the ones seeking a similar solution.

2 Likes