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: inject() must be called from an injection context #169

Open
deepa-7k opened this issue Apr 19, 2022 · 15 comments
Open

Error: inject() must be called from an injection context #169

deepa-7k opened this issue Apr 19, 2022 · 15 comments

Comments

@deepa-7k
Copy link

deepa-7k commented Apr 19, 2022

We get the below error when trying to call a microfrontend. The issue occurs only at times and fails to load the MFE

main.52b1fee5a0c17f23f7ca.js:1 ERROR Error: Uncaught (in promise): Error: inject() must be called from an injection context
Error: inject() must be called from an injection context
at _r (remoteEntry.js:471:169)
at wr (remoteEntry.js:471:342)
at Module.Ec (remoteEntry.js:902:157)
at po.e.ɵfac [as factory] (934.c221672f75e97217c5b8.js:1:7157)
at _t (main.52b1fee5a0c17f23f7ca.js:1:142547)
at main.52b1fee5a0c17f23f7ca.js:1:192374
at Wb (main.52b1fee5a0c17f23f7ca.js:1:192445)
at gh.create (main.52b1fee5a0c17f23f7ca.js:1:276234)
at py.createComponent (main.52b1fee5a0c17f23f7ca.js:1:253831)
at h. (405.627f7ca446d7f03d3443.js:1:6554)
at rn (polyfills.6a402a4b8539d95a42ad.js:1:1740824)
at polyfills.6a402a4b8539d95a42ad.js:1:1739784
at R (405.627f7ca446d7f03d3443.js:1:95547)
at ln.invoke (polyfills.6a402a4b8539d95a42ad.js:1:1730740)
at Object.onInvoke (main.52b1fee5a0c17f23f7ca.js:1:304055)
at ln.invoke (polyfills.6a402a4b8539d95a42ad.js:1:1730674)
at Ln.run (polyfills.6a402a4b8539d95a42ad.js:1:1725789)
at polyfills.6a402a4b8539d95a42ad.js:1:1741725
at ln.invokeTask (polyfills.6a402a4b8539d95a42ad.js:1:1731392)
at Object.onInvokeTask (main.52b1fee5a0c17f23f7ca.js:1:303871)
Tv @ main.52b1fee5a0c17f23f7ca.js:1

We tried giving the exact version of Angular packages required in our webpack configs both in shell and child applications and also tried using singleton but none of them has solved the issue. Please advise on what can be done or what could be the issue here
image

@vugar005
Copy link

I covered this in my blog. Check this out: https://medium.com/p/258f331bc11e

@deepa-7k
Copy link
Author

We tried the same thing mentioned in the blog in the config file but that does not solve the issue
image
Should sharedMappings.getPlugin() be removed?

@vugar005
Copy link

@deepa-7k. It happens when you load Angular more than once. In other words, you load different versions of Angular. Checkout this config from https://github.com/vugar005/youtube-webapp-lerna/blob/master/likes-app/src/bootstrap.ts

@manfredsteyer
Copy link
Contributor

As @vugar005 says. You are very likely using different (perhaps only slightly different) versions of Angular (due to package.json or package-lock.json)

@deepa-7k
Copy link
Author

Thank you for the response. We will check the config which you have shared. Also just wanted to know, if this inject error occurs due to different versions of Angular packages, will it occur every time we try to load the MFE? What we face is something that occurs very randomly and mostly not reproducible with manual testing.

@gobedson
Copy link

hello i'm having the same issue using nx repo with angular/core 12.2.16

core.js:6498 ERROR Error: Uncaught (in promise): Error: inject() must be called from an injection context
Error: inject() must be called from an injection context
at injectInjectorOnly (core.js:4764:15)
at Module.ɵɵinject (core.js:4774:12)
at Object.RouterModule_Factory [as factory] (router.js:6002:102)
at R3Injector.hydrate (core.js:11457:35)
at R3Injector.get (core.js:11276:33)
at core.js:11314:55
at Set.forEach ()
at R3Injector._resolveInjectorDefTypes (core.js:11314:31)
at new NgModuleRef$1 (core.js:25345:26)
at NgModuleFactory$1.create (core.js:25399:16)
at resolvePromise (zone.js:1211:1)
at resolvePromise (zone.js:1165:1)
at zone.js:1278:1
at _ZoneDelegate.invokeTask (zone.js:406:1)
at Object.onInvokeTask (core.js:28679:33)
at _ZoneDelegate.invokeTask (zone.js:405:1)
at Zone.runTask (zone.js:178:1)
at drainMicroTaskQueue (zone.js:585:1)

@vugar005
Copy link

@deepa-7k yes it would happen whenever a new Angular app is going to be bootstrapped.

@jure123
Copy link

jure123 commented May 25, 2022

If I understand correctly: if we want to use some shared angular lib that calls inject() funcion there is NO WAY we can run multiple different versions of Angular (like explained in this article https://www.angulararchitects.io/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/). In that case all micro frontend apps must be build on the same Angual version? Is that right @vugar005, @manfredsteyer?

My example: this error Error: NG0203: inject() must be called from an injection context happened to me when I tried to use shared angular library @swimlane/ngx-datatable in angular 13 shell app and in angular 12 remote app. I setup both apps according to Manfred Steyer's "multiple framework versions" article mentioned above. If I only use core angular libraries (different versions for shell and remote app) it works fine. But when I add ngx-datatable shared libary I get that inject() error. Is there no solution for this problem (allowing to use different angular versions)?

UPDATE:
I have fixed my problem by removing @swimlane/ngx-datatable from shared module (in module federation config for my angular 12 remote app). This has caused that the remote app is using a separate instance of module, which is apparently linked to angular 12 core libraries and works fine. Disadvantage is that ngx-datatable module must be bundled and downloaded with every remote app that is running different version of angular than shell app.

UPDATE 2:
I have found out this can be solved more elegantly by using shareKey option in ModuleFederationPlugin (https://webpack.js.org/plugins/module-federation-plugin/#sharekey). For each shared library that is causing conflicts we can define different shareKey value for each angular version. So module federation will only instantiate one instance per angular version, which will still be quite efficient.

I have tested by using this configuation in wepack.config.js under ModuleFederationPlugin:

Config for host app (running angular 13):

shared: share({
  '@angular/core': { shareKey: '@angular/core for angular 13', requiredVersion: 'auto', singleton: true },
   ...
  '@swimlane/ngx-datatable': { shareKey: '@swimlane/ngx-datatable for angular 13', requiredVersion: 'auto', singleton: true }
})

Config for remote app (running angular 12):

shared: share({
  '@angular/core': { shareKey: '@angular/core for angular 12', requiredVersion: 'auto', singleton: true },
   ...
  '@swimlane/ngx-datatable': { shareKey: '@swimlane/ngx-datatable for angular 12', requiredVersion: 'auto', singleton: true }
})

With this configuration, host app downloads ngx-datatable module, and also remote app downloads it and use it's own instance. Works brilliant. BTW: I also tried to use shareScope option instead of shareKey but it didn't work (it loaded the same module with same shareScope twice which is not as expected; maybe I don't understand what shareScope does, or maybe this is related to shareScope bug - webpack/webpack#15769).

@ArgV04
Copy link

ArgV04 commented Jul 7, 2023

Hi jure123 ... can you maybe explain a bit more of steps how you solved multiple angular (core) versions at the same time? My shell is running for example with A16 and my MFEs are under A15. So I am getting thus mentioned error above Error: inject() must be called from an injection context ... is there a way with shareKey or shareScope from webpack only to get this working?

@jure123
Copy link

jure123 commented Jul 8, 2023

So I am getting thus mentioned error above Error: inject() must be called from an injection context ... is there a way with shareKey or shareScope from webpack only to get this

@ArgV04, my example mentioned two comments above solved my problem. I have posted config for host/shell app that is running Angular 13 and useing ngx-datatable 13; and config for remote apps that are running on Angular 12 and using ngx-datatable 12.

You shall probably use similar config, but instead of ngx-datatable (that was causing the issue in my project) you should define library or more libs that are causing inject() error (I think you can find it in error stack trace, but I forgot). If you can't find the libs you can try to add to both configs (or only remote config maybe) all libs that each project is using. Make sure shareKey has different value in shell and remote config. If you put in shell config all libs that shell is using, and in remote config all libs remote is using, and define different shareKey values, then I believe it should work, because remote federation will make sure that each project only uses their own libs.

I haven't tested this in later Angular versions, I hope they don't cause some additional issues.

@ArgV04
Copy link

ArgV04 commented Jul 10, 2023

Yeah I tried your config ... and here is an error log output for example :) ... so I think shareKey is maybe working somehow but Angular uses some globalState dependencies, liked mentioned here. So just wondering how this was working on your side, when trying to initialize multiple angular/core, zone dependencies trying to setup multiple global states at runtime ...

image

@jure123
Copy link

jure123 commented Jul 10, 2023

From your error stack trace it seems there is issue with RouterModule. Did you try to use "solution 3" mentioned here (the article you already posted).

mfe1 webpack config
shared: {
"@angular/core": {requiredVersion: '13.1.1'},
"@angular/common": {requiredVersion: '13.1.1'},
"@angular/router": {requiredVersion: '13.1.1'},
"@angular/common/http": {requiredVersion: '13.1.1'}
}

I guess in your case shell config should have requiredVersion 16, remote should have 15. Apparently in my case I haven't had this issue because I didn't use conficting providers from @angular/router, or maybe there was not conflict between Angular 12 and 13 that I used.

Our team found these kind of version conflicts too risky (and unpredictable) for the serious long term use of module federation for larger projects (unless we could agree that we always updated shell and all remotes to the same Angular version, which is not possible for larger long term projects).

@digaus
Copy link

digaus commented Mar 13, 2024

I am currently also evaluating using module federation for our products.

A simple test trying to use an mfe with angular 16 in a shell with angular 17 fails with this mentioned error.

Is this simply not possible? Our intention is to be able to update our products gradually to new versions which would not be possible if we cannot get different versions to work with each other.

@jure123
Have you found other approaches using different versions for large projects?

@jure123
Copy link

jure123 commented Mar 13, 2024

@digaus, as I mentioned two comments above, I think that possiblilty of different Angular versions conflict is too unpredictable and too risky to use MFE concept in large-scale long term projects. The risk is not only with differnt versions of Angular core libraries, but also with different versions of other 3rd party UI libraries that may cause incompatible JS or even CSS.

In our large project we have decided to develop different custom approach that we called "Multi-SPA", where different MFE apps are built separately (like standard SPA), but not with header, footer and navigation menu, only their content part. Then we built a special "Layout app" that is loaded in each SPA (as a single JS file), which renders header/footer/menu around the SPA content. Each SPA is deployed in a separate folder (/mfe1, /mfe2, ...). When user jumps from one MFE to another the browser it's similar like your would jump from one web site to another: the first SPA is destroyed from browser DOM completely (HTML/JS/CSS), and the new MFE app is rendered from stratch. But both load the same Layout script that renders the same Layout, so the user experiences seprate apps to look and feel as one portal.

The advantage of our approach is absolutely no risk of conflicts between MFE apps (we tested with Angular 14,15,16, Angular Universal, React, Vue), and each SPA can use it's own 3rd party libs without in any versions.

The down side is that switching MFE apps takes a performance hit, as all JS bundles have to be loaded and parsed from stracth. But in practice, only the first load takes a bit more time, after that browser loads JS/CSS from disk or memory cache and it works quite fast (although not as smootly as in the classic MFE shell-remote concept).

We use this approach for CRM portal which is assembled with many separate SPAs that load the same "Layout script", and we are very pleased with the solution. However it took quite some investigations and custom solutions how to communicate between SPA and our Layout app (who controls user authentication, how navigation menu causes route change when jumping pages inside one SPA or jumping to different SPA, etc.). No simple MFE solution in the world yet :)

@angelo-v
Copy link

We are getting this error also when using native-federation with webcomponents. While we thought this is the most robust / independent variant, the following situation leads to the error:

  • use 2 different angular versions in both shell and mfe
  • use the same version of an angular library (e.g. primeng) in both shell and mfe

In this case, both versions of angular will be loaded (as expected). Also only a single version of primeng will be loaded (shared, since the version is the same).

But now primeng tries to call inject, in both shell and mfe. One of those will fail, since they target different versions of angular and different injection context.

To solve this it would be helpful to be able to couple the sharing of angular and a lib like primeng: Share the lib version if and only if the angular version is also shared. Use a separate bundle of the lib, even if the version is the same, if the angular version is not shared. While in module federation this could be solved with a share key, if I understand the above comment correctly, I do not see any option for this in native federation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants