Our angular 4.3.6 application has lazy-loaded modules, such as Fleet, Maintenance, Car, etc.
My top-level app router looks like this:
const routes: Routes = [
{ path: '', redirectTo: 'fleet', pathMatch: 'full' },
{
path: '',
component: AppComponent,
canActivate: [AuthenticationGuard],
children: [
{
path: 'fleet',
loadChildren: "./modules/fleet.module",
canActivate: [AuthenticationGuard]
},
{
path: 'car/:id',
loadChildren: "./modules/car.module",
canActivate: [AuthenticationGuard]
},
{
path: 'maintenanceProgram',
loadChildren: "./modules/maintenanceProgram.module",
canActivate: [AuthenticationGuard]
}
}
We do have a shared module with generic components (we have lots of them) used throughout the application. There are, however, some components like modals which are shared only by the MaintenanceProgram
and the Car
modules, but not used anywhere else.
In an effort to keep the shared module reasonable, I include these once-reused components only in the MaintenanceProgram
module, export them, and import the MaintenanceProgram
module into the Car
module to have access to the exported components.
Both the Car
and MaintenanceProgram
modules have the following embedded child routes invoked in their respective @NgModule.imports[]
:
Car:
const CarModuleRoutes = RouterModule.forChild([
{
path: '',
component: CarComponent,
canActivate: [AuthenticationGuard]
},
{
path: ':id',
component: CarComponent,
canActivate: [AuthenticationGuard]
}
]);
and Maintenance Program:
const MaintenanceProgramModuleRoutes = RouterModule.forChild([
{
path: '',
component: MaintenanceProgramComponent,
canActivate: [AuthenticationGuard]
}
]);
This is obviously not the correct approach either
because when I load the Car route, I get the Maintenance Program's default route.
I have tried:
MaintenanceProgramModuleRoutes
and CarModuleRoutes
in CarModule
's @NgModule.imports[]
,*Is the only solution to create another shared module that contains the shared components without a router, or is there another, more elegant way to do this?*
This is a reduced example, we actually have many routes and hundreds of components which are reused only twice or three times. This problem will surely persist into the future as the application grows, so I need a scalable and maintainable solution. Creating tens or more extra shared modules is just infeasible.
Actually, when creating a shared module, there is no need to care about modules which uses a little part of the shared module, because Angular's tree-shaking will keep only the used code from an imported module, and remove the rest.
I've prepared a minimal project to demonstrate it: https://github.com/youkouleley/Angular-treeshaking-demo
This project has two lazy modules: AModule
and BModule
. Both of these modules import SharedModule
. SharedModule
exports three components:
AComponent
which is used in AModule
only
BComponent
which is used in BModule
only
BothComponent
which is used in AModule
and BModule
Here is what you'll get when ng build
ing this project in production mode and opening the 4-es2015.<hash>.js
file:
(window.webpackJsonp = window.webpackJsonp || []).push([[4], {
KImX: function (n, l, u) {
"use strict";
u.r(l);
var t = u("8Y7J");
class a {
constructor() {}
ngOnInit() {}
}
var r = u("phyl");
class o {}
var c = u("pMnS"),
s = t.gb({
encapsulation: 0,
styles: [[""]],
data: {}
});
function b(n) {
return t.sb(0, [(n()(), t.ib(0, 0, null, null, 1, "p", [], null, null, null, null, null)), (n()(), t.rb(-1, null, ["a works!"]))], null, null)
}
function i(n) {
return t.sb(0, [(n()(), t.ib(0, 0, null, null, 1, "app-a", [], null, null, null, b, s)), t.hb(1, 114688, null, 0, a, [], null, null)], function (n, l) {
n(l, 1, 0)
}, null)
}
var p = t.eb("app-a", a, i, {}, {}, []),
e = u("gJxL"),
f = u("SVse"),
h = u("iInd"),
d = u("PCNd");
u.d(l, "AModuleNgFactory", function () {
return v
});
var v = t.fb(o, [], function (n) {
return t.ob([t.pb(512, t.j, t.T, [[8, [c.a, p, e.a]], [3, t.j], t.u]), t.pb(4608, f.i, f.h, [t.r, [2, f.o]]), t.pb(1073742336, f.b, f.b, []), t.pb(1073742336, h.l, h.l, [[2, h.q], [2, h.k]]), t.pb(1073742336, d.a, d.a, []), t.pb(1073742336, o, o, []), t.pb(1024, h.i, function () {
return [[{
path: "a",
component: a
}, {
path: "both",
component: r.a
}
]]
}, [])])
})
},
PCNd: function (n, l, u) {
"use strict";
u.d(l, "a", function () {
return t
});
class t {}
},
gJxL: function (n, l, u) {
"use strict";
var t = u("8Y7J"),
a = u("phyl");
u.d(l, "a", function () {
return s
});
var r = t.gb({
encapsulation: 0,
styles: [[""]],
data: {}
});
function o(n) {
return t.sb(0, [(n()(), t.ib(0, 0, null, null, 1, "p", [], null, null, null, null, null)), (n()(), t.rb(-1, null, ["both works!"]))], null, null)
}
function c(n) {
return t.sb(0, [(n()(), t.ib(0, 0, null, null, 1, "app-both", [], null, null, null, o, r)), t.hb(1, 114688, null, 0, a.a, [], null, null)], function (n, l) {
n(l, 1, 0)
}, null)
}
var s = t.eb("app-both", a.a, c, {}, {}, [])
},
phyl: function (n, l, u) {
"use strict";
u.d(l, "a", function () {
return t
});
class t {
constructor() {}
ngOnInit() {}
}
}
}
]);
Note that the BComponent
from the SharedModule
is missing from the AModule
chunk. Sames goes for BModule
chunk that excludes AComponent
.
Also, note that this behavior is obtained when setting commonChunk
to false
in the build options. This option allows you to choose between:
false
: Bundle the needed parts of the SharedModule
directly into the lazy modules that imported it. Pro: Even loading time between lazy modules. Con: Some code from SharedModule
is duplicated between lazy modules chunks, higher app size overalltrue
(default): Have a common chunk that contains the parts of the SharedModule
which are used at least by two lazy modules (the rest is bundled into the lazy modules themselves). Pro: No duplicated code, lower app size overall. Con: The first lazy module is slower to load (it loads the common chunk even if the current route doesn't need it)As a conclusion, Angular build provides optimizations for the SharedModule
with commonChunk
set either to true
or false
(depending on your context) you don't really have to worry about your SharedModule
size. Thus, you don't have to try strange patterns like you did, with hybrid modules fulfilling the feature module role, and the shared module role at the same time.
Try this in top-level app router:
const routes: Routes = [
{ path: '', redirectTo: 'fleet', pathMatch: 'full' },
{
path: '',
component: AppComponent,
canActivate: [AuthenticationGuard],
children: [
{
path: 'fleet',
loadChildren: "./modules/fleet.module#FleetModule",
canActivate: [AuthenticationGuard]
},
{
path: 'car/:id',
loadChildren: "./modules/car.module#CarModule",
canActivate: [AuthenticationGuard]
},
{
path: 'maintenanceProgram',
loadChildren: "./modules/maintenanceProgram.module#MaintenanceProgrammodule",
canActivate: [AuthenticationGuard]
}
}
Your needed change: add
#module_class_name
in end of router path.
The way that I use to handle big shared module is to create a module per component. In your example, you should created a ModalModule
, declare and export your ModalComponent
. It should look something like this
@NgModule({
declarations: [
ModalComponent,
],
imports: [
RouterModule,
CommonModule, // any modules that your modal need
],
exports: [
ModalComponent,
]
})
export class ModalModule {}
Then simply include ModalModule
in your CarModule imports, and MaintenanceProgramModule
imports.
@NgModule({
declarations: [CarComponent],
imports: [ CarRoutingModule, ModalModule ],
})
export class CarModule
You should create module for all shared component, and only include each shared component's module in the route that need it. It do help greatly reduced the size of each page.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With