The situation:

I am working on a hybrid project with AngularJS as core where the app slowly needs to be upgraded to Angular 9. So the new Angular modules have to be downgraded to be accessable to AngularJS. The AngularJS app consists of multiple modules and submodules. Here just the core(shared) module and the app.module for simplification:

core.ng2.module.ts

@NgModule({
    imports: [
        CommonModule
    ],
    declarations: [
        ModuleBootstrapComponent
    ],
    entryComponents: [
        ModuleBootstrapComponent
    ]
})
export class CoreNg2Module {

    constructor() {
    }

    public ngDoBootstrap(): void {
    }
}

let rootInjectorPromise: Promise<Injector>| null = null;
const getRootInjector = (extraProviders: StaticProvider[]) => {
    if (!rootInjectorPromise) {
        rootInjectorPromise = platformBrowserDynamic(extraProviders)
            .bootstrapModule(AppNg2Module)
            .then(moduleRef => moduleRef.injector);
    }
    return rootInjectorPromise;
};

const bootstrapCoreFn = async (extraProviders: StaticProvider[]) => {
    const rootInjector = await getRootInjector(extraProviders);
    const moduleFactory = await rootInjector.get(Compiler).compileModuleAsync(CoreNg2Module);
    return moduleFactory.create(rootInjector);
};

const downgradedCoreNg2Module = downgradeModule(bootstrapCoreFn);

export const coreNg2ModuleName =
    angular
        .module('core.ng2', [
            downgradedCoreNg2Module
        ])
        .directive('moduleBootstrap',
            downgradeComponent({
                component: ModuleBootstrapComponent,
                downgradedModule: downgradedCoreNg2Module
            }))
        .name;

the app.module.ts

@NgModule({
    imports: [
        BrowserModule,
        CommonModule,
        CoreNg2Module,
    ]
})
export class AppNg2Module {

    constructor() {
    }

    public ngDoBootstrap(): void {
        // Don't use this lifecycle hook since the hybrid app is bootstrapped manually inside Bootstrap.ts.
    }
}


export const appModuleName = angular
    .module('app.ng2', [
        coreNg2ModuleName,
    ]).factory('translateSyncService',
        downgradeInjectable(TranslateSyncService))
    .name;

The current problem:

I downgraded and bootstraped the module according to the official guide: https://angular.io/api/upgrade/static/downgradeModule#downgrading-multiple-modules to have my app.module as 'root' for my TranslateSyncService. Because according to this https://angular.io/guide/upgrade#how-ngupgrade-works singleton services are shared between AngularJS and Angular IF they are provided in 'root'. I need the translate-sync-service to synchronize the state of my Angular components with the AngularJS app.

@Injectable({
    providedIn: 'root'
})
export class TranslateSyncService {

    languageSource = new BehaviorSubject<string>('en');
    usedLanguage$ = this.languageSource.asObservable();

    constructor() {
    }


    changeLanguage(language: string) {
        this.languageSource.next(language);
    }
}

I downgraded the module and service to make it accessable in AngularJS. The problem is that the service is working, but every module gets its own instance of the service instead of a global singleton service. So core module has its own service and every other module I created.

tldr; I end up with a singleton service per module instead of a global one.

My wish: I want to have an Angular service that is a singleton across the downgraded Angular modules and the AngularJS app.

Anyone has experience with that issue?

1

There are 1 best solutions below

0
On

I know this is old but I had the same issue. I found an answer that worked for me (similar situation). We use a common pattern of a downgraded module that we inject into the angular JS app. (there are quite a few examples of this elsewhere).

export const downgradedAngularAppModule = angular.module('downgraded-angular-module', [downgradedAngularModule])
                                                 .service('SomeAngularXService', downgradeInjectable(SomeAngularXService))

and then in angularJS

export const angularJsModule: ng.IModule = angular.module(angularjsAppModule, [list of angularJS modules, downgradedAngularAppModule.name])

While still in hybrid mode, we are bootstrapping angular inside angular JS. In the singleton angular X service ensure you decorate with:

@Injectable(
    { providedIn: 'root' } // this means app wide singleton
)

If you wish to use the same instance of the service that you have in your angularJS app, in components that you have migrated to angular X, rather than importing the module directly like this:

import { SomeAngularXService } from 'SomeAngularXService.service.ts'

and injecting it in your constructor:

constructor(private someAngularXService: SomeAngularXService)

you should instead use the angularJS instance in your constructor

constructor(@Inject('SomeAngularXService') private someAngularXService: any)

You will need to remember to provide it in your app.module.ts

{
    provide: 'SomeAngularXService',
    useFactory: ($injector: any) => $injector.get('SomeAngularXService'),
    deps: ['$injector']
}