Tuesday, 29 September 2020

Angular 9 hybrid with ui router causes Trying to get the AngularJS injector before it being set. How to debug/fix?

[EDIT] I've created a demo repository with this error: https://github.com/SamanthaAdrichem/hybrid-lazy-load-angularjs-injector-error

We've followed the instructions, and all runs fine on normal hybrid, but adding lazy loading fails with the strange error above, and always implemented ui router the way the docs say (as you can see in the code below).

When loading the app all is fine, but when then clicking onto a lazy-loaded route we get the error

Transition Rejection($id: 0 type: 6, message: The transition errored, detail: Error: Trying to get the AngularJS injector before it being set.)

I've tried the solutions listed here

https://github.com/ui-router/angular-hybrid/issues/93

https://github.com/ui-router/angular-hybrid/issues/150

https://github.com/ui-router/angular-hybrid/issues/395

https://github.com/ui-router/sample-app-angular-hybrid/issues/10

But none of these solve it. We don't use ng serve because that crashes with random memory issues (probably also due to hybrid), and we need to proxy to a login page, but the login page and our project would be running on the same port, which isn't possible. So that needs some refactoring in the backend.

It does sometimes load a new chunk file depending on what page i'm going to. But mostly i get this error.

Funny part is, in the page i'm testing we're not using any hybrid code. Basically we're are no longer upgrading any code, we're only downgrading some observers to open modals of the older pages.

These are the versions we're running

"@angular/cli": "9.1.7",
"@angular/animations": "9.0.7",
"@angular/common": "9.0.7",
"@angular/compiler": "9.0.7",
"@angular/core": "9.0.7",
"@angular/forms": "9.0.7",
"@angular/localize": "9.0.7",
"@angular/platform-browser": "9.0.7",
"@angular/platform-browser-dynamic": "9.0.7",
"@angular/router": "9.0.7",
"@angular/upgrade": "9.0.7",
"@uirouter/angular": "6.0.1",
"@uirouter/angular-hybrid": "10.0.1",
"@uirouter/angularjs": "1.0.26",
"@uirouter/core": "6.0.5",
"@uirouter/rx": "0.6.5",

Below the detailed error and some code, don't know what else to share or what i can safely share.

Any suggestions on how to debug this would be lovely!

detailed error

stateService.ts?8765:537 Transition Rejection($id: 0 type: 6, message: The transition errored, detail: Error: Trying to get the AngularJS injector before it being set.)
$defaultErrorHandler @ stateService.ts?8765:537
eval @ stateService.ts?8765:379
processQueue @ angular.js?21b1:17963
eval @ angular.js?21b1:18037
$digest @ angular.js?21b1:19176
eval @ upgrade_module.ts?423f:268
schedulerFn @ event_emitter.ts?938d:96
SafeSubscriber.__tryOrUnsub @ Subscriber.ts?face:266
SafeSubscriber.next @ Subscriber.ts?face:208
Subscriber._next @ Subscriber.ts?face:140
Subscriber.next @ Subscriber.ts?face:100
Subject.next @ Subject.ts?4a10:71
EventEmitter.emit @ event_emitter.ts?938d:86
checkStable @ ng_zone.ts?9664:224
onHasTask @ ng_zone.ts?9664:314
ZoneDelegate.hasTask @ zone.js?d135:440
ZoneDelegate._updateTaskCount @ zone.js?d135:462
Zone._updateTaskCount @ zone.js?d135:284
Zone.runTask @ zone.js?d135:205
drainMicroTaskQueue @ zone.js?d135:601
Promise.then (async)
scheduleMicroTask @ zone.js?d135:584
ZoneDelegate.scheduleTask @ zone.js?d135:410
onScheduleTask @ zone.js?d135:294
ZoneDelegate.scheduleTask @ zone.js?d135:400
Zone.scheduleTask @ zone.js?d135:231
Zone.scheduleMicroTask @ zone.js?d135:251
scheduleResolveOrReject @ zone.js?d135:881
resolvePromise @ zone.js?d135:819
eval @ zone.js?d135:739
webpackJsonpCallback @ app.js?v=1591962370205:26
(anonymous) @ 0.js?v=1591962370205:1
stateService.ts?8765:538 Error: Trying to get the AngularJS injector before it being set.
    at injectorFactory (angular1_providers.ts?397c:22)
    at _callFactory (ng_module.ts?c185:163)
    at _createProviderInstance (ng_module.ts?c185:74)
    at resolveNgModuleDep (ng_module.ts?c185:74)
    at _callFactory (ng_module.ts?c185:163)
    at _createProviderInstance (ng_module.ts?c185:74)
    at resolveNgModuleDep (ng_module.ts?c185:74)
    at _callFactory (ng_module.ts?c185:163)
    at _createProviderInstance (ng_module.ts?c185:74)
    at resolveNgModuleDep (ng_module.ts?c185:74)
    at NgModuleRef_.get (refs.ts?151c:134)
    at applyNgModule (lazyLoadNgModule.ts?7cfb:143)
    at applyModule (lazyLoadNgModule.ts?7cfb:79)
    at ZoneDelegate.invoke (zone.js?d135:384)
    at Object.onInvoke (ng_zone.ts?9664:224)
    at ZoneDelegate.invoke (zone.js?d135:384)
    at Zone.run (zone.js?d135:143)
    at eval (zone.js?d135:891)
    at ZoneDelegate.invokeTask (zone.js?d135:419)
    at Object.onInvokeTask (ng_zone.ts?9664:224)
    at ZoneDelegate.invokeTask (zone.js?d135:419)
    at Zone.runTask (zone.js?d135:188)
    at drainMicroTaskQueue (zone.js?d135:601)

Our main.ts

// Bootstrapping
import {enableProdMode, NgModuleRef, NgZone, ViewEncapsulation} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {dom, library} from '@fortawesome/fontawesome-svg-core';
import {UIRouter, UrlService} from '@uirouter/core';
import {icons} from 'app/scripts/modules/app-layout/icons/icons';
import 'bootstrap/dist/js/bootstrap.js';
import 'expose-loader?jQuery!expose-loader?$!jquery';
import 'expose-loader?moment!moment';
import 'scripts/app.module';
import 'scripts/homer';
import 'scripts/static.assets';
import {AppModule} from 'src/app/app.module';
import {environment} from 'src/environments/environment';

library.add(...icons);
dom.watch();

// If ready
if (/comp|inter|loaded/.test(document.readyState)) {
    bootstrap();
} else {
    document.addEventListener('DOMContentLoaded', bootstrap);
}

function bootstrap(): void {

    if (true === environment.production) {
        enableProdMode();
    }

    platformBrowserDynamic().bootstrapModule(
        AppModule,
        [{
            defaultEncapsulation: ViewEncapsulation.None
        }]
    ).then((platformRef: NgModuleRef<AppModule>) => {
        // Initialize the Angular Module
        // get() the UIRouter instance from DI to initialize the router
        const urlService: UrlService = platformRef.injector.get(UIRouter).urlService;

        // Instruct UIRouter to listen to URL changes
        platformRef.injector.get<NgZone>(NgZone).run(() => {
            urlService.listen();
            urlService.sync();
        });
    });
}

Our app.module.ts

import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {DoBootstrap, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {setAngularJSGlobal, UpgradeModule} from '@angular/upgrade/static';
import {MissingTranslationHandler, TranslateModule, TranslateService} from '@ngx-translate/core';
import {UIRouterUpgradeModule} from '@uirouter/angular-hybrid';
import * as angular from 'angular';
import {AngularModalObserver} from 'hybrid/modal/modal.observer';
import {AdvertiserLastTransactionsModalObserver} from 'hybrid/modals/advertiser-last-transactions-modal.observer';
import {CampaignsModalObserver} from 'hybrid/modals/campaigns-modal.observer';
import {GoogleModalObserver} from 'hybrid/modals/google-modal.observer';
import {InvoiceModalObserver} from 'hybrid/modals/invoice-modal.observer';
import {PublisherLastTransactionsModalObserver} from 'hybrid/modals/publisher-last-transactions-modal.observer';
import {TicketsModalObserver} from 'hybrid/modals/tickets-modal.observer';
import {AngularTranslationsObserver} from 'hybrid/translations/translations.observer';
import {DatepickerModule} from 'ngx-bootstrap/datepicker';
import {ModalModule} from 'ngx-bootstrap/modal';
import {PagesAdminModule} from 'pages/admin/admin.a2.module';
import {GdprCleanupModule} from 'pages/publisher/account/gdpr-cleanup/gdpr-cleanup.module';
import {LayoutEventsObserver} from 'scripts/modules/app-layout/layout-events.observer';
import {ElementsExternalLinkModule} from 'scripts/modules/elements/external-link.a2/external-link.module';
import {ElementsPageHeaderModule} from 'scripts/modules/elements/page-header/page-header.module';
import {LocalizationMissingTranslationsService} from 'scripts/modules/localization/missing-translations.service';
import {AdminRoutingModule} from 'src/app/admin/admin-routing.module';
import {AdvertiserModule} from 'src/app/advertiser/advertiser.module';
import {AccountModel} from 'src/app/core/auth/account.model';
import {AuthObserver} from 'src/app/core/auth/auth.observer';
import {AuthService} from 'src/app/core/auth/auth.service';
import {IdentityService} from 'src/app/core/auth/identity.service';
import {TokenModel} from 'src/app/core/auth/token.model';
import {CoreModule} from 'src/app/core/core.module';
import {UserProfileModel} from 'src/app/core/data/backend/user/profile/user-profile.model';
import {AuthInterceptor} from 'src/app/core/interceptors/auth.interceptor';
import {CacheInterceptor} from 'src/app/core/interceptors/cache.interceptor';
import {GetAsPostInterceptor} from 'src/app/core/interceptors/get-as-post.interceptor';
import {GetMultiInterceptor} from 'src/app/core/interceptors/get-multi.interceptor';
import {LocalizationService} from 'src/app/core/localization/localization.service';
import {LeadgenerationModule} from 'src/app/leadgeneration/leadgeneration.module';
import {EnergyNlModule} from 'src/app/publisher/pages/widgets/widgets/energy/energy-nl/energy-nl.module';
import {PublisherModule} from 'src/app/publisher/publisher.module';

setAngularJSGlobal(angular);

@NgModule({
    exports: [
        TranslateModule
    ],
    imports: [
        AdminRoutingModule,
        AdvertiserModule,
        BrowserModule,
        BrowserAnimationsModule,
        CoreModule,
        DatepickerModule.forRoot(),
        ElementsExternalLinkModule,
        ElementsPageHeaderModule,
        GdprCleanupModule,
        HttpClientModule,
        LeadgenerationModule,
        ModalModule.forRoot(),
        PagesAdminModule,
        PublisherModule,
        TranslateModule.forRoot({
            missingTranslationHandler: {
                provide: MissingTranslationHandler,
                useClass: LocalizationMissingTranslationsService
            }
        }),
        UIRouterUpgradeModule.forRoot(),
        UpgradeModule,

        // These modules are needed here during hybrid
        EnergyNlModule, // This one can be removed once the entire widget pages has been migrated
    ],
    // All providers you need in AngularJS
    providers: [
        // These observers need to be here, it's required outside of it's own scope.
        // Angular creates a new instance based on the require module level
        AdvertiserLastTransactionsModalObserver,
        AngularModalObserver,
        AngularTranslationsObserver,
        AuthObserver,
        CampaignsModalObserver,
        GoogleModalObserver,
        InvoiceModalObserver,
        LayoutEventsObserver,
        PublisherLastTransactionsModalObserver,
        TicketsModalObserver,

        // Request interceptors, might be able to move them to the actual modules, but those are generated
        { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: GetAsPostInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: GetMultiInterceptor, multi: true },
    ]
})
export class AppModule implements DoBootstrap {

    constructor(
        private angularTranslationsObserver: AngularTranslationsObserver,
        private identityService: IdentityService,
        private authObserver: AuthObserver,
        private authService: AuthService,
        private localizationService: LocalizationService,
        private translateService: TranslateService,
        private upgrade: UpgradeModule
    ) {}

    ngDoBootstrap(): void {
        this.upgrade.bootstrap(document.body, ['dcApp'], { strictDi: true });

        this.angularTranslationsObserver.languageChange$.subscribe(
            (language: string) => this.localizationService.setLanguage(language)
        );
        this.angularTranslationsObserver.translationsChange$.subscribe(
            (translations: object) => this.setTranslations(translations)
        );
        this.authObserver.accountChange$.subscribe(
            (account: AccountModel) => this.identityService.setAccount(account)
        );
        this.authObserver.tokenChange$.subscribe(
            (token: string) => {
                this.authService.setToken(token);
                let decryptedToken: TokenModel = this.authService.getDecryptedToken();
                this.identityService.setAdmin(Boolean(decryptedToken && decryptedToken.admin));
                this.identityService.setPermissions(decryptedToken.usg);
            }
        );
        this.authObserver.userChange$.subscribe(
            (user: UserProfileModel) => {
                this.identityService.setUser(user);
                this.localizationService.checkLanguageLocale();
            }
        );
    }

    setTranslations(translations: object): void {
        for (const language in translations) {
            if (translations.hasOwnProperty(language)) {
                this.translateService.setTranslation(language, translations[language], true);
            }
        }
    }

}

The admin-routing.module.ts

import {NgModule} from '@angular/core';
import {NgHybridStateDeclaration, UIRouterUpgradeModule} from '@uirouter/angular-hybrid';

const states: NgHybridStateDeclaration[] = [{
    name: 'admin.**',
    url: '/admin',
    loadChildren: () => import('src/app/admin/admin.module')
        .then((result: {AdminModule: any}) => result.AdminModule)
}];

@NgModule({
    imports: [
        UIRouterUpgradeModule.forChild({states: states})
    ],
    exports: [
        UIRouterUpgradeModule
    ],
})
export class AdminRoutingModule {}

And then the admin module

import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MessageCenterModule} from 'src/app/admin/pages/message-center/message-center.module';
import {NewsModule} from 'src/app/admin/pages/news/news.module';
import {SearchModule} from 'src/app/admin/pages/search/search.module';

@NgModule({
    declarations: [],
    imports: [
        CommonModule,
        MessageCenterModule,
        NewsModule,
        SearchModule
    ]
})
export class AdminModule {}

and for example search.module

import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {TranslateModule} from '@ngx-translate/core';
import {ElementsMessagesModule} from 'scripts/modules/elements/messages.a2/messages.module';
import {ElementsNoResultsModule} from 'scripts/modules/elements/no-results.a2/no-results.module';
import {ElementsPageHeaderModule} from 'scripts/modules/elements/page-header/page-header.module';
import {BackendModule} from 'src/app/admin/data/backend/backend.module';
import {ItemModule} from 'src/app/admin/pages/search/item/item.module';
import {SearchRoutingModule} from 'src/app/admin/pages/search/search-routing.module';
import {SearchComponent} from 'src/app/admin/pages/search/search.component';
import {FilterModule} from 'src/app/core/components/filter/filter.module';
import {InputModule} from 'src/app/core/components/filter/input/input.module';
import {PanelModule} from 'src/app/core/components/panel/panel.module';

@NgModule({
    declarations: [SearchComponent],
    imports: [
        BackendModule,
        CommonModule,
        ElementsMessagesModule,
        ElementsNoResultsModule,
        ElementsPageHeaderModule,
        InputModule,
        ItemModule,
        PanelModule,
        SearchRoutingModule,
        TranslateModule,
        FilterModule,
    ],
    exports: [SearchComponent]
})
export class SearchModule { }

with search routing module as you can see not yet lazy loaded, when that will be introduced we would include search-routing module inside the admin module and refer to the search module in the routing module

import {NgModule} from '@angular/core';
import {UIRouterUpgradeModule} from '@uirouter/angular-hybrid';
import {StateDeclaration} from 'hybrid/state.declaration';
import {SearchComponent} from 'src/app/admin/pages/search/search.component';
import {BASIC} from 'src/app/core/auth/permissions/admin.constant';

const states: StateDeclaration[] = [{
    name: 'admin.search',
    url: '/admin/search',
    component: SearchComponent,
    permissions: [BASIC],
    requiresAuth: true
}];

@NgModule({
    imports: [
        UIRouterUpgradeModule.forChild({states: states})
    ],
    exports: [
        UIRouterUpgradeModule
    ],
})
export class SearchRoutingModule {}

Our angularjs module

'use strict';

import angular from 'angular';
// Enable this line and the .run line to enable uiRouter visualizer
// import { visualizer } from '@uirouter/visualizer';

angular.module('dcApp', [
    'dcApp.config', // this needs to be the very first dependency
    'dcApp.publisher.campaigns.modal',

    'dcLayout',
    'dcLibRedirect',
    'dcLibUniqueId',

    /* Pages */
    'dcApp.admin',
    'dcApp.advertiser',
    'dcApp.common',
    'dcApp.leadgeneration',
    'dcApp.publisher'
]);
// Enable this line and the import line to enable uiRouter visualizer
// angular.module('dcApp').run(['$uiRouter', ($uiRouter) => visualizer($uiRouter) ]);

/* Config files */
require('scripts/app-config/app-config.module');
require('scripts/helper/downloadfix');
require('scripts/helper/helper');

/* main module files */
require('scripts/app.controller');
require('hybrid/modal/modal.downgrade');
require('hybrid/cache-service.downgrade');
require('hybrid/modals/campaigns-modal.downgrade');
require('hybrid/modals/invoice-modal.downgrade');
require('hybrid/modals/tickets-modal.downgrade');
require('hybrid/modals/google-modal.downgrade');
require('hybrid/modals/advertiser-last-transactions-modal.downgrade');
require('hybrid/modals/publisher-last-transactions-modal.downgrade');

/* Other main modules */
require('pages/admin/admin.module');
require('pages/advertiser/advertiser.module');
require('pages/common.module');
require('pages/leadgeneration/leadgeneration.module');
require('pages/publisher/publisher.module');
```

and main app.controller
```
'use strict';
import angular               from 'angular';
import $                     from 'jquery';

require('scripts/modules/app-layout/layout.module');

angular.module('dcApp')
    .controller( 'MainController', MainController );

MainController.$inject = [
    '$location',
    '$transitions',
    '$window',
    'AdvertiserFinancialInvoicesInvoiceDetailModal',
    'AdvertiserLastTransactionsModalObserver',
    'AdvertiserTransactionsOverviewDetailModal',
    'AngularModalObserver',
    'AppConfig',
    'AppLayout',
    'AuthAuthenticator',
    'AuthAuthenticatorHelper',
    'AuthIdentity',
    'AuthObserver',
    'CampaignInformationModal',
    'ConnectionsGoogleLoginModal',
    'ElementsMessagesHelper',
    'GoogleModalObserver',
    'InvoiceModalObserver',
    'MediaVerificationModal',
    'PublisherLastTransactionsModalObserver',
    'TicketDetailModal',
    'TicketsModalObserver',
    'TransactionModal'
];

function MainController(
    $location,
    $transitions,
    $window,
    AdvertiserFinancialInvoicesInvoiceDetailModal,
    AdvertiserLastTransactionsModalObserver,
    AdvertiserTransactionsOverviewDetailModal,
    AngularModalObserver,
    AppConfig,
    AppLayout,
    AuthAuthenticator,
    AuthAuthenticatorHelper,
    AuthIdentity,
    AuthObserver,
    CampaignInformationModal,
    ConnectionsGoogleLoginModal,
    ElementsMessagesHelper,
    GoogleModalObserver,
    InvoiceModalObserver,
    MediaVerificationModal,
    PublisherLastTransactionsModalObserver,
    TicketDetailModal,
    TicketsModalObserver,
    TransactionModal
) {

    let $ctrl = this;

    $ctrl.layout = AppLayout;

    $ctrl.$onInit = onInit;

    function onInit() {

        initPage(true);

        $transitions.onError( {}, function( transition ) {
            let transitionError = transition.error();
            if ([4,6].indexOf(transitionError.type) !== -1)
            {
                ElementsMessagesHelper.getInstance('generalError').addMessage( transition.error(), 'error', 'primaryError' );
                AppLayout.hideSplashPage();
                $location.path( '/error' );
            }
        } );

        $transitions.onSuccess({}, function(transition) {
            initPage(transition.from().name !== transition.to().name);
            if ($window.ga) {
                $window.ga('send', 'pageview', { page: $location.url() });
            }
        });

        // No on destroy since the entire app is then gone
        AuthObserver.loginAsChange$.subscribe(function __loginAs(loginAsModel) {
            AuthAuthenticatorHelper.loginAs(
                {
                    id: loginAsModel.account_id,
                    type: loginAsModel.account_type
                },
                loginAsModel.user_id || null,
                loginAsModel.destination || null,
                loginAsModel.sameWindow || null
            );
        });

        AuthObserver.activityChange$.subscribe(AuthAuthenticator.updateLastActivity);
        AuthObserver.logoutExpire$.subscribe(AuthAuthenticator.logoutExpire);
        AuthObserver.updateAccount$.subscribe((account) => AuthIdentity.initAccount(AuthIdentity.getAccountType(), account));

        AngularModalObserver.showMediaVerification$.subscribe((event) => MediaVerificationModal.show(event.mediaId));
        InvoiceModalObserver.openModal$.subscribe(AdvertiserFinancialInvoicesInvoiceDetailModal.show);
        PublisherLastTransactionsModalObserver.openModal$.subscribe((affiliatemarketingId) => TransactionModal.show(affiliatemarketingId));
        AdvertiserLastTransactionsModalObserver.openModal$.subscribe((affiliatemarketingId) => AdvertiserTransactionsOverviewDetailModal.show(affiliatemarketingId));
        TicketsModalObserver.openModal$.subscribe((ticketId) => TicketDetailModal.show(ticketId, false));
        GoogleModalObserver.openModal$.subscribe(() => ConnectionsGoogleLoginModal.show())
    }

    function initPage(pageChange) {
        if (pageChange) {
            $(window).scrollTop(0);
        }

        $ctrl.currentPage = $location.path();
        AppLayout.hideSplashPage();
    }

}


from Angular 9 hybrid with ui router causes Trying to get the AngularJS injector before it being set. How to debug/fix?

No comments:

Post a Comment