πΎ Archived View for gemini.robrohan.com βΊ 2018-02-04-sharing-angular5.md captured on 2022-04-28 at 17:28:28. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
---
author: rob
date: 2018-02-04 08:00:00+11:00
slug: sharing-angular5-modules
title: Sharing Angular 5 Modules
---
If you've ever worked on a project where you've tried to share code between two (or more) projects, you probably understand the pain. Sadly, I've worked on a few.
Admittedly, on the surface it seems like a tempting proposition. On paper, it sounds quite easy. For example, one system I worked on we had a single nuget library where we kept our domain objects. The logic went: "All you have to do when you want to make a change is update the domain object project, build, push the new library to the nuget server, then pull down the new library to the other projects, and then rebuild those. It's easy."
It's a disaster.
You quickly start running into versioning problems, build timing problems, people updating things without letting other people know - not to mention the amount of time to do something very simple start to take forever because all of the steps - it just gets messy really fast.
It's popular in Golang, for example, to pull down all the source code you need for a project, save that and build from there. That works well, unless you're trying to share interfaces for a rest service that is in active development and the contract is changing frequently. It winds up being the same problem, you just `git pull` instead of `update`.
I am working for the New Zealand based company [PartPay](https://partpay.co.nz/), and we have started running into this problem in our Angular code. We now have three Angular 5 projects that are using some similar code. Some interfaces for services, some angular 5 services, and some customized logic that would be really nice to have in one place.
Over the last week we refactored some bits and found, what I think, is a very nice solution. We didn't so much find it, as just implement what Angular 5 Cli just does. However, I haven't seen anyone really talk about how to do this.
The executive summary is: we put all the project's code in one project, referenced bits between projects, and let webpack build the separate apps.
Initially, we put all the Angular code in one repository, but in separate Angular projects like this:
Common/ src/ app/ Project1/ src/ app/ Project2/ src/ app/ Project3/ src/ app/
And we then built everything at once. That made sure everything was always building with the same interfaces and services, and it would error if a _common_ change broke something in one of the _projects_.
So we tried just referencing different projects from within other projects. Something like this:
// in Project 1 import { StuffService } form '../../../Common/src/app/module/stuff.service'
Logically this should work. It even seems to work as it "compiles" and webpack seemingly has no problems bundling the code. The problem with this is at runtime. For some reason, _NgZone_ just freaks out. My guess is that with other frameworks (or your own framework) this would work just fine - but Angular can't handle this setup.
The fix is to do it backwards. Instead of multiple Angular projects, you make one Angular project with several apps. You do the layout this way:
SuperProject/ src/ app-common/ app-project1/ app-project2/ app-project3/
And then share via:
// in Project 1 import { StuffService } form '../../app-common/module/stuff.service'
this even seems to work:
// in Project 1 import { StuffService } form 'app-common/module/stuff.service'
This is really cool as you now only need one _npm install_ (or _yarn install_) for all the projects which saves times on the build server.
I should also mention that this *requires you to be using the angular-cli*. If you eject the webpack config, it doesn't work out of the box. You need to be using _ng serve_ and _ng build_ for this to Just Work™.
When you go to build the projects, you'll want them to go into different directories and also to likely have different index.html pages. To sort this out, you'll need to update the _.angular-cli.json_ file. Here is an example of ours:
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "project": { "name": "part-pay.common.website" }, "apps": [ { "name": "app-common", "root": "src", "outDir": "dist/common", "assets": [ "assets", "settings.json", "favicon.ico" ], "index": "index-common.html", "main": "main-common.ts", "polyfills": "polyfills.ts", "test": "test.ts", "tsconfig": "tsconfig.app.json", "testTsconfig": "tsconfig.spec.json", "prefix": "pp", "styles": [], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } }, { "name": "app-project1", "root": "src", "outDir": "dist/project1", "assets": [ "assets", "settings.json", "web.config" ], "index": "index-project1.html", "main": "main-project1.ts", "polyfills": "polyfills.ts", "test": "test.ts", "tsconfig": "tsconfig.app.json", "testTsconfig": "tsconfig.spec.json", "prefix": "pp", "styles": [ "styles/partpay-theme.scss", "styles/merchant/styles.scss" ], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } },
Notice the different sections of the app array, and also notice that we're using different _index-[something].html_ and _main-[something].ts_ for entry files. We have an extra scripting layer when we deploy that moves and renames files as needed (like rename to _index.html_).
Like _angular-cli.json_ you'll need to mess a bit with the _package.json_. Here is an example of ours:
... "test": "ng test --browsers ChromeHeadless --single-run", "lint": "ng lint", ... "start:checkout": "ng serve --app app-project1 --host 0.0.0.0 --port 9001", "start:customer": "ng serve --app app-project2 --host 0.0.0.0 --port 9002", "start:merchant": "ng serve --app app-project3 --host 0.0.0.0 --port 9003", "start:common": "ng serve --app app-common --host 0.0.0.0 --port 9010", "build:checkout": "ng build --app app-project1 --prod --aot true -t production -e prod --vendor-chunk=true", "build:customer": "ng build --app app-project2 --prod --aot true -t production -e prod --vendor-chunk=trueβ, "build:merchant": "ng build --app app-project3 --prod --aot true -t production -e prod --vendor-chunk=true",
Aside from just needing the one _yarn install_, you also just need one test run too. I really like this because it gives us confidence that when there is a change to common, all the projects that are currently using that code also run their tests. Noice.
If you configure your _.angular-cli.json_ similarly to the above example, doing a build will give you something like the following (in your dist directory):
Robs-MacBook-Pro:Common robrohan$ tree -L 2 dist dist βββ project3 β βββ 3rdpartylicenses.txt β βββ assets β βββ index-project1.html β βββ inline.e6d173c400ee8368d31d.bundle.js β βββ main.d5aa665aaf038618d003.bundle.js β βββ polyfills.30f325d8f0721457d5e0.bundle.js β βββ styles.1bee0de3213989a458f2.bundle.css β βββ vendor.1b61d1024c384094bb6b.bundle.js β βββ web.config βββ project3 β βββ 3rdpartylicenses.txt β βββ assets β βββ index-project2.html β βββ inline.72852e24a300baea5af4.bundle.js β βββ main.d14c37882bf3210788bc.bundle.js β βββ polyfills.30f325d8f0721457d5e0.bundle.js β βββ styles.7696b644715a9f256722.bundle.css β βββ vendor.ba82eb312e9aab2cc8b5.bundle.js β βββ web.config βββ project3 βββ 3rdpartylicenses.txt βββ assets βββ index-project3.html βββ inline.88740e7a4e3716c1efcc.bundle.js βββ main.db8e228549fbe96675f9.bundle.js βββ polyfills.30f325d8f0721457d5e0.bundle.js βββ styles.914bbb5bdb522acc71dc.bundle.css βββ vendor.090fe093e78811aa6f93.bundle.js βββ web.config
From here it's just a matter of deploying.
I quite like this setup. It's not only helped us remove some duplicate / similar code, it has also increased our confidence around testing as well as made our build time shorter. Here is a quick pro / con list:
Reduce, Reuse, Recycle, Refactor!