transloco icon indicating copy to clipboard operation
transloco copied to clipboard

Module Federation

Open yamidvo opened this issue 3 years ago • 6 comments

Has the team thought of including some way to work with Module Federation? I have tried using scopes but have not been able to get it to work.

yamidvo avatar Aug 18 '21 19:08 yamidvo

Basically the issue is with providedIn: "root" in TranslocoService. In federated modules Shell and any federated module creates a separate injector root. And even if @ngneat/transloco library is declared as a shared lib in a webpack.config for the federation plugin, there still will be two (or more) different instances of TranslocoService in runtime - one for a shell and a one per federated module. The general approach should be avoid of using providedIn in libraries, instead use SomeModule.forRoot() to return a set of providers. That's how it could be fixed here. Probably a bit later I'll create a PR for it. We'll see if I could avoid a breaking change. @NetanelBasal What do you think?

statyan avatar Sep 01 '21 18:09 statyan

I'm gonna chime in.

I don't think the right solution is to change Transloco and stop it from using providedIn which is the defacto option from Angular as it supports Tree-Shaking.

We shouldn't be trying to change a ThirdParty lib and how it works simply for Module Federation.

Now, what you could do instead, is create a local library in your repo that uses an InjectionToken that registers TranslocoService, then share that local library with your shell and remote.

It's easy to do if you're using Nx.

Also, are you certain that your are getting two TranslocoService instances?

What's the exact problem that's occuring with the application?

If you are not sharing rxjs and the problem is that Observables aren't firing, then you should share rxjs

Coly010 avatar Sep 01 '21 22:09 Coly010

I'll create an example to showcase it. I'm sure there were two different instances. And if I remove providedIn: root from service metadata and add provider for shell only - that works. For my case I have a working workaround, but going back to what you've said: I think, library must not "decide" on what level its services will be provided. What if I need translation support only for one lazy loaded module, for example? Why should I be forced to provide service in root ? That's not the responsibility of library to decide such kind of things. That's basically not about changing the lib to support federation, rather to change the lib, removing the responsibility which it should not handle. And then it would work for any described scenario. I completely agree with you we should not change 3rd party for a specific case. But here we have, I would say, an opposite: when 3rd party lib trying to change the application in its own way which is not the right thing to do.

statyan avatar Sep 01 '21 22:09 statyan

I'm not quite sure you fully understand providedIn: 'root', or perhaps I'm not understanding you correctly.

providedIn: 'root' will only set the Provider at the root level of the Injection Tree, when the lazy loaded module has been loaded.

If that lazy loaded module is the only place it is being used, then there's no issue.

If that InjectionToken already exists at root (from a previously loaded module), then the instance already in the root of the Tree is used.

This actually prevents multiple instances from being added to different levels of the InjectionTree which would actually create even more adverse effects and difficult to solve bugs for a lot of applications.

Coly010 avatar Sep 01 '21 22:09 Coly010

OK, let me describe it in another way. I understand the way providedIn root works and anyway you've already described it in details so let's start from that point.

Let's imagine: I have an app with a lazy-loaded module and a 3rd-party lib. I use 3rd-party lib only in that lazy-loaded module. I import lib module in lazy-loaded module and then I navigate to that module - service from lib will be provided in root injector and then singleton instance will be created.

The question is - what if I don't want to keep that service in memory after module destruction ? If 3rd-party lib developer used providedIn: root there is no easy way for me to achieve that. That's why I'm saying 3rd party lib should not "decide" whether it should be a root injector or a module injector. If library relies on some services to be provided in root - library module should expose .forRoot() method to register the module. (forRoot() method name is not really good but it's some kind of standard already... )

I guess that's easy to imagine some examples: 3rdparty lib patches console methods and never un-patches until application destroy etc.

statyan avatar Sep 02 '21 00:09 statyan

Both of you have some reasonable arguments. I was testing module federation by myself just for fun and I encountered an issue with ngx-translate so I simply start to look for something which could provide me with a better approach. I ended here 😄

But my architectural idea at the beginning was the same as @Coly010 provided as a workaround. From what I know, translation files could be a real mess when the application grows over years. Therefore I wanted to keep all translations in one place and just bundle it properly so I can reuse a lot of them.

I would love if anybody provides an idea of how this should be solved. Also, I think this topic is going to be very hot in near future.

Vevl avatar Apr 04 '22 20:04 Vevl