nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.63k stars 2.36k forks source link

best practices to share environment variables with lib modules #208

Closed xmlking closed 6 years ago

xmlking commented 6 years ago

in my lib module CoreModule I need to access environment which part of my app myapp

libs/core/src/core.module.ts

import {environment} from "../../../apps/myapp/src/environments/environment";

TSLint is complinting this kind of inport.

looking for guideline how we can share app's environment with lib module.

JefferE commented 5 years ago

Went one better (I think). I just added you as a collaborator on a barebones repo illustrating the issue.

Put in your project config and you should get the issue right away.

SachinShekhar commented 5 years ago

@JefferE Push the commit. Currently, it's on initial commit.

JefferE commented 5 years ago

@SachinShekhar, Sorry, pushed now.

SachinShekhar commented 5 years ago

@JefferE I checked. Your code is correct. Firebase config is getting passed to the library with correct type. But, due to some mysterious reasons, AngularFireModule has problem with it (interestingly, my multiple projects are working fine with the same code).

I strongly believe that the issue is with AngularFireModule's dependencies (I didn't have time to check everything). Good luck debugging it.

After solving the issue, let me know what the issue was. I am interested.

JefferE commented 5 years ago

ok, perhaps a versioning issue somewhere in dependencies.

I'll check it out. Thanks a bunch!

JefferE commented 5 years ago

@SachinShekhar, if you could when you have time, post your nx package.json so I have something to compare to at that basic level?

SachinShekhar commented 5 years ago

@JefferE I tried to debug your code (because issue is interesting). The issue is: AngularFireModule is being initialized before static data is set in FirebaseConfig service. To solve this, lazy-load the module using @wishtack/reactive-component-loader (You need to create a new module in the same library which return a ModuleWithProvider having FirebaseService and AngularFireModule; keep the setting of static data separate). It works with AOT compilation.

Or, to simplify things, just initialize AngularFireModule in app module and remove the initialization from library module.

JefferE commented 5 years ago

@SachinShekhar, ok, I'll give that loader a try.

I tried initializing in the root module long ago and it didn't work (my FirebaseService wouldn't have the config it needed), that's what led me to try passing the config to the shared lib, which made me find this thread.

NgxDev commented 5 years ago

@SachinShekhar Following your example, I've just tried to do the same thing (but with Ngxs instead of Firebase). From my app.module I'm passing { production: true } so the logger plugin (initialized in store.module.ts) should be initialized with disabled: true. Made this stackblitz: https://stackblitz.com/edit/ngxs-app-initializer?file=src%2Fapp%2Fstore%2Fstore.module.ts And it seems that Ngxs & plugins initialize before APP_INITIALIZER runs/resolves:

prev state
next state
APP_INITIALIZER
APP_INITIALIZER Resolved
NGXS Logger shouldn't log anymore (but it does)
payload
prev state
next state
Angular is running in the development mode. Call enableProdMode() to enable the production mode.

Anyway, I shouldn't even see the first 2 lines because nothing should be allowed to happen before APP_INITIALIZER, right? Could it be that the same thing happens for firebase initialization also (initializing wrongly, without the appropriate config), only that that config is "updated" at a later time, when APP_INITIALIZER resolves?

EDIT: by the way, it doesn't work when building for production: Function expressions are not supported in decorators.

JefferE commented 4 years ago

@SachinShekhar - Been a while since I reported here but your suggestion worked ('just initialize AngularFireModule in app module and remove the initialization from library module).

However, I'm having he same issue as @MrCroft above, this whole approach doesn't work when building for production - I get the same 'Function expressions are not supported in decorator' as he is getting.

It doesn't really bother me at the moment as I'm not building for production yet and I'm looking into it but figured I'd mention it as soon as possible.

export class SharedDataAccessFirebaseServiceModule {
  static config(data: ModuleConfig): ModuleWithProviders {
    return {
      ngModule: SharedDataAccessFirebaseServiceModule,
      providers: [
        FirebaseConfigService,
        {
          provide: APP_INITIALIZER,
          multi: true,
          useFactory: (firebaseConfig: FirebaseConfigService) => () => {
            firebaseConfig.setFirebaseConfig(data);
          },
          deps: [FirebaseConfigService]
        }
      ]
    };
  }
}

It doesn't like this part:

          useFactory: (firebaseConfig: FirebaseConfigService) => () => {
            firebaseConfig.setFirebaseConfig(data);
          },
Grymlot commented 4 years ago

I had this same issue and this thread was helpful to give me ideas to try. I have a library module that I'm trying to setup google maps in (@agm/core), which requires you pass the google maps api key in the forRoot method. I found no other way to set that key.

After none of the ideas in this thread worked, I was able to get it working by simply not calling the forRoot method in the library module, and instead calling it in my app module. My app module did not need the AgmCoreModule reference in it's 'imports' but I added it there anyway and did the forRoot call there, where I have my environment variable available. Then, in the library module, I just used 'AgmCoreModule' in the 'imports', without the '.forRoot(...)' part.

jurr90 commented 4 years ago

What about react app, how can I get environments for react libs?

ghost commented 4 years ago

I was following this article: https://indepth.dev/tiny-angular-application-projects-in-nx-workspaces/#extract-an-environments-workspace-library, which creates a separate shared environment lib, and then simply exports the environment constant in index.ts, and imports it where used. E.g. a shared NgRx store lib that cuts across all other NgRx feature stores can have dev tools toggled via a single environment constant.

I guess the injector tokens approach, where the app-specific environment variables are injected in the app app.module.ts, works too.

jurr90 commented 4 years ago

@adamm-monek thanks for the answer, I almost did the same as described in the article :)

ghost commented 2 years ago

Is there a way to 'inject' stuff into modules and use it there, @vsavkin ? I created a stackoverflow question, I'd appreciate an answer on stackoverflow, or here :)

shaifulborhan commented 2 years ago

Imo the best way to share env variables is to do something like this:

import {config} from '../environments/environment';

@NgModule({
    providers: [
        {
            provider: 'someEndpointUrl', useValue: config.someEndpointUrl
        }
    ]
})
class AppModule {
}

Then do this somewhere in your lib:

@Component({...})
class MyComponent {
    constructor(@Inject('someEndpointUrl') endpointUrl: string) {
    }
}
  1. It keeps your libs env independent.
  2. It allows you to build libs once, without knowing the apps they are used by or the env they ran in. In CLI 1.x this isn't a big deal, but it will get more important once we start more aggressively cache the results of lib compilation.

Using @env/ has some advantages though. It allows to use to improve treeshaking in some situations.

Hi,

Using the method @vsavkin suggested, how would I provide the environment var into the service .spec files in the /libs?

I know I can use the following code to solve it but it's definitely not right to import directly from the /apps.

import {environment} from '../../../../../../apps/my-app/src/environments/environment';

TestBed.configureTestingModule({
   imports: [HttpClientTestingModule],
      providers: [
         {
            provide: 'environment', useValue: environment
         }
      ]
});

Thank you.

Falven commented 2 years ago

Imo the best way to share env variables is to do something like this:

import {config} from '../environments/environment';

@NgModule({
    providers: [
        {
            provider: 'someEndpointUrl', useValue: config.someEndpointUrl
        }
    ]
})
class AppModule {
}

Then do this somewhere in your lib:

@Component({...})
class MyComponent {
    constructor(@Inject('someEndpointUrl') endpointUrl: string) {
    }
}
  1. It keeps your libs env independent.
  2. It allows you to build libs once, without knowing the apps they are used by or the env they ran in. In CLI 1.x this isn't a big deal, but it will get more important once we start more aggressively cache the results of lib compilation.

Using @env/ has some advantages though. It allows to use to improve treeshaking in some situations.

@vsavkin

I realize you posted this in 2018, however, what would be your intended method of sharing these environment variables across library modules in NestJs? Does every library module that need access to environment variables need to be a dynamic module in order to take advantage of environment variables this way? What would this do to performance?

Example:

@Module({})
export class MyLibModule {
  static register(providers: Provider[]): DynamicModule {
    return {
      module: MyLibModule ,
      providers: [...providers],
    };
  }
}
const ENV_VALUE_PROVIDERS: ValueProvider[] = [
  {
    provide: 'PRODUCTION',
    useValue: environment.production,
  },
];
@Module({
  providers: ENV_VALUE_PROVIDERS,
  imports: [MyLibModule.register(ENV_VALUE_PROVIDERS)],
})
export class AppModule {}

I do not understand why we are forcing any lib modules to be dynamic to access environment variables if these variables are available at compile-time.

alisissa commented 2 years ago

anyone has a best practices for React? I have a data-access lib which has all redux code related including the async actions to fetch the api.

What would be a good practice to read env variables values from within the data-access lib?

AdditionAddict commented 1 year ago

Reading this thread I still got lost on how to do something like following and what to replace this with

imports: [
    !environment.production ? StoreDevtoolsModule.instrument() : [],
]

I understand you can use providers to pass environment to classes but how to use to determine imports for modules? I'm going to use example here https://github.com/trungk18/angular-spotify

Namely, a web shell that has build-specifics determined by fileReplacements

          "fileReplacements": [
            {
              "replace": "apps/angular-spotify/src/environments/environment.ts",
              "with": "apps/angular-spotify/src/environments/environment.prod.ts"
            },
            {
              "replace": "libs/web/shell/feature/src/lib/build-specifics/index.ts", <---------------------------
              "with": "libs/web/shell/feature/src/lib/build-specifics/index.prod.ts"
            }
          ],
github-actions[bot] commented 1 year ago

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.