49

I've created company internal library using angualr2-library yeoman generator.

Some of the angular services are using environment variables in our current applications (api endpoints are changed on each env). I was wondering what is the best way to pass the current environment object to the angular2 library services?

Mohammed Osman
  • 3,232
  • 1
  • 23
  • 21
Dima Grossman
  • 2,570
  • 2
  • 18
  • 24

6 Answers6

74

In case you still searching for a solution, here's how I accomplished something simliar to what you were asking for (using Angular 4.2.4).

In your AppModule (or the place where you want to import your library), call the forRoot() method on your LibraryModule. With the help of this function, you can pass any config values to you library, e.g. your app's environment.

import {environment} from "../environments/environment";
...

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        ...
        LibraryModule.forRoot(environment)
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

You LibraryModule of course needs to offer the forRoot() method. In the providers array you then can provide services, values and more. In this case, 'env' acts as the token holding the given environment object for simplicity. You can also use an InjectionToken instead.

@NgModule({
    ...
})
export class LibraryModule {

    public static forRoot(environment: any): ModuleWithProviders {

        return {
            ngModule: LibraryModule,
            providers: [
                ImageService,
                {
                    provide: 'env', // you can also use InjectionToken
                    useValue: environment
                }
            ]
        };
    }
}

Since the token env is now provided by your LibraryModule, you can inject it in all of its child services or components.

@Injectable()
export class ImageService {

    constructor(private http: Http, @Inject('env') private env) {
    }

    load(): Observable<any> {
        // assume apiUrl exists in you app's environment:
        return this.http.get(`${this.env.apiUrl}/images`)
            .map(res => res.json());
    }

}

I hope that helps!

pschild
  • 2,578
  • 1
  • 17
  • 26
  • This is nice idea but in my case my appModule is also use environment variable and it dependes on if my Library changes something? But both are not in sync. is there a was it can be resolved – Aniruddha Das Jul 24 '18 at 15:50
  • What if I want to change `environment` value and want to display changed environment values in my appModule? Can I do that. Because when LibraryModule changes environment value outside app module, it does not reflect in app module @Günter Zöchbauer – Aniruddha Das Jul 24 '18 at 18:35
  • @pschild i did exactly what you have said, i get the environment configuration in the providers array, but when inject it in the service the value is undefined. Do you have any idea why? – bsh Oct 25 '18 at 12:59
  • @bsh It's hard to say what's going on. Could you create a simple Demo with [stackblitz](https://stackblitz.com/) showing the problem? Which version of Angular do you use? – pschild Oct 25 '18 at 18:10
  • Instead of forRoot()-method I use InjectionToken for each paramater what I need in library. then main app will provide them. – Janne Harju Feb 13 '19 at 11:31
  • 1
    @pschild then how would you perform the conditional loading of development module in library like !environment.production ? StoreDevtoolsModule.instrument() : [] – sameer Jun 09 '19 at 05:36
28

I found an alternative solution to this problem in a GitHub issue. The solution in the GitHub thread has a bug (a typo), so I'm including the fixed solution here:

To begin, add a provider to your top-level AppModule that contains your environment file.

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

@NgModule({
  providers: [
    {provide: 'environment', useValue: environment}
  ]
  // object properties omitted for brevity...
})
class AppModule {}

Finally, make use of an Inject decorator to include your environment file in any other part of your application you wish (library or otherwise):

@Component({
  // object properties omitted for brevity
})
class MyComponent {

  private environment

  constructor(
    @Inject('environment')
    environment
  ) {
    this.environment = environment
  }

}
ant
  • 831
  • 12
  • 18
9

A complete working solution using Angular 11. Assuming you generated a library named library1 using the command ng generate library library1.

AppModule file

// Your other imports ...
import { Library1Module } from 'library1';
import { environment } from 'src/environments/environment';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,

    // Pass the data you want to share with your library. Here we will pass 'apiUrl'. 
    Library1Module.forRoot({ apiUrl: environment.apiUrl }),

    AppRoutingModule
  ]
})
export class AppModule { }

library1.module.ts ('Library1' module main file):

import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Configurations } from './configurations';
import { Library1Component } from './library1.component';

@NgModule({
  declarations: [Library1Component],
  imports: [CommonModule],
  exports: [Library1Component]
})
export class Library1Module {

  // Create this static method in the library module.
  public static forRoot(config: Configurations): ModuleWithProviders<Library1Module> {

    return {
      ngModule: Library1Module,
      providers: [
        { provide: Configurations, useValue: config }
      ]
    };
  }

}

configurations.ts (Declare this class inside 'projects\library1\src\lib' folder). Declare all the configurations you need to get from the 'environment' file.

export class Configurations {
  public apiUrl: string;

  constructor() {
    this.apiUrl = '';
  }

}

library1.service.ts ('Library1Service' will receive the config object).

import { Injectable, Optional } from '@angular/core';
import { Configurations } from './configurations';

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

  private _apiUrl = 'No value';

  constructor(@Optional() config?: Configurations) {
    if (config) {
      this._apiUrl = config.apiUrl;
    }
  }

  get apiUrl() {
    return this._apiUrl;
  }

}

library1.component.ts (Library1 Component that uses the configuration data).

import { Component } from '@angular/core';
import { Library1Service } from '../public-api';

@Component({
  selector: 'lib-library1',
  template: `
    <p>
      This component inside "library1" library and reads the values from "environment.ts" file.
    </p>

    <h1>API URL : {{apiUrl}}</h1>`
})
export class Library1Component {
  apiUrl = '';

  constructor(library1Service: Library1Service) {
    this.apiUrl = library1Service.apiUrl;
  }
}

app.component.ts (Confirm that the URL already passed to your library by using the library1 component).

.......
<lib-library1></lib-library1>
.......
Mohammed Osman
  • 3,232
  • 1
  • 23
  • 21
4

There is a Better Way I Think from here

I thought you guys would find this useful: Using Abstract Classes As Dependency-Injection Tokens For Swappable Behaviors

I have 3 Angular apps in an Nx project that share some common environment variables. A public site, a private portal, and an admin app. In my libs, I am using an abstract Environment class that defines common environment variables.

export abstract class Environment {
  abstract readonly production: boolean;
  abstract readonly appUrls: {
    readonly public: string;
    readonly portal: string;
    readonly admin: string;
  };
}

Then I changed the 3 environment.ts files to be as follows.

import { Environment } from '@my-lib-prefix/common';

class EnvironmentImpl implements Environment {
  production = false;
  appUrls = {
    public: 'http://localhost:4200',
    portal: 'http://localhost:4201',
    admin: 'http://localhost:4202'
  };
}

export const environment = new EnvironmentImpl(); The environment.prod.ts would of course be symmetric to the dev environment.ts. Then I provide the respective environment dependency, in the root app.module.ts for each of the Angular apps.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { Environment } from '@my-lib-prefix/common';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [{ provide: Environment, useValue: environment }],
  bootstrap: [AppComponent]
})
export class AppModule {}

Now any component can inject environment dependencies in a clean, and common manner.

import { Component } from '@angular/core';
import { Environment } from '@my-lib-prefix/common';

@Component({
  selector: 'my-login',
  templateUrl: './my-login.component.html'
})
export class MyLoginComponent {
  constructor(private env: Environment) {}
}

It enforces each environment.ts to implement the "common" environment variables defined in the abstract class. Also, each respective EnvironmentImpl can be extended with their own specific environment variables specific to the app. This approach seems very flexible and clean. Cheers! ^_^

Masoud Bimmar
  • 8,635
  • 4
  • 26
  • 32
3

If you're trying to pass the environment variable for firebase, simply use this in your library:

@NgModule({
  declarations: [MyAngularComponent],
  exports: [MyAngularComponent],
  imports: [
    AngularFireModule,
    AngularFirestoreModule,
    CommonModule
  ]
})
export class MyAngularModule {

  public static forRoot(firebaseConfig: FirebaseOptions): ModuleWithProviders<MyAngularModule> {

    return {
      ngModule: MyAngularModule,
      providers: [
        { provide: FIREBASE_OPTIONS, useValue: firebaseConfig }
      ]
    }
  }
}

And import it just like AngularFire...

MyAngularModule.forRoot(environment.firebase)

From this post: pass angularFire config imported in library using forRoot

Jonathan
  • 2,697
  • 2
  • 32
  • 64
-3

If You are still looking for the answer. In the current version i.e. Angular > 6, you don't have to do anything.

The angular-cli commands "ng build --prod (for Production)& ng build (for Development)" will take care of it for you.

Example: If you are running the project in development environment, all the variables are captured from src/environments/environment.ts. In your component library project just import "import { environment } from 'environments/environment';" (please make sure about the path) will takecare of the environment depending upon the angular-cli build command.

  • This didn't work for me. When I tried doing a build, I got an error: "File '/environment.ts' is not under 'rootDir' – erin Oct 15 '18 at 19:54
  • 1
    I think this will work only when there is no projects folder and only src folder with your code. Then compiler will found correct environment file. But if you use environment/environment import under projects directory it will not take projects own environment file rather than environment file from src folder. I fix this situation by using tsconfig paths configuration and added @environment path to 'projects/MyProject/src/environment' – Janne Harju Feb 13 '19 at 11:29