Doubt about lazy loaded components

I’m moving my app to lazy loading and I found 2 ways of having components loaded.

One is having X components and just one global components.module (e.g. https://www.9lessons.info/2017/12/ionic-angular-lazy-loading-child-components.html) that we’d need to import on our pages. But what happens if we just want one component?

That’s the other way which I saw on some @mhartington’s repo. We could have again one .module per component as we have with pages:

Is this second way better than the first one?
What’s the point of having all component loaded on one page if we’re using lazy loading?

In my opinion, absolutely. I have always hated the monolithic component module.

I believe the stated reason is something about “simplicity of coding”, but I find it supremely unpersuasive.

Well, to be fair… it’s not all components in one module, or a separate module for each component.

What I do is put all components that are used often, and on many pages in one module, then I group some components that are only used on certain features in my app into a featurex.component.module… and if I have components that don’t fit any logical grouping or are only used once they get their own module…

So, bottom line is to figure out what makes the most sense for you app, and the way users will use it

@bgies for or a bunch of modules that we need always as same package we could have something like this 2 approaches:

Thanks @rapropos, it sounds like that. I’ll start creating separate modules for each component now

create-account.module (page):

import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { CreateAccountPage } from './create-account';
import { LanguageChangeComponentModule } from '../../components/language-change/language-change.module';

@NgModule({
  declarations: [
    CreateAccountPage
  ],
  imports: [
    IonicPageModule.forChild(CreateAccountPage),
    LanguageChangeComponentModule
  ],
})
export class CreateAccountPageModule { }

language-change.module (component):

import { NgModule } from '@angular/core';

import { IonicModule } from 'ionic-angular';

import { LanguageChangeComponent } from './language-change';

@NgModule({

declarations: [LanguageChangeComponent],

imports: [IonicModule],

exports: [LanguageChangeComponent]

})

export class LanguageChangeComponentModule { }

I’m using the second one has child component of the first one (also this first one is imported on another page) but I’m getting this issue:

Uncaught (in promise): Error: Template parse errors:
‘language-change’ is not a known element:

  1. If ‘language-change’ is an Angular component, then verify that it is part of this module.

@Xiwi What are the components you have that have language-change in their HTML? Are you sure that the error is in the CreateAccountPage, or is it in another component?

<language-change></language-change> is only inside CreateAccountPage.html

Also CreateAccountPage is the root component so I don’t need to pass through other different components

if I add import { LanguageChangeComponent } from '../../components/language-change/language-change'; and this component to declarations I don’t get any issue but the component doesn’t load. I see CreateAccountPage but without languageChangeComponent.

But I should import the module, not the component, right?

Also tried with a different component and facing similar issue

@Xiwi Just to be sure, do you have a @Component decorator with a seletor language-change in the LanguageChangeComponent? Like:

@Component({
    selector: 'language-change',
    templateUrl: 'language-change.html'
})
export class LanguageChangeComponent { ... }

Make sure there aren’t any typos.

About your problem when you add it to the declarations of the other module (although I recommend to include each component in their own module and import the modules), you could change their templates to be very simple to help to find errors, like:

create-account.html:

<span>Create Account 01</span>
<language-change></language-change>
<span>Create Account 02</span>

language-change.html:

<span>Language Change</span>

And see what is rendered in the browser, to avoid cases like part of the html not being rendered due to conditions (like a ngIf).

language-change.ts (copied literally from it):

@Component({
  selector: "language-change",
  templateUrl: "language-change.html"
})
export class LanguageChangeComponent { ... }

Doubled checked typos, but nothing weird. Also say that this was working before without lazy loading so names haven’t been changed.
About the declarations I was just trying but the idea as you said is import everything as modules.

Don’t really have too much stuff inside:
create-account.html:

<ion-content padding>
  <language-change></language-change>
  <div class="h3 h3--margin">Create your account</div>
</ion-content>

language-change.html:
LANGUAGE WORKS!

----- till here everything is the same as you suggested -----

Also I commented out the rest of the components so the app is just loading create-account with this child component inside. Well, it’s also loading another page on top of create-account which is a welcome page with some slides. The last one is create-account.

'welcome.module.ts`:

import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { WelcomePage } from './welcome';
import { CreateAccountPage } from "../create-account/create-account";

@NgModule({
  declarations: [
    WelcomePage,
    CreateAccountPage
  ],
  imports: [
    IonicPageModule.forChild(WelcomePage),
  ]
})
export class WelcomePageModule { }

welcome.html:

...
<ion-slide class="slide5>
   <page-create-account></page-create-account>
</ion-slide>
...

This works perfectly and also other links to other imported pages inside declarations. The problem comes loading component inside imports (or anywhere)

@Xiwi The problem is your WelcomePageModule, remember, one module per component, but you have:

@NgModule({
  declarations: [
    WelcomePage,
    CreateAccountPage
  ],
  imports: [
    IonicPageModule.forChild(WelcomePage),
  ]
})
export class WelcomePageModule { }

Both WelcomePage and CreateAccountPage are there.

The module CreateAccountPageModule is already importing CreateAccountPage, so you should do:

@NgModule({
  declarations: [
    WelcomePage
  ],
  imports: [
    CreateAccountPageModule,
    IonicPageModule.forChild(WelcomePage),
  ]
})
export class WelcomePageModule { }

I assume that CreateAccountPageModule isn’t been used at all (otherwise you would be receiving errors of duplicate declaration of component in modules).

You said before:

Also CreateAccountPage is the root component so I don’t need to pass through other different components

CreateAccountPage is not the root component. Also, you are not using it as a page, but as a normal component (you could change its name to CreateAccountComponent to make it more clear).

Your CreateAccountPageModule would only be used if you used CreateAccountPage as a page, like:

this.navCtrl.push('create-account')

To solve that I advise to change your module as I posted above and remove @IonicPage() from CreateAccountPage (if it has this decorator), because it is not being used as a page.

Also, make sure you export the component in the module (include the component in declarations, entryComponents and exports).

I made a stackblitz project with those components and modules for you:

1 Like

Sorry, you’re right, CreateAccountPage is not my root component. It’s WelcomePage. What I meant is that it’s the first component loaded on a page with loads (was thinking on WelcomePage as a normal page).

If I move CreateAccountPage to CreateAccountPageModule in WelcomePage.module I get similar issue but with page-create-account (not known element). And this is not using elsewhere.

I removed @IonicPage(). As you said this is not going to be used as an independent page. It could be used like that but the user who wants to register will need to see the welcome slides anyway.

//// DOUBT ////
If we have a “page” (without @IonicPage() and without route) that we are going to use just once on our app, should it be placed inside pages or components? I see components as parts of our app that we need on different places.
//// END DOUBT ////

Thanks for the link. I added entryComponents on all modules to have exactly the same configuration. Why some modules don’t have this? (https://github.com/mhartington/lazyLoad2-components/blob/master/src/pages/detail/detail.module.ts).

THIS last part (and moving createAccountPage from imports to declarations as module) made the difference. I needed these 2 things. I don’t know why on previous link those components work without exports and entryComponents.

Thanks dude, you saved my day here!!

Just one thing, what about shared components as translations or other providers? Do we need to keep them on app.module or we need change every single component?

@Xiwi

If we have a “page” (without @IonicPage() and without route) that we are going to use just once on our app, should it be placed inside pages or components? I see components as parts of our app that we need on different places.

Ionic pages are just components, but they have some specific behaviour, like having ion-content in it and receiving parameters with navParams (instead of @Input) and being created with NavController.

If your component doesn’t behave like that, I advise you to consider it as a simple component, not a page.

I added entryComponents on all modules to have exactly the same configuration. Why some modules don’t have this?

Using entryComponents allow you to create components dynamically, either with ComponentFactoryResolver explicitly or under the hood.

For example, when you do this.navCtrl.push(MyComponent) you instantiate the component through its reference, instead of including it in the template like <my-component></my-component>, so you need to include it in entryComponents, otherwise you receive an error.

If you use the component only in the template (HTML) you don’t need to include it in entryComponents.

But when creating a module per component, I include it in entryComponents as a best practice, because I don’t want to assume the way the component consumers will create it, so it will work in both cases (defining it in the template or creating it by reference).

I don’t know why on previous link those components work without exports and entryComponents .

If the 2 components A and B are declared in the same module, then you don’t need to export B to be used by A (because both are in the same module). If a component C in another module uses B, then you need to export B.

When you use one module per component, you should export the component always (because its consumers will all be in other modules).

About entryComponents, you don’t need it every time, but I recommend as a best practice (see what I said before, for more details).

Just one thing, what about shared components as translations or other providers? Do we need to keep them on app.module or we need change every single component?

I don’t have a definition of shared components (by itself). When one component is used by several others, I just include its module in the modules of the components that use it, just like with any other component.

I let the task of reusing or not this component code (in the generated files) with the module bundler (webpack in this case, although the way it’s setup in Ionic3 don’t make it very efficient in some cases). No complexities here. I don’t store shared data in components, tough, but in providers.

About providers, I’m incuding all of them in AppModule, while I’m still in Ionic3. When I migrate to Ionic4, I will be using Angular6 and define the providers with @Injectable({ providedIn: 'root' }). So I won’t need to import the provider in any module, it will be only one in the application, and will have the benefit of lazy loading (for now, it doesn’t have it because I include all of them in AppModule, so all of them are eager loaded, unfortunately).

hmmm… I’m just using it on the html but apparently needs this. So you agree having exports and entryComponents by default always is a good practique?

Yes, of course, we can load shared modules across different components but let’s talk about some component to manage the translations. We are going to need this everywhere. Better to load this in just one file to have one reference, right?

I’ll keep that link on mind because I’m also using Angular 5 + Ionic 3 and I’ll like to move to newer versions when Ionic 4 get released.

Thanks again!

@Xiwi

I’m just using it on the html but apparently needs this.

It shouldn’t need entryComponents. It should need exports though. What component it is?

So you agree having exports and entryComponents by default always is a good practique?

Yes, if you are using one component per module. Actually, in this case, exports is needed (not only a best practice). If it is only inside your project, though (you are not creating a reusable component to be used in other projects) you may choose not include in entryComponents, and when you need to crete it dynamically you can just include in the module (it’s easy because it’s in your project).

Better to load this in just one file to have one reference, right?

It’s not clear what you be by just one file, but I assume in only one module. Actually you can only load in one module, but you can export it to be used in other modules. Keep in mind that whenever you define it in a component, a new component instance of that type will be created when the parent component is created, independently of it being in a shared module or one component per module.

Also, even if you create a shared module will several components that you reuse, all modules that include the shared module that are in separate files (lazy loaded) will have different references for the shared components (a shared module doesn’t help there).

Actually, the shared module is worse in this case, because in Ionic3 each lazy loaded module will include all of the components of the shared module in each file related to each lazy loaded module, even the ones that are not used.

The only point I see of a shared module is have to write less code (because with one component per module you have to import each one when you use it). So, basically, convenience, and not that much IMO.

But this can come back and bite you later when you create more and more components and your shared module becomes huge, with all dependencies mixed together.

If with “just one file” you mean only one component, you could do that using one component per module and import its module when needed, each component would have each one their own data (and they could use a provider to share data if needed).

Dunno what component it was because I already changed it but I thing it was language-change.
But if you say that is a good practique having that by default doesn’t matter, I’ll add it always.

“Actually you can only load in one module, but you can export it to be used in other modules”. So at the end we need to modify every .module of each component/page?

“The only point I see of a shared module is have to write less code”. Totally agree with this, but this is quite handy for components that we know 100% that they need to be used across the entire app like the one we are talking about here, which manages the translations.

I know that probably this doesn’t sound like lazy loading (because it’s not) but considering that we need to load something everywhere, what’s the point of having it separately if we are not going to risk perfomance?

@Xiwi I consider one module per component better because the dependencies are already defined and you only need to care about direct dependencies.

If you use a shared module, say, for component A, and then you add B, C and D to that module because they too are used frequently, and B depends on A, so all components that depends on B will also depends on A.

If in the future you don’t need to make A be in the shared module anymore, or will use a better component than A for that purpose, you would need to find all components that depends on A, but how will you find that? The components are importing the shared module, so they can be importing it because of A, but not necessarily, it could be because of any other component in it.

So you would need to find every component that uses A and now import the module a explicitly, or you just let A in the shared module making the shared module have components that it doesn’t need. As times passes it will grow and grow and you probably won’t want to remove anything from the shared module because it could break your app and you would need to find every dependency of every component you use.

Another point is that you make it clear what are the dependencies of your component. If you import SharedModule everywhere you don’t know why you are importing it (maybe it isn’t even needed there).

A final point is that you also would have to import the dependencies of the components in the shared module, making it even larger, and at some point you won’t know why some component is in there (maybe because it is very used, or because some component that is very used uses it).

What is better is a bit subjective, but I don’t waste a huge amount of time importing each component module in the modules that use it, and I consider that the pros far surpasses the cons. You can just consider for your use case the tradeoffs and choose what you think it’s better.

It’s not like one module per component is the best possible solution. If angular could detect each reference without the need of creating modules explicitly (just like it does now with providers), that would be much better. But as it is now, I consider one module per component the best approach.

Don’t know exactly what you mean here, sorry.

Is the dependency horizontal o vertical? I mean, if I have a parent component (A) with 2 child component imported (B and C), and inside B I have D. Is C available from this last child component (D) or is it just available from the first level?

@Xiwi In your example: A depends on [B and C] and B depends on [D]. C will only be available to D if they are in the same module or the module that has D imports a module that exports C.

Don’t know exactly what you mean here, sorry.

What I mean is about indirect dependency. In your example, A depends indirectly on D. Using 1 module per component you only need to import its direct dependencies (B and C) and will work fine. If you create a shared module for A, then instead you would need to include A, B, C and D in the module. It may become ugly and huge as more components are added (you have to add the component and all dependencies, direct and indirect. Because of A, you would need to include D, even tough A don’t use it directly. Also, the consumers of C will have to load a module with A, B and D, even tough they don’t use them. Using one module per component you avoid it.

In the case of libraries things get more evident (and worse). You can look at ionic3 (ionic4 should fix that). You need to load all their components (through IonicModule) even tough you use just a few of them in your project. You could say that their components may be used and reused in several cases, and that is true, but this don’t mean that most projects will need most of them. But the way it is now, it’s all or nothing. That’s more than 292KB (not counting providers they depend on) and, of course, have a significant impact in the app performance at startup.

I think your project is not a library, so feel free to do the way you seem fit. One module per component is my recommendation, based on what I said before, but you can try wichever way you see better (like I said before, there are tradeoffs, so choose the way you prefer).

Ok, makes sense now. So C won’t be available on D, that’s ok, but what happens with A? Do we still having everything from A available on C right? At least B should be accessible from C with @Input() parent; but not sure if we can access more than one level. In this case I’d import my shared library just inside A.

And yes, my app is not a library, it’ll be a social network with several features so in terms of size is going to be big. That’s why I need to have all of this quite clear at beginning.

I hope the transition between Ionic 3 and 4 is not going to be painful. But unfortunately from 1 and 2 was. And from AngularJS to Angular. Finger crossed