Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error Application start, functional guard injection in the app_routes_config #54692

Closed
tonia211262 opened this issue Mar 4, 2024 · 9 comments
Labels
area: core Issues related to the framework runtime needs reproduction This issue needs a reproduction in order for the team to investigate further
Milestone

Comments

@tonia211262
Copy link

tonia211262 commented Mar 4, 2024

Which @angular/* package(s) are the source of the bug?

router

Is this a regression?

Yes

Description

ERROR Error: NG0203: inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://angular.io/errors/NG0203
  at injectInjectorOnly (core.mjs:3824:15)
    at ɵɵinject2 (core.mjs:3837:42)
    at inject2 (core.mjs:3921:12)
    **at app.routes.ts:75:15**
    at Generator.next (<anonymous>)
    at chunk-5AA32OEI.js:44:61
    at new ZoneAwarePromise (zone.js:1425:21)
    at __async (chunk-5AA32OEI.js:28:10)
    at routes.canActivate (app.routes.ts:74:73)
    at router.mjs:3357:134

app.routes.ts

export const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent,
    title: '..'
  },
  {
    path: '',
    resolve: {},
    canActivate: [
      **async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) =>
        await inject(AuthenticationGuard).canActivate(route, state)**
    ],
    component: NavigationComponent,
    children: [...]
  }
];

authentication.guard.ts

export type CanActivateType =
  | Observable<boolean | UrlTree>
  | Promise<boolean | UrlTree>
  | boolean
  | UrlTree;

@Injectable({
  providedIn: 'root'
})
export class AuthenticationGuard {
  canActivate: CanActivateFn = (
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): CanActivateType => {
    // const authentication = route.data.authentication;
    const authentication = inject(AuthenticationService).getAuthentication();

    if (!authentication || FunctionDates.lessNow(authentication.dataScadenza)) {
      inject(GlobalService).clear();

      return inject(Router).createUrlTree(['login']);
    }
    return !!authentication; // eslint-disable-line
  };
}

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

No response

Anything else?

No response

@JeanMeche JeanMeche added the needs reproduction This issue needs a reproduction in order for the team to investigate further label Mar 4, 2024
@JeanMeche
Copy link
Member

Hi, your code doesn't reproduce the issue. Could you try to provide a reproduction on stackblitz ?

Also is your Guard comming from a library ?

@JeanMeche JeanMeche added the area: core Issues related to the framework runtime label Mar 4, 2024
@ngbot ngbot bot added this to the Backlog milestone Mar 4, 2024
@tonia211262
Copy link
Author

tonia211262 commented Mar 5, 2024

Hi, your code doesn't reproduce the issue. Could you try to provide a reproduction on stackblitz ?

Also is your Guard comming from a library ?

Not. It's the functional version of the framework's native guard.

The project is very large, I encountered this error after upgrading angular to the latest version and integrating the new features offered such as standalone components. Please try checking here first for errors:

app.config.ts

const RETRY_INTERCEPTOR_CONFIG: RetryConfig = { count: 3, delay: 1000 };

const checkForUpdates = async (swUpdate: SwUpdate) => {
  try {
    // Check if running in Browser
    if (!isPlatformBrowser(inject(PLATFORM_ID))) return;

    // Check if Service Worker is supported by the Browser
    if (!swUpdate.isEnabled) return;

    // Check if new version is available
    const isNewVersion = await swUpdate.checkForUpdate();

    if (!isNewVersion) return;

    // Check if new version is activated
    const isNewVersionActivated = await swUpdate.activateUpdate();

    // Reload the application with new version if new version is activated
    if (isNewVersionActivated) window.location.reload();
  } catch (error) {
    console.log('Service Worker - Error when checking for new version of the application:', error);
    window.location.reload();
  }
};

export const appConfig: ApplicationConfig = {
  providers: [
    importProvidersFrom([
      CalendarModule.forRoot({ provide: DateAdapter, useFactory: adapterFactory })
    ]),
    provideLocaleConfig(),
    provideCharts(withDefaultRegisterables()),
    provideRouter(
      routes,
      withComponentInputBinding(),
      withDebugTracing(),
      withInMemoryScrolling({
        anchorScrolling: 'enabled',
        scrollPositionRestoration: 'enabled'
      }),
      withDisabledInitialNavigation(),
      withEnabledBlockingInitialNavigation(),
      withPreloading(PreloadAllModules),
      withRouterConfig({ onSameUrlNavigation: 'reload' }),
      withViewTransitions({ skipInitialTransition: true }),
      withPreloading(PreloadAllModules),
      withDebugTracing()
    ),
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWhenStable:10000'
    }),
    provideHttpClient(
      withFetch(),
      withInterceptors([
        authInterceptor,
        errorInterceptor,
        loaderInterceptor,
        retryInterceptor(RETRY_INTERCEPTOR_CONFIG)
      ]),
      withJsonpSupport(),
      withXsrfConfiguration({ cookieName: 'TOKEN', headerName: 'X-TOKEN' })
    ),
    provideAnimations(),
    provideClientHydration(withHttpTransferCacheOptions({ includePostRequests: true })),
    { provide: APP_ID, useValue: 'serverApp' },
    { provide: APP_BASE_HREF, useValue: '/app' },
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [SwUpdate, AppConfigService],
      useFactory: (swUpdate: SwUpdate, appConfigService: AppConfigService) => {
        return async () => {
          const config = await appConfigService.loadAppConfig();

          checkForUpdates(swUpdate);

          return config;
        };
      }
    },
    {
      provide: IMAGE_CONFIG,
      useValue: {
        disableImageSizeWarning: false,
        disableImageLazyLoadWarning: false
      }
    },
    { provide: LocationStrategy, useClass: PathLocationStrategy }
  ]
};

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  styleUrls: ['./app.component.scss'],
  standalone: true,
  imports: [
    CommonModule,
    MaterialModule,
    BrowserModule,
    BrowserAnimationsModule,
    NgxTranslateModule,
    TransferHttpCacheModule,
    RouterOutlet
  ],
  providers: []
})
export class AppComponent implements OnInit {
  title = '...';

  constructor(
    private readonly dateAdapter: DateCoreAdapter<Date>,
    private readonly localeService: LocaleService,
    private readonly translate: TranslateService,
    private readonly iconService: IconService,
    private readonly faLibrary: FaIconLibrary,
    private readonly faConfig: FaConfig
  ) {
    this.translate.addLangs(['en', 'it']);
    this.useLanguage('it');
    this.notificationBackground();

    const browserLang: any = translate.getBrowserLang();

    this.translate.use(browserLang && /en|it/.test(browserLang) ? browserLang : 'it');
  }

  ngOnInit() {
    moment.locale(this.localeService.defaultLocale(), {
      week: { dow: 1, doy: 4 }
    });
    registerLocaleData(locale);
    findLocation(this.environment);
    this.faConfig.fixedWidth = true;
    this.faLibrary.addIcons(
      faFilePdf,
      faFileExcel,
      faServer,
      faCode,
      faUserShield,
      faShare,
      farFilePdf,
      farFileExcel
    );
    this.iconService.registerAll(appIcons);
  }

  public default() {
    this.dateAdapter.setLocale(this.localeService.getCurrentLocale());
  }

  private useLanguage(language: string) {
    this.translate.setTranslation(language, defaultLanguage);
    this.translate.setDefaultLang(language);
    this.translate.use(language);
  }

  private environment(location: string | null) {
    console.group('🔑 Variabili ambiente');
    console.log(`1. 🏳️‍🌈 Lingua browser: ${this.localeService.defaultLocale()}`);
    console.log(`2. 🏳️‍🌈 Lingua applicazione: ${moment.locale()}`);
    console.log(`3. 🌙 Tema notturno: ${isDarkMode() ? '✅' : '✖️'}`);
    console.log(`4. 🌏 Connessione rete: ${window.navigator.onLine ? '✓' : '✖️'}`);
    console.log(`5. 🧭 Posizione corrente: ${location ?? 'n.d.'}`);
    console.groupEnd();
  }

  private notificationBackground() {
    let notification: any;
    let interval: any;

    fromVisibilityChange().subscribe(res => {
      if (document.visibilityState === 'hidden') {
        const leaveDate = new Date();

        interval = setInterval(() => {
          notification = new Notification(this.title, {
            body: `In modalità background da ${Math.round(
              (Date.now() - leaveDate.getTime()) / 1000
            )} secondi.`,
            tag: 'Torna Indietro'
          });
        }, 100);
      } else {
        if (interval) clearInterval(interval);

        if (notification) notification.close();
      }
    });
  }
}

app.config.server.ts

const serverConfig: ApplicationConfig = {
  providers: [provideServerRendering()]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

main.ts

if (environment.production) enableProdMode();
bootstrapApplication(AppComponent, appConfig).catch(e => console.error(e));

@JoostK
Copy link
Member

JoostK commented Mar 5, 2024

At least checkForUpdates is using inject while being called from inside a useFactory callback after an await point, so checkForUpdates doesn't run in an injection context there (everything after the await executes in a microtick in a new stack). There's likely a similar thing going on in your CanActivateFn callback.

@tonia211262
Copy link
Author

tonia211262 commented Mar 5, 2024

At least checkForUpdates is using inject while being called from inside a useFactory callback after an await point, so checkForUpdates doesn't run in an injection context there (everything after the await executes in a microtick in a new stack). There's likely a similar thing going on in your CanActivateFn callback.

The object relative to APP_INITIALIZER was commented but error continues. if you tell me what I can comment or change.

The non-functional guard encounters the same problem.

{
    path: '',
    resolve: {},
    canActivate: [AuthenticationGuard],
    component: NavigationComponent,
    children: [...]
}
@Injectable({
  providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate {
  constructor(
    private readonly authenticationService: AuthenticationService,
    private readonly globalService: GlobalService,
    private readonly router: Router
  ) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    const authentication = this.authenticationService.getAuthentication();

    if (!authentication || FunctionDates.lessNow(authentication.dataScadenza)) {
      this.globalService.clear();
      this.router.navigateByUrl('/login');
    }
    return !!authentication; // eslint-disable-line
  }
}

@JoostK
Copy link
Member

JoostK commented Mar 5, 2024

I won't be able to help further without runnable reproduction.

@atscott
Copy link
Contributor

atscott commented Mar 5, 2024

Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on StackOverflow using tag angular.

If you are wondering why we don't resolve support issues via the issue tracker, please check out this explanation.

@atscott atscott closed this as not planned Won't fix, can't repro, duplicate, stale Mar 5, 2024
@tonia211262
Copy link
Author

I add project on stackblitz.

@JoostK
Copy link
Member

JoostK commented Mar 5, 2024

It's the same as in angular/angular-cli#27122. The path mapping for @angular causes @angular/core to be included multiple times during ng serve (with the Vite devserver), each having their own, independent, injection context.

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 5, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime needs reproduction This issue needs a reproduction in order for the team to investigate further
Projects
None yet
Development

No branches or pull requests

4 participants