ui-router / angular-hybrid

Upgrade an ng1 UI-Router app to a ng1+ng2 hybrid using ng-upgrade
MIT License
167 stars 75 forks source link

Trying to get the AngularJS injector before it being set #438

Closed SamanthaAdrichem closed 4 years ago

SamanthaAdrichem commented 4 years ago

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!

[EDIT]: Running it on ng-serve with only minimal webpack config (loading po files and such), causes the same issue.

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();
    }

}
pcapon commented 4 years ago

I have the same error, did you find a work around ?

SamanthaAdrichem commented 4 years ago

No, i gave up after not getting an answer on stack overflow either. You could check the three linked issues. Hope that solves it for you.

SamanthaAdrichem commented 4 years ago

@pcapon I've finally resolved it. The mistake on our side was that for some reason we're exporting the UIRouterUpgradeModule. That's a big no-no

exports: [ UIRouterUpgradeModule ],

If you're still stuck and can provide a demo repository, i'm glad to help! Otherwise compare your repository with their demo or mine

@oobleck, thanks for the thumbsdown ;) But in my defence, i'd been working on it for over a week and there was no reply anywhere. Been contacting people on a lot of sides.

oobleck commented 4 years ago

@SamanthaAdrichem it was meant as a sympathetic gesture. 😊

Your shared links were actually very helpful

aroop commented 4 years ago

Thanks @SamanthaAdrichem. I was also running into this issue and I did the same mistake as you

SamanthaAdrichem commented 4 years ago

oobleck Hehe no problem.

And aroop, your welcome.

I actually managed to break it again today, for people finding this issue. You cannot inject the TransitionService in the app.module.ts, you'll get the same error.

I was trying to add a loader for lazy loaded childs, well the solution is do that in the angular js main controller until hybrid is gone