[typescript] Define global constants

In Angular 1.x you can define constants like this:

angular.module('mainApp.config', [])
    .constant('API_ENDPOINT', 'http://127.0.0.1:6666/api/')

What would be the equivalent in Angular (with TypeScript)?

I just don't want to repeat the API base url over and over again in all my services.

This question is related to typescript angular

The answer is


This is my recent experience with this scenario:

  • @angular/cli: 1.0.0
  • node: 6.10.2
  • @angular/core: 4.0.0

I've followed the official and updated docs here:

https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#dependency-injection-tokens

Seems OpaqueToken is now deprecated and we must use InjectionToken, so these are my files running like a charm:

app-config.interface.ts

export interface IAppConfig {

  STORE_KEY: string;

}

app-config.constants.ts

import { InjectionToken } from "@angular/core";
import { IAppConfig } from "./app-config.interface";

export const APP_DI_CONFIG: IAppConfig = {

  STORE_KEY: 'l@_list@'

};

export let APP_CONFIG = new InjectionToken< IAppConfig >( 'app.config' );

app.module.ts

import { APP_CONFIG, APP_DI_CONFIG } from "./app-config/app-config.constants";

@NgModule( {
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    ...,
    {
      provide: APP_CONFIG,
      useValue: APP_DI_CONFIG
    }
  ],
  bootstrap: [ ... ]
} )
export class AppModule {}

my-service.service.ts

  constructor( ...,
               @Inject( APP_CONFIG ) private config: IAppConfig) {

    console.log("This is the App's Key: ", this.config.STORE_KEY);
    //> This is the App's Key:  l@_list@

  }

The result is clean and there's no warnings on console thank's to recent comment of John Papa in this issue:

https://github.com/angular/angular-cli/issues/2034

The key was implement in a different file the interface.


In Angular2, you have the following provide definition, which allows you to setup different kinds of dependencies:

provide(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}

Comparing to Angular 1

app.service in Angular1 is equivalent to useClass in Angular2.

app.factory in Angular1 is equivalent to useFactory in Angular2.

app.constant and app.value has been simplified to useValue with less constraints. i.e. there is no config block anymore.

app.provider - There is no equivalent in Angular 2.

Examples

To setup with the root injector:

bootstrap(AppComponent,[provide(API_ENDPOINT, { useValue='http://127.0.0.1:6666/api/' })]);

Or setup with your component's injector:

providers: [provide(API_ENDPOINT, { useValue: 'http://127.0.0.1:6666/api/'})]

provide is short hand for:

var injectorValue = Injector.resolveAndCreate([
  new Provider(API_ENDPOINT, { useValue: 'http://127.0.0.1:6666/api/'})
]);

With the injector, getting the value is easy:

var endpoint = injectorValue.get(API_ENDPOINT);

One approach for Angular4 would be defining a constant at module level:

const api_endpoint = 'http://127.0.0.1:6666/api/';

@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [
    MessageService,
    {provide: 'API_ENDPOINT', useValue: api_endpoint}
  ]
})
export class AppModule {
}

Then, in your service:

import {Injectable, Inject} from '@angular/core';

@Injectable()
export class MessageService {

    constructor(private http: Http, 
      @Inject('API_ENDPOINT') private api_endpoint: string) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(this.api_endpoint+'/messages')
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    }

    private parseData(data): Message {
        return new Message(data);
    }
}

I have another way to define global constants. Because if we defined in ts file, if build in production mode it is not easy to find constants to change value.

export class SettingService  {

  constructor(private http: HttpClient) {

  }

  public getJSON(file): Observable<any> {
      return this.http.get("./assets/configs/" + file + ".json");
  }
  public getSetting(){
      // use setting here
  }
}

In app folder, i add folder configs/setting.json

Content in setting.json

{
    "baseUrl": "http://localhost:52555"
}

In app module add APP_INITIALIZER

   {
      provide: APP_INITIALIZER,
      useFactory: (setting: SettingService) => function() {return setting.getSetting()},
      deps: [SettingService],
      multi: true
    }

with this way, I can change value in json file easier. I also use this way for constant error/warning messages.


AngularJS's module.constant does not define a constant in the standard sense.

While it stands on its own as a provider registration mechanism, it is best understood in the context of the related module.value ($provide.value) function. The official documentation states the use case clearly:

Register a value service with the $injector, such as a string, a number, an array, an object or a function. This is short for registering a service where its provider's $get property is a factory function that takes no arguments and returns the value service. That also means it is not possible to inject other services into a value service.

Compare this to the documentation for module.constant ($provide.constant) which also clearly states the use case (emphasis mine):

Register a constant service with the $injector, such as a string, a number, an array, an object or a function. Like the value, it is not possible to inject other services into a constant. But unlike value, a constant can be injected into a module configuration function (see angular.Module) and it cannot be overridden by an AngularJS decorator.

Therefore, the AngularJS constant function does not provide a constant in the commonly understood meaning of the term in the field.

That said the restrictions placed on the provided object, together with its earlier availability via the $injector, clearly suggests that the name is used by analogy.

If you wanted an actual constant in an AngularJS application, you would "provide" one the same way you would in any JavaScript program which is

export const p = 3.14159265;

In Angular 2, the same technique is applicable.

Angular 2 applications do not have a configuration phase in the same sense as AngularJS applications. Furthermore, there is no service decorator mechanism (AngularJS Decorator) but this is not particularly surprising given how different they are from each other.

The example of

angular
  .module('mainApp.config', [])
  .constant('API_ENDPOINT', 'http://127.0.0.1:6666/api/');

is vaguely arbitrary and slightly off-putting because $provide.constant is being used to specify an object that is incidentally also a constant. You might as well have written

export const apiEndpoint = 'http://127.0.0.1:6666/api/';

for all either can change.

Now the argument for testability, mocking the constant, is diminished because it literally does not change.

One does not mock p.

Of course your application specific semantics might be that your endpoint could change, or your API might have a non-transparent failover mechanism, so it would be reasonable for the API endpoint to change under certain circumstances.

But in that case, providing it as a string literal representation of a single URL to the constant function would not have worked.

A better argument, and likely one more aligned with the reason for the existence of the AngularJS $provide.constant function is that, when AngularJS was introduced, JavaScript had no standard module concept. In that case, globals would be used to share values, mutable or immutable, and using globals is problematic.

That said, providing something like this through a framework increases coupling to that framework. It also mixes Angular specific logic with logic that would work in any other system.

This is not to say it is a wrong or harmful approach, but personally, if I want a constant in an Angular 2 application, I will write

export const p = 3.14159265;

just as I would have were I using AngularJS.

The more things change...


Updated for Angular 4+

Now we can simply use environments file which angular provide default if your project is generated via angular-cli.

for example

In your environments folder create following files

  • environment.prod.ts
  • environment.qa.ts
  • environment.dev.ts

and each file can hold related code changes such as:

  • environment.prod.ts

    export const environment = {
         production: true,
         apiHost: 'https://api.somedomain.com/prod/v1/',
         CONSUMER_KEY: 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
    
  • environment.qa.ts

    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/qa/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
    
  • environment.dev.ts

    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/dev/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
    

Use-case in application

You can import environments into any file such as services clientUtilServices.ts

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

getHostURL(): string {
    return environment.apiHost;
  }

Use-case in build

Open your angular cli file .angular-cli.json and inside "apps": [{...}] add following code

 "apps":[{
        "environments": {
            "dev": "environments/environment.ts",
            "prod": "environments/environment.prod.ts",
            "qa": "environments/environment.qa.ts",
           }
         }
       ]

If you want to build for production, run ng build --env=prod it will read configuration from environment.prod.ts , same way you can do it for qa or dev

## Older answer

I have been doing something like below, in my provider:

import {Injectable} from '@angular/core';

@Injectable()
export class ConstantService {

API_ENDPOINT :String;
CONSUMER_KEY : String;

constructor() {
    this.API_ENDPOINT = 'https://api.somedomain.com/v1/';
    this.CONSUMER_KEY = 'someReallyStupidTextWhichWeHumansCantRead'
  }
}

Then i have access to all Constant data at anywhere

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/map';

import {ConstantService} from  './constant-service'; //This is my Constant Service


@Injectable()
export class ImagesService {
    constructor(public http: Http, public ConstantService: ConstantService) {
    console.log('Hello ImagesService Provider');

    }

callSomeService() {

    console.log("API_ENDPOINT: ",this.ConstantService.API_ENDPOINT);
    console.log("CONSUMER_KEY: ",this.ConstantService.CONSUMER_KEY);
    var url = this.ConstantService.API_ENDPOINT;
    return this.http.get(url)
  }
 }

Just use a Typescript constant

export var API_ENDPOINT = 'http://127.0.0.1:6666/api/';

You can use it in the dependency injector using

bootstrap(AppComponent, [provide(API_ENDPOINT, {useValue: 'http://127.0.0.1:6666/api/'}), ...]);

If you're using Webpack, which I recommend, you can set-up constants for different environments. This is especially valuable when you have different constant values on a per environment basis.

You'll likely have multiple webpack files under your /config directory (e.g., webpack.dev.js, webpack.prod.js, etc.). Then you'll have a custom-typings.d.ts you'll add them there. Here's the general pattern to follow in each file and a sample usage in a Component.

webpack.{env}.js

const API_URL = process.env.API_URL = 'http://localhost:3000/';
const JWT_TOKEN_NAME = "id_token";
...
    plugins: [
      // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
      new DefinePlugin({
        'API_URL': JSON.stringify(API_URL),
        'JWT_TOKEN_NAME': JSON.stringify(JWT_TOKEN_NAME)
      }),

custom-typings.d.ts

declare var API_URL: string;
declare var JWT_TOKEN_NAME: string;
interface GlobalEnvironment {
  API_URL: string;
  JWT_TOKEN_NAME: string;
}

Component

export class HomeComponent implements OnInit {
  api_url:string = API_URL;
  authToken: string = "Bearer " + localStorage.getItem(JWT_TOKEN_NAME)});
}

You can make a class for your global variable and then export this class like this:

export class CONSTANT {
    public static message2 = [
        { "NAME_REQUIRED": "Name is required" }
    ]

    public static message = {
        "NAME_REQUIRED": "Name is required",
    }
}

After creating and exporting your CONSTANT class, you should import this class in that class where you want to use, like this:

import { Component, OnInit                       } from '@angular/core';
import { CONSTANT                                } from '../../constants/dash-constant';


@Component({
  selector   : 'team-component',
  templateUrl: `../app/modules/dashboard/dashComponents/teamComponents/team.component.html`,
})

export class TeamComponent implements OnInit {
  constructor() {
    console.log(CONSTANT.message2[0].NAME_REQUIRED);
    console.log(CONSTANT.message.NAME_REQUIRED);
  }

  ngOnInit() {
    console.log("oninit");
    console.log(CONSTANT.message2[0].NAME_REQUIRED);
    console.log(CONSTANT.message.NAME_REQUIRED);
  }
}

You can use this either in constructor or ngOnInit(){}, or in any predefine methods.


Using a property file that is generated during a build is simple and easy. This is the approach that the Angular CLI uses. Define a property file for each environment and use a command during build to determine which file gets copied to your app. Then simply import the property file to use.

https://github.com/angular/angular-cli#build-targets-and-environment-files


All solutions seems to be complicated. I'm looking for the simplest solution for this case and I just want to use constants. Constants are simple. Is there anything which speaks against the following solution?

app.const.ts

'use strict';

export const dist = '../path/to/dist/';

app.service.ts

import * as AppConst from '../app.const'; 

@Injectable()
export class AppService {

    constructor (
    ) {
        console.log('dist path', AppConst.dist );
    }

}

The solution for the configuration provided by the angular team itself can be found here.

Here is all the relevant code:

1) app.config.ts

import { OpaqueToken } from "@angular/core";

export let APP_CONFIG = new OpaqueToken("app.config");

export interface IAppConfig {
    apiEndpoint: string;
}

export const AppConfig: IAppConfig = {    
    apiEndpoint: "http://localhost:15422/api/"    
};

2) app.module.ts

import { APP_CONFIG, AppConfig } from './app.config';

@NgModule({
    providers: [
        { provide: APP_CONFIG, useValue: AppConfig }
    ]
})

3) your.service.ts

import { APP_CONFIG, IAppConfig } from './app.config';

@Injectable()
export class YourService {

    constructor(@Inject(APP_CONFIG) private config: IAppConfig) {
             // You can use config.apiEndpoint now
    }   
}

Now you can inject the config everywhere without using the string names and with the use of your interface for static checks.

You can of course separate the Interface and the constant further to be able to supply different values in production and development e.g.


In Angular 4, you can use environment class to keep all your globals.

You have environment.ts and environment.prod.ts by default.

For example

export const environment = {
  production: false,
  apiUrl: 'http://localhost:8000/api/'
};

And then on your service:

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

While the approach with having a AppSettings class with a string constant as ApiEndpoint works, it is not ideal since we wouldn't be able to swap this real ApiEndpoint for some other values at the time of unit testing.

We need to be able to inject this api endpoints into our services (think of injecting a service into another service). We also do not need to create a whole class for this, all we want to do is to inject a string into our services being our ApiEndpoint. To complete the excellent answer by pixelbits, here is the complete code as to how it can be done in Angular 2:

First we need to tell Angular how to provide an instance of our ApiEndpoint when we ask for it in our app (think of it as registering a dependency):

bootstrap(AppComponent, [
        HTTP_PROVIDERS,
        provide('ApiEndpoint', {useValue: 'http://127.0.0.1:6666/api/'})
]);         


And then in the service we inject this ApiEndpoint into the service constructor and Angular will provide it for us based on our registration above:

import {Http} from 'angular2/http';
import {Message} from '../models/message';
import {Injectable, Inject} from 'angular2/core';  // * We import Inject here
import {Observable} from 'rxjs/Observable';
import {AppSettings} from '../appSettings';
import 'rxjs/add/operator/map';

@Injectable()
export class MessageService {

    constructor(private http: Http, 
                @Inject('ApiEndpoint') private apiEndpoint: string) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(`${this.apiEndpoint}/messages`)
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    } 
    // the rest of the code...
}

The best way to create application wide constants in Angular 2 is by using environment.ts files. The advantage of declaring such constants is that you can vary them according to the environment as there can be a different environment file for each environment.


Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7