Search code examples
angularangular18

Angular 4 to 18 Migration - Bootstrap error: RangeError: Maximum call stack size exceeded


Issue

I'm updating an application from Angular 4 to Angular 18 with Django as the backend. When i build the application it gives a infinity loop with the app.component.ts. Added some debuggers and it run the constructor on a loop. But it only happens when i add the . If i add the content of the index without it the loop doesn't happen.

When looping the developers tools is blank and no console show.

Errors

Bootstrap error: RangeError: Maximum call stack size exceeded

Exception: error : Maximum call stack size exceeded

RangeError: Maximum call stack size exceeded

App Flow

Environment

  • Angular CLI: 18.2.4
  • Node: 18.20.4
  • npm: 10.7.0
  • OS: Linux x64

Configuration

Here's my angular.json file:


{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "static": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser-esbuild",
          "options": {
            "allowedCommonJsDependencies": ["handsontable"],
            "outputPath": "src/dist/dev",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": [
              "zone.js",
              "@angular/localize/init"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "public",
                "output": "/assets"
              }
            ],
            "styles": [
              "src/app/shared/styles.css",
              "handsontable/custom_handsontable.css",
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "node_modules/font-awesome/css/font-awesome.min.css",
              "node_modules/handsontable/dist/handsontable.full.min.css"

            ],
            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.js"
            ]
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "3mb",
                  "maximumError": "3mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "outputHashing": "none",
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "static:build:production"
            },
            "development": {
              "buildTarget": "static:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "buildTarget": "static:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing",
              "@angular/localize/init"
            ],
            "tsConfig": "tsconfig.spec.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "public",
                "output": "/assets"
              }
            ],
            "styles": [
              "src/app/shared/styles.css",
              "handsontable/custom_handsontable.css",
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "node_modules/font-awesome/css/font-awesome.min.css",
              "node_modules/handsontable/dist/handsontable.full.min.css"
            ],
            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.js"
            ]
          }
        }
      }
    }
  }
}

Code

app.module.ts


import { ErrorHandler, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app.routing';
import { GlobalErrorHandlerService } from './shared/global-error-handler-service';
import { SharedModule } from './shared/shared.module';
import { ProgramsModule } from './programs/programs.module';
import { TeamsModule } from './teams/teams.module';
import { AllocationsModule } from './allocations/allocations.module';
import { ExportModule } from './export/export.module';
import { ValidationModule } from './validation/validation.module';
import { ProjectsService } from 'src/services';


@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
    FormsModule,
    BrowserAnimationsModule, // Required for animations
    ToastrModule.forRoot({
      timeOut: 3000,
      positionClass: 'toast-top-right',
      preventDuplicates: true,
    }), // ToastrModule added
    ProgramsModule,
    TeamsModule,
    AllocationsModule,
    ValidationModule,
    ExportModule,
    SharedModule,
    RouterModule,
    AppRoutingModule,
  ],
  providers: [{ provide: ErrorHandler, useClass: GlobalErrorHandlerService }, ProjectsService],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
})
export class AppModule {
  constructor() {
    console.log('AppModule loaded');
    debugger;
  }
}


app.component.ts

import { filter } from 'rxjs/operators';
import '../utils';
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';

import {
  ResourcesService,
  TeamsService,
  AllocationsService,
  ScrumTeamService,
  ProjectsService,
  BusinessUnitService,
  BusinessLineService,
  BusinessCategoryService,
  GlobalListenersService,
  ProductClassesService,
  ExportService,
  SessionService,
  ADUserQueryService,
  HeaderService,
  ResourceFunctionsService,
  ResourceCostCentersService,
  InitiativesService,
} from '../services';

@Component({
  selector: 'app-root',
  templateUrl: '../index.html',
  providers: [
    GlobalListenersService,
    ResourcesService,
    TeamsService,
    AllocationsService,
    ScrumTeamService,
    BusinessUnitService,
    BusinessLineService,
    BusinessCategoryService,
    ProjectsService,
    ProductClassesService,
    InitiativesService,
    HeaderService,
    ExportService,
    SessionService,
    ADUserQueryService,
    ResourceFunctionsService,
    ResourceCostCentersService,
  ],
})
export class AppComponent {
  protected activeRouteTitle!: string;
  protected menuToggle = false;
  protected supported = true;
  private initCount = 0;

  constructor(private route: ActivatedRoute, private router: Router) {
    console.log("App Component Constructor");
    this.router.events
      .pipe(filter((event) => event instanceof NavigationEnd))
      .subscribe(() => {
        console.log('Navigation Start');
        debugger;
        let currentRoute = this.route.root;
        console.log('Current Route', currentRoute);
        while (
          currentRoute.children[0] !== undefined &&
          currentRoute.snapshot.data['title'] == undefined
        ) {
          console.log('Navigation While Loop');
          currentRoute = currentRoute.children[0];
          break;
        }
        console.log('Navigation URL Choosen');
        this.activeRouteTitle = currentRoute.snapshot.data['title'];
      });
    debugger;
  }

  ngOnInit() {
    if (navigator.userAgent.indexOf('.NET') != -1) {
      this.supported = false;
    }
    console.log("App Component NgOnInit");
    debugger;
  }

  ngOnDestroy() {
    console.log('AppComponent destroyed');
    debugger;
  }

  toggleMenu(event: any) {
    let el = event.target;
    if (el.classList.contains('toggle-nav')) {
      return;
    }
    this.menuToggle = el.id == 'menu-toggler' ? !this.menuToggle : false;
  }
}

app.routing.ts:


import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AllocationsComponent } from './allocations/allocations.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { CanDeactivateGuard } from './shared/routing.guards';

const routes: Routes = [
  { path: '', redirectTo: '/allocations', pathMatch: 'full' },
  { 
    path: 'allocations', 
    component: AllocationsComponent, 
    canDeactivate: [CanDeactivateGuard],
    data: { title: 'Allocations' }
  },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { enableTracing: true })],
  exports: [RouterModule]
})
export class AppRoutingModule {
  constructor() {
    console.log('AppRoutingModule loaded');
  }
}


index.html


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>App</title>
  <base href="/static/src/dist/dev/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="shortcut icon" sizes="16x16" href="/public/favicon.ico" type="image/jpeg" />

</head>

<body>
  <app-root>
    <nav id="header" class="navbar navbar-expand-md" (click)="toggleMenu($event)">
      <div class="navbar-brand">{{ activeRouteTitle }}</div>
      <ul id="menu-navbar" class="navbar-nav mr-auto main-menu toggle-nav" [class.active]="menuToggle">
        <li class="nav-item toggle-nav">
          <a class="nav-link" routerLink="/allocations" routerLinkActive="active"><span>Allocations</span></a>
        </li>
        <li class="nav-item toggle-nav">
          <a class="nav-link" routerLink="/validation" routerLinkActive="active"><span>Validation</span></a>
        </li>
        <li class="nav-item toggle-nav">
          <a class="nav-link" routerLink="/groups" routerLinkActive="active"><span>Resources</span></a>
        </li>
        <li class="nav-item toggle-nav">
          <a class="nav-link" routerLink="/projects" routerLinkActive="active"><span>Projects</span></a>
        </li>
        <li class="nav-item toggle-nav">
          <a class="nav-link" routerLink="/export" routerLinkActive="active"><span>Export</span></a>
        </li>
      </ul>
      <div class="menu-hamburger"><i id="menu-toggler" class="fa fa-bars" aria-hidden="true"></i></div>
    </nav>
    <div *ngIf="!supported" class="ie-info"><span>Internet Explorer is not a&nbsp;supported browser. </span></div>
    <div class="ispinner ispinner--gray ispinner--animating ispinner--large">
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
      <div class="ispinner__blade"></div>
    </div>
    <div (click)="menuToggle = false">
      <ng-template ngbModalContainer></ng-template>
      <router-outlet></router-outlet>
    </div>
  </app-root>
</body>
</html>

Tried removing from app.module.ts the bootstrap AppComponent and loop is gonne, but system don't work.


Solution

  • As the comment says first create a app html file, then reference that instead, the index.html is auto rendered.

    @Component({
      selector: 'app-root',
      templateUrl: 'app.component.html',
      providers: [
        ...
    

    Then make sure you have eouter-outlet set in the app.component.html. This is used to render the routing component.

    <nav id="header" class="navbar navbar-expand-md" (click)="toggleMenu($event)">
          <div class="navbar-brand">{{ activeRouteTitle }}</div>
          <ul id="menu-navbar" class="navbar-nav mr-auto main-menu toggle-nav" [class.active]="menuToggle">
            <li class="nav-item toggle-nav">
              <a class="nav-link" routerLink="/allocations" routerLinkActive="active"><span>Allocations</span></a>
            </li>
            <li class="nav-item toggle-nav">
              <a class="nav-link" routerLink="/validation" routerLinkActive="active"><span>Validation</span></a>
            </li>
            <li class="nav-item toggle-nav">
              <a class="nav-link" routerLink="/groups" routerLinkActive="active"><span>Resources</span></a>
            </li>
            <li class="nav-item toggle-nav">
              <a class="nav-link" routerLink="/projects" routerLinkActive="active"><span>Projects</span></a>
            </li>
            <li class="nav-item toggle-nav">
              <a class="nav-link" routerLink="/export" routerLinkActive="active"><span>Export</span></a>
            </li>
          </ul>
          <div class="menu-hamburger"><i id="menu-toggler" class="fa fa-bars" aria-hidden="true"></i></div>
        </nav>
        <div *ngIf="!supported" class="ie-info"><span>Internet Explorer is not a&nbsp;supported browser. </span></div>
        <div class="ispinner ispinner--gray ispinner--animating ispinner--large">
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
          <div class="ispinner__blade"></div>
        </div>
        <div (click)="menuToggle = false">
          <ng-template ngbModalContainer></ng-template>
          <router-outlet></router-outlet>
        </div>
    

    The index.html should contain only the app-root selector and nothing else.

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="utf-8">
      <title>App</title>
      <base href="/static/src/dist/dev/">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="shortcut icon" sizes="16x16" href="/public/favicon.ico" type="image/jpeg" />
    
    </head>
    
    <body>
      <app-root></app-root>
    </body>
    </html>