rangle / angular-devtools

Moved to the Angular organization.
https://github.com/angular/angular/tree/master/devtools
255 stars 18 forks source link

Awaiting async calls in APP_INITIALIZER breaks Angular app detection #826

Closed rsheptolut closed 3 years ago

rsheptolut commented 3 years ago

Angular DevTools version (required): 1.0.1

Angular version (required): 12.0.2

Link to a minimal stackblitz reproduction (strongly encouraged): Apologise for not having this, will provide later upon request. See sample code below.

Description of issue:

The extension works fine with a new angular app, but stops working as soon as you await for an asynchronous real network call (or any asynchronous operation that doesn't complete very quickly) in the application initializer.

We're using the app initializer to fetch some very important user context (preferences, info, localization object). It's very convenient to then rely on this being in memory throughout the app.

Steps to reproduce:

  1. Create a new Angular app. That should work fine with the extension and the Angular icon will light up in red.

  2. Set up APP_INITIALIZER for the app using the sample code below that awaits for an asynchronous network call to an API (or does something else asynchronous) in the app initializer function. Each time you reload the app, the app initializes fully only after this network request has finished.

  3. Because of the asynchronous network call that delays app initialization, the "Angular" icon of the Angular DevTools extension shows up as black and says: "This page is not using Angular, or it has a strict extension policy".

  4. If you debug "ng_validate.js" of the extension, you'll see that it attempts to access windows.ng or document.querySelector("[ng-version]"), but both are undefined at this point, because the script is running too soon - the network request hasn't finished, so the angular app hasn't been fully initialized yet.

Additional details:

Sample code to configure the initializer that breaks Angular app detection:

import { CommonModule } from "@angular/common";
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from "@angular/core";

/**
 * Application initializer that awaits for an asynchronous network call or something
 */
export function initializeApplication(): () => Promise<void> {
    return async () => {
        // Simulating an async operation by resolving the promise after 1 second.
        // You can replace this by an httpClient request to an API
        await new Promise(resolve => setTimeout(resolve, 1000)); 
    };
}

@NgModule({
    imports: [
        CommonModule,
    ],
    providers: [
    ],
})
export class CoreModule {
    public static forRoot(): ModuleWithProviders<CoreModule> {
        return {
            ngModule: CoreModule,
            providers: [
                // ...
                // Configures the application initializer above to run before any app-level code
                {
                    provide: APP_INITIALIZER,
                    useFactory: initializeApplication,
                    deps: [],
                    multi: true,
                },
                // ...
            ],
        };
    }
}