29

I followed the official tutorials and made services for the Apis but absolute url of the Api is hardcoded in services.

I want to keep the base url of Api somewhere so that i can append it to the url path in each service before call. i also need to change the base url of Api before building (or after building).

i tried to put it in sessionstorage, but that is a bad idea as anyone can change it and my application will start hitting other domain.

so i kept it hard-coded and placed a post-push hook on the git to replace the url after build. but it is more like a hack than a solution.

Can i place a file in the root directory of angular and put Api url in json format . And include it in each service so that i can exclude the file from git and each teammate and the build server can have their own file having different url.

What should be the recommended way of doing it?

Ashutosh Raj
  • 1,006
  • 1
  • 11
  • 21
  • this may helps: https://stackoverflow.com/questions/44622058/how-can-we-use-angular4-environment-variable/44622195#44622195 – Ketan Akbari Jul 14 '17 at 04:40

7 Answers7

29

After release of Angular 4.3 we have a possibility to use HttpClient interceprtors. The advantage of this method is avoiding of import/injection of API_URL is all services with api calls.

This is my basic implementation:

@Injectable()
export class ApiUrlInterceptor implements HttpInterceptor {
constructor(@Inject(API_URL) private apiUrl: string) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    req = req.clone({url: this.prepareUrl(req.url)});
    return next.handle(req);
  }

  private isAbsoluteUrl(url: string): boolean {
    const absolutePattern = /^https?:\/\//i;
    return absolutePattern.test(url);
  }

  private prepareUrl(url: string): string {
    url = this.isAbsoluteUrl(url) ? url : this.apiUrl + '/' + url;
    return url.replace(/([^:]\/)\/+/g, '$1');
  }
}

InjectionToken declaration:

export const API_URL = new InjectionToken<string>('apiUrl');

Provider registration:

{provide: API_URL, useValue: environment.apiUrl}
{provide: HTTP_INTERCEPTORS, useClass: ApiUrlInterceptor, multi: true, deps: [API_URL]}

Environment.ts:

export const environment = {
 production: false,
 apiUrl: 'some-dev-api'
};
Mike Kovetsky
  • 1,528
  • 1
  • 13
  • 24
  • this looks good . even i was searching for HttpClient interceptors but can u elaborate on things like where should the code for "Injection Token declaration" and "Provider registration" be placed – Ashutosh Raj Jul 27 '17 at 16:29
  • 2
    Not sure about this right now. I`he placed this code in app.module.ts. I`ll check if this decision is scalable enough and leave a comment here later. – Mike Kovetsky Jul 28 '17 at 07:29
  • Where do you put the Injection Token declaration? – redOctober13 Feb 12 '18 at 15:28
  • @redOctober13 I`ve put export const API_URL = new InjectionToken('apiUrl'); just before ApiUrlInterceptor declaration – Mike Kovetsky Feb 12 '18 at 16:23
  • Provider registration is still in the app.module.file – Mike Kovetsky Feb 12 '18 at 16:26
  • Ok, I had it in with the provider registration (I have an interceptors/index.ts file to barrel all my interceptors together). But if I take it out of there, then the provider registration complains about `API_URL` being undefined. – redOctober13 Feb 12 '18 at 16:38
  • Why not just have this in the ApiUrlInterceptor: `import { environment } from '../../environments/environment'; export const API_URL = environment.API_URL` I still like this explanation, if it would be better to create a new questions so you can answer with this and maybe add file names, I can do that. – redOctober13 Feb 12 '18 at 17:02
  • 1
    @redOctober13 Yes, you can just import the api url from environment file. Injection token just serve for your convenience. They give you an opportunity to make your modules configurable. This will be useful, if you develop some sharable module (with this interceptor) between several application (maybe community also), as they can have different api urls. Injection token helped me to write the unit test also. I made `{provide: API_URL, useValue: 'http://example.com'}` in my spec file to make it not dependent from my environment. – Mike Kovetsky Feb 12 '18 at 19:37
  • But if you export the `const` in your interceptor file, what do you put in your provider file (e.g. `app.module`) so that API_URL is not undefined? – redOctober13 Feb 12 '18 at 19:41
  • 1
    @redOctober13 In your barrel file, you import API_URL: `import {API_URL, ApiUrlInterceptor} from './api-url.interceptor';` The "InjectionToken declaration" is in the ApiUrlInterceptor file. And this is what your barrel http interceptor file should look like : `/** "Barrel" of Http Interceptors */ import ... ; /** Http interceptor providers in outside-in order */ export const httpInterceptorProviders = [ {provide: API_URL, useValue: environment.apiUrl}, {provide: HTTP_INTERCEPTORS, useClass: ApiUrlInterceptor, multi: true, deps: [environment.apiUrl]}, ];` – Eric Jeker Dec 15 '19 at 15:22
11

Where To Store Angular Configurations from Dave's Notebook Where To Store Angular Configurations

Because this is a frequent problem, because it is so often done incorrectly and because there is a great alternative, today I want to discuss where to store Angular configurations. You know, all that information that changes as you move from your local development environment to the Development, QA and Production servers?

There’s a place for that!

Wrong Place I know it is tempting, but the environment.ts and environment.prod.ts files were never meant for configuration information other than to tell the run-time you are running a production version of the code instead of developing the code locally. Yes, I know it is possible to create a file for your different environments and you can effectively use the file for your configuration information. But, just because you can, doesn’t mean you should.

In an ideal world, you would build a release candidate and place it on your Development server and then move it from there to QA and then to Production. You would never rebuild the application. You want to be absolutely sure that the code you tested in the Development environment is the code you ran in the QA environment and that the code you ran in the QA environment is the code that is running in the Production environment. You want to know for sure that the only possible reason why something isn’t working is because the configuration information is incorrect.

There are other ways to mitigate the risk, but they all involve recompiling the code and tagging your repository so you can get the same code back. This works when there isn’t a better way. But, there is a better way!

Where Instead? If we can’t put our configuration information in our code, where do we put it? Obviously external to your code. This leaves us several solutions. One is to create a static json file that gets copied into your dist directory when the code is deployed to each environment. Another place that I’ve see work is to place the code in a database. The advantage to the database method is that you can have one database that handles the configuration information for all of your applications and even all of your environments. Put a good administration GUI on top of it and you can change the configuration easily without having to deploy even a file.

Jumping The Hurdle Now that you’ve put your configuration information in an external location, you realize that you’ll need to make a GET request to retrieve that information. You may also quickly realize that you need that configuration information as soon as your application starts up. Maybe putting that information in an external file wasn’t such a great idea after all?

Well, not so fast!

There is a little known API feature in Angular that lets us load stuff up front and will actually wait until the loading has completed before continuing on with our code.

APP_INITIALIZER APP_INITIALIZER is a multi provider type that lets you specify a factory that returns a promise. When the promise completes, the application will continue on. So when you get to the place in your code where you need the configuration information, you can be sure it has been loaded. It’s pretty slick.

import { APP_INITIALIZER } from '@angular/core'

@NgModule({
    ....
    providers: [
        ...
        {
            provide: APP_INITIALIZER,
            useFactory: load,
            multi: true
        }
    ]
)

where load is a function that returns a function that returns a Promise. The promise function loads your configuration information and stores it in your application. Once your configuration has been loaded, you resolve the promise using resolve(true).

This last point is really important. If you get this wrong, the code won’t wait for this to finish loading before moving on. useFactory points to a function that returns a function that returns a Promise!

The multi: true thing is because APP_INITIALIZER allows multiple instances of this provider. They all run simultaneously, but the code will not continue beyond APP_INTITIALIZER until all of the Promises have resolved.

An example. Now, as a discussion point, let’s assume that you have a regular Angular CLI based project and you need to load in the base location of your REST endpoints. You might have a config.json file that looks something like this:

{
    "baseUrl": "https://davembush.github.io/api"
}

You would create a different one of these for each of the environments you wanted to deploy to, and, as part of your deployment process, you would copy the appropriate file to config.json in the same location that you deploy all of your Angular CLI generated static files.

Basic App Initializer Now, the thing we want to do is to load this config file at runtime using APP_INITIALIZER. To do that, let’s add an APP_INITIALIZER provider to our app.module.ts file.

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

import { AppComponent } from './app.component';

function load() {

}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [{
    provide: APP_INITIALIZER,
    useFactory: load,
    multi: true
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }

Full resulting code Just in case I left out a step or you are otherwise lost or maybe all you really care about is the code so you can copy and paste it.

import { APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';

import { AppComponent } from './app.component';
import { ConfigService } from './config.service';
import { of, Observable, ObservableInput } from '../../node_modules/rxjs';
import { map, catchError } from 'rxjs/operators';

function load(http: HttpClient, config: ConfigService): (() => Promise<boolean>) {
  return (): Promise<boolean> => {
    return new Promise<boolean>((resolve: (a: boolean) => void): void => {
       http.get('./config.json')
         .pipe(
           map((x: ConfigService) => {
             config.baseUrl = x.baseUrl;
             resolve(true);
           }),
           catchError((x: { status: number }, caught: Observable<void>): ObservableInput<{}> => {
             if (x.status !== 404) {
               resolve(false);
             }
             config.baseUrl = 'http://localhost:8080/api';
             resolve(true);
             return of({});
           })
         ).subscribe();
    });
  };
}
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [{
      provide: APP_INITIALIZER,
      useFactory: load,
      deps: [
        HttpClient,
        ConfigService
      ],
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Masoud Bimmar
  • 8,635
  • 4
  • 26
  • 32
  • 1
    I agree with the fundamental argument of "release what you test" but on the specifics of where to store environmental URLs the Angular documentation disagrees with you. – Dave Jul 16 '19 at 09:39
7

Use the environment files

If youre using the cli you should use the environment files. By configuring the angular-cli.json file you can manage all the available environments and create new ones. For example you could create an environment.dev.js file and store the values there, by making git ignore it any member in your team can have a customized one.

Said environment file will overwrite the original environment.js

See this SO answer angular-cli for angular2 how to load environment variables

LookForAngular
  • 979
  • 7
  • 18
4

Usually I put these in the const environment file. If you are using angular-cli, this is already provided for you if not you can create your own:

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

You can have multiple environment file, like environment.production.ts, then with angular-cli you would run:

ng build --environment=production

If you are not using angular-cli, pretty sure you can build your own something similar.

penleychan
  • 5,067
  • 1
  • 14
  • 27
0
export class AppSettings {
   public static API_ENDPOINT='http://127.0.0.1:6666/api/';
}

And then in the service:

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

@Injectable()
export class MessageService {

    constructor(private http: Http) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(AppSettings.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);
    }
}
Rahul Cv
  • 725
  • 5
  • 12
-1

In app module add window object

import {provide} from 'angular2/core';
bootstrap([provide(Window, {useValue: window})]);

After that you can access window in controller

constructor(private window: Window) {
 var hostname = this.window.location.hostname;
}

There is no angular 2 specific solution.In angularJS(version 1) we use $location service

$location.protocol() + "://" + $location.host() + ":" + $location.port();
Arun Sivan
  • 1,332
  • 1
  • 10
  • 23
-2

You can create an Interceptor to add the base API URL

@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Custom Interceptor');

    // Adding serverHostURL to all APIs in Interceptor
    const serverHostURL = 'http://localhost:8080';
    request = request.clone({
      url: serverHostURL + request.url
    });


    return next.handle(request);
  }

Above code should do.

Sarat Chandra
  • 4,970
  • 28
  • 28