From 81ba34b9aa627d728d70e847a3bf23633ffee4ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Fri, 23 Jul 2021 22:12:26 +0200
Subject: [PATCH] The configuration is now in a file loaded at init

---
 client/src/app/admin/admin-auth.guard.ts      |  69 ++++-----
 client/src/app/admin/admin-routing.module.ts  |   8 +-
 client/src/app/admin/admin.component.html     |   2 +
 client/src/app/admin/admin.component.ts       |  11 +-
 .../attribute/add-attribute.component.ts      |   9 ++
 .../generate-option-list.component.ts         |   9 ++
 .../criteria/option-form.component.ts         |   9 ++
 .../criteria/option-list.component.ts         |   9 ++
 .../criteria/table-criteria.component.ts      |   9 ++
 .../criteria/tr-criteria.component.ts         |  11 +-
 .../components/attribute/design/index.ts      |   9 ++
 .../design/table-design.component.ts          |   9 ++
 .../attribute/design/tr-design.component.ts   |  11 +-
 .../components/attribute/detail/index.ts      |   9 ++
 .../detail/table-detail.component.ts          |   9 ++
 .../attribute/detail/tr-detail.component.ts   |  11 +-
 .../app/admin/components/attribute/index.ts   |   9 ++
 .../components/attribute/output/index.ts      |   9 ++
 .../output/table-output.component.ts          |   9 ++
 .../attribute/output/tr-output.component.ts   |  11 +-
 .../components/attribute/result/index.ts      |   9 ++
 .../renderers/detail-renderer.component.ts    |   9 ++
 .../renderers/download-renderer.component.ts  |   9 ++
 .../renderers/image-renderer.component.ts     |   9 ++
 .../attribute/result/renderers/index.ts       |   9 ++
 .../renderers/link-renderer.component.ts      |   9 ++
 .../result/renderers/renderer-form-factory.ts |   9 ++
 .../result/table-result.component.ts          |   9 ++
 .../attribute/result/tr-result.component.ts   |  11 +-
 .../{tr.component.css => tr.component.scss}   |   0
 .../admin/components/attribute/vo/index.ts    |   9 ++
 .../attribute/vo/table-vo.component.ts        |   9 ++
 .../attribute/vo/tr-vo.component.ts           |  11 +-
 client/src/app/app-config.service.ts          |  13 ++
 client/src/app/app-init.ts                    |  29 ++++
 client/src/app/app.module.ts                  |   6 +
 client/src/app/auth/auth.effects.ts           |  11 +-
 client/src/app/auth/auth.module.ts            |   9 +-
 client/src/app/auth/init.keycloak.ts          |  59 ++++----
 .../src/app/core/containers/app.component.ts  |   6 +-
 .../components/object-data.component.ts       |   5 +-
 .../spectra/spectra-object.component.ts       |   5 +-
 .../documentation/documentation.component.ts  |  10 +-
 .../src/app/instance/instance.component.html  |   2 +
 client/src/app/instance/instance.component.ts |  11 +-
 .../between-date.component.spec.ts            |  75 ----------
 .../search-type/between.component.spec.ts     |  94 -------------
 .../search-type/checkbox.component.spec.ts    |  86 ------------
 .../search-type/datalist.component.spec.ts    | 106 --------------
 .../search-type/date.component.spec.ts        | 105 --------------
 .../search-type/datetime.component.spec.ts    | 131 ------------------
 .../search-type/field.component.spec.ts       | 117 ----------------
 .../search-type/help-like.component.spec.ts   |  20 ---
 .../search-type/json.component.spec.ts        |  75 ----------
 .../search-type/list.component.spec.ts        |  73 ----------
 .../search-type/operator.component.spec.ts    |  83 -----------
 .../search-type/radio.component.spec.ts       |  73 ----------
 .../select-multiple.component.spec.ts         |  79 -----------
 .../search-type/select.component.spec.ts      |  98 -------------
 .../search-type/time.component.spec.ts        |  99 -------------
 .../components/result/download.component.ts   |   7 +-
 .../result/url-display.component.ts           |   5 +-
 .../renderer/download-renderer.component.ts   |   5 +-
 .../renderer/image-renderer.component.ts      |   7 +-
 .../instance/store/services/detail.service.ts |  11 +-
 .../instance/store/services/samp.service.ts   |   6 +-
 .../instance/store/services/search.service.ts |  10 +-
 .../services/attribute-distinct.service.ts    |   8 +-
 .../metamodel/services/attribute.service.ts   |  14 +-
 .../app/metamodel/services/column.service.ts  |   8 +-
 .../services/criteria-family.service.ts       |  14 +-
 .../metamodel/services/database.service.ts    |  14 +-
 .../services/dataset-family.service.ts        |  14 +-
 .../app/metamodel/services/dataset.service.ts |  14 +-
 .../app/metamodel/services/group.service.ts   |  14 +-
 .../metamodel/services/instance.service.ts    |  14 +-
 .../services/output-category.service.ts       |  14 +-
 .../services/output-family.service.ts         |  14 +-
 .../services/root-directory.service.ts        |   8 +-
 .../services/select-option.service.ts         |  14 +-
 .../app/metamodel/services/select.service.ts  |  14 +-
 .../app/metamodel/services/survey.service.ts  |  14 +-
 .../app/metamodel/services/table.service.ts   |   8 +-
 .../containers/portal-home.component.html     |   2 +
 .../containers/portal-home.component.ts       |  16 ++-
 .../app/shared/components/navbar.component.ts |   6 +-
 client/src/app/shared/utils.ts                |  10 +-
 client/src/assets/app.config.json             |  10 ++
 client/src/environments/environment.prod.ts   |  10 +-
 client/src/environments/environment.ts        |  10 +-
 90 files changed, 560 insertions(+), 1589 deletions(-)
 rename client/src/app/admin/components/attribute/{tr.component.css => tr.component.scss} (100%)
 create mode 100644 client/src/app/app-config.service.ts
 create mode 100644 client/src/app/app-init.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
 create mode 100644 client/src/assets/app.config.json

diff --git a/client/src/app/admin/admin-auth.guard.ts b/client/src/app/admin/admin-auth.guard.ts
index fb9fab7f..02ac7a5e 100644
--- a/client/src/app/admin/admin-auth.guard.ts
+++ b/client/src/app/admin/admin-auth.guard.ts
@@ -8,47 +8,52 @@
  */
 
 import { Injectable } from '@angular/core';
-import {
-    ActivatedRouteSnapshot,
-    Router,
-    RouterStateSnapshot,
-} from '@angular/router';
-import { Store } from '@ngrx/store';
+import { CanActivate, Router } from '@angular/router';
+import { Store, select } from '@ngrx/store';
 
-import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
+import { combineLatest, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
 
 import * as authActions from 'src/app/auth/auth.actions';
-import { environment } from 'src/environments/environment';
+import * as authSelector from 'src/app/auth/auth.selector';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable({
     providedIn: 'root',
 })
-export class AdminAuthGuard extends KeycloakAuthGuard {
+export class AdminAuthGuard implements CanActivate {
     constructor(
         protected readonly router: Router,
-        protected readonly keycloak: KeycloakService,
-        private store: Store<{ }>
-    ) {
-        super(router, keycloak);
-    }
+        private store: Store<{ }>,
+        private config: AppConfigService
+    ) { }
+
+    canActivate(): Observable<boolean> {
+        return combineLatest([
+            this.store.pipe(select(authSelector.selectUserRoles)),
+            this.store.pipe(select(authSelector.selectIsAuthenticated))
+        ]).pipe(
+            map(([userRoles, isAuthenticated]) => {
+                // Auth disabled
+                if (!this.config.authenticationEnabled) {
+                    return true;
+                }
+
+                // Force the user to log in if currently unauthenticated.
+                if (!isAuthenticated) {
+                    this.store.dispatch(authActions.login());
+                    return false;
+                }
+
+                // If authenticated but not admin go to unauthorized page.
+                if (!userRoles.includes(this.config.adminRole)) {
+                    this.router.navigateByUrl('/unauthorized');
+                    return false;
+                }
 
-    public async isAccessAllowed(
-        route: ActivatedRouteSnapshot,
-        state: RouterStateSnapshot
-    ) {
-        // Force the user to log in if currently unauthenticated.
-        if (!this.authenticated) {
-            this.store.dispatch(authActions.login());
-            return false;
-        }
-
-        // If authenticated but not admin go to unauthorized page.
-        if (!this.roles.includes(environment.adminRole)) {
-            this.router.navigateByUrl('/unauthorized');
-            return false;
-        }
-
-        // Else return true;
-        return true;
+                // Let "Router" allow user entering the page
+                return true;
+            })
+        );
     }
 }
diff --git a/client/src/app/admin/admin-routing.module.ts b/client/src/app/admin/admin-routing.module.ts
index 7fc65a51..d3aef6a6 100644
--- a/client/src/app/admin/admin-routing.module.ts
+++ b/client/src/app/admin/admin-routing.module.ts
@@ -29,11 +29,10 @@ import { DatabaseListComponent } from './containers/database/database-list.compo
 import { NewDatabaseComponent } from './containers/database/new-database.component';
 import { EditDatabaseComponent } from './containers/database/edit-database.component';
 import { SettingsComponent } from './containers/settings/settings.component';
-import { environment } from 'src/environments/environment';
 
 const routes: Routes = [
     { 
-        path: '', component: AdminComponent, children: [
+        path: '', component: AdminComponent, canActivate: [AdminAuthGuard], children: [
             { path: '', redirectTo: 'instance-list', pathMatch: 'full' },
             { path: 'instance-list', component: InstanceListComponent },
             { path: 'new-instance', component: NewInstanceComponent },
@@ -57,11 +56,6 @@ const routes: Routes = [
     }
 ];
 
-// Add auth guard if authentication is activated
-if (environment.authenticationEnabled) {
-    routes[0].canActivate = [ AdminAuthGuard ];
-}
-
 @NgModule({
     imports: [RouterModule.forChild(routes)],
     exports: [RouterModule]
diff --git a/client/src/app/admin/admin.component.html b/client/src/app/admin/admin.component.html
index 979df50e..61101934 100644
--- a/client/src/app/admin/admin.component.html
+++ b/client/src/app/admin/admin.component.html
@@ -3,6 +3,8 @@
         [links]="links"
         [isAuthenticated]="isAuthenticated | async"
         [userProfile]="userProfile | async"
+        [baseHref]="getBaseHref()"
+        [authenticationEnabled]="getAuthenticationEnabled()"
         (login)="login()"
         (logout)="logout()"
         (openEditProfile)="openEditProfile()">
diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts
index 18364e14..93e97de1 100644
--- a/client/src/app/admin/admin.component.ts
+++ b/client/src/app/admin/admin.component.ts
@@ -14,6 +14,7 @@ import { Store } from '@ngrx/store';
 import { UserProfile } from 'src/app/auth/user-profile.model';
 import * as authActions from 'src/app/auth/auth.actions';
 import * as authSelector from 'src/app/auth/auth.selector';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-admin',
@@ -37,12 +38,20 @@ export class AdminComponent {
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
 
-    constructor(private store: Store<{ }>) {
+    constructor(private store: Store<{ }>, private config: AppConfigService) {
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
     }
 
+    getBaseHref() {
+        return this.config.baseHref;
+    }
+
+    getAuthenticationEnabled() {
+        return this.config.authenticationEnabled;
+    }
+
     login(): void {
         this.store.dispatch(authActions.login());
     }
diff --git a/client/src/app/admin/components/attribute/add-attribute.component.ts b/client/src/app/admin/components/attribute/add-attribute.component.ts
index f7934d61..fc050468 100644
--- a/client/src/app/admin/components/attribute/add-attribute.component.ts
+++ b/client/src/app/admin/components/attribute/add-attribute.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, TemplateRef } from '@angular/core';
 
 import { BsModalService } from 'ngx-bootstrap/modal';
diff --git a/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts b/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts
index 1b6905a2..cc0899ce 100644
--- a/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts
+++ b/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, TemplateRef } from '@angular/core';
 
 import { BsModalService } from 'ngx-bootstrap/modal';
diff --git a/client/src/app/admin/components/attribute/criteria/option-form.component.ts b/client/src/app/admin/components/attribute/criteria/option-form.component.ts
index 5e2a620d..cff2bdcd 100644
--- a/client/src/app/admin/components/attribute/criteria/option-form.component.ts
+++ b/client/src/app/admin/components/attribute/criteria/option-form.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
diff --git a/client/src/app/admin/components/attribute/criteria/option-list.component.ts b/client/src/app/admin/components/attribute/criteria/option-list.component.ts
index 492bac06..c3e643a5 100644
--- a/client/src/app/admin/components/attribute/criteria/option-list.component.ts
+++ b/client/src/app/admin/components/attribute/criteria/option-list.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
 import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
 
diff --git a/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts b/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts
index fe5c7db6..9cf5e6de 100644
--- a/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts
+++ b/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component } from '@angular/core';
 
 @Component({
diff --git a/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts b/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts
index 87109e23..9c1f559f 100644
--- a/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts
+++ b/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core';
 import { FormArray, FormControl, FormGroup } from '@angular/forms';
 
@@ -6,7 +15,7 @@ import { Attribute, Option, CriteriaFamily, SelectOption } from 'src/app/metamod
 @Component({
     selector: '[criteria]',
     templateUrl: 'tr-criteria.component.html',
-    styleUrls: [ '../tr.component.css' ],
+    styleUrls: [ '../tr.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class TrCriteriaComponent implements OnInit {
diff --git a/client/src/app/admin/components/attribute/design/index.ts b/client/src/app/admin/components/attribute/design/index.ts
index 97b51ae6..e2a18cde 100644
--- a/client/src/app/admin/components/attribute/design/index.ts
+++ b/client/src/app/admin/components/attribute/design/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { TableDesignComponent } from './table-design.component';
 import { TrDesignComponent } from './tr-design.component';
 
diff --git a/client/src/app/admin/components/attribute/design/table-design.component.ts b/client/src/app/admin/components/attribute/design/table-design.component.ts
index ece9403e..4ca4666d 100644
--- a/client/src/app/admin/components/attribute/design/table-design.component.ts
+++ b/client/src/app/admin/components/attribute/design/table-design.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component } from '@angular/core';
 
 @Component({
diff --git a/client/src/app/admin/components/attribute/design/tr-design.component.ts b/client/src/app/admin/components/attribute/design/tr-design.component.ts
index 72d1dced..f9a42f6f 100644
--- a/client/src/app/admin/components/attribute/design/tr-design.component.ts
+++ b/client/src/app/admin/components/attribute/design/tr-design.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core';
 import { FormControl, FormGroup, Validators } from '@angular/forms';
 
@@ -6,7 +15,7 @@ import { Attribute, SelectOption } from 'src/app/metamodel/models';
 @Component({
     selector: '[design]',
     templateUrl: 'tr-design.component.html',
-    styleUrls: [ '../tr.component.css' ],
+    styleUrls: [ '../tr.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class TrDesignComponent implements OnInit {
diff --git a/client/src/app/admin/components/attribute/detail/index.ts b/client/src/app/admin/components/attribute/detail/index.ts
index 41a27ae4..d1581ea5 100644
--- a/client/src/app/admin/components/attribute/detail/index.ts
+++ b/client/src/app/admin/components/attribute/detail/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { TableDetailComponent } from './table-detail.component';
 import { TrDetailComponent } from './tr-detail.component';
 
diff --git a/client/src/app/admin/components/attribute/detail/table-detail.component.ts b/client/src/app/admin/components/attribute/detail/table-detail.component.ts
index 5ff070ee..f13fc516 100644
--- a/client/src/app/admin/components/attribute/detail/table-detail.component.ts
+++ b/client/src/app/admin/components/attribute/detail/table-detail.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component } from '@angular/core';
 
 @Component({
diff --git a/client/src/app/admin/components/attribute/detail/tr-detail.component.ts b/client/src/app/admin/components/attribute/detail/tr-detail.component.ts
index 2f268277..d485de9d 100644
--- a/client/src/app/admin/components/attribute/detail/tr-detail.component.ts
+++ b/client/src/app/admin/components/attribute/detail/tr-detail.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
 
@@ -6,7 +15,7 @@ import { Attribute, SelectOption } from 'src/app/metamodel/models';
 @Component({
     selector: '[detail]',
     templateUrl: 'tr-detail.component.html',
-    styleUrls: [ '../tr.component.css' ],
+    styleUrls: [ '../tr.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class TrDetailComponent implements OnInit {
diff --git a/client/src/app/admin/components/attribute/index.ts b/client/src/app/admin/components/attribute/index.ts
index 4d35bcb4..fc2f19be 100644
--- a/client/src/app/admin/components/attribute/index.ts
+++ b/client/src/app/admin/components/attribute/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { AddAttributeComponent } from './add-attribute.component';
 import { designComponents } from './design';
 import { criteriaComponents } from './criteria';
diff --git a/client/src/app/admin/components/attribute/output/index.ts b/client/src/app/admin/components/attribute/output/index.ts
index b5f11cda..1c915eff 100644
--- a/client/src/app/admin/components/attribute/output/index.ts
+++ b/client/src/app/admin/components/attribute/output/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { TableOutputComponent } from "./table-output.component";
 import { TrOutputComponent } from "./tr-output.component";
 
diff --git a/client/src/app/admin/components/attribute/output/table-output.component.ts b/client/src/app/admin/components/attribute/output/table-output.component.ts
index 30c36b2b..a2aefb31 100644
--- a/client/src/app/admin/components/attribute/output/table-output.component.ts
+++ b/client/src/app/admin/components/attribute/output/table-output.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component } from '@angular/core';
 
 @Component({
diff --git a/client/src/app/admin/components/attribute/output/tr-output.component.ts b/client/src/app/admin/components/attribute/output/tr-output.component.ts
index a2a7afc9..f75c8452 100644
--- a/client/src/app/admin/components/attribute/output/tr-output.component.ts
+++ b/client/src/app/admin/components/attribute/output/tr-output.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
 
@@ -6,7 +15,7 @@ import { Attribute, OutputCategory } from 'src/app/metamodel/models';
 @Component({
     selector: '[output]',
     templateUrl: 'tr-output.component.html',
-    styleUrls: [ '../tr.component.css' ],
+    styleUrls: [ '../tr.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class TrOutputComponent implements OnInit {
diff --git a/client/src/app/admin/components/attribute/result/index.ts b/client/src/app/admin/components/attribute/result/index.ts
index 4b25f427..3517973a 100644
--- a/client/src/app/admin/components/attribute/result/index.ts
+++ b/client/src/app/admin/components/attribute/result/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { TableResultComponent } from './table-result.component';
 import { TrResultComponent } from './tr-result.component';
 import { renderers } from './renderers';
diff --git a/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts
index 2939aecf..4dfc1917 100644
--- a/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts
+++ b/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
diff --git a/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts
index ef1fc0d8..f3ccfee5 100644
--- a/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts
+++ b/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
diff --git a/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts
index 87067115..1441a6a8 100644
--- a/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts
+++ b/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
diff --git a/client/src/app/admin/components/attribute/result/renderers/index.ts b/client/src/app/admin/components/attribute/result/renderers/index.ts
index bf18afa1..89c8ed9d 100644
--- a/client/src/app/admin/components/attribute/result/renderers/index.ts
+++ b/client/src/app/admin/components/attribute/result/renderers/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { DetailRendererComponent } from './detail-renderer.component';
 import { DownloadRendererComponent } from './download-renderer.component';
 import { ImageRendererComponent } from './image-renderer.component';
diff --git a/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts
index a67b8803..d8d13637 100644
--- a/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts
+++ b/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
diff --git a/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts b/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts
index 8a6e7ef3..22c2f5e3 100644
--- a/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts
+++ b/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { FormControl } from "@angular/forms";
 
 export abstract class RendererFormFactory {
diff --git a/client/src/app/admin/components/attribute/result/table-result.component.ts b/client/src/app/admin/components/attribute/result/table-result.component.ts
index d4aa1103..86ce85cb 100644
--- a/client/src/app/admin/components/attribute/result/table-result.component.ts
+++ b/client/src/app/admin/components/attribute/result/table-result.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component } from '@angular/core';
 
 @Component({
diff --git a/client/src/app/admin/components/attribute/result/tr-result.component.ts b/client/src/app/admin/components/attribute/result/tr-result.component.ts
index 38ae0028..fe0aa76e 100644
--- a/client/src/app/admin/components/attribute/result/tr-result.component.ts
+++ b/client/src/app/admin/components/attribute/result/tr-result.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
 
@@ -7,7 +16,7 @@ import { RendererFormFactory } from './renderers/renderer-form-factory';
 @Component({
     selector: '[result]',
     templateUrl: 'tr-result.component.html',
-    styleUrls: [ '../tr.component.css' ],
+    styleUrls: [ '../tr.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class TrResultComponent implements OnInit {
diff --git a/client/src/app/admin/components/attribute/tr.component.css b/client/src/app/admin/components/attribute/tr.component.scss
similarity index 100%
rename from client/src/app/admin/components/attribute/tr.component.css
rename to client/src/app/admin/components/attribute/tr.component.scss
diff --git a/client/src/app/admin/components/attribute/vo/index.ts b/client/src/app/admin/components/attribute/vo/index.ts
index 8b6aa3b6..80eb8323 100644
--- a/client/src/app/admin/components/attribute/vo/index.ts
+++ b/client/src/app/admin/components/attribute/vo/index.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { TableVoComponent } from './table-vo.component';
 import { TrVoComponent } from './tr-vo.component';
 
diff --git a/client/src/app/admin/components/attribute/vo/table-vo.component.ts b/client/src/app/admin/components/attribute/vo/table-vo.component.ts
index ba4352e0..57df81c8 100644
--- a/client/src/app/admin/components/attribute/vo/table-vo.component.ts
+++ b/client/src/app/admin/components/attribute/vo/table-vo.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component } from '@angular/core';
 
 @Component({
diff --git a/client/src/app/admin/components/attribute/vo/tr-vo.component.ts b/client/src/app/admin/components/attribute/vo/tr-vo.component.ts
index 52172506..e0dd9bb2 100644
--- a/client/src/app/admin/components/attribute/vo/tr-vo.component.ts
+++ b/client/src/app/admin/components/attribute/vo/tr-vo.component.ts
@@ -1,3 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
 
@@ -6,7 +15,7 @@ import { Attribute } from 'src/app/metamodel/models';
 @Component({
     selector: '[vo]',
     templateUrl: 'tr-vo.component.html',
-    styleUrls: [ '../tr.component.css' ],
+    styleUrls: [ '../tr.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class TrVoComponent implements OnInit {
diff --git a/client/src/app/app-config.service.ts b/client/src/app/app-config.service.ts
new file mode 100644
index 00000000..5944e78a
--- /dev/null
+++ b/client/src/app/app-config.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class AppConfigService {
+    public apiUrl: string;
+    public servicesUrl: string;
+    public baseHref: string;
+    public authenticationEnabled: boolean;
+    public ssoAuthUrl: string;
+    public ssoRealm: string;
+    public ssoClientId: string;
+    public adminRole: string;
+}
diff --git a/client/src/app/app-init.ts b/client/src/app/app-init.ts
new file mode 100644
index 00000000..b3dc0426
--- /dev/null
+++ b/client/src/app/app-init.ts
@@ -0,0 +1,29 @@
+import { APP_INITIALIZER } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { KeycloakService } from 'keycloak-angular';
+import { Store } from '@ngrx/store';
+
+import { AppConfigService } from './app-config.service';
+import { initializeKeycloak } from 'src/app/auth/init.keycloak';
+
+function appInit(http: HttpClient, appConfigService: AppConfigService, keycloak: KeycloakService, store: Store<{ }>) {
+    return () => {
+        return http.get('/assets/app.config.json')
+            .toPromise()
+            .then(data => {
+                Object.assign(appConfigService, data);
+                return appConfigService;
+            })
+            .then(appConfigService => {
+                return initializeKeycloak(keycloak, store, appConfigService)
+            });
+    }
+}
+
+export const appInitializer = {
+    provide: APP_INITIALIZER,
+    useFactory: appInit,
+    multi: true,
+    deps: [ HttpClient, AppConfigService, KeycloakService, Store ]
+};
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 981c03d2..cca4aece 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -25,6 +25,8 @@ import { CoreModule } from './core/core.module';
 import { AuthModule } from './auth/auth.module';
 import { MetamodelModule } from './metamodel/metamodel.module';
 import { AppComponent } from './core/containers/app.component';
+import { AppConfigService } from './app-config.service';
+import { appInitializer } from './app-init';
 
 @NgModule({
     imports: [
@@ -56,6 +58,10 @@ import { AppComponent } from './core/containers/app.component';
             logOnly: environment.production
         })
     ],
+    providers: [
+        AppConfigService,
+        appInitializer
+    ],
     bootstrap: [AppComponent]
 })
 export class AppModule { }
diff --git a/client/src/app/auth/auth.effects.ts b/client/src/app/auth/auth.effects.ts
index 0f7c6b9c..69cefb0e 100644
--- a/client/src/app/auth/auth.effects.ts
+++ b/client/src/app/auth/auth.effects.ts
@@ -15,7 +15,7 @@ import { tap, switchMap } from 'rxjs/operators';
 import { KeycloakService } from 'keycloak-angular';
 
 import * as authActions from './auth.actions';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
  
 @Injectable()
 export class AuthEffects {
@@ -35,8 +35,8 @@ export class AuthEffects {
             ofType(authActions.logout),
             tap(_ => {
                 let redirectUri = window.location.origin;
-                if (environment.baseHref !== '/') {
-                    redirectUri += environment.baseHref;
+                if (this.config.baseHref !== '/') {
+                    redirectUri += this.config.baseHref;
                 }
                 this.keycloak.logout(redirectUri);
             })        
@@ -61,13 +61,14 @@ export class AuthEffects {
     openEditProfile$ =  createEffect(() =>
         this.actions$.pipe(
             ofType(authActions.openEditProfile),
-            tap(_ => window.open(environment.ssoAuthUrl + '/realms/' + environment.ssoRealm + '/account', '_blank'))        
+            tap(_ => window.open(this.config.ssoAuthUrl + '/realms/' + this.config.ssoRealm + '/account', '_blank'))        
         ),
         { dispatch: false }
     );
  
     constructor(
         private actions$: Actions,
-        private keycloak: KeycloakService
+        private keycloak: KeycloakService,
+        private config: AppConfigService
     ) {}
 }
diff --git a/client/src/app/auth/auth.module.ts b/client/src/app/auth/auth.module.ts
index 1c04b526..5f928302 100644
--- a/client/src/app/auth/auth.module.ts
+++ b/client/src/app/auth/auth.module.ts
@@ -13,19 +13,14 @@ import { KeycloakAngularModule } from 'keycloak-angular';
 import { StoreModule } from '@ngrx/store';
 import { EffectsModule } from '@ngrx/effects';
 
-import { initializeKeycloakAnis } from './init.keycloak';
 import { authReducer } from './auth.reducer';
 import { AuthEffects } from './auth.effects';
-import { environment } from 'src/environments/environment';
 
 @NgModule({
     imports: [
-        environment.authenticationEnabled ? KeycloakAngularModule : [],
+        KeycloakAngularModule,
         StoreModule.forFeature('auth', authReducer),
-        environment.authenticationEnabled ? EffectsModule.forFeature([ AuthEffects ]): []
-    ],
-    providers: [
-        environment.authenticationEnabled ? initializeKeycloakAnis: []
+        EffectsModule.forFeature([ AuthEffects ])
     ]
 })
 export class AuthModule { }
diff --git a/client/src/app/auth/init.keycloak.ts b/client/src/app/auth/init.keycloak.ts
index f0509a31..de6c1bc3 100644
--- a/client/src/app/auth/init.keycloak.ts
+++ b/client/src/app/auth/init.keycloak.ts
@@ -7,48 +7,41 @@
  * file that was distributed with this source code.
  */
 
-import { APP_INITIALIZER } from '@angular/core';
 import { from } from 'rxjs';
 
 import { KeycloakService, KeycloakEventType } from 'keycloak-angular';
 import { Store } from '@ngrx/store';
 
 import * as keycloakActions from './auth.actions';
-import * as fromKeycloak from './auth.reducer';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from '../app-config.service';
 
-function initializeKeycloak(keycloak: KeycloakService, store: Store<{ keycloak: fromKeycloak.State }>) {
-    return async () => {
-        from(keycloak.keycloakEvents$).subscribe(event => {
-            if (event.type === KeycloakEventType.OnAuthSuccess) {
-                store.dispatch(keycloakActions.authSuccess());
-            }
-        })
+export function initializeKeycloak(keycloak: KeycloakService, store: Store<{ }>, appConfigService: AppConfigService) {
+    if (!appConfigService.authenticationEnabled) {
+        return true;
+    }
 
-        let silentCheckSsoRedirectUri = window.location.origin;
-        if (environment.baseHref != '/') {
-            silentCheckSsoRedirectUri += environment.baseHref;
+    from(keycloak.keycloakEvents$).subscribe(event => {
+        if (event.type === KeycloakEventType.OnAuthSuccess) {
+            store.dispatch(keycloakActions.authSuccess());
         }
-        silentCheckSsoRedirectUri += '/assets/silent-check-sso.html';
+    })
 
-        return keycloak.init({
-            config: {
-                url: environment.ssoAuthUrl,
-                realm: environment.ssoRealm,
-                clientId: environment.ssoClientId,
-            },
-            initOptions: {
-                onLoad: 'check-sso',
-                silentCheckSsoRedirectUri
-            },
-            loadUserProfileAtStartUp: true
-        });
+    let silentCheckSsoRedirectUri = window.location.origin;
+    if (appConfigService.baseHref != '/') {
+        silentCheckSsoRedirectUri += appConfigService.baseHref;
     }
-}
+    silentCheckSsoRedirectUri += '/assets/silent-check-sso.html';
 
-export const initializeKeycloakAnis = {
-    provide: APP_INITIALIZER,
-    useFactory: initializeKeycloak,
-    multi: true,
-    deps: [ KeycloakService, Store ],
-};
+    return keycloak.init({
+        config: {
+            url: appConfigService.ssoAuthUrl,
+            realm: appConfigService.ssoRealm,
+            clientId: appConfigService.ssoClientId,
+        },
+        initOptions: {
+            onLoad: 'check-sso',
+            silentCheckSsoRedirectUri
+        },
+        loadUserProfileAtStartUp: true
+    });
+}
diff --git a/client/src/app/core/containers/app.component.ts b/client/src/app/core/containers/app.component.ts
index c265196b..0de038c0 100644
--- a/client/src/app/core/containers/app.component.ts
+++ b/client/src/app/core/containers/app.component.ts
@@ -17,7 +17,7 @@ import * as fromAuth from '../../auth/auth.reducer';
 import * as authActions from '../../auth/auth.actions';
 import * as authSelector from '../../auth/auth.selector';
 import { UserProfile } from '../../auth/user-profile.model';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
   selector: 'app-root',
@@ -30,14 +30,14 @@ export class AppComponent {
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
 
-    constructor(private store: Store<{ auth: fromAuth.State }>) {
+    constructor(private store: Store<{ auth: fromAuth.State }>, private config: AppConfigService) {
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
     }
 
     authenticationEnabled(): boolean {
-        return environment.authenticationEnabled;
+        return this.config.authenticationEnabled;
     }
 
     login(): void {
diff --git a/client/src/app/instance/detail/components/object-data.component.ts b/client/src/app/instance/detail/components/object-data.component.ts
index d834874b..16de054f 100644
--- a/client/src/app/instance/detail/components/object-data.component.ts
+++ b/client/src/app/instance/detail/components/object-data.component.ts
@@ -11,6 +11,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 
 import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
 import { getHost } from 'src/app/shared/utils';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-object-data',
@@ -28,6 +29,8 @@ export class ObjectDataComponent {
     @Input() attributeList: Attribute[];
     @Input() object: any;
 
+    constructor(private appConfig: AppConfigService) { }
+
     /**
      * Returns category list sorted by display, for the given output family ID.
      *
@@ -78,6 +81,6 @@ export class ObjectDataComponent {
     }
 
     getDownloadHref(value: string) {
-        return getHost() + '/download-file/' + this.datasetSelected + '/' + value;
+        return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetSelected + '/' + value;
     }
 }
diff --git a/client/src/app/instance/detail/components/spectra/spectra-object.component.ts b/client/src/app/instance/detail/components/spectra/spectra-object.component.ts
index df397e7e..9c7095f2 100644
--- a/client/src/app/instance/detail/components/spectra/spectra-object.component.ts
+++ b/client/src/app/instance/detail/components/spectra/spectra-object.component.ts
@@ -11,6 +11,7 @@ import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter, OnInit
 
 import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
 import { getHost } from 'src/app/shared/utils';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-spectra-object',
@@ -35,6 +36,8 @@ export class SpectraObjectComponent implements OnInit {
     @Input() spectraCSV: string;
     @Output() getSpectraCSV: EventEmitter<string> = new EventEmitter();
 
+    constructor(private appConfig: AppConfigService) { }
+
     ngOnInit() {
         const attributeSpectraGraph = this.getAttributeSpectraGraph();
         if (attributeSpectraGraph) {
@@ -51,7 +54,7 @@ export class SpectraObjectComponent implements OnInit {
         const spectraAttribute: Attribute = this.attributeList
             .filter(a => a.detail)
             .find(attribute => attribute.search_flag === 'SPECTRUM_1D');
-        return getHost() + '/download-file/' + this.datasetSelected + '/' + this.object[spectraAttribute.label];
+        return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetSelected + '/' + this.object[spectraAttribute.label];
     }
 
     /**
diff --git a/client/src/app/instance/documentation/documentation.component.ts b/client/src/app/instance/documentation/documentation.component.ts
index 7a414850..ef1e591e 100644
--- a/client/src/app/instance/documentation/documentation.component.ts
+++ b/client/src/app/instance/documentation/documentation.component.ts
@@ -15,7 +15,7 @@ import { Observable } from 'rxjs';
 import * as datasetActions from 'src/app/metamodel/actions/dataset.actions';
 import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
 import { Dataset } from 'src/app/metamodel/models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-documentation',
@@ -33,7 +33,7 @@ export class DocumentationComponent implements OnInit {
     public datasetListIsLoaded: Observable<boolean>;
     public datasetList: Observable<Dataset[]>;
 
-    constructor(private store: Store<{ }>) {
+    constructor(private store: Store<{ }>, private config: AppConfigService) {
         this.datasetListIsLoading = store.select(datasetSelector.selectDatasetListIsLoading);
         this.datasetListIsLoaded = store.select(datasetSelector.selectDatasetListIsLoaded);
         this.datasetList = store.select(datasetSelector.selectAllDatasets);
@@ -49,10 +49,10 @@ export class DocumentationComponent implements OnInit {
      * @return string
      */
     getUrlServer(): string {
-        if (!environment.apiUrl.startsWith('http')) {
+        if (!this.config.apiUrl.startsWith('http')) {
             const url = window.location;
-            return url.protocol + '//' + url.host + environment.apiUrl;
+            return url.protocol + '//' + url.host + this.config.apiUrl;
         }
-        return environment.apiUrl;
+        return this.config.apiUrl;
     }
 }
diff --git a/client/src/app/instance/instance.component.html b/client/src/app/instance/instance.component.html
index 979df50e..61101934 100644
--- a/client/src/app/instance/instance.component.html
+++ b/client/src/app/instance/instance.component.html
@@ -3,6 +3,8 @@
         [links]="links"
         [isAuthenticated]="isAuthenticated | async"
         [userProfile]="userProfile | async"
+        [baseHref]="getBaseHref()"
+        [authenticationEnabled]="getAuthenticationEnabled()"
         (login)="login()"
         (logout)="logout()"
         (openEditProfile)="openEditProfile()">
diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts
index 6d2d3146..53fc798c 100644
--- a/client/src/app/instance/instance.component.ts
+++ b/client/src/app/instance/instance.component.ts
@@ -15,6 +15,7 @@ import { UserProfile } from 'src/app/auth/user-profile.model';
 import * as authActions from 'src/app/auth/auth.actions';
 import * as authSelector from 'src/app/auth/auth.selector';
 import * as metamodelActions from './store/actions/metamodel.actions';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-instance',
@@ -35,7 +36,7 @@ export class InstanceComponent implements OnInit {
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
 
-    constructor(private store: Store<{ }>) {
+    constructor(private store: Store<{ }>, private config: AppConfigService) {
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
@@ -46,6 +47,14 @@ export class InstanceComponent implements OnInit {
         Promise.resolve(null).then(() => this.store.dispatch(metamodelActions.loadInstanceMetamodel()));
     }
 
+    getBaseHref() {
+        return this.config.baseHref;
+    }
+
+    getAuthenticationEnabled() {
+        return this.config.authenticationEnabled;
+    }
+
     login(): void {
         this.store.dispatch(authActions.login());
     }
diff --git a/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts
deleted file mode 100644
index 72ebd410..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-
-import { BetweenDateComponent } from './between-date.component';
-import { BetweenCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: BetweenDateComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-between-date [criterion]='criterion'></app-between-date>`
-    })
-    class TestHostComponent {
-        @ViewChild(BetweenDateComponent, { static: false })
-        public testedComponent: BetweenDateComponent;
-        public criterion: BetweenCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: BetweenDateComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [BetweenDateComponent, TestHostComponent],
-            imports: [BsDatepickerModule.forRoot(), FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDateString() should format a datetime object into a string', () => {
-        const dateObject = new Date('February 08, 2019 15:47:00');
-        const expectedDateString = '2019-02-08';
-        const formatedDate = testedComponent.getDateString(dateObject);
-        expect(formatedDate).toEqual(expectedDateString);
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.field.value).toBeNull();
-        expect(testedComponent.field.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const criterion = { id: 1, type: 'between', min: '2019-02-08', max: '2019-02-17' } as BetweenCriterion;
-        const expectedFieldValue = [new Date(criterion.min), new Date(criterion.max)];
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.field.value).toEqual(expectedFieldValue);
-        expect(testedComponent.field.disabled).toBeTruthy();
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const dateMin = '2019-02-08';
-        const dateMax = '2019-02-17';
-        testedComponent.field = new FormControl([new Date(dateMin), new Date(dateMax)]);
-        const expectedCriterion = { id: testedComponent.id, type: 'between', min: dateMin, max: dateMax } as BetweenCriterion;
-        testedComponent.addCriterion.subscribe((event: BetweenCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts
deleted file mode 100644
index 80df0b7b..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { BetweenComponent } from './between.component';
-import { BetweenCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: BetweenComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-between [criterion]='criterion'></app-between>`
-    })
-    class TestHostComponent {
-        @ViewChild(BetweenComponent, { static: false })
-        public testedComponent: BetweenComponent;
-        public criterion: BetweenCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: BetweenComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [BetweenComponent, TestHostComponent],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.fieldMin.value).toBeNull();
-        expect(testedComponent.fieldMin.enabled).toBeTruthy();
-        expect(testedComponent.fieldMax.value).toBeNull();
-        expect(testedComponent.fieldMax.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const min = '10';
-        const max = '20';
-        const criterion = { id: 1, type: 'between', min, max } as BetweenCriterion;
-        const expectedFieldMinValue = min;
-        const expectedFieldMaxValue = max;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.fieldMin.value).toEqual(expectedFieldMinValue);
-        expect(testedComponent.fieldMin.disabled).toBeTruthy();
-        expect(testedComponent.fieldMax.value).toEqual(expectedFieldMaxValue);
-        expect(testedComponent.fieldMax.disabled).toBeTruthy();
-    });
-
-    it('#getPlaceholderMin() should fill the placeholder if defined', () => {
-        const placeholder = '10';
-        testedComponent.placeholderMin = placeholder;
-        expect(testedComponent.getPlaceholderMin()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholderMin() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholderMin()).toEqual('');
-    });
-
-    it('#getPlaceholderMax() should fill the placeholder if defined', () => {
-        const placeholder = '10';
-        testedComponent.placeholderMax = placeholder;
-        expect(testedComponent.getPlaceholderMax()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholderMax() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholderMax()).toEqual('');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const min = '10';
-        const max = '20';
-        testedComponent.fieldMin = new FormControl(min);
-        testedComponent.fieldMax = new FormControl(max);
-        const expectedCriterion = { id: testedComponent.id, type: 'between', min, max } as BetweenCriterion;
-        testedComponent.addCriterion.subscribe((event: BetweenCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts
deleted file mode 100644
index 0c10c5ec..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl, FormArray } from '@angular/forms';
-
-import { CheckboxComponent } from './checkbox.component';
-import { SelectMultipleCriterion } from '../../../store/model';
-import { Option } from '../../../../metamodel/model';
-
-describe('[Search][Criteria][SearchType] Component: CheckboxComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-checkbox [options]='options' [criterion]='criterion'></app-checkbox>`
-    })
-    class TestHostComponent {
-        @ViewChild(CheckboxComponent, { static: false })
-        public testedComponent: CheckboxComponent;
-        public criterion: SelectMultipleCriterion = undefined;
-        public options: Option[] = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 },
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: CheckboxComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [CheckboxComponent, TestHostComponent],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should not fill and enable form if criterion not defined', () => {
-        expect(testedComponent.checkboxes.length).toEqual(3);
-        expect(testedComponent.isChecked()).toBeFalsy();
-        expect(testedComponent.checkboxes.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const options: Option[] = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 }
-        ];
-        const criterion = { id: 1, type: 'multiple', options} as SelectMultipleCriterion;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.isChecked()).toBeTruthy();
-        const values = testedComponent.checkboxes.value as boolean[];
-        expect(values.filter(v => v).length).toEqual(2);
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.checkboxes = new FormArray([
-            new FormControl(false),
-            new FormControl(true),
-            new FormControl(true)
-        ])
-        const expectedCriterion = {
-            id: 1,
-            type: 'multiple',
-            options: [
-                { label: 'Two', value: 'two', display: 2 },
-                { label: 'Three', value: 'three', display: 3 }
-            ]
-        };
-        testedComponent.addCriterion.subscribe((event: SelectMultipleCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts
deleted file mode 100644
index 588ac8c5..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { DatalistComponent } from './datalist.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: DatalistComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-datalist [criterion]='criterion'></app-datalist>`
-    })
-    class TestHostComponent {
-        @ViewChild(DatalistComponent, { static: false })
-        public testedComponent: DatalistComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-        @Output() changeOperator: EventEmitter<string> = new EventEmitter();
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: DatalistComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DatalistComponent,
-                TestHostComponent,
-                OperatorStubComponent,
-                HelpLikeStubComponent
-            ],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.field.value).toBeNull();
-        expect(testedComponent.field.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const value = '10';
-        const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion;
-        const expectedFieldValue = value;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.field.value).toEqual(expectedFieldValue);
-        expect(testedComponent.field.disabled).toBeTruthy();
-    });
-
-    it('#getPlaceholder() should fill the placeholder if defined', () => {
-        const placeholder = '10';
-        testedComponent.placeholder = placeholder;
-        expect(testedComponent.getPlaceholder()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholder() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholder()).toEqual('');
-    });
-
-    it('#getDatalistId() should return an id', () => {
-        testedComponent.id = 1;
-        expect(testedComponent.getDatalistId()).toEqual('datalist_' + 1);
-    });
-
-    it('#changeOperator() should change the operator', () => {
-        expect(testedComponent.operator).toBeUndefined();
-        testedComponent.changeOperator('toto');
-        expect(testedComponent.operator).toBe('toto');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = '=';
-        const value = '10';
-        testedComponent.field = new FormControl(value);
-        testedComponent.operator = operator;
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts
deleted file mode 100644
index a67b65f6..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-
-import { DateComponent } from './date.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: DateComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-date [criterion]='criterion'></app-date>`
-    })
-    class TestHostComponent {
-        @ViewChild(DateComponent, { static: false })
-        public testedComponent: DateComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-        @Output() changeOperator: EventEmitter<string> = new EventEmitter();
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: DateComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DateComponent, 
-                TestHostComponent,
-                OperatorStubComponent
-            ],
-            imports: [BsDatepickerModule.forRoot(), FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.field.value).toBeNull();
-        expect(testedComponent.field.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const value = '2019-02-17';
-        const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion;
-        const expectedFieldValue = new Date(value);
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.field.value).toEqual(expectedFieldValue);
-        expect(testedComponent.field.disabled).toBeTruthy();
-    });
-
-    it('#getPlaceholder() should fill the placeholder if defined', () => {
-        const placeholder = '2019-02-17';
-        testedComponent.placeholder = placeholder;
-        expect(testedComponent.getPlaceholder()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholder() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholder()).toEqual('');
-    });
-
-    it('#getDateString() should return a date as string', () => {
-        const dateString = '2019-02-17';
-        const date = new Date(dateString);
-        expect(testedComponent.getDateString(date)).toEqual(dateString);
-    });
-
-    it('#changeOperator() should change the operator', () => {
-        expect(testedComponent.operator).toBeUndefined();
-        testedComponent.changeOperator('toto');
-        expect(testedComponent.operator).toBe('toto');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = '=';
-        testedComponent.operator = operator;
-        const date = '2019-02-17';
-        testedComponent.field = new FormControl(new Date(date));
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: date } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts
deleted file mode 100644
index 045aeeee..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { DatetimeComponent } from './datetime.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: DatetimeComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-datetime [criterion]='criterion'></app-datetime>`
-    })
-    class TestHostComponent {
-        @ViewChild(DatetimeComponent, { static: false })
-        public testedComponent: DatetimeComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-        @Output() changeOperator: EventEmitter<string> = new EventEmitter();
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: DatetimeComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DatetimeComponent,
-                TestHostComponent,
-                OperatorStubComponent
-            ],
-            imports: [BsDatepickerModule.forRoot(), NgSelectModule, FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.date.value).toBeNull();
-        expect(testedComponent.date.enabled).toBeTruthy();
-        expect(testedComponent.hh.value).toBeNull();
-        expect(testedComponent.hh.enabled).toBeTruthy();
-        expect(testedComponent.mm.value).toBeNull();
-        expect(testedComponent.mm.enabled).toBeTruthy();
-        expect(testedComponent.isValidFields).toBeFalsy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const value = '2019-02-17 15:47';
-        const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion;
-        const expectedDate = new Date('2019-02-17');
-        const expectedHour = '15';
-        const expectedMinute = '47';
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.date.value).toEqual(expectedDate);
-        expect(testedComponent.date.disabled).toBeTruthy();
-        expect(testedComponent.hh.value).toEqual(expectedHour);
-        expect(testedComponent.hh.disabled).toBeTruthy();
-        expect(testedComponent.mm.value).toEqual(expectedMinute);
-        expect(testedComponent.mm.disabled).toBeTruthy();
-        expect(testedComponent.isValidFields).toBeTruthy();
-    });
-
-    it('#initTime(t) should return an array of string with 2 digits from 0 to t', () => {
-        const n = 10;
-        expect(testedComponent.initTime(n).length).toEqual(n);
-        expect(testedComponent.initTime(n)[5]).toEqual('05');
-    });
-
-    it('#change() should set #isValidFields to false if one or more fields are not defined', () => {
-        testedComponent.change();
-        expect(testedComponent.isValidFields).toBeFalsy();
-        testedComponent.hh = new FormControl('15');
-        testedComponent.change();
-        expect(testedComponent.isValidFields).toBeFalsy();
-        testedComponent.mm = new FormControl('47');
-        testedComponent.change();
-        expect(testedComponent.isValidFields).toBeFalsy();
-        testedComponent.date = new FormControl(new Date('2019-02-17'));
-        testedComponent.change();
-        expect(testedComponent.isValidFields).toBeTruthy();
-    });
-
-    it('#change() should set #datetime with fields value when defined', () => {
-        testedComponent.date = new FormControl(new Date('2019-02-17'));
-        testedComponent.hh = new FormControl('15');
-        testedComponent.mm = new FormControl('47');
-        testedComponent.change();
-        expect(testedComponent.datetime).toEqual(new Date('2019-02-17 15:47'));
-    });
-
-    it('#changeOperator() should change the operator', () => {
-        expect(testedComponent.operator).toBeUndefined();
-        testedComponent.changeOperator('toto');
-        expect(testedComponent.operator).toBe('toto');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = '=';
-        testedComponent.operator = operator;
-        testedComponent.datetime = new Date('2019-02-17 15:47');
-        testedComponent.hh = new FormControl('15');
-        testedComponent.mm = new FormControl('47');
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: '2019-02-17 15:47' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts
deleted file mode 100644
index 4c7d5512..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { FieldComponent } from './field.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: FieldComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-field [criterion]='criterion'></app-field>`
-    })
-    class TestHostComponent {
-        @ViewChild(FieldComponent, { static: false })
-        public testedComponent: FieldComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-        @Output() changeOperator: EventEmitter<string> = new EventEmitter();
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: FieldComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                FieldComponent,
-                TestHostComponent,
-                OperatorStubComponent,
-                HelpLikeStubComponent
-            ],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.field.value).toBeNull();
-        expect(testedComponent.field.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const value = 'test';
-        const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion;
-        const expectedFieldValue = value;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.field.value).toEqual(expectedFieldValue);
-        expect(testedComponent.field.disabled).toBeTruthy();
-    });
-
-    it('#getType() should return `number` if criterion is a number type', () => {
-        testedComponent.attributeType = 'smallint';
-        expect(testedComponent.getType()).toEqual('number');
-        testedComponent.attributeType = 'integer';
-        expect(testedComponent.getType()).toEqual('number');
-        testedComponent.attributeType = 'decimal';
-        expect(testedComponent.getType()).toEqual('number');
-        testedComponent.attributeType = 'float';
-        expect(testedComponent.getType()).toEqual('number');
-    });
-
-    it('#getType() should return `text` if criterion is not a number type', () => {
-        testedComponent.attributeType = 'char';
-        expect(testedComponent.getType()).toEqual('text');
-    });
-
-    it('#getPlaceholder() should fill the placeholder if defined', () => {
-        const placeholder = 'placeholder';
-        testedComponent.placeholder = placeholder;
-        expect(testedComponent.getPlaceholder()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholder() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholder()).toEqual('');
-    });
-
-    it('#changeOperator() should change the operator', () => {
-        expect(testedComponent.operator).toBeUndefined();
-        testedComponent.changeOperator('toto');
-        expect(testedComponent.operator).toBe('toto');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = '=';
-        testedComponent.operator = operator;
-        const value = 'test';
-        testedComponent.field = new FormControl(value);
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts
deleted file mode 100644
index 62a5a611..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { HelpLikeComponent } from './help-like.component';
-
-describe('[Search][Criteria][SearchType] Component: HelpLikeComponent', () => {
-    let component: HelpLikeComponent;
-    let fixture: ComponentFixture<HelpLikeComponent>;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [HelpLikeComponent]
-        });
-        fixture = TestBed.createComponent(HelpLikeComponent);
-        component = fixture.componentInstance;
-    });
-
-    it('should create the component', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts
deleted file mode 100644
index 29767ff5..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { JsonComponent } from './json.component';
-import { JsonCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: JsonComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-json-criteria [criterion]='criterion'></app-json-criteria>`
-    })
-    class TestHostComponent {
-        @ViewChild(JsonComponent, { static: false })
-        public testedComponent: JsonComponent;
-        public criterion: JsonCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: JsonComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [JsonComponent, TestHostComponent],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.jsonForm.controls.path.value).toBeNull();
-        expect(testedComponent.jsonForm.controls.operator.value).toBeNull();
-        expect(testedComponent.jsonForm.controls.value.value).toBeNull();
-        expect(testedComponent.jsonForm.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const path = 'path';
-        const operator = '=';
-        const value = 'test';
-        const criterion = { id: 1, type: 'json', path, operator, value } as JsonCriterion;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.jsonForm.controls.path.value).toEqual(path);
-        expect(testedComponent.jsonForm.controls.operator.value).toEqual(operator);
-        expect(testedComponent.jsonForm.controls.value.value).toEqual(value);
-        expect(testedComponent.jsonForm.disabled).toBeTruthy();
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const path = 'path';
-        const operator = '=';
-        const value = 'test';
-        testedComponent.jsonForm.controls.path.setValue(path);
-        testedComponent.jsonForm.controls.operator.setValue(operator);
-        testedComponent.jsonForm.controls.value.setValue(value);
-        const expectedCriterion = { id: testedComponent.id, type: 'json', path, operator, value } as JsonCriterion;
-        testedComponent.addCriterion.subscribe((event: JsonCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
deleted file mode 100644
index d08f64b7..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { ListComponent } from './list.component';
-import { ListCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: ListComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-list [criterion]='criterion'></app-list>`
-    })
-    class TestHostComponent {
-        @ViewChild(ListComponent, { static: false })
-        public testedComponent: ListComponent;
-        public criterion: ListComponent = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: ListComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                ListComponent,
-                TestHostComponent
-            ],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.list.value).toBeNull();
-        expect(testedComponent.list.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const values = ['1', '2'];
-        const criterion = { id: 1, type: 'list', values } as ListCriterion;
-        const expectedListValues = values.join('\n');
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.list.value).toBe(expectedListValues);
-        expect(testedComponent.list.disabled).toBeTruthy();
-    });
-
-    it('#getPlaceholder() should fill the placeholder if defined', () => {
-        const placeholder = 'placeholder';
-        testedComponent.placeholder = placeholder;
-        expect(testedComponent.getPlaceholder()).toBe(placeholder);
-    });
-
-    it('#getPlaceholder() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholder()).toBe('');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const values = '1\n2';
-        testedComponent.list = new FormControl(values);
-        const expectedCriterion = { id: testedComponent.id, type: 'list', values: ['1', '2'] } as ListCriterion;
-        testedComponent.addCriterion.subscribe((event: ListCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts
deleted file mode 100644
index cec4db09..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { OperatorComponent } from './operator.component';
-
-describe('[Search][Criteria][SearchType] Component: OperatorComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-operator
-                [operator]='operator'
-                [searchType]='searchType'
-                [advancedForm]='advancedForm'
-                [disabled]='disabled'>
-            </app-operator>`
-    })
-    class TestHostComponent {
-        @ViewChild(OperatorComponent, { static: false })
-        testedComponent: OperatorComponent;
-        operator = 'eq';
-        searchType: string = undefined;
-        advancedForm: boolean = undefined;
-        disabled: boolean = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: OperatorComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [OperatorComponent, TestHostComponent],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getLabel() should return the correct operator form label', () => {
-        expect(testedComponent.getLabel('eq')).toBe('=');
-        expect(testedComponent.getLabel('neq')).toBe('≠');
-        expect(testedComponent.getLabel('gt')).toBe('>');
-        expect(testedComponent.getLabel('gte')).toBe('>=');
-        expect(testedComponent.getLabel('lt')).toBe('<');
-        expect(testedComponent.getLabel('lte')).toBe('<=');
-        expect(testedComponent.getLabel('lk')).toBe('like');
-        expect(testedComponent.getLabel('nlk')).toBe('not like');
-    });
-
-    it('raises the changeOperator event when the value change', () => {
-        testedComponent.changeOperator.subscribe((event: string) => expect(event).toEqual('eq'));
-        testedComponent.emitChange('eq');
-    });
-
-    it('should display the select box when it\'s an enabled advanced form', () => {
-        testHostComponent.advancedForm = true;
-        testHostComponent.disabled = false;
-        testHostFixture.detectChanges();
-        const template = testHostFixture.nativeElement;
-        expect(template.querySelector('select')).toBeTruthy();
-        expect(template.querySelector('.readonly')).toBeFalsy();
-    });
-
-    it('should display the readonly field when it\'s not an advanced form or not enabled advanced form', () => {
-        testHostComponent.advancedForm = true;
-        testHostComponent.disabled = true;
-        testHostFixture.detectChanges();
-        const template = testHostFixture.nativeElement;
-        expect(template.querySelector('select')).toBeFalsy();
-        expect(template.querySelector('.readonly')).toBeTruthy();
-        testHostComponent.advancedForm = true;
-        testHostFixture.detectChanges();
-        expect(template.querySelector('select')).toBeFalsy();
-        expect(template.querySelector('.readonly')).toBeTruthy();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
deleted file mode 100644
index e6b5658f..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { Component, ViewChild } from '@angular/core';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { RadioComponent } from './radio.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: RadioComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-radio [criterion]='criterion'></app-radio>`
-    })
-    class TestHostComponent {
-        @ViewChild(RadioComponent, { static: false })
-        public testedComponent: RadioComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: RadioComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [RadioComponent, TestHostComponent],
-            imports: [FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.radio.value).toBeNull();
-        expect(testedComponent.radio.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const operator = '=';
-        const value = 'test';
-        const criterion = { id: 1, type: 'field', operator, value } as FieldCriterion;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.radio.value).toEqual(value);
-        expect(testedComponent.radio.disabled).toBeTruthy();
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = '=';
-        testedComponent.operator = operator;
-        const value = 'three';
-        testedComponent.radio = new FormControl(value);
-        testedComponent.options = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 },
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
deleted file mode 100644
index e6f8ccfd..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { Component, ViewChild } from '@angular/core';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { SelectMultipleComponent } from './select-multiple.component';
-import { SelectMultipleCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: SelectMultipleComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-select-multiple [criterion]='criterion'></app-select-multiple>`
-    })
-    class TestHostComponent {
-        @ViewChild(SelectMultipleComponent, { static: false })
-        public testedComponent: SelectMultipleComponent;
-        public criterion: SelectMultipleCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: SelectMultipleComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [SelectMultipleComponent, TestHostComponent],
-            imports: [NgSelectModule, FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.ms.value).toBeNull();
-        expect(testedComponent.ms.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const options = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 },
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-        const criterion = { id: 1, type: 'multiple', options} as SelectMultipleCriterion;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.ms.value).toEqual(['one', 'two', 'three']);
-        expect(testedComponent.ms.disabled).toBeTruthy();
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.options = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 },
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-        const value = ['three'];
-        testedComponent.ms = new FormControl(value);
-        const expectedValue = [
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-        const expectedCriterion = { id: testedComponent.id, type: 'multiple', options: expectedValue } as SelectMultipleCriterion;
-        testedComponent.addCriterion.subscribe((event: SelectMultipleCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts
deleted file mode 100644
index 05f8edff..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { SelectComponent } from './select.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: SelectComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-select [criterion]='criterion'></app-select>`
-    })
-    class TestHostComponent {
-        @ViewChild(SelectComponent, { static: false })
-        public testedComponent: SelectComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-        @Output() changeOperator: EventEmitter<string> = new EventEmitter();
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: SelectComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                SelectComponent, 
-                TestHostComponent,
-                OperatorStubComponent,
-                HelpLikeStubComponent
-            ],
-            imports: [NgSelectModule, FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.se.value).toBeNull();
-        expect(testedComponent.se.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const operator = '=';
-        const value = 'test';
-        const criterion = { id: 1, type: 'field', operator, value } as FieldCriterion;
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.se.value).toEqual(value);
-        expect(testedComponent.se.disabled).toBeTruthy();
-    });
-
-    it('#changeOperator() should change the operator', () => {
-        expect(testedComponent.operator).toBeUndefined();
-        testedComponent.changeOperator('toto');
-        expect(testedComponent.operator).toBe('toto');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = '=';
-        testedComponent.operator = operator;
-        const value = 'three';
-        testedComponent.se = new FormControl(value);
-        testedComponent.options = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 },
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
deleted file mode 100644
index f93f526c..00000000
--- a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { TimeComponent } from './time.component';
-import { FieldCriterion } from '../../../store/model';
-
-describe('[Search][Criteria][SearchType] Component: TimeComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `<app-time [criterion]='criterion'></app-time>`
-    })
-    class TestHostComponent {
-        @ViewChild(TimeComponent, { static: false })
-        public testedComponent: TimeComponent;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-        @Output() changeOperator: EventEmitter<string> = new EventEmitter();
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: TimeComponent;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                TimeComponent,
-                TestHostComponent,
-                OperatorStubComponent
-            ],
-            imports: [NgSelectModule, FormsModule, ReactiveFormsModule]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    }));
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('#getDefault() should enable and not fill form as criterion not defined in host component', () => {
-        expect(testedComponent.hh.value).toBeNull();
-        expect(testedComponent.hh.enabled).toBeTruthy();
-        expect(testedComponent.mm.value).toBeNull();
-        expect(testedComponent.mm.enabled).toBeTruthy();
-    });
-
-    it('#getDefault() should fill and disable form if criterion is defined', () => {
-        const criterion = { id: 1, type: 'field', operator: 'eq', value: '15:47' } as FieldCriterion;
-        const expectedHour = '15';
-        const expectedMinute = '47';
-        testedComponent.getDefault(criterion);
-        expect(testedComponent.hh.value).toEqual(expectedHour);
-        expect(testedComponent.hh.disabled).toBeTruthy();
-        expect(testedComponent.mm.value).toEqual(expectedMinute);
-        expect(testedComponent.mm.disabled).toBeTruthy();
-    });
-
-    it('#initTime(t) should return an array of string with 2 digits from 0 to t', () => {
-        const n = 10;
-        expect(testedComponent.initTime(n).length).toEqual(n);
-        expect(testedComponent.initTime(n)[5]).toEqual('05');
-    });
-
-    it('#changeOperator() should change the operator', () => {
-        expect(testedComponent.operator).toBeUndefined();
-        testedComponent.changeOperator('toto');
-        expect(testedComponent.operator).toBe('toto');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = 'eq';
-        testedComponent.operator = operator;
-        testedComponent.hh = new FormControl('15');
-        testedComponent.mm = new FormControl('47');
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: '15:47' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('raises the delete criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1));
-        testedComponent.emitDelete();
-    });
-});
diff --git a/client/src/app/instance/search/components/result/download.component.ts b/client/src/app/instance/search/components/result/download.component.ts
index aedba808..729dfe59 100644
--- a/client/src/app/instance/search/components/result/download.component.ts
+++ b/client/src/app/instance/search/components/result/download.component.ts
@@ -13,6 +13,7 @@ import { Criterion, criterionToString } from '../../../store/models';
 import { Dataset } from 'src/app/metamodel/models';
 import { getHost as host } from 'src/app/shared/utils';
 // import { ConeSearch } from '../../../shared/cone-search/store/model';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-download',
@@ -36,6 +37,8 @@ export class DownloadComponent {
     @Input() sampRegistered: boolean;
     @Output() broadcast: EventEmitter<string> = new EventEmitter();
 
+    constructor(private appConfig: AppConfigService) { }
+
     /**
      * Returns dataset label.
      *
@@ -76,7 +79,7 @@ export class DownloadComponent {
      * @return string
      */
     getUrl(format: string): string {
-        let query: string = host() + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';');
+        let query: string = host(this.appConfig.apiUrl) + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';');
         if (this.criteriaList.length > 0) {
             query += '&c=' + this.criteriaList.map(criterion => criterionToString(criterion)).join(';');
         }
@@ -88,7 +91,7 @@ export class DownloadComponent {
     }
 
     getUrlArchive(): string {
-        let query: string = host() + '/archive/' + this.datasetSelected + '?a=' + this.outputList.join(';');
+        let query: string = host(this.appConfig.apiUrl) + '/archive/' + this.datasetSelected + '?a=' + this.outputList.join(';');
         if (this.criteriaList.length > 0) {
             query += '&c=' + this.criteriaList.map(criterion => criterionToString(criterion)).join(';');
         }
diff --git a/client/src/app/instance/search/components/result/url-display.component.ts b/client/src/app/instance/search/components/result/url-display.component.ts
index 469bc9b4..1ed461ba 100644
--- a/client/src/app/instance/search/components/result/url-display.component.ts
+++ b/client/src/app/instance/search/components/result/url-display.component.ts
@@ -14,6 +14,7 @@ import { ToastrService } from 'ngx-toastr';
 import { Criterion, ConeSearch, criterionToString } from 'src/app/instance/store/models';
 import { Dataset } from 'src/app/metamodel/models';
 import { getHost as host } from 'src/app/shared/utils';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-url-display',
@@ -32,7 +33,7 @@ export class UrlDisplayComponent {
     @Input() criteriaList: Criterion[];
     @Input() outputList: number[];
 
-    constructor(private toastr: ToastrService) { }
+    constructor(private toastr: ToastrService, private appConfig: AppConfigService) { }
 
     /**
      * Checks if URL display is enabled.
@@ -55,7 +56,7 @@ export class UrlDisplayComponent {
      * @return string
      */
     getUrl(): string {
-        let query: string = host() + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';');
+        let query: string = host(this.appConfig.apiUrl) + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';');
         if (this.criteriaList.length > 0) {
             query += '&c=' + this.criteriaList.map(criterion => criterionToString(criterion)).join(';');
         }
diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts
index 74fc0a7f..19bcc385 100644
--- a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts
+++ b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts
@@ -11,6 +11,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 
 import { DownloadRendererConfig } from 'src/app/metamodel/models/renderers/download-renderer-config.model';
 import { getHost } from 'src/app/shared/utils';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-download-renderer',
@@ -26,13 +27,15 @@ export class DownloadRendererComponent {
     @Input() datasetName: string;
     @Input() config: DownloadRendererConfig;
 
+    constructor(private appConfig: AppConfigService) { }
+
     /**
      * Returns link href.
      *
      * @return string
      */
     getHref(): string {
-        return getHost() + '/download-file/' + this.datasetName + '/' + this.value;
+        return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetName + '/' + this.value;
     }
 
     /**
diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts b/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts
index 75521e7d..677dfd73 100644
--- a/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts
+++ b/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts
@@ -13,7 +13,7 @@ import { BsModalService } from 'ngx-bootstrap/modal';
 import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
 
 import { ImageRendererConfig } from 'src/app/metamodel/models/renderers';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-image-renderer',
@@ -28,11 +28,10 @@ export class ImageRendererComponent {
     @Input() value: string | number;
     @Input() datasetName: string;
     @Input() config: ImageRendererConfig;
-    private SERVICES_PATH: string = environment.servicesUrl;
 
     modalRef: BsModalRef;
 
-    constructor(private modalService: BsModalService) { }
+    constructor(private modalService: BsModalService, private appConfig: AppConfigService) { }
 
     openModal(template: TemplateRef<any>) {
         this.modalRef = this.modalService.show(template);
@@ -45,7 +44,7 @@ export class ImageRendererComponent {
      */
     getValue(): string {
         if (this.config.type === 'fits') {
-            return `${this.SERVICES_PATH}/fits-to-png/${this.datasetName}?filename=${this.value}`
+            return `${this.appConfig.servicesUrl}/fits-to-png/${this.datasetName}?filename=${this.value}`
                 + `&stretch=linear&pmin=0.25&pmax=99.75&axes=true`;
         } else {
             return this.value as string;
diff --git a/client/src/app/instance/store/services/detail.service.ts b/client/src/app/instance/store/services/detail.service.ts
index e1b5db35..3b946c5b 100644
--- a/client/src/app/instance/store/services/detail.service.ts
+++ b/client/src/app/instance/store/services/detail.service.ts
@@ -12,7 +12,7 @@ import { HttpClient } from '@angular/common/http';
 
 import { Observable } from 'rxjs';
 
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 /**
@@ -20,10 +20,7 @@ import { environment } from 'src/environments/environment';
  * @classdesc Detail service.
  */
 export class DetailService {
-    private API_PATH: string = environment.apiUrl + '/search/';
-    private SERVICES_PATH: string = environment.servicesUrl;
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     /**
      * Retrieves object details for the given parameters.
@@ -37,7 +34,7 @@ export class DetailService {
      */
     retrieveObject(dname: string, criterionId: number, objectSelected: string, outputList: number[]): Observable<any[]> {
         const query = dname + '?c=' + criterionId + '::eq::' + objectSelected + '&a=' + outputList.join(';');
-        return this.http.get<any[]>(this.API_PATH + query);
+        return this.http.get<any[]>(this.config.apiUrl + '/search/' + query);
     }
 
     /**
@@ -48,6 +45,6 @@ export class DetailService {
      * @return Observable<string>
      */
     retrieveSpectra(dname: string, spectraFile: string): Observable<string> {
-        return this.http.get(this.SERVICES_PATH + '/spectra-to-csv/' + dname + '?filename=' + spectraFile, { responseType: 'text' });
+        return this.http.get(this.config.servicesUrl + '/spectra-to-csv/' + dname + '?filename=' + spectraFile, { responseType: 'text' });
     }
 }
diff --git a/client/src/app/instance/store/services/samp.service.ts b/client/src/app/instance/store/services/samp.service.ts
index c4a8a9db..1429c9bf 100644
--- a/client/src/app/instance/store/services/samp.service.ts
+++ b/client/src/app/instance/store/services/samp.service.ts
@@ -11,7 +11,7 @@ import { Injectable } from '@angular/core';
 
 import { Observable } from 'rxjs';
 
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 declare var samp: any;
 
@@ -23,8 +23,8 @@ declare var samp: any;
 export class SampService {
     private connector = null;
     
-    constructor() {
-        const baseUrl = window.location.protocol + "//" + window.location.host + environment.baseHref;
+    constructor(private config: AppConfigService) {
+        const baseUrl = window.location.protocol + "//" + window.location.host + this.config.baseHref;
         const meta = {
             "samp.name": "ANIS",
             "samp.description.text": "AstroNomical Information System",
diff --git a/client/src/app/instance/store/services/search.service.ts b/client/src/app/instance/store/services/search.service.ts
index 7f532131..17181432 100644
--- a/client/src/app/instance/store/services/search.service.ts
+++ b/client/src/app/instance/store/services/search.service.ts
@@ -12,7 +12,7 @@ import { HttpClient } from '@angular/common/http';
 
 import { Observable } from 'rxjs';
 
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 /**
@@ -20,9 +20,7 @@ import { environment } from 'src/environments/environment';
  * @classdesc Search service.
  */
 export class SearchService {
-    API_PATH: string = environment.apiUrl;
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     /**
      * Retrieves results for the given parameters.
@@ -32,7 +30,7 @@ export class SearchService {
      * @return Observable<any[]>
      */
     retrieveData(query: string): Observable<any[]> {
-        return this.http.get<any[]>(this.API_PATH + '/search/' + query);
+        return this.http.get<any[]>(this.config.apiUrl + '/search/' + query);
     }
 
     /**
@@ -43,6 +41,6 @@ export class SearchService {
      * @return Observable<{ nb: number }[]>
      */
     retrieveDataLength(query: string): Observable<{ nb: number }[]> {
-        return this.http.get<{ nb: number }[]>(this.API_PATH + '/search/' + query);
+        return this.http.get<{ nb: number }[]>(this.config.apiUrl + '/search/' + query);
     }
 }
diff --git a/client/src/app/metamodel/services/attribute-distinct.service.ts b/client/src/app/metamodel/services/attribute-distinct.service.ts
index bc89ba7c..e5466159 100644
--- a/client/src/app/metamodel/services/attribute-distinct.service.ts
+++ b/client/src/app/metamodel/services/attribute-distinct.service.ts
@@ -13,15 +13,13 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Attribute } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class AttributeDistinctService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveAttributeDistinctList(datasetName: string, attribute: Attribute): Observable<string[]> {
-        return this.http.get<string[]>(this.API_PATH + 'dataset/' + datasetName + '/attribute/' + attribute.id + '/distinct');
+        return this.http.get<string[]>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute/' + attribute.id + '/distinct');
     }
 }
diff --git a/client/src/app/metamodel/services/attribute.service.ts b/client/src/app/metamodel/services/attribute.service.ts
index 08c84d6c..9c2583c7 100644
--- a/client/src/app/metamodel/services/attribute.service.ts
+++ b/client/src/app/metamodel/services/attribute.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Attribute } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class AttributeService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveAttributeList(datasetName: string): Observable<Attribute[]> {
-        return this.http.get<Attribute[]>(this.API_PATH + 'dataset/' + datasetName + '/attribute');
+        return this.http.get<Attribute[]>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute');
     }
 
     addAttribute(datasetName: string, attribute: Attribute): Observable<Attribute> {
-        return this.http.post<Attribute>(this.API_PATH + 'dataset/' + datasetName + '/attribute', attribute);
+        return this.http.post<Attribute>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute', attribute);
     }
 
     editAttribute(datasetName: string, attribute: Attribute): Observable<Attribute> {
-        return this.http.put<Attribute>(this.API_PATH + 'dataset/' + datasetName + '/attribute/' + attribute.id, attribute);
+        return this.http.put<Attribute>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute/' + attribute.id, attribute);
     }
 
     deleteAttribute(datasetName: string, attribute: Attribute) {
-        return this.http.delete(this.API_PATH + 'dataset/' + datasetName + '/attribute/' + attribute.id); 
+        return this.http.delete(this.config.apiUrl + '/dataset/' + datasetName + '/attribute/' + attribute.id); 
     }
 }
diff --git a/client/src/app/metamodel/services/column.service.ts b/client/src/app/metamodel/services/column.service.ts
index 881006be..0da2af53 100644
--- a/client/src/app/metamodel/services/column.service.ts
+++ b/client/src/app/metamodel/services/column.service.ts
@@ -13,15 +13,13 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Column } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class ColumnService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveColumns(datasetName: string): Observable<Column[]> {
-        return this.http.get<Column[]>(this.API_PATH + 'dataset/' + datasetName + '/column');
+        return this.http.get<Column[]>(this.config.apiUrl + '/dataset/' + datasetName + '/column');
     }
 }
diff --git a/client/src/app/metamodel/services/criteria-family.service.ts b/client/src/app/metamodel/services/criteria-family.service.ts
index 32e2ecfa..09d6dd21 100644
--- a/client/src/app/metamodel/services/criteria-family.service.ts
+++ b/client/src/app/metamodel/services/criteria-family.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { CriteriaFamily } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class CriteriaFamilyService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveCriteriaFamilyList(datasetName: string): Observable<CriteriaFamily[]> {
-        return this.http.get<CriteriaFamily[]>(this.API_PATH + 'dataset/' + datasetName + '/criteria-family');
+        return this.http.get<CriteriaFamily[]>(this.config.apiUrl + '/dataset/' + datasetName + '/criteria-family');
     }
 
     addCriteriaFamily(datasetName: string, newCriteriaFamily: CriteriaFamily): Observable<CriteriaFamily> {
-        return this.http.post<CriteriaFamily>(this.API_PATH + 'dataset/' + datasetName + '/criteria-family', newCriteriaFamily);
+        return this.http.post<CriteriaFamily>(this.config.apiUrl + '/dataset/' + datasetName + '/criteria-family', newCriteriaFamily);
     }
 
     editCriteriaFamily(criteriaFamily: CriteriaFamily): Observable<CriteriaFamily> {
-        return this.http.put<CriteriaFamily>(this.API_PATH + 'criteria-family/' + criteriaFamily.id, criteriaFamily);
+        return this.http.put<CriteriaFamily>(this.config.apiUrl + '/criteria-family/' + criteriaFamily.id, criteriaFamily);
     }
 
     deleteCriteriaFamily(criteriaFamilyId: number) {
-        return this.http.delete(this.API_PATH + 'criteria-family/' + criteriaFamilyId);
+        return this.http.delete(this.config.apiUrl + '/criteria-family/' + criteriaFamilyId);
     }
 }
diff --git a/client/src/app/metamodel/services/database.service.ts b/client/src/app/metamodel/services/database.service.ts
index 567254fd..c569d351 100644
--- a/client/src/app/metamodel/services/database.service.ts
+++ b/client/src/app/metamodel/services/database.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Database } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class DatabaseService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveDatabaseList(): Observable<Database[]> {
-        return this.http.get<Database[]>(this.API_PATH + 'database');
+        return this.http.get<Database[]>(this.config.apiUrl + '/database');
     }
 
     addDatabase(newDatabase: Database): Observable<Database> {
-        return this.http.post<Database>(this.API_PATH + 'database', newDatabase);
+        return this.http.post<Database>(this.config.apiUrl + '/database', newDatabase);
     }
 
     editDatabase(database: Database): Observable<Database> {
-        return this.http.put<Database>(this.API_PATH + 'database/' + database.id, database);
+        return this.http.put<Database>(this.config.apiUrl + '/database/' + database.id, database);
     }
 
     deleteDatabase(databaseId: number) {
-        return this.http.delete(this.API_PATH + 'database/' + databaseId);
+        return this.http.delete(this.config.apiUrl + '/database/' + databaseId);
     }
 }
diff --git a/client/src/app/metamodel/services/dataset-family.service.ts b/client/src/app/metamodel/services/dataset-family.service.ts
index 490a14af..96fc3446 100644
--- a/client/src/app/metamodel/services/dataset-family.service.ts
+++ b/client/src/app/metamodel/services/dataset-family.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { DatasetFamily } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class DatasetFamilyService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveDatasetFamilyList(instanceName: string): Observable<DatasetFamily[]> {
-        return this.http.get<DatasetFamily[]>(this.API_PATH + 'instance/' + instanceName + '/dataset-family');
+        return this.http.get<DatasetFamily[]>(this.config.apiUrl + '/instance/' + instanceName + '/dataset-family');
     }
 
     addDatasetFamily(instanceName: string, newDatasetFamily: DatasetFamily): Observable<DatasetFamily> {
-        return this.http.post<DatasetFamily>(this.API_PATH + 'instance/' + instanceName + '/dataset-family', newDatasetFamily);
+        return this.http.post<DatasetFamily>(this.config.apiUrl + '/instance/' + instanceName + '/dataset-family', newDatasetFamily);
     }
 
     editDatasetFamily(datasetFamily: DatasetFamily): Observable<DatasetFamily> {
-        return this.http.put<DatasetFamily>(this.API_PATH + 'dataset-family/' + datasetFamily.id, datasetFamily);
+        return this.http.put<DatasetFamily>(this.config.apiUrl + '/dataset-family/' + datasetFamily.id, datasetFamily);
     }
 
     deleteDatasetFamily(datasetFamilyId: number) {
-        return this.http.delete(this.API_PATH + 'dataset-family/' + datasetFamilyId);
+        return this.http.delete(this.config.apiUrl + '/dataset-family/' + datasetFamilyId);
     }
 }
diff --git a/client/src/app/metamodel/services/dataset.service.ts b/client/src/app/metamodel/services/dataset.service.ts
index 4b7c9b83..e8109448 100644
--- a/client/src/app/metamodel/services/dataset.service.ts
+++ b/client/src/app/metamodel/services/dataset.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Dataset } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class DatasetService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveDatasetList(instanceName: string): Observable<Dataset[]> {
-        return this.http.get<Dataset[]>(this.API_PATH + 'instance/' + instanceName + '/dataset');
+        return this.http.get<Dataset[]>(this.config.apiUrl + '/instance/' + instanceName + '/dataset');
     }
 
     addDataset(newDataset: Dataset): Observable<Dataset> {
-        return this.http.post<Dataset>(this.API_PATH + 'dataset-family/' + newDataset.id_dataset_family + '/dataset', newDataset);
+        return this.http.post<Dataset>(this.config.apiUrl + '/dataset-family/' + newDataset.id_dataset_family + '/dataset', newDataset);
     }
 
     editDataset(dataset: Dataset): Observable<Dataset> {
-        return this.http.put<Dataset>(this.API_PATH + 'dataset/' + dataset.name, dataset);
+        return this.http.put<Dataset>(this.config.apiUrl + '/dataset/' + dataset.name, dataset);
     }
 
     deleteDataset(datasetName: string) {
-        return this.http.delete(this.API_PATH + 'dataset/' + datasetName);
+        return this.http.delete(this.config.apiUrl + '/dataset/' + datasetName);
     }
 }
diff --git a/client/src/app/metamodel/services/group.service.ts b/client/src/app/metamodel/services/group.service.ts
index c4cbdde6..b83489be 100644
--- a/client/src/app/metamodel/services/group.service.ts
+++ b/client/src/app/metamodel/services/group.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Group } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class GroupService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveGroupList(instanceName: string): Observable<Group[]> {
-        return this.http.get<Group[]>(this.API_PATH + 'instance/' + instanceName +  '/group');
+        return this.http.get<Group[]>(this.config.apiUrl + '/instance/' + instanceName +  '/group');
     }
 
     addGroup(instanceName: string, newGroup: Group): Observable<Group> {
-        return this.http.post<Group>(this.API_PATH + 'instance/' + instanceName + '/group', newGroup);
+        return this.http.post<Group>(this.config.apiUrl + '/instance/' + instanceName + '/group', newGroup);
     }
 
     editGroup(group: Group): Observable<Group> {
-        return this.http.put<Group>(this.API_PATH + 'group/' + group.id, group);
+        return this.http.put<Group>(this.config.apiUrl + '/group/' + group.id, group);
     }
 
     deleteGroup(groupId: number) {
-        return this.http.delete(this.API_PATH + 'group/' + groupId);
+        return this.http.delete(this.config.apiUrl + '/group/' + groupId);
     }
 }
diff --git a/client/src/app/metamodel/services/instance.service.ts b/client/src/app/metamodel/services/instance.service.ts
index a5f4ab0e..d90f7f15 100644
--- a/client/src/app/metamodel/services/instance.service.ts
+++ b/client/src/app/metamodel/services/instance.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Instance } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class InstanceService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveInstanceList(): Observable<Instance[]> {
-        return this.http.get<Instance[]>(this.API_PATH + 'instance');
+        return this.http.get<Instance[]>(this.config.apiUrl + '/instance');
     }
 
     addInstance(newInstance: Instance): Observable<Instance> {
-        return this.http.post<Instance>(this.API_PATH + 'instance', newInstance);
+        return this.http.post<Instance>(this.config.apiUrl + '/instance', newInstance);
     }
 
     editInstance(instance: Instance): Observable<Instance> {
-        return this.http.put<Instance>(this.API_PATH + 'instance/' + instance.name, instance);
+        return this.http.put<Instance>(this.config.apiUrl + '/instance/' + instance.name, instance);
     }
 
     deleteInstance(instanceName: string) {
-        return this.http.delete(this.API_PATH + 'instance/' + instanceName);
+        return this.http.delete(this.config.apiUrl + '/instance/' + instanceName);
     }
 }
diff --git a/client/src/app/metamodel/services/output-category.service.ts b/client/src/app/metamodel/services/output-category.service.ts
index 2d35525d..5cc3e49d 100644
--- a/client/src/app/metamodel/services/output-category.service.ts
+++ b/client/src/app/metamodel/services/output-category.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { OutputCategory } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class OutputCategoryService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveOutputCategoryList(datasetName: string): Observable<OutputCategory[]> {
-        return this.http.get<OutputCategory[]>(this.API_PATH + 'dataset/' + datasetName + '/output-category');
+        return this.http.get<OutputCategory[]>(this.config.apiUrl + '/dataset/' + datasetName + '/output-category');
     }
 
     addOutputCategory(newOutputCategory: OutputCategory): Observable<OutputCategory> {
-        return this.http.post<OutputCategory>(this.API_PATH + 'output-family/' + newOutputCategory.id_output_family + '/output-category', newOutputCategory);
+        return this.http.post<OutputCategory>(this.config.apiUrl + '/output-family/' + newOutputCategory.id_output_family + '/output-category', newOutputCategory);
     }
 
     editOutputCategory(outputCategory: OutputCategory): Observable<OutputCategory> {
-        return this.http.put<OutputCategory>(this.API_PATH + 'output-category/' + outputCategory.id, outputCategory);
+        return this.http.put<OutputCategory>(this.config.apiUrl + '/output-category/' + outputCategory.id, outputCategory);
     }
 
     deleteOutputCategory(outputCategoryId: number) {
-        return this.http.delete(this.API_PATH + 'output-category/' + outputCategoryId);
+        return this.http.delete(this.config.apiUrl + '/output-category/' + outputCategoryId);
     }
 }
diff --git a/client/src/app/metamodel/services/output-family.service.ts b/client/src/app/metamodel/services/output-family.service.ts
index 61494ed2..825e1919 100644
--- a/client/src/app/metamodel/services/output-family.service.ts
+++ b/client/src/app/metamodel/services/output-family.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { OutputFamily } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class OutputFamilyService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveOutputFamilyList(datasetName: string): Observable<OutputFamily[]> {
-        return this.http.get<OutputFamily[]>(this.API_PATH + 'dataset/' + datasetName + '/output-family');
+        return this.http.get<OutputFamily[]>(this.config.apiUrl + '/dataset/' + datasetName + '/output-family');
     }
 
     addOutputFamily(datasetName: string, newOutputFamily: OutputFamily): Observable<OutputFamily> {
-        return this.http.post<OutputFamily>(this.API_PATH + 'dataset/' + datasetName + '/output-family', newOutputFamily);
+        return this.http.post<OutputFamily>(this.config.apiUrl + '/dataset/' + datasetName + '/output-family', newOutputFamily);
     }
 
     editOutputFamily(criteriaFamily: OutputFamily): Observable<OutputFamily> {
-        return this.http.put<OutputFamily>(this.API_PATH + 'output-family/' + criteriaFamily.id, criteriaFamily);
+        return this.http.put<OutputFamily>(this.config.apiUrl + '/output-family/' + criteriaFamily.id, criteriaFamily);
     }
 
     deleteOutputFamily(outputFamilyId: number) {
-        return this.http.delete(this.API_PATH + 'output-family/' + outputFamilyId);
+        return this.http.delete(this.config.apiUrl + '/output-family/' + outputFamilyId);
     }
 }
diff --git a/client/src/app/metamodel/services/root-directory.service.ts b/client/src/app/metamodel/services/root-directory.service.ts
index c34f2c41..d173f00d 100644
--- a/client/src/app/metamodel/services/root-directory.service.ts
+++ b/client/src/app/metamodel/services/root-directory.service.ts
@@ -13,15 +13,13 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { FileInfo } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class RootDirectoryService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveRootDirectory(path: string): Observable<FileInfo[]> {
-        return this.http.get<FileInfo[]>(this.API_PATH + 'file-explorer/' + path);
+        return this.http.get<FileInfo[]>(this.config.apiUrl + '/file-explorer/' + path);
     }
 }
diff --git a/client/src/app/metamodel/services/select-option.service.ts b/client/src/app/metamodel/services/select-option.service.ts
index 07012cfa..e18a0dd3 100644
--- a/client/src/app/metamodel/services/select-option.service.ts
+++ b/client/src/app/metamodel/services/select-option.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { SelectOption } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class SelectOptionService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveSelectOptionList(): Observable<SelectOption[]> {
-        return this.http.get<SelectOption[]>(this.API_PATH + 'option');
+        return this.http.get<SelectOption[]>(this.config.apiUrl + '/option');
     }
 
     addSelectOption(settingsSelectOption: SelectOption): Observable<SelectOption> {
-        return this.http.post<SelectOption>(this.API_PATH + 'option', settingsSelectOption);
+        return this.http.post<SelectOption>(this.config.apiUrl + '/option', settingsSelectOption);
     }
 
     editSelectOption(settingsSelectOption: SelectOption): Observable<SelectOption> {
-        return this.http.put<SelectOption>(this.API_PATH + 'option/' + settingsSelectOption.id, settingsSelectOption);
+        return this.http.put<SelectOption>(this.config.apiUrl + '/option/' + settingsSelectOption.id, settingsSelectOption);
     }
 
     deleteSelectOption(id: number) {
-        return this.http.delete(this.API_PATH + 'option/' + id);
+        return this.http.delete(this.config.apiUrl + '/option/' + id);
     }
 }
diff --git a/client/src/app/metamodel/services/select.service.ts b/client/src/app/metamodel/services/select.service.ts
index e88b7289..b146a874 100644
--- a/client/src/app/metamodel/services/select.service.ts
+++ b/client/src/app/metamodel/services/select.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Select } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class SelectService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveSelectList(): Observable<Select[]> {
-        return this.http.get<Select[]>(this.API_PATH + 'select');
+        return this.http.get<Select[]>(this.config.apiUrl + '/select');
     }
 
     addSelect(select: Select): Observable<Select> {
-        return this.http.post<Select>(this.API_PATH + 'select', select);
+        return this.http.post<Select>(this.config.apiUrl + '/select', select);
     }
 
     editSelect(select: Select): Observable<Select> {
-        return this.http.put<Select>(this.API_PATH + 'select/' + select.name, select);
+        return this.http.put<Select>(this.config.apiUrl + '/select/' + select.name, select);
     }
 
     deleteSelect(name: string) {
-        return this.http.delete(this.API_PATH + 'select/' + name);
+        return this.http.delete(this.config.apiUrl + '/select/' + name);
     }
 }
diff --git a/client/src/app/metamodel/services/survey.service.ts b/client/src/app/metamodel/services/survey.service.ts
index 6f482874..faf0c1ea 100644
--- a/client/src/app/metamodel/services/survey.service.ts
+++ b/client/src/app/metamodel/services/survey.service.ts
@@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { Survey } from '../models';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class SurveyService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveSurveyList(): Observable<Survey[]> {
-        return this.http.get<Survey[]>(this.API_PATH + 'survey');
+        return this.http.get<Survey[]>(this.config.apiUrl + '/survey');
     }
 
     addSurvey(newSurvey: Survey): Observable<Survey> {
-        return this.http.post<Survey>(this.API_PATH + 'survey', newSurvey);
+        return this.http.post<Survey>(this.config.apiUrl + '/survey', newSurvey);
     }
 
     editSurvey(survey: Survey): Observable<Survey> {
-        return this.http.put<Survey>(this.API_PATH + 'survey/' + survey.name, survey);
+        return this.http.put<Survey>(this.config.apiUrl + '/survey/' + survey.name, survey);
     }
 
     deleteSurvey(surveyName: string) {
-        return this.http.delete(this.API_PATH + 'survey/' + surveyName);
+        return this.http.delete(this.config.apiUrl + '/survey/' + surveyName);
     }
 }
diff --git a/client/src/app/metamodel/services/table.service.ts b/client/src/app/metamodel/services/table.service.ts
index 96625dfd..3a3722b7 100644
--- a/client/src/app/metamodel/services/table.service.ts
+++ b/client/src/app/metamodel/services/table.service.ts
@@ -12,15 +12,13 @@ import { HttpClient } from '@angular/common/http';
 
 import { Observable } from 'rxjs';
 
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Injectable()
 export class TableService {
-    private API_PATH: string = environment.apiUrl + '/';
-
-    constructor(private http: HttpClient) { }
+    constructor(private http: HttpClient, private config: AppConfigService) { }
 
     retrieveTableList(idDatabase: number): Observable<string[]> {
-        return this.http.get<string[]>(this.API_PATH + 'database/' + idDatabase + '/table');
+        return this.http.get<string[]>(this.config.apiUrl + '/database/' + idDatabase + '/table');
     }
 }
diff --git a/client/src/app/portal/containers/portal-home.component.html b/client/src/app/portal/containers/portal-home.component.html
index 16f93da0..7fab8209 100644
--- a/client/src/app/portal/containers/portal-home.component.html
+++ b/client/src/app/portal/containers/portal-home.component.html
@@ -3,6 +3,8 @@
         [links]="links"
         [isAuthenticated]="isAuthenticated | async"
         [userProfile]="userProfile | async"
+        [baseHref]="getBaseHref()"
+        [authenticationEnabled]="getAuthenticationEnabled()"
         (login)="login()"
         (logout)="logout()"
         (openEditProfile)="openEditProfile()">
diff --git a/client/src/app/portal/containers/portal-home.component.ts b/client/src/app/portal/containers/portal-home.component.ts
index 49c13a47..4405a972 100644
--- a/client/src/app/portal/containers/portal-home.component.ts
+++ b/client/src/app/portal/containers/portal-home.component.ts
@@ -17,7 +17,7 @@ import * as authActions from 'src/app/auth/auth.actions';
 import * as authSelector from 'src/app/auth/auth.selector';
 import * as instanceActions from 'src/app/metamodel/actions/instance.actions';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
-import { environment } from 'src/environments/environment';
+import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
     selector: 'app-portal-home',
@@ -42,7 +42,7 @@ export class PortalHomeComponent implements OnInit, OnDestroy {
 
     public userRolesSubscription: Subscription;
 
-    constructor(private store: Store<{ }>) {
+    constructor(private store: Store<{ }>, private config: AppConfigService) {
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
@@ -54,17 +54,25 @@ export class PortalHomeComponent implements OnInit, OnDestroy {
     ngOnInit() {
         this.store.dispatch(instanceActions.loadInstanceList());
         const adminLink = { label: 'Admin', icon: 'fas fa-tools', routerLink: '/admin' };
-        if (!environment.authenticationEnabled) {
+        if (!this.config.authenticationEnabled) {
             this.links.push(adminLink);
         } else {
             this.userRolesSubscription = this.userRoles.subscribe(userRoles => {
-                if (userRoles.includes(environment.adminRole)) {
+                if (userRoles.includes(this.config.adminRole)) {
                     this.links.push(adminLink);
                 }
             });
         }
     }
 
+    getBaseHref() {
+        return this.config.baseHref;
+    }
+
+    getAuthenticationEnabled() {
+        return this.config.authenticationEnabled;
+    }
+    
     login(): void {
         this.store.dispatch(authActions.login());
     }
diff --git a/client/src/app/shared/components/navbar.component.ts b/client/src/app/shared/components/navbar.component.ts
index c88f7420..733e5578 100644
--- a/client/src/app/shared/components/navbar.component.ts
+++ b/client/src/app/shared/components/navbar.component.ts
@@ -10,7 +10,6 @@
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
 
 import { UserProfile } from 'src/app/auth/user-profile.model';
-import { environment } from 'src/environments/environment'
 
 @Component({
     selector: 'app-navbar',
@@ -22,10 +21,9 @@ export class NavbarComponent {
     @Input() links: {label: string, icon: string, routerLink: string}[];
     @Input() isAuthenticated: boolean;
     @Input() userProfile: UserProfile = null;
+    @Input() baseHref: string;
+    @Input() authenticationEnabled: boolean;
     @Output() login: EventEmitter<any> = new EventEmitter();
     @Output() logout: EventEmitter<any> = new EventEmitter();
     @Output() openEditProfile: EventEmitter<any> = new EventEmitter();
-    
-    baseHref: string = environment.baseHref;
-    authenticationEnabled: boolean = environment.authenticationEnabled;
 }
diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts
index d5250e7a..b43ae07d 100644
--- a/client/src/app/shared/utils.ts
+++ b/client/src/app/shared/utils.ts
@@ -1,5 +1,3 @@
-import { environment } from 'src/environments/environment';
-
 /**
  * Returns strict url address.
  *
@@ -8,12 +6,12 @@ import { environment } from 'src/environments/environment';
  * @example
  * const url: string = getHost() + '/following-url/';
  */
-export const getHost = (): string => {
-    if (!environment.apiUrl.startsWith('http')) {
+export const getHost = (apiUrl: string): string => {
+    if (!apiUrl.startsWith('http')) {
         const url = window.location;
-        return url.protocol + '//' + url.host + environment.apiUrl;
+        return url.protocol + '//' + url.host + apiUrl;
     }
-    return environment.apiUrl;
+    return apiUrl;
 }
 
 /**
diff --git a/client/src/assets/app.config.json b/client/src/assets/app.config.json
new file mode 100644
index 00000000..0270c50c
--- /dev/null
+++ b/client/src/assets/app.config.json
@@ -0,0 +1,10 @@
+{
+    "apiUrl": "http://localhost:8080",
+    "servicesUrl": "http://localhost:5000",
+    "baseHref": "/",
+    "authenticationEnabled": true,
+    "ssoAuthUrl": "http://localhost:8180/auth",
+    "ssoRealm": "anis",
+    "ssoClientId": "anis-client",
+    "adminRole": "anis_admin"
+}
\ No newline at end of file
diff --git a/client/src/environments/environment.prod.ts b/client/src/environments/environment.prod.ts
index e1cbd3d4..5d083316 100644
--- a/client/src/environments/environment.prod.ts
+++ b/client/src/environments/environment.prod.ts
@@ -1,11 +1,3 @@
 export const environment = {
-    production: true,
-    apiUrl: '/server',
-    servicesUrl: '/services',
-    baseHref: '/',
-    authenticationEnabled: true,
-    ssoAuthUrl: 'https://keycloak.lam.fr/auth/',
-    ssoRealm: 'anis',
-    ssoClientId: 'anis-dev',
-    adminRole: 'anis_admin'
+    production: true
 };
diff --git a/client/src/environments/environment.ts b/client/src/environments/environment.ts
index 2a90c74f..458476a4 100644
--- a/client/src/environments/environment.ts
+++ b/client/src/environments/environment.ts
@@ -3,15 +3,7 @@
 // The list of file replacements can be found in `angular.json`.
 
 export const environment = {
-    production: false,
-    apiUrl: 'http://localhost:8080',
-    servicesUrl: 'http://localhost:5000',
-    baseHref: '/',
-    authenticationEnabled: true,
-    ssoAuthUrl: 'http://localhost:8180/auth',
-    ssoRealm: 'anis',
-    ssoClientId: 'anis-client',
-    adminRole: 'anis_admin'
+    production: false
 };
 
 /*
-- 
GitLab