One module that imports all component classes, no individual modules for each component
`
components/
- components.module.ts (ComponentsModule)
- imports and exports all components the user creates
`
Can anyone provide clarification on this? What should components.module.ts look like? Do I include the components module in the declarations: [] array in app.module.ts? Are these components then also lazy loaded?
Side question: I presumed lazy loaded in all these cases meant that modules were imported completely when first requested via an http request. This doesnāt appear to be the case (webpack is still bundling everything). Is lazy loading in this sense referring to the entire codebase being packaged but angular does not process the modules into existence (internal representation) until requested?
Iām having trouble with it. Hereās my components.module.ts:
import { NgModule } from '@angular/core';
import { MakeModelYear } from './make-model-year/make-model-year';
import { ClaimsBaseCollection } from './claims-base-collection/claims-base-collection';
@NgModule({
declarations: [
MakeModelYear,
ClaimsBaseCollection
],
exports: [
MakeModelYear,
ClaimsBaseCollection
]
})
export class ComponentsModule { }
But now Iām getting āCanāt bind to āngModelā since it isnāt a known property of āion-inputā.ā
Iāve included FormsModule from @angular/forms in the main app.module.ts, along with importing the components.module.ts. Any idea what could be causing that? Itās like it doesnāt recognize ngModel as an angular directive.
Yes, but I donāt have the authority to say anything official about it.
If you want my unofficial opinion, if you have custom components you are better off simply declaring them in your AppModule. I am perfectly willing to be corrected on this point, but I find the official recommendation of making a module for all custom components and importing it in all pages that use any of them to be pretty much the worst of all possible worlds. It exacerbates code duplication and facilitates problems such as you are seeing here. If you insist on persisting with pursuing it, I would suggest importing IonicModule in your ComponentsModule.
Yea I had to import both FormsModule and IonicModule in that components.module.ts. I imported components.module.ts in app.module.ts. I donāt think I needed to import components.module.ts in every page that uses the component, because it works on pages that have the custom components by nature of it being included in app.module.ts (the root app). It does seem weird to me to import it into every page individually. The only reason I could see for that is because the modules themselves are being lazy loaded, that implicitly the components.module.ts will also not be loaded until the first page that needs itā¦ Seems likely?
At that point I donāt see the point of having a ComponentsModule in the first place over just putting all the custom components in the AppModule.
That was what I was hoping for and would be much better (at least for me) than the current state of affairs. What happens now is that a separate complete version of the code in ComponentsModule ends up in every page chunk that imports it. It bloated my app binary by about a third and delivered no measurable benefit to startup time, so I decided to ditch lazy page loading for now. YMMV.
Like you said, I moved the custom components back into my app.module.ts. But itās not recognizing them. Itās saying model-make-year is not recognized. Iām importing it and then have it in the imports: [] array, but itās still not getting it. Itās exactly the same as I had it in my ionic2 app but now it doesnāt seem to like it.
In app.module.ts:
import { MakeModelYear } from '../components/make-model-year/make-model-year';
import { ClaimsBaseCollection } from '../components/claims-base-collection/claims-base-collection';
@NgModule({
declarations: [
MyApp,
MakeModelYear,
ClaimsBaseCollection
And then in MakeModelYear.ts:
@Component({
selector: 'make-model-year',
templateUrl: 'make-model-year.html'
})
export class MakeModelYear {
But for some reason itās throwing the error that it does not know what make-model-year is. Do I need to put this in the declarations: [] of the page that includes it? Is this a new property of lazy loading the pages (and having them be their own modules?) Iāll have to include MakeModelYear in the pageās .module.ts instead of in the top level app.module.ts? That makes it seem like the whole including components.module.ts in every module.ts is the ONLY way to do it if I want to lazy load pages. Is webpack really that dumb as to include a copy of components.module.ts for each import in separate files?
@danbucholtz if you donāt mind, this is a follow-up on the question you answered for me a few days ago. Could you (or someone else on the team) please address this?
Is there something in the pipeline to address this? Or is there a reason this approach is a good thing despite surface appearance? Or is this just the way things are going to be for a long time, and if we want to lazyload we have to deal with it? Iām running out of pre-beta things to do, so I need to decide soon whether I am going to lazyload or not, and Iād like to know what the plan is on your end, going forward. Thanks very much.
No, the imports array is only for other modules. In addition to declaring it in AppModule, is it also listed as an entryComponent? If you donāt do that, ngc wonāt recognize itās needed and will drop it on the floor.
Yea, sorry I wasnāt clear on that. I was putting my components.module.ts in the imports array. I ended up going with their recommendation (as this is just a prototype) despite your warnings, hoping that MyMMV with webpack bloating my compiled bundle. Since all my pages are now lazy loaded with all their own .module.ts files, adding components.module.ts to the imports of app.module.ts didnāt do anything and it was necessary to add it to the imports of any page.module.ts that requires it. Thanks for the help though. My upgrade to ionic 3 is complete and allowed me to solve my other problem that you had answered while helping me understand whatās actually happening with the imports and giving every page itās own module.
I checked my output compiled files and I do see that it is duplicating the import in each page that imports components.module.ts, so you are correct. Like I mentioned, this is fine for my prototype (and technically since the pages including these components are lazy loaded, by proxy the components are lazy loadedā¦ but that doesnāt take in the negative of code duplication).
Is there any way to prevent this though? It seems strange that the code splitting is happening, but itās not smart enough to not inline the importsā¦ Why wouldnāt it just use the separately compiled components.module.ts file and http that in first and THEN load the lazy loaded page that requires it, passing the reference in (like require.js, it is cached after load, or loaded via http if itās the first time the module has been requested). Each page imports ionic-angular and various other things but those do NOT get inlined, unlike my components moduleā¦
Apparently itās a tough enough problem and/or falls between the cracks between webpack and the build systems that use it that nobodyās managed to solve it yet. Strictly speaking, itās not just Ionic. Angular-cli apps have the same issue.
There isnāt any "http"ing happening. If youāre interested in the internals, start by looking at deep-linker.ts.
I believe those are in the main app module, which is where I was trying to get you to put all your custom components as well. It doesnāt sound like that was successful.
I get that in app.module.ts there are things being imported and not inlined. But what Iām confused about is that my lazy loaded pages have a page.module.ts now (due to lazy loading) and they import things like NgModule, and IonicPageModule, and then my custom ComponentsModule (components.module.ts), and the only import that gets inlined by webpack is the components.module.ts. So how are IonicPageModule and NgModule, for instance, not being inlined but my custom ComponentsModule is?
I see now that the CommonChunksPlugin that webpack uses to decide what not to inline only respects things in node_modules, and a specific subset of such:
Unfortunately as ionic consumers we donāt get access to this webpack config which is buried in @ionic/app-scripts. If I could add my components.module.ts to this it would consider it a common chunk and stop inlining it where it is imported in each pageās .module.ts file.
Thatās a great find. I was coming at it from a different direction, and discovered that this problem doesnāt just affect components. Things like ionic-storage (and presumably all native plugin shims, although those are pretty small) also get duplicated in all lazily loaded pages that reference them.
Actually, you do. You can copy it to (for example) ./my-webpack.config.js, hack it up and then reference it using the ionic_webpack config setting documented in here.
EDIT: never mind, I now see that youāre not talking about the ordinary build webpack config, but rather a plugin, so thatās going to be harder to override.
EDIT 2: and @rlouie might be interested in this: based on your earlier sleuthing, I did take a look at this, and at first blush it seems like one can have more than one of these critters in a project. If so, it could theoretically be possible to decouple the code splitting structure from the deeplinker, allowing lazy loading of chunk-by-feature and ameliorating the code duplication problem somewhat.
Good info on customizing the webpack config by overriding it in the package.json. I wonder if you could create a custom webpack that overrides the one plugin the default one has (ionicWebpackFactory.getIonicEnvironmentPlugin()) and replace it with something local to the project that instead uses a custom chunking plugin. For instance, this is the ionicWebPack factory:
So you could potentially have a custom version of that which supplies another function getCustomCommonChunksPlugin or something that checks some āchunks.configā in your project and makes sure to add those specified files as common chunks to code split?
Ya, I spent a good while messing with commons chunk plugin in vanilla Angular to see if I could get rid of your duplicate code issue. It was a good learning experience but ultimately unsuccessful. The problem (I believe) was that the Angular compiler took care of the code splitting in itās own way rather than using normal chunks so it didnāt recognize the lazy loaded bundled code.
So in vanilla Angular I ended up with main.js, vendor.js, 01.js and 02.js. Regardless of any settings it just didnāt recognize the 01.js and 02.js. I believe in order to make it work with that comons chunk plugin youād need an entry point for each lazily loaded module.
Itās possible I missed something but I went pretty deep into it. Oh, and yes you can have multiple of them in a project, the vanilla Angular CLI config uses multiple out of the box, but in this case it doesnāt seem to help unfortunately. Maybe it works differently in Ionic, but I seriously doubt it as Iām fairly confident it uses the same Angular compiler behind the scenes.
EDIT: Iām sure thatās why in the github issue on Angular their answer is specifically that they need to use a dependency tree when bundling to see several layers deep. If it was as simple as adding another commons chunk config into their webpack config Iām sure they just would have done that.
Hereās my .02 on this for what itās worth (and the Angular teamās honestly). You should group your code by feature not by type. If your component is shared across the most of the app put it in a shared feature module: https://angular.io/docs/ts/latest/guide/style-guide.html#!#04-10. It will be eagerly loaded and it will be very clear what components are shared by your entire app.
Then you continue to group your code by feature: https://angular.io/docs/ts/latest/guide/style-guide.html#!#04-07. So, for example letās say user profile is one of your features. Make a new folder named profile, create a profile module in it. Letās say you have a profile photo component that lets users upload, crop, and set a profile photo. Thatās only going to be needed by this feature, so in that same folder create profile-photo.component.ts and import it into the profile module. Youād also presumably create a profile service and import it into that module as well.
Now you have a nice self contained reusable module thatās isolated from the rest of your code and can easily be tested and reused. Youāve also eliminated duplication of component code because your profile photo component is only loaded with the profile feature, rather than being copied into every single lazy loaded module.
Now, thereās still an issue here a little because as Iāve complained about already Ionic only lazy loads individual pages, so if your profile feature needed two pages youād be in trouble. All this really comes from the Ionic team going against Angular best practices and encouraging archaic design patterns like grouping by type instead of feature.
If Ionic supported lazy loaded modules instead of pages (they are working on this) this issue would be very minimal because most components youād want to lazy load would be specific to a feature area of the app, but Iāve ranted enough on that topic already