Search code examples
angularangular-routingangular-routerangular-routerlinkangular-lazyloading

NgModuleFactoryLoader.load() is not able to find the module


Please refer my project structure here I have create a project with multiple lazy loaded nested modules.

  1. On the application start up AppModule is loaded
  2. When the user clicks on login button LoginModule is loaded
  3. If the user exists i.e if login is successful IntegratedPmntsModule which is another feature module of AppModule should be lazily loaded from login-routing.module.ts. It is loading IntegratedPmntsModule perfectly with 'button routerLink='integratePayments' class="pmntButton"' but that's not what i want.

My requirement here is to conditionally load the feature child module as shown in the component login.component.ts, when the user login is failed he should be navigated to login(its working fine as LoginModule is already lazy loaded) and when login is successful user should be navigated to IntegratedPaymentsComponent present in IntegratedPmntsModule (which is not happening).

  1. this.router.navigate(['integratedPayments']); -- is giving 'Uncaught (in promise): Error: Cannot match any routes 'integratedPayments'

  2. I even tried loading the IntegratedPmntsModule using load() method of NgModuleFactoryLoader.load() which is giving 'module is not found error'

login.component.ts

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  loginFormGroup: FormGroup;
  customerModel:CustomerLoginModel = new CustomerLoginModel();
  userExist: Boolean = true;

  constructor(private loginService: LoginService, private router: Router, private readonly loader: NgModuleFactoryLoader) { }

  ngOnInit(): void {
    this.loginFormGroup = new FormGroup({
      customerId: new FormControl('', [Validators.required,Validators.maxLength(20),Validators.pattern('^[a-zA-Z0-9]+([._]?[a-zA-Z0-9]+)*$')]),
      password: new FormControl('', [Validators.required, Validators.maxLength(15)])
    })
  }
  submitLoginForm(formGroup: FormGroup): any{
   
   this.customerModel.setCustomerId(formGroup.value.customerId);
   this.customerModel.setPassword(formGroup.value.password);
   this.loginService.authenticateCustomer(this.customerModel).subscribe(
     response=>{
       this.userExist=response},
      error=>{
        console.log(error)
      }
    );
      if(this.userExist){
          //this.loader.load('./app/integrate-pmnts-module/integrate-pmnts.module#IntegratePmntsModule').
          //then(factory =>this.router.navigate(['integratedPayments']));
          this.router.navigate(['integratedPayments']);
      }
      else
        this.router.navigate(['login']);
  }

}

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginauthGuard } from './login/loginauth.guard';


const routes: Routes = [
 
  {
    path: 'login',
    loadChildren: () => import('./login/login.module').then(mdule=>mdule.LoginModule),
  },
  
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

login-routing.module.ts


const routes: Routes = [
  {
    path: '',
    component: LoginComponent,
    //canDeactivate: [LoginauthGuard]
  },
  {
    path: 'integratedPayments',
    loadChildren: () => import('../integrate-pmnts-module/integrate-pmnts.module').then(mdule=>mdule.IntegratePmntsModule)
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class LoginRoutingModule { 

}

login.component.html


<html>
    <form [formGroup]="loginFormGroup" (ngSubmit)="submitLoginForm(loginFormGroup)"> 
            Customer Id:<input type="text" formControlName="customerId" placeholder="Enter Customer ID"><br><br>
            Password: <input type="password" formControlName="password" placeholder="Enter Password"><br><br>
            <div *ngIf='!userExist'>
                Please enter valid credentials!
            </div><br><br>
        <button class="pmntButton">Login</button>
    </form>
</html>

integratepayments-routing.module.ts


const routes: Routes = [
    {
        path: '',
        component: IntegratedPaymentsComponent
    },
    {
      path: 'eftPaymentsPage',
      loadChildren: () => import('src/app/integrate-pmnts-module/eft-payments-module/eft-payments-module').then(mod=>mod.EftPaymentsModuleModule)
    }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class IntegratedPaymentsRoutingModule { }


Solution

  • Since the integratedPayments route is a child of login route, you should use: this.router.navigateByUrl('login/integratedPayments').

    This might not still work though, because the routes from login-routing.module.ts are defined as follows:

    const routes: Routes = [
      {
        path: '',
        component: LoginComponent,
        //canDeactivate: [LoginauthGuard]
      },
      {
        path: 'integratedPayments',
        loadChildren: () => import('../integrate-pmnts-module/integrate-pmnts.module').then(mdule=>mdule.IntegratePmntsModule)
      }
    ];
    

    which means that the first route (path: '') will always match. What you can do in order to avoid this is to add the pathMatch: 'full' option:

    const routes: Routes = [
      {
        path: '',
        component: LoginComponent,
        pathMatch: 'full'
      },
      {
        path: 'integratedPayments',
        loadChildren: () => import('../integrate-pmnts-module/integrate-pmnts.module').then(mdule=>mdule.IntegratePmntsModule)
      }
    ];
    

    perfectly with 'button routerLink='integratePayments' class="pmntButton"'

    this is because of how RouterLink directive works internally.

    Let's see what happens when you click on a button which has this directive:

    @HostListener('click')
      onClick(): boolean {
        const extras = {
          skipLocationChange: attrBoolValue(this.skipLocationChange),
          replaceUrl: attrBoolValue(this.replaceUrl),
          state: this.state,
        };
        this.router.navigateByUrl(this.urlTree, extras);
        return true;
      }
    
      get urlTree(): UrlTree {
        return this.router.createUrlTree(this.commands, {
          relativeTo: this.route, // !
          queryParams: this.queryParams,
          fragment: this.fragment,
          preserveQueryParams: attrBoolValue(this.preserve),
          queryParamsHandling: this.queryParamsHandling,
          preserveFragment: attrBoolValue(this.preserveFragment),
        });
      }
    

    as you can see, it's using relativeTo option. this.route is injected as private route: ActivatedRoute.

    What this means is that you can make it work with this.router.navigate(['integratedPayments']); from you login component.

    You'll have to first inject the ActivatedRoute inside login component and then add the relativeTo option to route.navigate:

    this.router.navigate(['integratedPayments'], { relativeTo: this.route });