Skip to content

Lazy loading components does not load FormModule/ngModel unable to bind #33654

Closed

Description

@neofuture

🐞 bug report

Affected Package

IVY/9/RC0

Is this a regression?

no

Description

I have set up a lazy load for a component as follows in V9 RC0

constructor(private cfr: ComponentFactoryResolver) {}

...

@ViewChild('viewContainer', {read: ViewContainerRef}) viewContainer: ViewContainerRef;

...

const {DemoComponent} = await import('../demo/demo.component');
const componentRef = this.viewContainer.createComponent(this.cfr.resolveComponentFactory(DemoComponent));

Which works and gets into the child view

<ng-container #viewContainer></ng-container>

But if i try and use any [(ngModel)] in the lazy loaded component, i get the following issue

Can't bind to 'ngModel' since it isn't a known property of 'select'

If i import the component in the app module it works as expected, but then its already pre loaded and does not lazy load

🔥 Exception or Error


    ERROR in src/app/modules/contact-manager/contact-manager.component.html(12,9): error TS8002: Can't bind to 'ngModel' since it isn't a known property of 'select'.

🌍 Your Environment

Angular Version:


Angular CLI: 9.0.0-rc.0
Node: 12.2.0
OS: darwin x64
Angular: 9.0.0-rc.0
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.0-rc.0
@angular-devkit/build-angular     0.900.0-rc.0
@angular-devkit/build-optimizer   0.900.0-rc.0
@angular-devkit/build-webpack     0.900.0-rc.0
@angular-devkit/core              9.0.0-rc.0
@angular-devkit/schematics        9.0.0-rc.0
@angular/cdk                      8.2.3
@ngtools/webpack                  9.0.0-rc.0
@schematics/angular               9.0.0-rc.0
@schematics/update                0.900.0-rc.0
rxjs                              6.5.3
typescript                        3.6.4
webpack                           4.41.2

Activity

  1. changed the title [-]Lazy loading modules does not load FormModule[/-] [+]Lazy loading components does not load FormModule/ngModel unable to bind[/+] on Nov 7, 2019
  2. added this to the needsTriage milestone on Nov 7, 2019
  3. modified the milestones: needsTriage, Backlog on Nov 12, 2019
  4. modified the milestones: Backlog, v9-candidates on Nov 12, 2019
  5. neofuture commented on Dec 3, 2019

    @neofuture
    Author

    Does anyone know a work around for this issue ?

  6. neofuture commented on Dec 13, 2019

    @neofuture
    Author

    Since updating to rc6 this is now now returned as an error but now as a warning

    Can't bind to 'ngModel' since it isn't a known property of 'select'.

  7. neofuture commented on Dec 13, 2019

    @neofuture
    Author

    I have created a repo to illustrate the issue

    https://github.com/neofuture/lazyload

  8. fkolar commented on Jan 15, 2020

    @fkolar

    Please conside #34780 case as well, Where in this case we try to instantiate a component which using ngModel the #34780 is trying to bind it programatically.

  9. 18 remaining items

  10. JoostK commented on Jul 24, 2020

    @JoostK
    Member

    @neofuture Let me try to go over it:

    declaring them in the NgModule means they are not lazy

    Luckily, this isn't entirely accurate although I can most certainly see where the confusion comes from. Declaring a component in an NgModule does not necessarily mean they are not lazy. It all depends on how that NgModule is integrated in the full NgModule graph. Each NgModule can have imports and can be imported by other NgModules. The NgModules that are (transitively) imported from the root NgModule (typically AppModule) are not lazy loaded, but the NgModules that are not referenced in this way will not be loaded until you load them. This is typically done today using @angular/router using the loadChildren idiom, where you load the NgModule form within a dynamic import, allowing it to be lazily loaded as the Router is asked to load those children.

    So to have a component that is supposed to be lazily loaded and use any declarations, you'd need an NgModule that declares the component and imports the necessary NgModules, but is not imported into any other NgModule. This allows the component to use the declarations that have become visible through the NgModule.

    The tricky bit is what to do with that extra NgModule. In some situations, you could only lazily load the component without loading the NgModule that declares it, provided that you're using AOT compilations. During AOT compilation, the compiled component will have explicit references to the declarations that it actually uses, so the NgModule is not strictly necessary for the component to render. However, it could be that the NgModule is also used to provide additional providers for DI that were not already loaded into an Injector from AppModule. In that case, the NgModule must also be lazily loaded and instantiated to obtain its Injector which contains the registered providers. This is exactly how the Router operates, by the way.

    The confusing part is how NgModules serve two purposes: declaration scopes and DI provider registration. In AOT compilations the scope information is not really needed, but the DI information may certainly be. Unfortunately it's almost impossible to know if a certain NgModule and the NgModules that it imports do register providers for DI purposes, so it's always guessing whether a component can be lazily imported without needing to load its NgModule as well.

    More information can be found here: #35759 (comment)

  11. neofuture commented on Jul 24, 2020

    @neofuture
    Author

    Thank you for the excellent explanation, I think I understand that, however it doesn't help me understand how to do what I need to do, or if what I'm trying to do will be possible.

  12. added and removed
    regressionIndicates than the issue relates to something that worked in a previous version
    on Jul 24, 2020
  13. neofuture commented on Jul 25, 2020

    @neofuture
    Author

    Yes, so I can not do what I want too, I think lazy loading components is pretty much a a requirement for app optimisation, I was excited at angular connect when this was demo'ed. could you explain a way to lazy load a component and even if its maybe a future feature, or be able to assign the declarations in the component and outside of the module ?

  14. mlc-mlapis commented on Jul 25, 2020

    @mlc-mlapis
    Contributor

    @neofuture Is it still the problem that if you lazy-load only a component, it's not able to load also all types of dependencies except providers because only them you are able to declare on that component level? If there are other parts, like pipes, other modules and so, you have to lazy-load a module and not that component itself?

  15. levani132 commented on Jul 25, 2020

    @levani132

    Have the same issue on CLI 9.0.2
    ` ERROR in src/app/configurations/openings/edit-opening/edit-opening.component.html:3:64 - error NG8004: No pipe found with name 'async'.

      3     <h4 class="modal-title" id="modal-title">{{ SelectedDate | async }}</h4>
                                                                       ~~~~~~
    
        src/app/configurations/openings/edit-opening/edit-opening.component.ts:7:18
          7     templateUrl: './edit-opening.component.html',
                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          Error occurs in the template of component EditOpeningComponent.`
    
    @NgModule({
      declarations: [EditOpeningComponent],
      imports: [CommonModule]
    })
    class EditOpeningModule {
    }
    

    Try adding this lines in the end of the edit-opening.component.ts @maxstee

    @neofuture Why don't you use this approach? There's a clear explanation of why you can't use plain component (without declaring in module) as lazy loaded component by @JoostK, but as I see and as you really want to use it without of declaring it in any other modules just give the component its own module.

    Also you can support my issue here if you think this will be a good approach of doing what you want to do.

  16. neofuture commented on Jul 25, 2020

    @neofuture
    Author

    WOW

    It is possible

    Template

    <h2>Example</h2>
    <input [(ngModel)]="demo">
    <pre>{{ windowItem | json }}</pre>
    

    Component

    import {Component, Input, NgModule, OnInit} from '@angular/core';
    import {FormsModule} from '@angular/forms';
    import {CommonModule} from '@angular/common';
    
    @Component({
      selector: 'app-example',
      templateUrl: './example.component.html',
      styleUrls: ['./example.component.css']
    })
    export class ExampleComponent implements OnInit {
      @Input() windowItem;
      demo = 'Testing';
    
      constructor() { }
    
      ngOnInit(): void {
      }
    }
    
    @NgModule({
      imports: [
        FormsModule,
        CommonModule
      ],
      declarations: [ExampleComponent]
    })
    
    export class ExampleModule {}
    

    Lazy loading

        import('../components/system/example/example.component').then(c => {
          const ngModuleFactory = this.compiler.compileModuleSync(c.ExampleModule);
          const ngModule = ngModuleFactory.create(viewContainer.injector);
          const factory = ngModule.componentFactoryResolver.resolveComponentFactory(c.ExampleComponent);
          componentRef = viewContainer.createComponent(factory);
          componentRef.instance.windowItem = windowItem;
        });
    

    @JoostK you are a star thank you for giving me the solution, the forms work fine now, understanding is a great thing

    It seems having a module declaration in the component that handles the declarations allow you to have the module definitions too, as you factory those in too, I probably didn't explain that right but I get the pattern now

    Screenshot 2020-07-26 at 00 27 14

  17. neofuture commented on Jul 25, 2020

    @neofuture
    Author

    @levani132 awesome thank you I didn't read that until I went away and did what I did, but that's pretty much my solution, thank you for your comments and advice, what an awesome community 👍

  18. neofuture commented on Jul 25, 2020

    @neofuture
    Author

    also thanks to @mlc-mlapis you are absolutely correct... thank you

  19. neofuture commented on Jul 29, 2020

    @neofuture
    Author

    This is possible and is not a bug, with the kind explanations, there is a way to do this so this should be closed now

  20. angular-automatic-lock-bot commented on Aug 29, 2020

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  21. locked and limited conversation to collaborators on Aug 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions