diff --git a/client/src/app/instance/search/components/criteria/abstract-search-type.component.ts b/client/src/app/instance/search/components/criteria/abstract-search-type.component.ts
deleted file mode 100644
index 3f2f316803480b304f57ca1cb767b3434f2d8995..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/abstract-search-type.component.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface AbstractSearchTypeComponent {
-    data: any;
-}
diff --git a/client/src/app/instance/search/components/criteria/criteria-by-family.component.html b/client/src/app/instance/search/components/criteria/criteria-by-family.component.html
index cdf6dbf0fd601d2b8c5a9cd2d1eaddebcec666ac..fa4bc0823c2528089f3fa980ec930dacec8997eb 100644
--- a/client/src/app/instance/search/components/criteria/criteria-by-family.component.html
+++ b/client/src/app/instance/search/components/criteria/criteria-by-family.component.html
@@ -2,6 +2,7 @@
     <app-criterion 
         [attribute]="attribute"
         [criterion]="getCriterion(attribute.id)"
+        [criteriaList]="criteriaList"
         (addCriterion)="emitAdd($event)"
         (deleteCriterion)="emitDelete($event)">
     </app-criterion>
diff --git a/client/src/app/instance/search/components/criteria/criterion.component.html b/client/src/app/instance/search/components/criteria/criterion.component.html
index 3a9c1c003dd5b2b390d676d2ff866dc1c8328e9b..f2229d60c77545672ca338be7804676419533b60 100644
--- a/client/src/app/instance/search/components/criteria/criterion.component.html
+++ b/client/src/app/instance/search/components/criteria/criterion.component.html
@@ -5,12 +5,12 @@
         </label>
         <ng-template searchType></ng-template>
     </div>
-    <div class="col-2 text-center align-self-end pb-3">
-        <!-- <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
+    <div class="col-2 text-center align-self-center">
+        <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!searchTypeComponent.isValid()" (click)="emitAdd()">
             <span class="fas fa-plus fa-fw"></span>
         </button>
         <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
             <span class="fa fa-times fa-fw"></span>
-        </button> -->
+        </button>
     </div>
 </div>
diff --git a/client/src/app/instance/search/components/criteria/criterion.component.ts b/client/src/app/instance/search/components/criteria/criterion.component.ts
index fc2f97fd5ae975d3703e00272d0472b92f49b4f1..6145ccb5a8bd5f777ad765cec2fe1efa35d61f80 100644
--- a/client/src/app/instance/search/components/criteria/criterion.component.ts
+++ b/client/src/app/instance/search/components/criteria/criterion.component.ts
@@ -7,30 +7,54 @@
  * file that was distributed with this source code.
  */
 
-import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ContentChild } from '@angular/core';
+import { Component, Input, Output, EventEmitter, ViewChild, SimpleChanges, OnInit, OnChanges } from '@angular/core';
 
 import { Attribute } from 'src/app/metamodel/models';
 import { Criterion } from 'src/app/instance/store/models';
-import { SearchTypeLoaderDirective } from './search-type-loader.directive';
-import { AbstractSearchTypeComponent } from './abstract-search-type.component';
-import { TestSearchTypeComponent } from './test-search-type.component';
+import { SearchTypeLoaderDirective } from './search-type/search-type-loader.directive';
+import { AbstractSearchTypeComponent } from './search-type/abstract-search-type.component';
 import { FieldComponent } from './search-type/field.component';
+import { BetweenComponent } from './search-type/between.component';
+import { SelectComponent } from './search-type/select.component';
+import { SelectMultipleComponent } from './search-type/select-multiple.component';
+import { DatalistComponent } from './search-type/datalist.component';
+import { ListComponent } from './search-type/list.component';
+import { RadioComponent } from './search-type/radio.component';
+import { CheckboxComponent } from './search-type/checkbox.component';
+import { BetweenDateComponent } from './search-type/between-date.component';
+import { DateComponent } from './search-type/date.component';
+import { TimeComponent } from './search-type/time.component';
+import { DateTimeComponent } from './search-type/datetime.component';
+import { JsonComponent } from './search-type/json.component';
 
 @Component({
     selector: 'app-criterion',
     templateUrl: 'criterion.component.html'
 })
-export class CriterionComponent {
+export class CriterionComponent implements OnInit, OnChanges {
     @Input() attribute: Attribute;
     @Input() criterion: Criterion;
+    @Input() criteriaList: Criterion[];
     @Output() addCriterion: EventEmitter<Criterion> = new EventEmitter();
     @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
 
-    @ViewChild(SearchTypeLoaderDirective, {static: true}) searchType!: SearchTypeLoaderDirective;
+    @ViewChild(SearchTypeLoaderDirective, {static: true}) SearchTypeLoaderDirective!: SearchTypeLoaderDirective;
+
+    public searchTypeComponent: AbstractSearchTypeComponent;
 
     ngOnInit() {
-        const componentRef = this.searchType.viewContainerRef.createComponent<AbstractSearchTypeComponent>(TestSearchTypeComponent);
-        componentRef.instance.data = { text: 'Bonjour Yannick !' };
+        const componentRef = this.SearchTypeLoaderDirective.viewContainerRef.createComponent<AbstractSearchTypeComponent>(this.getSearchTypeComponent());
+        console.log(componentRef.instance);
+        componentRef.instance.setAttribute(this.attribute);
+        componentRef.instance.setCriterion(this.criterion);
+        componentRef.instance.setCriteriaList(this.criteriaList);
+        this.searchTypeComponent = componentRef.instance;
+    }
+
+    ngOnChanges(changes: SimpleChanges): void {
+        if (changes.criterion && !changes.criterion.firstChange) {
+            this.searchTypeComponent.setCriterion(changes.criterion.currentValue);
+        }
     }
 
     /**
@@ -38,7 +62,70 @@ export class CriterionComponent {
      *
      * @fires EventEmitter<Criterion>
      */
-    emitAdd(criterion: Criterion): void {
-        this.addCriterion.emit(criterion);
+    emitAdd(): void {
+        this.addCriterion.emit(this.searchTypeComponent.getCriterion());
+    }
+
+    getSearchTypeComponent() {
+        let nameOfSearchTypeComponent = null;
+        switch(this.attribute.search_type) { 
+            case 'field': {
+                nameOfSearchTypeComponent = FieldComponent;
+                break;
+            }
+            case 'between': {
+                nameOfSearchTypeComponent = BetweenComponent;
+                break;
+            }
+            case 'select': {
+                nameOfSearchTypeComponent = SelectComponent;
+                break;
+            }
+            case 'select-multiple': {
+                nameOfSearchTypeComponent = SelectMultipleComponent;
+                break;
+            }
+            case 'datalist': {
+                nameOfSearchTypeComponent = DatalistComponent;
+                break;
+            }
+            case 'list': {
+                nameOfSearchTypeComponent = ListComponent;
+                break;
+            }
+            case 'radio': {
+                nameOfSearchTypeComponent = RadioComponent;
+                break;
+            }
+            case 'checkbox': {
+                nameOfSearchTypeComponent = CheckboxComponent;
+                break;
+            }
+            case 'between-date': {
+                nameOfSearchTypeComponent = BetweenDateComponent;
+                break;
+            }
+            case 'date': {
+                nameOfSearchTypeComponent = DateComponent;
+                break;
+            }
+            case 'time': {
+                nameOfSearchTypeComponent = TimeComponent;
+                break;
+            }
+            case 'date-time': {
+                nameOfSearchTypeComponent = DateTimeComponent;
+                break;
+            }
+            case 'json': {
+                nameOfSearchTypeComponent = JsonComponent;
+                break;
+            }
+            default: {
+                nameOfSearchTypeComponent = null;
+                break;
+            }
+        }
+        return nameOfSearchTypeComponent;
     }
 }
diff --git a/client/src/app/instance/search/components/criteria/index.ts b/client/src/app/instance/search/components/criteria/index.ts
index 7dd250f7a445fe5837f101d14ec3f25ee4fe10b5..384fec9fedf22ed74f2127a6d7272fbee81c43df 100644
--- a/client/src/app/instance/search/components/criteria/index.ts
+++ b/client/src/app/instance/search/components/criteria/index.ts
@@ -1,8 +1,6 @@
 import { ConeSearchTabComponent } from './cone-search-tab.component';
 import { CriteriaTabsComponent } from './criteria-tabs.component';
 import { CriteriaByFamilyComponent } from './criteria-by-family.component';
-import { SearchTypeLoaderDirective } from './search-type-loader.directive';
-import { TestSearchTypeComponent } from './test-search-type.component';
 import { CriterionComponent } from './criterion.component';
 import { searchTypeComponents } from './search-type';
 
@@ -10,8 +8,6 @@ export const criteriaComponents = [
     ConeSearchTabComponent,
     CriteriaTabsComponent,
     CriteriaByFamilyComponent,
-    SearchTypeLoaderDirective,
-    TestSearchTypeComponent,
     CriterionComponent,
     searchTypeComponents
 ];
diff --git a/client/src/app/instance/search/components/criteria/search-type/abstract-search-type.component.ts b/client/src/app/instance/search/components/criteria/search-type/abstract-search-type.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..adfd77078836e4d919a62d7a79202f184f87bbb6
--- /dev/null
+++ b/client/src/app/instance/search/components/criteria/search-type/abstract-search-type.component.ts
@@ -0,0 +1,55 @@
+import { Directive, Input } from '@angular/core';
+import { FormGroup } from '@angular/forms';
+
+import { Attribute } from 'src/app/metamodel/models';
+import { Criterion } from 'src/app/instance/store/models';
+import { searchTypeOperators } from 'src/app/shared/utils';
+
+@Directive()
+export abstract class AbstractSearchTypeComponent {
+    attribute: Attribute;
+    criteriaList: Criterion[];
+
+    form: FormGroup;
+    operators = searchTypeOperators;
+
+    constructor() { }
+
+    setAttribute(attribute: Attribute) {
+        this.attribute = attribute;
+    }
+
+    setCriterion(criterion: Criterion) {
+        if (criterion) {
+            this.form.patchValue(criterion);
+            this.form.disable();
+        } else {
+            this.form.enable();
+            this.form.reset();
+        }
+    }
+
+    setCriteriaList(criteriaList: Criterion[]) {
+        this.criteriaList = criteriaList;
+    }
+
+    abstract getCriterion(): Criterion;
+
+    isValid() {
+        return this.form.valid;
+    }
+
+    /**
+     * Return field type.
+     *
+     * @return string
+     */
+    getType(): string {
+        const numberTypeList = ['smallint', 'integer', 'decimal', 'float'];
+        if (this.attribute.operator === 'in' || this.attribute.operator === 'nin' || !numberTypeList.includes(this.attribute.type)) {
+            return 'text';
+        } else {
+            return 'number';
+        }
+    }
+}
diff --git a/client/src/app/instance/search/components/criteria/search-type/between-date.component.html b/client/src/app/instance/search/components/criteria/search-type/between-date.component.html
index 0b10358d0d29c2d9f72afe7c1c218d17dc3d0159..a0f849148797c06e26c69c89b936836ae7e82028 100644
--- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/between-date.component.html
@@ -1,33 +1,18 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
-                    <div class="readonly">bw</div>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <input type="text" 
-                        placeholder="Pick a date range..." 
-                        class="form-control" 
-                        formControlName="dateRange"
-                        [bsValue]="form.controls.dateRange.value" 
-                        [bsConfig]="{ rangeInputFormat: 'YYYY-MM-DD', isAnimated: true }" 
-                        autocomplete="off"
-                        bsDaterangepicker>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
+            <div class="operator_readonly">bw</div>
         </div>
-        <div class="col-2 text-center align-self-end mb-sm-1 pb-3">
-            <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <input type="text" 
+                placeholder="Pick a date range..." 
+                class="form-control" 
+                formControlName="dateRange"
+                [bsValue]="form.controls.dateRange.value" 
+                [bsConfig]="{ rangeInputFormat: 'YYYY-MM-DD', isAnimated: true }" 
+                autocomplete="off"
+                bsDaterangepicker>
         </div>
     </div>
 </form>
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 a0dceaf5a411fd258544594c0bc211f3239d4b9d..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { BetweenDateComponent } from './between-date.component';
-import { BetweenCriterion } from '../../../../store/models/criterion';
-import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-
-describe('[Instance][Search][Component][Criteria][SearchType] BetweenDateComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-between-date 
-                    [id]="id"
-                    [operator]="operator"
-                    [label]="label"
-                    [criterion]="criterion">
-            </app-between-date>`
-    })
-    class TestHostComponent {
-        @ViewChild(BetweenDateComponent, { static: false })
-        public testedComponent: BetweenDateComponent;
-        public id: number = undefined;
-        public operator: string = undefined;
-        public label: string = undefined;
-        public criterion: BetweenCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: BetweenDateComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                BetweenDateComponent,
-                TestHostComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                BsDatepickerModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'between', operator: 'eq', min: '2019-02-17', max: '2021-04-12' } as BetweenCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.dateRange.value[0].getDate()).toEqual(17);
-        expect(testedComponent.form.controls.dateRange.value[0].getMonth()).toEqual(1);
-        expect(testedComponent.form.controls.dateRange.value[0].getFullYear()).toEqual(2019);
-        expect(testedComponent.form.controls.dateRange.value[1].getDate()).toEqual(12);
-        expect(testedComponent.form.controls.dateRange.value[1].getMonth()).toEqual(3);
-        expect(testedComponent.form.controls.dateRange.value[1].getFullYear()).toEqual(2021);
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.dateRange.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = 'eq';
-        testedComponent.operator = operator;
-        const dateMin: Date = new Date('2019-02-17');
-        const dateMax: Date = new Date('2021-04-12');
-        testedComponent.form.controls.dateRange.setValue([dateMin, dateMax]);
-        const expectedCriterion = { id: testedComponent.id, type: 'between', operator, min: '2019-02-17', max: '2021-04-12' } as BetweenCriterion;
-        testedComponent.addCriterion.subscribe((event: BetweenCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('#getDateString() should return a date as string', () => {
-        const dateString = '2019-02-17';
-        const date = new Date(dateString);
-        expect(testedComponent.getDateString(date)).toEqual(dateString);
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts b/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts
index cbd754df9e5cef5115f689476c76e78efc3ec906..f90a2cb2d658fda3141dc57cda5bc440bf4e8a9c 100644
--- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts
@@ -1,71 +1,47 @@
-/**
- * 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,
-    SimpleChanges,
-    OnChanges
-} from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, BetweenCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
 
-/**
- * @class
- * @classdesc Between date search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-between-date',
-    templateUrl: 'between-date.component.html',
-    styleUrls: [ 'operator.component.scss' ],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'between-date.component.html'
 })
-export class BetweenDateComponent implements OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<BetweenCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        dateRange: new FormControl('', [Validators.required])
-    });
-
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const c = changes.criterion.currentValue as BetweenCriterion;
-            this.form.controls.dateRange.setValue([new Date(c.min), new Date(c.max)]);
-            this.form.disable();
-        }
+export class BetweenDateComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            dateRange: new FormControl('', [Validators.required])
+        });
+    }
 
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            const betweenCriterion = criterion as BetweenCriterion;
+            this.form.controls.dateRange.setValue([
+                new Date(betweenCriterion.min),
+                new Date(betweenCriterion.max)
+            ]);
         }
     }
-
+    
     /**
-     * Emits event to add criterion to the criteria list.
+     * Return new criterion
      *
-     * @fires EventEmitter<BetweenCriterion>
+     * @return Criterion
      */
-    emitAdd(): void {
+    getCriterion(): Criterion {
         const dateMin = this.getDateString(this.form.controls.dateRange.value[0]);
         const dateMax = this.getDateString(this.form.controls.dateRange.value[1]);
-        const fd: BetweenCriterion = { id: this.attribute.id, type: 'between', min: dateMin, max: dateMax };
-        this.addCriterion.emit(fd);
+
+        return {
+            id: this.attribute.id,
+            type: 'between',
+            min: dateMin,
+            max: dateMax
+        } as BetweenCriterion;
     }
 
     /**
diff --git a/client/src/app/instance/search/components/criteria/search-type/between.component.html b/client/src/app/instance/search/components/criteria/search-type/between.component.html
index 526d2bfd2a742464e36847ccca4da1cb155d0b13..ae9e68b658b98c544906909461875c9c14e3e984 100644
--- a/client/src/app/instance/search/components/criteria/search-type/between.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/between.component.html
@@ -1,34 +1,19 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1 mb-lg-0">
-                    <div class="readonly">min</div>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1 mb-1 mb-sm-0">
-                    <input type="text" class="form-control" [placeholder]="getPlaceholderMin()" formControlName="min" autocomplete="off" />
-                </div>
-                <div class="w-100 d-block d-lg-none"></div>
-                <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1">
-                    <div class="readonly">max</div>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <input type="text"  class="form-control" [placeholder]="getPlaceholderMax()" formControlName="max" autocomplete="off" />
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1 mb-lg-0">
+            <div class="operator_readonly">min</div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.controls.min.value && !form.controls.max.value" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1 mb-1 mb-sm-0">
+            <input type="text" class="form-control" [placeholder]="getPlaceholderMin()" formControlName="min" autocomplete="off" />
+        </div>
+        <div class="w-100 d-block d-lg-none"></div>
+        <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1">
+            <div class="operator_readonly">max</div>
+        </div>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <input type="text" class="form-control" [placeholder]="getPlaceholderMax()" formControlName="max" autocomplete="off" />
         </div>
     </div>
 </form>
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 2cf50ce7679f38dd0d5a6437011cbf222f8b8d99..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { TooltipModule } from 'ngx-bootstrap/tooltip';
-
-import { BetweenComponent } from './between.component';
-import { BetweenCriterion } from '../../../../store/models/criterion';
-
-describe('[Instance][Search][Component][Criteria][SearchType] BetweenComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-between 
-                    [id]="id"
-                    [label]="label"
-                    [placeholderMin]="placeholderMin"
-                    [placeholderMax]="placeholderMax"
-                    [criterion]="criterion">
-            </app-between>`
-    })
-    class TestHostComponent {
-        @ViewChild(BetweenComponent, { static: false })
-        public testedComponent: BetweenComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public placeholderMin: string = undefined;
-        public placeholderMax: string = undefined;
-        public criterion: BetweenCriterion = undefined;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: BetweenComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                BetweenComponent,
-                TestHostComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                TooltipModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'between', operator: 'eq', min: 'one', max: 'two' } as BetweenCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.min.value).toEqual('one');
-        expect(testedComponent.form.controls.max.value).toEqual('two');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.min.value).toBeNull();
-        expect(testedComponent.form.controls.max.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        const operator = 'eq';
-        testedComponent.form.controls.min.setValue('one');
-        testedComponent.form.controls.max.setValue('two');
-        const expectedCriterion = { id: testedComponent.id, type: 'between', operator, min: 'one', max: 'two' } as BetweenCriterion;
-        testedComponent.addCriterion.subscribe((event: BetweenCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('#getPlaceholderMin() should fill the placeholder for the minimum value if defined', () => {
-        const placeholder = 'placeholder';
-        testedComponent.placeholderMin = placeholder;
-        expect(testedComponent.getPlaceholderMin()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholderMin() should not fill the placeholder for the minimum value if not defined', () => {
-        expect(testedComponent.getPlaceholderMin()).toEqual('');
-    });
-
-    it('#getPlaceholderMax() should fill the placeholder for the maximum value if defined', () => {
-        const placeholder = 'placeholder';
-        testedComponent.placeholderMax = placeholder;
-        expect(testedComponent.getPlaceholderMax()).toEqual(placeholder);
-    });
-
-    it('#getPlaceholderMax() should not fill the placeholder for the maximum value if not defined', () => {
-        expect(testedComponent.getPlaceholderMax()).toEqual('');
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/between.component.ts b/client/src/app/instance/search/components/criteria/search-type/between.component.ts
index 244d30b489e5b315b70f556fc8e1f2f26bd58110..c111105c04db07aedbf6ccda7faacfbb4e386a3c 100644
--- a/client/src/app/instance/search/components/criteria/search-type/between.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/between.component.ts
@@ -1,60 +1,33 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, BetweenCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
 
-/**
- * @class
- * @classdesc Between search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-between',
-    templateUrl: 'between.component.html',
-    styleUrls: ['operator.component.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'between.component.html'
 })
-export class BetweenComponent implements OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<BetweenCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        min: new FormControl('', [Validators.required]),
-        max: new FormControl('', [Validators.required])
-    });
-
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            this.form.patchValue(this.criterion);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
-        }
+export class BetweenComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            min: new FormControl('', [Validators.required]),
+            max: new FormControl('', [Validators.required])
+        });
     }
 
     /**
-     * Emits event to add criterion to the criteria list.
+     * Return new criterion
      *
-     * @fires EventEmitter<BetweenCriterion>
+     * @return Criterion
      */
-    emitAdd(): void {
-        this.addCriterion.emit({id: this.attribute.id, type: 'between', ...this.form.value});
+    getCriterion(): Criterion {
+        return {
+            id: this.attribute.id,
+            type: 'between',
+            ...this.form.value
+        } as BetweenCriterion;
     }
 
     /**
@@ -66,7 +39,7 @@ export class BetweenComponent implements OnChanges {
         if (!this.attribute.placeholder_min) {
             return '';
         } else {
-            return this.attribute.placeholder_max;
+            return this.attribute.placeholder_min;
         }
     }
 
@@ -82,4 +55,8 @@ export class BetweenComponent implements OnChanges {
             return this.attribute.placeholder_max;
         }
     }
+
+    isValid() {
+        return this.form.controls.min.value || this.form.controls.max.value;
+    }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html
index 46a29878a32bdc7424b54732233df9880df3c7da..9cae6f77eda95db993c576f0934e4610adc07ed1 100644
--- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html
@@ -1,29 +1,14 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <div class="readonly">in</div>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1" formArrayName="checkboxes">
-                    <div *ngFor="let _ of getCheckboxes().controls; index as i" class="custom-control custom-checkbox form-check form-check-inline form-control-lg">
-                        <input class="custom-control-input" type="checkbox" id="cb_{{attribute.options[i].value}}" [formControlName]="i">
-                        <label class="custom-control-label" for="cb_{{attribute.options[i].value}}">{{ attribute.options[i].label }}</label>
-                    </div>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <div class="operator_readonly">in</div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button *ngIf="!form.disabled" class="btn btn-outline-success" [hidden]="!isChecked()" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button *ngIf="form.disabled" class="btn btn-outline-danger" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1" formArrayName="checkboxes">
+            <div *ngFor="let _ of getCheckboxes().controls; index as i" class="custom-control custom-checkbox form-check form-check-inline form-control-lg">
+                <input class="custom-control-input" type="checkbox" id="cb_{{attribute.options[i].value}}" [formControlName]="i">
+                <label class="custom-control-label" for="cb_{{attribute.options[i].value}}">{{ attribute.options[i].label }}</label>
+            </div>
         </div>
     </div>
 </form>
diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.scss b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.scss
index 6b7fac9718ce69ef27a51d714dc78570e669c383..850526ebd59e8bae4f44b684f4e69c8e8cb1cc2f 100644
--- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.scss
+++ b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.scss
@@ -24,4 +24,4 @@
     top: .8rem;
     width: 1.25rem;
     height: 1.25rem;
-}
\ No newline at end of file
+}
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 98d1cc28f5126b03d46103efc14e043415d380cc..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { CheckboxComponent } from './checkbox.component';
-import { SelectMultipleCriterion } from '../../../../store/models/criterion';
-import { Option } from '../../../../../metamodel/models';
-
-describe('[Instance][Search][Component][Criteria][SearchType] CheckboxComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-checkbox
-                    [id]="id"
-                    [label]="label"
-                    [options]="options"
-                    [criterion]="criterion">
-            </app-checkbox>`
-    })
-    class TestHostComponent {
-        @ViewChild(CheckboxComponent, { static: false })
-        public testedComponent: CheckboxComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public options: Option[] = [];
-        public criterion: SelectMultipleCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: CheckboxComponent;
-
-    beforeEach(() => {
-        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('should execute ngOnInit lifecycle', () => {
-    //     testedComponent.options = [
-    //         { label: 'One', value: 'one', display: 1 },
-    //         { label: 'Two', value: 'two', display: 2 },
-    //         { label: 'Three', value: 'three', display: 3 }
-    //     ];
-    //     testedComponent.ngOnInit();
-    //     console.log(testedComponent.options.length);
-    //     const formArray = testedComponent.form.controls.checkboxes as FormArray;
-    //     // console.log(formArray.controls[0]);
-    //     expect(testedComponent).toBeTruthy();
-    // });
-
-    // it('should call ngOnChanges and apply changes', () => {
-    //     const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-    //     const options: Option[] = [
-    //         { label: 'One', value: 'one', display: 1 },
-    //         { label: 'Two', value: 'two', display: 2 },
-    //         { label: 'Three', value: 'three', display: 3 }
-    //     ];
-    //     testHostComponent.criterion = { id: 1, type: 'multiple', options } as SelectMultipleCriterion;
-    //     testHostFixture.detectChanges();
-    //     expect(testedComponent.form.controls.select.value).toEqual(['one', 'two', 'three']);
-    //     expect(testedComponent.form.disabled).toBeTruthy();
-    //     testHostComponent.criterion = undefined;
-    //     testHostFixture.detectChanges();
-    //     expect(testedComponent.form.controls.select.value).toBeNull();
-    //     expect(testedComponent.form.enabled).toBeTruthy();
-    //     expect(spy).toHaveBeenCalledTimes(2);
-    // });
-
-    // it('#getCheckboxes() should return an array of checkbox', () => {
-    //     const formArray: FormArray = null;
-    //     expect(testedComponent.getCheckboxes()).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 }
-    //     ];
-    //     testedComponent.form.controls.select.setValue(['three']);
-    //     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();
-    // });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts
index a12f43287745025bf1a29824eb377799b2b49e69..8d9b1b070290fb878fc4a34812982b8464711981 100644
--- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts
@@ -1,64 +1,62 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
-import { FormGroup, FormArray, FormControl } from '@angular/forms';
+import { Component } from '@angular/core';
+import { FormGroup, FormControl, FormArray, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, SelectMultipleCriterion } from 'src/app/instance/store/models';
 import { Attribute } from 'src/app/metamodel/models';
 
-/**
- * @class
- * @classdesc Checkbox search type component.
- *
- * @implements OnInit
- * @implements OnChanges
- */
 @Component({
     selector: 'app-checkbox',
     templateUrl: 'checkbox.component.html',
-    styleUrls: ['checkbox.component.scss', 'operator.component.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    styleUrls: ['checkbox.component.scss' ],
 })
-export class CheckboxComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<SelectMultipleCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({ });
+export class CheckboxComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({});
+    }
 
-    ngOnInit(): void {
+    setAttribute(attribute: Attribute): void {
+        super.setAttribute(attribute);
         // Initialization of checkboxes (1 per option)
         const formControls: FormControl[] = [];
-        for (let i = 0; i < this.attribute.options.length; i++) {
+        for (let i = 0; i < attribute.options.length; i++) {
             formControls.push(new FormControl());
         }
         this.form.addControl('checkboxes', new FormArray(formControls));
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const multipleCriterion = this.criterion as SelectMultipleCriterion;
-
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
             for (let i = 0; i < this.attribute.options.length; i++) {
-                if (multipleCriterion.options.find(o => o.label === this.attribute.options[i].label)) {
+                if ((criterion as SelectMultipleCriterion).options.find(o => o.label === this.attribute.options[i].label)) {
                     this.getCheckboxes().controls[i].setValue(true);
                 }
             }
-            this.form.disable();
         }
+    }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        const selected = this.getCheckboxes().value;
+        const values = [...this.attribute.options.filter((option, index) => selected[index])];
 
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
-        }
+        return {
+            id: this.attribute.id,
+            type: 'multiple',
+            options: values
+        } as SelectMultipleCriterion;
+    }
+
+    isValid(): boolean {
+        const selected = this.getCheckboxes().value;
+        const values = [...this.attribute.options.filter((option, index) => selected[index])];
+        return values.length > 0;
     }
 
     /**
@@ -70,17 +68,6 @@ export class CheckboxComponent implements OnInit, OnChanges {
         return this.form.controls.checkboxes as FormArray;
     }
 
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<SelectMultipleCriterion>
-     */
-    emitAdd(): void {
-        const selected = this.getCheckboxes().value;
-        const values = [...this.attribute.options.filter((option, index) => selected[index])];
-        this.addCriterion.emit({ id: this.attribute.id, type: 'multiple', options: values });
-    }
-
     /**
      * Checks if one of the checkboxes is checked.
      *
diff --git a/client/src/app/instance/search/components/criteria/search-type/datalist.component.html b/client/src/app/instance/search/components/criteria/search-type/datalist.component.html
index dedc999a702689cbca447febcc721fe6e008b94d..4af8b445c943b4532fabf34eb41de654924ed231 100644
--- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/datalist.component.html
@@ -1,43 +1,21 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <span *ngIf="attribute.operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip">
-                <span class="far fa-question-circle fa-sm"></span>
-            </span>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
-                    <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
-                        <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
-                    </select>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <input [attr.list]="getDatalistId()" 
-                        type="text" 
-                        class="form-control" 
-                        [placeholder]="getPlaceholder()"
-                        formControlName="value"
-                        autocomplete="off" />
-                    <datalist [id]="getDatalistId()">
-                        <option *ngFor="let option of attribute.options" [value]="option.value">
-                    </datalist>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
+            <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
+                <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
+            </select>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <input [attr.list]="getDatalistId()" 
+                type="text" 
+                class="form-control" 
+                [placeholder]="getPlaceholder()"
+                formControlName="value"
+                autocomplete="off" />
+            <datalist [id]="getDatalistId()">
+                <option *ngFor="let option of attribute.options" [value]="option.value">
+            </datalist>
         </div>
     </div>
 </form>
-
-<ng-template #helpLike>
-    <app-help-like></app-help-like>
-</ng-template>
\ No newline at end of file
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 4167d6071d4045c88b9cab73c7b3f9eedfc594af..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { TooltipModule } from 'ngx-bootstrap/tooltip';
-
-import { DatalistComponent } from './datalist.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-import { Option } from '../../../../../metamodel/models';
-
-describe('[Instance][Search][Component][Criteria][SearchType] DatalistComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-datalist 
-                    [id]="id"
-                    [operator]="operator"
-                    [label]="label"
-                    [placeholder]="placeholder"
-                    [options]="options"
-                    [criterion]="criterion"
-                    [advancedForm]="advancedForm">
-            </app-datalist>`
-    })
-    class TestHostComponent {
-        @ViewChild(DatalistComponent, { static: false })
-        public testedComponent: DatalistComponent;
-        public id: number = undefined;
-        public operator: string = undefined;
-        public label: string = undefined;
-        public placeholder: string = undefined;
-        public options: Option[] = undefined;
-        public criterion: FieldCriterion = undefined;
-        public advancedForm: boolean = false;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: DatalistComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DatalistComponent,
-                TestHostComponent,
-                OperatorStubComponent,
-                HelpLikeStubComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                TooltipModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: 'myValue' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.value.value).toEqual('myValue');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.value.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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.form.controls.value.setValue('myValue');
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: 'myValue' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    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('#getDatalistId() should return an id', () => {
-        testedComponent.id = 1;
-        expect(testedComponent.getDatalistId()).toEqual('datalist_' + 1);
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts b/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts
index 2e693faaa9d552555d8164025b8e753aa646e0c9..f826281fbe9a762e2bc2af6db1ea9b39b88ffaba 100644
--- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts
@@ -1,58 +1,25 @@
-/**
- * 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, SimpleChanges, OnInit, OnChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Datalist search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-datalist',
-    templateUrl: 'datalist.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'datalist.component.html'
 })
-export class DatalistComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        value: new FormControl('', [Validators.required])
-    });
-
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+export class DatalistComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            value: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            this.form.patchValue(this.criterion);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (!criterion) {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -60,6 +27,23 @@ export class DatalistComponent implements OnInit, OnChanges {
             this.operatorOnChange();
         }
     }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            ...this.form.value
+        } as FieldCriterion;
+    }
+
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
+    }
 
     /**
      * Modifies operator with the given one.
@@ -73,16 +57,7 @@ export class DatalistComponent implements OnInit, OnChanges {
     }
 
     /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        this.addCriterion.emit({ id: this.attribute.id, type: 'field', ...this.form.value });
-    }
-
-    /**
-     * Returns placeholder.
+     * Return placeholder.
      *
      * @return string
      */
diff --git a/client/src/app/instance/search/components/criteria/search-type/date.component.html b/client/src/app/instance/search/components/criteria/search-type/date.component.html
index 2c5b4ef2c400d3cc145ad9d39c2e3516ee8025a2..8c2b7950eb1be667647c3adcf67a680d9fd85f44 100644
--- a/client/src/app/instance/search/components/criteria/search-type/date.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/date.component.html
@@ -1,34 +1,19 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
-                    <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
-                        <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
-                    </select>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <input type="text" 
-                        placeholder="Pick a date..." 
-                        class="form-control" 
-                        formControlName="date"
-                        [bsValue]="form.controls.date.value" 
-                        [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', isAnimated: true }" 
-                        bsDatepicker>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
+            <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
+                <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
+            </select>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <input type="text" 
+                placeholder="Pick a date..." 
+                class="form-control" 
+                formControlName="date"
+                [bsValue]="form.controls.date.value" 
+                [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', isAnimated: true }" 
+                bsDatepicker>
         </div>
     </div>
-</form>
\ No newline at end of file
+</form>
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 ee97c9214ca33d14151d8c4893b61cfc6a97be1c..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { DateComponent } from './date.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-
-describe('[Instance][Search][Component][Criteria][SearchType] DateComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-date 
-                    [id]="id"
-                    [operator]="operator"
-                    [label]="label"
-                    [placeholder]="placeholder"
-                    [criterion]="criterion"
-                    [advancedForm]="advancedForm">
-            </app-date>`
-    })
-    class TestHostComponent {
-        @ViewChild(DateComponent, { static: false })
-        public testedComponent: DateComponent;
-        public id: number = undefined;
-        public operator: string = undefined;
-        public label: string = undefined;
-        public placeholder: string = undefined;
-        public criterion: FieldCriterion = undefined;
-        public advancedForm: boolean = false;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: DateComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DateComponent,
-                TestHostComponent,
-                OperatorStubComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                BsDatepickerModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: '2019-02-17' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.date.value.getDate()).toEqual(17);
-        expect(testedComponent.form.controls.date.value.getMonth()).toEqual(1);
-        expect(testedComponent.form.controls.date.value.getFullYear()).toEqual(2019);
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.date.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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;
-        const date: Date = new Date('2019-02-17');
-        testedComponent.form.controls.date.setValue(date);
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: '2019-02-17' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    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('#getDateString() should return a date as string', () => {
-        const dateString = '2019-02-17';
-        const date = new Date(dateString);
-        expect(testedComponent.getDateString(date)).toEqual(dateString);
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/date.component.ts b/client/src/app/instance/search/components/criteria/search-type/date.component.ts
index b80eb2158ae1ce130dafcf132a0f603da6e3ad62..903c0f5f20a3f37c8e138b449329368d77b22b14 100644
--- a/client/src/app/instance/search/components/criteria/search-type/date.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/date.component.ts
@@ -1,60 +1,27 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Date search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-date',
-    templateUrl: 'date.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'date.component.html'
 })
-export class DateComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        date: new FormControl('', [Validators.required])
-    });
-
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+export class DateComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            date: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const criterion = changes.criterion.currentValue as FieldCriterion;
-            this.form.controls.operator.setValue(criterion.operator);
-            this.form.controls.date.setValue(new Date(criterion.value));
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            this.form.controls.date.setValue(new Date((criterion as FieldCriterion).value));
+        } else {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -62,6 +29,29 @@ export class DateComponent implements OnInit, OnChanges {
             this.operatorOnChange();
         }
     }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        let value = null;
+        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
+            value = this.getDateString(this.form.value.date)
+        }
+
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            operator: this.form.controls.operator.value,
+            value
+        } as FieldCriterion;
+    }
+
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
+    }
 
     /**
      * Modifies operator with the given one.
@@ -75,21 +65,7 @@ export class DateComponent implements OnInit, OnChanges {
     }
 
     /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        let value = null;
-        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
-            value = this.getDateString(this.form.value.date)
-        }
-        const fd = { id: this.attribute.id, type: 'field', operator: this.form.controls.operator.value, value };
-        this.addCriterion.emit(fd);
-    }
-
-    /**
-     * Returns placeholder.
+     * Return placeholder.
      *
      * @return string
      */
diff --git a/client/src/app/instance/search/components/criteria/search-type/datetime.component.html b/client/src/app/instance/search/components/criteria/search-type/datetime.component.html
index 97b1912d60ea6b7a9ad4c98ecb17f2521212fe95..c199416c1c6805a4e6bd9820b685e8459f34f2a6 100644
--- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/datetime.component.html
@@ -1,50 +1,35 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
+                <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
+            </select>
+        </div>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1 mb-1 mb-lg-0 pr-lg-1">
+            <input type="text" 
+                placeholder="Pick a date..." 
+                class="form-control" 
+                formControlName="date" 
+                [bsValue]="form.controls.date.value"
+                [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', isAnimated: true }"
+                bsDatepicker>
+        </div>
+        <div class="w-100 d-block d-lg-none"></div>
+        <div class="col col-lg-auto">
             <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
-                        <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
-                    </select>
+                <div class="col-auto pl-lg-1 pr-1">
+                    <ng-select formControlName="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time">
+                        <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option>
+                    </ng-select>
                 </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1 mb-1 mb-lg-0 pr-lg-1">
-                    <input type="text" 
-                        placeholder="Pick a date..." 
-                        class="form-control" 
-                        formControlName="date" 
-                        [bsValue]="form.controls.date.value"
-                        [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', isAnimated: true }"
-                        bsDatepicker>
-                </div>
-                <div class="w-100 d-block d-lg-none"></div>
-                <div class="col col-lg-auto">
-                    <div class="row">
-                        <div class="col-auto pl-lg-1 pr-1">
-                            <ng-select formControlName="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time">
-                                <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option>
-                            </ng-select>
-                        </div>
-                        <div class="col col-lg-auto p-0 text-center">:</div>
-                        <div class="col-auto pl-1">
-                            <ng-select formControlName="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time">
-                                <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option>
-                            </ng-select>
-                        </div>
-                    </div>
+                <div class="col col-lg-auto p-0 text-center">:</div>
+                <div class="col-auto pl-1">
+                    <ng-select formControlName="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time">
+                        <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option>
+                    </ng-select>
                 </div>
             </div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
-        </div>
     </div>
 </form>
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 fa8f17a23e61118a784119974f2f5cf16a265b3e..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { DatetimeComponent } from './datetime.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-import { NgSelectModule } from '@ng-select/ng-select';
-
-describe('[Instance][Search][Component][Criteria][SearchType] DatetimeComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-datetime 
-                    [id]="id"
-                    [operator]="operator"
-                    [label]="label"
-                    [criterion]="criterion"
-                    [advancedForm]="advancedForm">
-            </app-datetime>`
-    })
-    class TestHostComponent {
-        @ViewChild(DatetimeComponent, { static: false })
-        public testedComponent: DatetimeComponent;
-        public id: number = undefined;
-        public operator: string = undefined;
-        public label: string = undefined;
-        public criterion: FieldCriterion = undefined;
-        public advancedForm: boolean = false;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: DatetimeComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DatetimeComponent,
-                TestHostComponent,
-                OperatorStubComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                NgSelectModule,
-                BsDatepickerModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: '2019-02-17 15:47' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.date.value.getDate()).toEqual(17);
-        expect(testedComponent.form.controls.date.value.getMonth()).toEqual(1);
-        expect(testedComponent.form.controls.date.value.getFullYear()).toEqual(2019);
-        expect(testedComponent.form.controls.hh.value).toEqual('15');
-        expect(testedComponent.form.controls.mm.value).toEqual('47');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.date.value).toBeNull();
-        expect(testedComponent.form.controls.hh.value).toBeNull();
-        expect(testedComponent.form.controls.mm.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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;
-        const date: Date = new Date('2019-02-17');
-        testedComponent.form.controls.date.setValue(date);
-        testedComponent.form.controls.hh.setValue('15');
-        testedComponent.form.controls.mm.setValue('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('#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('#getDateString() should return a date as string', () => {
-        const dateString = '2019-02-17';
-        const date = new Date(dateString);
-        expect(testedComponent.getDateString(date)).toEqual(dateString);
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts b/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts
index 6ee1b2f8de75c2ce60851532f3a2496611ac8968..f293941115a2f35ea8ee68423cabaf4d971f068f 100644
--- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts
@@ -1,70 +1,39 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Datetime search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-datetime',
-    templateUrl: 'datetime.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'datetime.component.html'
 })
-export class DatetimeComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
+export class DateTimeComponent extends AbstractSearchTypeComponent {
     hours: string[] = this.initTime(24);
     minutes: string[] = this.initTime(60);
 
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        date: new FormControl('', [Validators.required]),
-        hh: new FormControl('', [Validators.required]),
-        mm: new FormControl('', [Validators.required]) 
-    });
-
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            date: new FormControl('', [Validators.required]),
+            hh: new FormControl('', [Validators.required]),
+            mm: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const criterion = changes.criterion.currentValue as FieldCriterion;
-            this.form.controls.operator.setValue(criterion.operator);
-            if (criterion.operator != 'nl' && criterion.operator != 'nnl') {
-                const [date, time] = criterion.value.split(' ');
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            const fieldCriterion = criterion as FieldCriterion;
+            this.form.controls.operator.setValue(fieldCriterion.operator);
+            if (fieldCriterion.operator != 'nl' && fieldCriterion.operator != 'nnl') {
+                const [date, time] = fieldCriterion.value.split(' ');
                 this.form.controls.date.setValue(new Date(date));
                 this.form.controls.hh.setValue(time.slice(0, 2));
                 this.form.controls.mm.setValue(time.slice(3, 5));
             }
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+        } else {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -72,6 +41,31 @@ export class DatetimeComponent implements OnInit, OnChanges {
             this.operatorOnChange();
         }
     }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        let value = null;
+        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
+            const date = this.getDateString(this.form.value.date);
+            const time = `${this.form.value.hh}:${this.form.value.mm}`;
+            value = `${date} ${time}`;
+        }
+
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            operator: this.form.controls.operator.value,
+            value
+        } as FieldCriterion;
+    }
+
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
+    }
 
     /**
      * Modifies operator with the given one.
@@ -88,23 +82,6 @@ export class DatetimeComponent implements OnInit, OnChanges {
         }
     }
 
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        let value = null;
-        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
-            const date = this.getDateString(this.form.value.date);
-            const time = `${this.form.value.hh}:${this.form.value.mm}`;
-            value = `${date} ${time}`;
-        }
-        
-        const fd = {id: this.attribute.id, type: 'field', operator: this.form.controls.operator.value, value };
-        this.addCriterion.emit(fd);
-    }
-
     /**
      * Returns string array to represent the given time.
      *
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 04076f228dbd6c3a30500425910a63755e36da4b..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { TooltipModule } from 'ngx-bootstrap/tooltip';
-
-import { FieldComponent } from './field.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-
-describe('[Instance][Search][Component][Criteria][SearchType] FieldComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-field 
-                    [id]="id"
-                    [operator]="operator"
-                    [label]="label"
-                    [placeholder]="placeholder"
-                    [attributeType]="attributeType"
-                    [criterion]="criterion"
-                    [advancedForm]="advancedForm">
-            </app-field>`
-    })
-    class TestHostComponent {
-        @ViewChild(FieldComponent, { static: false })
-        public testedComponent: FieldComponent;
-        public id: number = undefined;
-        public operator: string = undefined;
-        public label: string = undefined;
-        public placeholder: string = undefined;
-        public attributeType: string = undefined;
-        public criterion: FieldCriterion = undefined;
-        public advancedForm: boolean = false;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: FieldComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                FieldComponent,
-                TestHostComponent,
-                OperatorStubComponent,
-                HelpLikeStubComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                TooltipModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: 'myValue' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.value.value).toEqual('myValue');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.value.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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.form.controls.value.setValue('myValue');
-        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: 'myValue' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    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('');
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/field.component.ts b/client/src/app/instance/search/components/criteria/search-type/field.component.ts
index 8565fe22405348e1c6e721f37ad0c0e8e7f2466e..4dfe994d230fa2c8062f54bc572c8d7771b27fec 100644
--- a/client/src/app/instance/search/components/criteria/search-type/field.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/field.component.ts
@@ -1,67 +1,25 @@
-/**
- * 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,
-    SimpleChanges,
-    OnInit,
-    OnChanges
-} from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
-import { FieldCriterion, Criterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
+import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
 
-/**
- * @class
- * @classdesc Field search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-field',
-    templateUrl: 'field.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush,
+    templateUrl: 'field.component.html'
 })
-export class FieldComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        value: new FormControl('', [Validators.required])
-    });
-
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+export class FieldComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            value: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            this.form.patchValue(this.criterion);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (!criterion) {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -69,38 +27,32 @@ export class FieldComponent implements OnInit, OnChanges {
             this.operatorOnChange();
         }
     }
-
+    
     /**
-     * Modifies operator with the given one.
+     * Return new criterion
+     *
+     * @return Criterion
      */
-    operatorOnChange(): void {
-        if (this.form.controls.operator.value == 'nl' || this.form.controls.operator.value == 'nnl') {
-            this.form.controls.value.disable();
-        } else {
-            this.form.controls.value.enable();
-        }
+    getCriterion(): Criterion {
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            ...this.form.value
+        } as FieldCriterion;
     }
 
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        this.addCriterion.emit({ id: this.attribute.id, type: 'field', ...this.form.value });
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
     }
 
     /**
-     * Return field type.
-     *
-     * @return string
+     * Modifies operator with the given one.
      */
-    getType(): string {
-        const numberTypeList = ['smallint', 'integer', 'decimal', 'float'];
-        if (this.attribute.operator === 'in' || this.attribute.operator === 'nin' || !numberTypeList.includes(this.attribute.type)) {
-            return 'text';
+    operatorOnChange(): void {
+        if (this.form.controls.operator.value == 'nl' || this.form.controls.operator.value == 'nnl') {
+            this.form.controls.value.disable();
         } else {
-            return 'number';
+            this.form.controls.value.enable();
         }
     }
 
diff --git a/client/src/app/instance/search/components/criteria/search-type/help-like.component.html b/client/src/app/instance/search/components/criteria/search-type/help-like.component.html
deleted file mode 100644
index fae1f66543a312b706a260a1c52d0bb892afc7b0..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/help-like.component.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="text-left">
-    <div class="lead">Operators <span class="font-weight-bold">like</span> and <span class="font-weight-bold">not like</span></div>
-    <hr class="my-1">
-    With <strong>like</strong> operator, you will search anything that look like your criterion. <br>
-    Ex: like <code>mac</code>, you will find anything that contains mac inside, like <code>bigmac</code>, <code>macbook</code>, <code>smack</code>...<br>
-    <br>
-    With <strong>not like</strong>, it's the opposite, you will find anything that not look like your criterion. <br>
-    Ex: not like <code>toto</code>, you will find anything that not contains toto inside, like <code>beer</code>, <code>crisps</code>, <code>friend</code>...<br>
-    <br>
-    <strong>Be careful: it's case sensitive.</strong>
-</div>
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 bb1eb2c0c12dc402d9ca65f7c6bc8223bd936f73..0000000000000000000000000000000000000000
--- 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('[Instance][Search][Component][Criteria][SearchType] 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/help-like.component.ts b/client/src/app/instance/search/components/criteria/search-type/help-like.component.ts
deleted file mode 100644
index 57598de55d2dac348413b3c91b9957d49b3b782b..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/help-like.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * 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';
-
-/**
- * @class
- * @classdesc Help like operator component.
- */
-@Component({
-    selector: 'app-help-like',
-    templateUrl: 'help-like.component.html'
-})
-export class HelpLikeComponent { }
diff --git a/client/src/app/instance/search/components/criteria/search-type/index.ts b/client/src/app/instance/search/components/criteria/search-type/index.ts
index 5954cfafcf1b43d37a9812c99fb99bb1358751a6..7150b01bafb61f26522f6b1ed9d517e4c1dade8d 100644
--- a/client/src/app/instance/search/components/criteria/search-type/index.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/index.ts
@@ -1,3 +1,4 @@
+import { SearchTypeLoaderDirective } from './search-type-loader.directive';
 import { FieldComponent } from './field.component';
 import { BetweenComponent } from './between.component';
 import { SelectComponent } from './select.component';
@@ -6,15 +7,14 @@ import { DatalistComponent } from './datalist.component';
 import { ListComponent } from './list.component';
 import { RadioComponent } from './radio.component';
 import { CheckboxComponent } from './checkbox.component';
-import { DateComponent } from './date.component';
 import { BetweenDateComponent } from './between-date.component';
+import { DateComponent } from './date.component';
 import { TimeComponent } from './time.component';
-import { DatetimeComponent } from './datetime.component';
-import { JsonComponent } from './json.component';
-import { SvomJsonKwComponent } from './svom-json-kw.component';
-import { HelpLikeComponent } from './help-like.component';
+import { DateTimeComponent } from './datetime.component';
+import { JsonComponent } from './json.component';
 
 export const searchTypeComponents = [
+    SearchTypeLoaderDirective,
     FieldComponent,
     BetweenComponent,
     SelectComponent,
@@ -23,11 +23,9 @@ export const searchTypeComponents = [
     ListComponent,
     RadioComponent,
     CheckboxComponent,
-    DateComponent,
     BetweenDateComponent,
+    DateComponent,
     TimeComponent,
-    DatetimeComponent,
-    JsonComponent,
-    SvomJsonKwComponent,
-    HelpLikeComponent
+    DateTimeComponent,
+    JsonComponent
 ];
diff --git a/client/src/app/instance/search/components/criteria/search-type/json.component.html b/client/src/app/instance/search/components/criteria/search-type/json.component.html
index 930fc5dc53510ff8c6fde4aeb675a3c3c8f60268..4a3923a18207afe5282e7b0ea69bcecbe5810bf7 100644
--- a/client/src/app/instance/search/components/criteria/search-type/json.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/json.component.html
@@ -1,44 +1,29 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <div class="operator_readonly">json</div>
+        </div>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
             <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <div class="readonly">json</div>
+                <div class="col mb-1 mb-sm-0">
+                    <input class="form-control" id="path" name="path" placeholder="Path" autocomplete="off" formControlName="path">
+                </div>
+                <div class="w-100 d-block d-sm-none"></div>
+                <div class="col col-sm-auto mb-1 mb-sm-0 px-sm-0">
+                    <ng-select [clearable]="false" [multiple]="false" [hideSelected]="true" class="ng-select-custom" formControlName="operator">
+                        <ng-option value="eq">=</ng-option >
+                        <ng-option value="gt">></ng-option >
+                        <ng-option value="gte">>=</ng-option >
+                        <ng-option value="lt"><</ng-option >
+                        <ng-option value="lte"><=</ng-option >
+                    </ng-select>
                 </div>
                 <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <div class="row">
-                        <div class="col mb-1 mb-sm-0">
-                            <input class="form-control" id="path" name="path" placeholder="Path" autocomplete="off" formControlName="path">
-                        </div>
-                        <div class="w-100 d-block d-sm-none"></div>
-                        <div class="col col-sm-auto mb-1 mb-sm-0 px-sm-0">
-                            <ng-select [clearable]="false" [multiple]="false" [hideSelected]="true" class="ng-select-custom" formControlName="operator">
-                                <ng-option value="eq">=</ng-option >
-                                <ng-option value="gt">></ng-option >
-                                <ng-option value="gte">>=</ng-option >
-                                <ng-option value="lt"><</ng-option >
-                                <ng-option value="lte"><=</ng-option >
-                            </ng-select>
-                        </div>
-                        <div class="w-100 d-block d-sm-none"></div>
-                        <div class="col">
-                            <input class="form-control" id="value" name="value" placeholder="Value" autocomplete="off" formControlName="value">
-                        </div>
-                    </div>
+                <div class="col">
+                    <input class="form-control" id="value" name="value" placeholder="Value" autocomplete="off" formControlName="value">
                 </div>
             </div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
-        </div>
     </div>
 </form>
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 673739f3f74f1f5c918828186d1bee27709d0c07..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { JsonComponent } from './json.component';
-import { JsonCriterion } from '../../../../store/models/criterion';
-
-describe('[Instance][Search][Component][Criteria][SearchType] JsonComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-json-criteria 
-                    [id]="id"
-                    [label]="label"
-                    [criterion]="criterion">
-            </app-json-criteria>`
-    })
-    class TestHostComponent {
-        @ViewChild(JsonComponent, { static: false })
-        public testedComponent: JsonComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public criterion: JsonCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: JsonComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                JsonComponent,
-                TestHostComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                NgSelectModule
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: testedComponent.id, type: 'json', path: 'myPath', operator: 'myOperator', value: 'myValue' } as JsonCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.path.value).toEqual('myPath');
-        expect(testedComponent.form.controls.operator.value).toEqual('myOperator');
-        expect(testedComponent.form.controls.value.value).toEqual('myValue');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.path.value).toBeNull();
-        expect(testedComponent.form.controls.operator.value).toBeNull();
-        expect(testedComponent.form.controls.value.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.form.controls.path.setValue('myPath');
-        testedComponent.form.controls.operator.setValue('myOperator');
-        testedComponent.form.controls.value.setValue('myValue');
-        const expectedCriterion = { id: testedComponent.id, type: 'json', path: 'myPath', operator: 'myOperator', value: 'myValue' } as JsonCriterion;
-        testedComponent.addCriterion.subscribe((event: JsonCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/json.component.ts b/client/src/app/instance/search/components/criteria/search-type/json.component.ts
index 7e28f1d4b6c479ce40fe7c2250feca59be589cd1..5e0cdc5b49e0644d33ab34c773d1e752dc4af651 100644
--- a/client/src/app/instance/search/components/criteria/search-type/json.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/json.component.ts
@@ -1,61 +1,33 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
-import { JsonCriterion, Criterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
+import { Criterion, JsonCriterion } from 'src/app/instance/store/models';
 
-/**
- * @class
- * @classdesc JSON search type component.
- *
- * @implements OnChanges
- */
 @Component({
-    selector: 'app-json-criteria',
-    templateUrl: 'json.component.html',
-    styleUrls: [ 'operator.component.scss' ],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    selector: 'app-json',
+    templateUrl: 'json.component.html'
 })
-export class JsonComponent implements OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<JsonCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        path: new FormControl('', [Validators.required]),
-        operator: new FormControl('', [Validators.required]),
-        value: new FormControl('', [Validators.required])
-    });
-
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            this.form.patchValue(changes.criterion.currentValue);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
-        }
+export class JsonComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            path: new FormControl('', [Validators.required]),
+            operator: new FormControl('', [Validators.required]),
+            value: new FormControl('', [Validators.required])
+        });
     }
-
+    
     /**
-     * Emits event to add criterion to the criteria list.
+     * Return new criterion
      *
-     * @fires EventEmitter<JsonCriterion>
+     * @return Criterion
      */
-    emitAdd(): void {
-        const js = { id: this.attribute.id, type: 'json', ...this.form.value };
-        this.addCriterion.emit(js);
+    getCriterion(): Criterion {
+        return {
+            id: this.attribute.id,
+            type: 'json',
+            ...this.form.value
+        } as JsonCriterion;
     }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.html b/client/src/app/instance/search/components/criteria/search-type/list.component.html
index 62351c9f13f25dde74d75e74a40779abf6b63a02..259c3bd6f026f43675dc19de9252f81abafc5bba 100644
--- a/client/src/app/instance/search/components/criteria/search-type/list.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/list.component.html
@@ -1,26 +1,11 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <div class="readonly">=</div>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <textarea class="form-control" rows="3" [placeholder]="getPlaceholder()" formControlName="list" autocomplete="off"></textarea>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <div class="operator_readonly">=</div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <textarea class="form-control" rows="3" [placeholder]="getPlaceholder()" formControlName="list" autocomplete="off"></textarea>
         </div>
     </div>
-</form>
\ No newline at end of file
+</form>
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 ea009ff97fffc95a0f6daa9090212ff192d258f5..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { ListComponent } from './list.component';
-import { ListCriterion } from '../../../../store/models/criterion';
-
-describe('[Instance][Search][Component][Criteria][SearchType] ListComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-list 
-                    [id]="id"
-                    [label]="label"
-                    [placeholder]="placeholder"
-                    [criterion]="criterion">
-            </app-list>`
-    })
-    class TestHostComponent {
-        @ViewChild(ListComponent, { static: false })
-        public testedComponent: ListComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public placeholder: string = undefined;
-        public criterion: ListCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: ListComponent;
-
-    beforeEach(() => {
-        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('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: testedComponent.id, type: 'list', values: ['1', '2'] } as ListCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.list.value).toEqual('1\n2');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.list.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    it('#getPlaceholder() should fill the placeholder if defined', () => {
-        testedComponent.placeholder = 'placeholder';
-        expect(testedComponent.getPlaceholder()).toEqual('placeholder');
-    });
-
-    it('#getPlaceholder() should not fill the placeholder if not defined', () => {
-        expect(testedComponent.getPlaceholder()).toEqual('');
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.form.controls.list.setValue('1\n2');
-        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/list.component.ts b/client/src/app/instance/search/components/criteria/search-type/list.component.ts
index 77c32159cc450d40318202066aaa47021b84fd79..d065ccdc32db9a0cc686e33644f6c916275ede09 100644
--- a/client/src/app/instance/search/components/criteria/search-type/list.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/list.component.ts
@@ -1,56 +1,44 @@
-/**
- * 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, SimpleChanges, OnChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
-import { ListCriterion, Criterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
+import { Criterion, ListCriterion } from 'src/app/instance/store/models';
 
-/**
- * @class
- * @classdesc List search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-list',
-    templateUrl: 'list.component.html',
-    styleUrls: ['operator.component.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush,
+    templateUrl: 'list.component.html'
 })
-export class ListComponent implements OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<ListCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        list: new FormControl('', [Validators.required])
-    });
-
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const criterion = changes.criterion.currentValue as ListCriterion;
+export class ListComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            list: new FormControl('', [Validators.required])
+        });
+    }
 
-            this.form.controls.list.setValue(criterion.values.join('\n'));
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            this.form.controls.list.setValue((criterion as ListCriterion).values.join('\n'));
             this.form.disable();
         }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
-        }
+    }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        return {
+            id: this.attribute.id,
+            type: 'list',
+            values: this.form.value.list.split('\n')
+        } as ListCriterion;
     }
 
     /**
-     * Returns placeholder.
+     * Return placeholder.
      *
      * @return string
      */
@@ -61,14 +49,4 @@ export class ListComponent implements OnChanges {
             return this.attribute.placeholder_min;
         }
     }
-
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<ListCriterion>
-     */
-    emitAdd(): void {
-        const ls = { id: this.attribute.id, type: 'list', values: this.form.value.list.split('\n') };
-        this.addCriterion.emit(ls);
-    }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.scss b/client/src/app/instance/search/components/criteria/search-type/operator.component.scss
deleted file mode 100644
index a4a0ae49ffd3de25f534641694bc346d6723ac91..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/operator.component.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * 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.
- */
-
-.readonly {
-    background-color: #e9ecef;
-    border: 1px solid #ced4da;
-    border-radius: .25rem;
-    display: block;
-    width: 100%;
-    height: calc(1.5em + .75rem + 2px);
-    padding: .375rem .75rem;
-    font-size: 1rem;
-    font-weight: 400;
-    line-height: 1.5;
-    color:#495057;
-}
\ No newline at end of file
diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.html b/client/src/app/instance/search/components/criteria/search-type/radio.component.html
index 934827eef3973a10e4f2bf6f207d3a0a46e59720..77994fdbb0e8f944b13c27837233136a47a3dfbc 100644
--- a/client/src/app/instance/search/components/criteria/search-type/radio.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/radio.component.html
@@ -1,31 +1,16 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
-                    <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
-                        <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
-                    </select>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <div *ngFor="let option of attribute.options" class="form-check form-check-inline">
-                        <input class="form-check-input" type="radio" id="cb_{{option.value}}" formControlName="radio" [value]="option.value">
-                        <label class="form-check-label" for="cb_{{option.value}}">{{ option.label }}</label>
-                    </div>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
+            <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
+                <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
+            </select>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <div *ngFor="let option of attribute.options" class="form-check form-check-inline">
+                <input class="form-check-input" type="radio" id="cb_{{option.value}}" formControlName="radio" [value]="option.value">
+                <label class="form-check-label" for="cb_{{option.value}}">{{ option.label }}</label>
+            </div>
         </div>
     </div>
 </form>
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 c6abb7b3a56111ef3aa113622958b7fed939998c..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { RadioComponent } from './radio.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-import { Option } from '../../../../../metamodel/models';
-
-describe('[Instance][Search][Component][Criteria][SearchType] RadioComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-radio 
-                    [id]="id"
-                    [label]="label"
-                    [operator]="operator"
-                    [options]="options"
-                    [criterion]="criterion">
-            </app-radio>`
-    })
-    class TestHostComponent {
-        @ViewChild(RadioComponent, { static: false })
-        public testedComponent: RadioComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public operator: string = undefined;
-        public options: Option[] = undefined;
-        public criterion: FieldCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: RadioComponent;
-
-    beforeEach(() => {
-        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('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: 'three' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.radio.value).toEqual('three');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.radio.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.operator = 'eq';
-        testedComponent.form.controls.radio.setValue('three');
-        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: 'eq', value: 'three' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.ts b/client/src/app/instance/search/components/criteria/search-type/radio.component.ts
index cdd24f18740e49d806b4afd9886381255c6cd56d..195fd921235604ddae7b07414b0bd900af088676 100644
--- a/client/src/app/instance/search/components/criteria/search-type/radio.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/radio.component.ts
@@ -1,59 +1,27 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Radio search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-radio',
-    templateUrl: 'radio.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'radio.component.html'
 })
-export class RadioComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        radio: new FormControl('', [Validators.required])
-    });
-
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+export class RadioComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            radio: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const criterion = this.criterion as FieldCriterion;
-            this.form.controls.radio.setValue(criterion.value);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            this.form.controls.radio.setValue((criterion as FieldCriterion).value);
+        } else {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -61,6 +29,30 @@ export class RadioComponent implements OnInit, OnChanges {
             this.operatorOnChange();
         }
     }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        let value = null;
+        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
+            const option = this.attribute.options.find(o => o.value === this.form.value.radio);
+            value = option.value;
+        }
+
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            operator: this.form.controls.operator.value,
+            value
+        } as FieldCriterion;
+    }
+
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
+    }
 
     /**
      * Modifies operator with the given one.
@@ -72,19 +64,4 @@ export class RadioComponent implements OnInit, OnChanges {
             this.form.controls.radio.enable();
         }
     }
-
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        let value = null;
-        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
-            const option = this.attribute.options.find(o => o.value === this.form.value.radio);
-            value = option.value;
-        }
-        const cb = {id: this.attribute.id, type: 'field', operator: this.form.controls.operator.value, value };
-        this.addCriterion.emit(cb);
-    }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type-loader.directive.ts b/client/src/app/instance/search/components/criteria/search-type/search-type-loader.directive.ts
similarity index 100%
rename from client/src/app/instance/search/components/criteria/search-type-loader.directive.ts
rename to client/src/app/instance/search/components/criteria/search-type/search-type-loader.directive.ts
diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html
index 35ef36a978be38b2ea9d96115a73836dade6d1c2..48de3a3d3a17c5636c59a4ae49005727e2e7a989 100644
--- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html
@@ -1,28 +1,13 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <div class="readonly">in</div>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <ng-select formControlName="select" [multiple]="true" [hideSelected]="true" class="ng-select-custom">
-                        <ng-option *ngFor="let option of attribute.options" [value]="option.value">{{option.label}}</ng-option>
-                    </ng-select>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <div class="operator_readonly">in</div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <ng-select formControlName="select" [multiple]="true" [hideSelected]="true" class="ng-select-custom">
+                <ng-option *ngFor="let option of attribute.options" [value]="option.value">{{option.label}}</ng-option>
+            </ng-select>
         </div>
     </div>
-</form>
\ No newline at end of file
+</form>
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 9ad8f715d2a455559043a0e28a6912a6853b874d..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { SelectMultipleComponent } from './select-multiple.component';
-import { SelectMultipleCriterion } from '../../../../store/models/criterion';
-import { Option } from '../../../../../metamodel/models';
-
-describe('[Instance][Search][Component][Criteria][SearchType] SelectMultipleComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-select-multiple 
-                    [id]="id"
-                    [label]="label"
-                    [options]="options"
-                    [criterion]="criterion">
-            </app-select-multiple>`
-    })
-    class TestHostComponent {
-        @ViewChild(SelectMultipleComponent, { static: false })
-        public testedComponent: SelectMultipleComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public options: Option[] = undefined;
-        public criterion: SelectMultipleCriterion = undefined;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: SelectMultipleComponent;
-
-    beforeEach(() => {
-        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('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        const options: Option[] = [
-            { label: 'One', value: 'one', display: 1 },
-            { label: 'Two', value: 'two', display: 2 },
-            { label: 'Three', value: 'three', display: 3 }
-        ];
-        testHostComponent.criterion = { id: 1, type: 'multiple', options } as SelectMultipleCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.select.value).toEqual(['one', 'two', 'three']);
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.select.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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 }
-        ];
-        testedComponent.form.controls.select.setValue(['three']);
-        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();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts
index d66ee9a957a1384f834ecb88bfdaddb28386c421..17c7b8d7e330453490055ad4197b1d49af2b297a 100644
--- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts
@@ -1,65 +1,44 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, SelectMultipleCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
 
-/**
- * @class
- * @classdesc Select multiple search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-select-multiple',
-    templateUrl: 'select-multiple.component.html',
-    styleUrls: ['operator.component.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'select-multiple.component.html'
 })
-export class SelectMultipleComponent implements OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<SelectMultipleCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        select: new FormControl('', [Validators.required])
-    });
+export class SelectMultipleComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            select: new FormControl('', [Validators.required])
+        });
+    }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const multipleCriterion = this.criterion as SelectMultipleCriterion;
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            const multipleCriterion = criterion as SelectMultipleCriterion;
 
             const values = multipleCriterion.options.map(option => option.value);
             this.form.controls.select.setValue(values);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
         }
     }
-
+    
     /**
-     * Emits event to add criterion to the criteria list.
+     * Return new criterion
      *
-     * @fires EventEmitter<SelectMultipleCriterion>
+     * @return Criterion
      */
-    emitAdd(): void {
+    getCriterion(): Criterion {
         const values = this.form.value.select as string[];
-
         const options = this.attribute.options.filter(option => values.includes(option.value));
-        const ms = { id: this.attribute.id, type: 'multiple', options };
-        this.addCriterion.emit(ms);
+
+        return {
+            id: this.attribute.id,
+            type: 'multiple',
+            options
+        } as SelectMultipleCriterion;
     }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.html b/client/src/app/instance/search/components/criteria/search-type/select.component.html
index da79d656047719a8615a8e5b0bed6f10f86d5822..3e74403cdc522839caccafde2258b2e5e645097c 100644
--- a/client/src/app/instance/search/components/criteria/search-type/select.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/select.component.html
@@ -1,37 +1,15 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <span *ngIf="attribute.operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip">
-                <span class="far fa-question-circle fa-sm"></span>
-            </span>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
-                    <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
-                        <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
-                    </select>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <ng-select formControlName="select" [multiple]="false" [hideSelected]="true" class="ng-select-custom">
-                        <ng-option *ngFor="let option of attribute.options" [value]="option.value">{{option.label}}</ng-option>
-                    </ng-select>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0">
+            <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
+                <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
+            </select>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
+            <ng-select formControlName="select" [multiple]="false" [hideSelected]="true" class="ng-select-custom">
+                <ng-option *ngFor="let option of attribute.options" [value]="option.value">{{option.label}}</ng-option>
+            </ng-select>
         </div>
     </div>
 </form>
-
-<ng-template #helpLike>
-    <app-help-like></app-help-like>
-</ng-template>
\ No newline at end of file
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 b4496d00f25b3d1efe1e88eaa6ca0968fa782b08..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-import { TooltipModule } from 'ngx-bootstrap/tooltip';
-
-import { SelectComponent } from './select.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-import { Option } from '../../../../../metamodel/models';
-
-describe('[Instance][Search][Component][Criteria][SearchType] SelectComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-select 
-                    [id]="id"
-                    [label]="label"
-                    [operator]="operator"
-                    [options]="options"
-                    [criterion]="criterion"
-                    [advancedForm]="advancedForm">
-            </app-select>`
-    })
-    class TestHostComponent {
-        @ViewChild(SelectComponent, { static: false })
-        public testedComponent: SelectComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public operator: string = undefined;
-        public options: Option[] = undefined;
-        public criterion: FieldCriterion = undefined;
-        public advancedForm: boolean = false;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    @Component({ selector: 'app-help-like', template: '' })
-    class HelpLikeStubComponent { }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: SelectComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                SelectComponent,
-                TestHostComponent,
-                OperatorStubComponent,
-                HelpLikeStubComponent
-            ],
-            imports: [
-                NgSelectModule,
-                FormsModule,
-                ReactiveFormsModule,
-                TooltipModule.forRoot()
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: 'three' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.select.value).toEqual('three');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.select.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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;
-        testedComponent.operator = 'eq';
-        testedComponent.form.controls.select.setValue('three');
-        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: 'eq', value: 'three' } as FieldCriterion;
-        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.ts b/client/src/app/instance/search/components/criteria/search-type/select.component.ts
index 3fa487334565ce082f4b2ca755f131a26d03e198..9b49b4c5c1cb76118713203ef65aaaf0ef6c196c 100644
--- a/client/src/app/instance/search/components/criteria/search-type/select.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/select.component.ts
@@ -1,58 +1,27 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Select search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-select',
-    templateUrl: 'select.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'select.component.html'
 })
-export class SelectComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        select: new FormControl('', [Validators.required])
-    });
-    
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+export class SelectComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            select: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            this.form.controls.select.setValue(changes.criterion.currentValue.value);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            this.form.controls.select.setValue((criterion as FieldCriterion).value);
+        } else {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -61,6 +30,29 @@ export class SelectComponent implements OnInit, OnChanges {
         }
     }
 
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        let value = null;
+        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
+            const option = this.attribute.options.find(o => o.value === this.form.value.select);
+            value = option.value;
+        }
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            operator: this.form.controls.operator.value,
+            value
+        } as FieldCriterion;
+    }
+
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
+    }
+
     /**
      * Modifies operator with the given one.
      */
@@ -71,19 +63,4 @@ export class SelectComponent implements OnInit, OnChanges {
             this.form.controls.select.enable();
         }
     }
-
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        let value = null;
-        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
-            const option = this.attribute.options.find(o => o.value === this.form.value.select);
-            value = option.value;
-        }
-        const se = {id: this.attribute.id, type: 'field', operator: this.form.controls.operator.value, value };
-        this.addCriterion.emit(se);
-    }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.html b/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.html
index 26edbf9879ec6317bca480c58178dafbe23df283..482a50291107fe2c42f6063751789e1e1f16f70a 100644
--- a/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.html
@@ -1,47 +1,32 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <div class="operator_readonly">json</div>
+        </div>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col pl-sm-1">
             <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <div class="readonly">json</div>
+                <div class="col mb-1 mb-sm-0">
+                    <input *ngIf="svomKeywords.length < 1" class="form-control" id="path" name="path" placeholder="Path" autocomplete="off" formControlName="path">
+                    <ng-select *ngIf="svomKeywords.length > 0" [multiple]="false" [hideSelected]="true" placeholder="Select svom product keyword" class="ng-select-custom" formControlName="path">
+                        <ng-option *ngFor="let svomKeyword of svomKeywords" [value]="getKeywordValue(svomKeyword)">{{ svomKeyword.extension }}/{{ svomKeyword.name }}</ng-option>
+                    </ng-select>
+                </div>
+                <div class="w-100 d-block d-sm-none"></div>
+                <div class="col col-sm-auto mb-1 mb-sm-0 px-sm-0">
+                    <ng-select [clearable]="false" [multiple]="false" [hideSelected]="true" class="ng-select-custom" formControlName="operator">
+                        <ng-option value="eq">=</ng-option >
+                        <ng-option value="gt">></ng-option >
+                        <ng-option value="gte">>=</ng-option >
+                        <ng-option value="lt"><</ng-option >
+                        <ng-option value="lte"><=</ng-option >
+                    </ng-select>
                 </div>
                 <div class="w-100 d-block d-sm-none"></div>
-                <div class="col pl-sm-1">
-                    <div class="row">
-                        <div class="col mb-1 mb-sm-0">
-                            <input *ngIf="svomKeywords.length < 1" class="form-control" id="path" name="path" placeholder="Path" autocomplete="off" formControlName="path">
-                            <ng-select *ngIf="svomKeywords.length > 0" [multiple]="false" [hideSelected]="true" placeholder="Select svom product keyword" class="ng-select-custom" formControlName="path">
-                                <ng-option *ngFor="let svomKeyword of svomKeywords" [value]="getKeywordValue(svomKeyword)">{{ svomKeyword.extension }}/{{ svomKeyword.name }}</ng-option>
-                            </ng-select>
-                        </div>
-                        <div class="w-100 d-block d-sm-none"></div>
-                        <div class="col col-sm-auto mb-1 mb-sm-0 px-sm-0">
-                            <ng-select [clearable]="false" [multiple]="false" [hideSelected]="true" class="ng-select-custom" formControlName="operator">
-                                <ng-option value="eq">=</ng-option >
-                                <ng-option value="gt">></ng-option >
-                                <ng-option value="gte">>=</ng-option >
-                                <ng-option value="lt"><</ng-option >
-                                <ng-option value="lte"><=</ng-option >
-                            </ng-select>
-                        </div>
-                        <div class="w-100 d-block d-sm-none"></div>
-                        <div class="col">
-                            <input class="form-control" id="value" name="value" placeholder="Value" autocomplete="off" formControlName="value">
-                        </div>
-                    </div>
+                <div class="col">
+                    <input class="form-control" id="value" name="value" placeholder="Value" autocomplete="off" formControlName="value">
                 </div>
             </div>
         </div>
-        <div class="col-2 text-center align-self-end pb-3">
-            <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
-        </div>
     </div>
 </form>
diff --git a/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.spec.ts
deleted file mode 100644
index 05f55eec0e209ff39dc6559c31db1c4b60a81554..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.spec.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { Component, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { SvomJsonKwComponent } from './svom-json-kw.component';
-import { Criterion, JsonCriterion, SvomKeyword } from '../../../../store/models';
-
-describe('[Instance][Search][Component][Criteria][SearchType] SvomJsonKwComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-svom-json-kw-criteria 
-                    [id]="id"
-                    [label]="label"
-                    [criterion]="criterion"
-                    [criteriaList]="criteriaList"
-                    [svomKeywords]="svomKeywords">
-            </app-svom-json-kw-criteria >`
-    })
-    class TestHostComponent {
-        @ViewChild(SvomJsonKwComponent, { static: false })
-        public testedComponent: SvomJsonKwComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public criterion: JsonCriterion = undefined;
-        public criteriaList: Criterion[] = [];
-        public svomKeywords: SvomKeyword[] = [];
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: SvomJsonKwComponent;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                SvomJsonKwComponent,
-                TestHostComponent
-            ],
-            imports: [
-                FormsModule,
-                ReactiveFormsModule,
-                NgSelectModule
-            ]
-        });
-        testHostFixture = TestBed.createComponent(TestHostComponent);
-        testHostComponent = testHostFixture.componentInstance;
-        testHostFixture.detectChanges();
-        testedComponent = testHostComponent.testedComponent;
-    });
-
-    it('should create the component', () => {
-        expect(testedComponent).toBeTruthy();
-    });
-
-    it('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: testedComponent.id, type: 'json', path: 'myPath', operator: 'myOperator', value: 'myValue' } as JsonCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.path.value).toEqual('myPath');
-        expect(testedComponent.form.controls.operator.value).toEqual('myOperator');
-        expect(testedComponent.form.controls.value.value).toEqual('myValue');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.path.value).toBeNull();
-        expect(testedComponent.form.controls.operator.value).toBeNull();
-        expect(testedComponent.form.controls.value.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    it('raises the add criterion event when clicked', () => {
-        testedComponent.id = 1;
-        testedComponent.form.controls.path.setValue('myPath');
-        testedComponent.form.controls.operator.setValue('myOperator');
-        testedComponent.form.controls.value.setValue('myValue');
-        const expectedCriterion = { id: testedComponent.id, type: 'json', path: 'myPath', operator: 'myOperator', value: 'myValue' } as JsonCriterion;
-        testedComponent.addCriterion.subscribe((event: JsonCriterion) => expect(event).toEqual(expectedCriterion));
-        testedComponent.emitAdd();
-    });
-
-    it('Calculates the value of ng-option', () => {
-        expect(testedComponent.getKeywordValue({
-            extension: 'PrimaryHDU',
-            name: 'CARD',
-            data_type: 'string',
-            default: ''
-        })).toEqual('PrimaryHDU,CARD');
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.ts b/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.ts
index 7f77d22b8b3d9916190ae39cd805dff80e000709..6196a5157fa1d0893f0e5019dd82cccb894990a3 100644
--- a/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/svom-json-kw.component.ts
@@ -1,77 +1,33 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
- 
-import { JsonCriterion, Criterion, SvomKeyword } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
+
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
+import { Criterion, JsonCriterion } from 'src/app/instance/store/models';
 
 @Component({
-    selector: 'app-svom-json-kw-criteria',
-    templateUrl: 'svom-json-kw.component.html',
-    styleUrls: [ 'operator.component.scss' ],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    selector: 'app-svom-json-kw',
+    templateUrl: 'svom-json-kw.component.html'
 })
-export class SvomJsonKwComponent implements OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Input() criteriaList: Criterion[];
-    @Input() svomKeywords: SvomKeyword[];
-    @Output() selectSvomAcronym: EventEmitter<string> = new EventEmitter();
-    @Output() resetSvomKeywords: EventEmitter<{}> = new EventEmitter();
-    @Output() addCriterion: EventEmitter<JsonCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        path: new FormControl('', [Validators.required]),
-        operator: new FormControl('', [Validators.required]),
-        value: new FormControl('', [Validators.required])
-    });
-
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            this.form.patchValue(changes.criterion.currentValue);
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
-        }
-
-        if (changes.criteriaList && this.svomKeywords.length < 1 && changes.criteriaList.currentValue.find((c: Criterion) => c.id === 3)) {
-            this.selectSvomAcronym.emit(changes.criteriaList.currentValue.find((c: Criterion) => c.id === 3).value);
-        }
-
-        if (changes.criteriaList && this.svomKeywords.length > 0 && !changes.criteriaList.currentValue.find((c: Criterion) => c.id === 3)) {
-            this.resetSvomKeywords.emit();
-        }
-    }
-
-    /**
-     * Transform a SVOM json Keyword to as path value (anis json search)
-     * 
-     * @param svomKeyword Keyword selected by user
-     * @returns string path value
-     */
-    getKeywordValue(svomKeyword: SvomKeyword): string {
-        return `${svomKeyword.extension},${svomKeyword.name}`
+export class SvomJsonKwComponent extends AbstractSearchTypeComponent {
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            path: new FormControl('', [Validators.required]),
+            operator: new FormControl('', [Validators.required]),
+            value: new FormControl('', [Validators.required])
+        });
     }
-
+    
     /**
-     * Emits event to add criterion to the criteria list.
+     * Return new criterion
      *
-     * @fires EventEmitter<JsonCriterion>
+     * @return Criterion
      */
-    emitAdd(): void {
-        const js = { id: this.attribute.id, type: 'json', ...this.form.value };
-        this.addCriterion.emit(js);
+    getCriterion(): Criterion {
+        return {
+            id: this.attribute.id,
+            type: 'json',
+            ...this.form.value
+        } as JsonCriterion;
     }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.html b/client/src/app/instance/search/components/criteria/search-type/time.component.html
index 4e8a8e91bddeb843353c9e295430709936da038d..4d940d450c614160079f24e028379a357d4b230a 100644
--- a/client/src/app/instance/search/components/criteria/search-type/time.component.html
+++ b/client/src/app/instance/search/components/criteria/search-type/time.component.html
@@ -1,36 +1,21 @@
 <form [formGroup]="form" novalidate>
-    <div class="row">
-        <div class="col form-group">
-            <label>
-                <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label>
-            </label>
-            <div class="row">
-                <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
-                    <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
-                        <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
-                    </select>
-                </div>
-                <div class="w-100 d-block d-sm-none"></div>
-                <div class="col-auto pl-sm-1 pr-1">
-                    <ng-select formControlName="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time">
-                        <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option>
-                    </ng-select>
-                </div>
-                <div class="col col-sm-auto p-0 text-center">:</div>
-                <div class="col-auto pl-1">
-                    <ng-select formControlName="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time">
-                        <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option>
-                    </ng-select>
-                </div>
-            </div>
+    <div class="row form-group">
+        <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0">
+            <select class="custom-select" formControlName="operator" (change)="operatorOnChange()">
+                <option *ngFor="let o of operators" [ngValue]="o.value">{{ o.label }}</option>
+            </select>
         </div>
-        <div class="col-2 text-center align-self-end mb-0 mb-sm-1 pb-3">
-            <button class="btn btn-outline-success" *ngIf="!criterion" [hidden]="!form.valid && form.controls.operator.value != 'nl' && form.controls.operator.value != 'nnl'" (click)="emitAdd()">
-                <span class="fas fa-plus fa-fw"></span>
-            </button>
-            <button class="btn btn-outline-danger" *ngIf="criterion" (click)="deleteCriterion.emit(attribute.id)">
-                <span class="fa fa-times fa-fw"></span>
-            </button>
+        <div class="w-100 d-block d-sm-none"></div>
+        <div class="col-auto pl-sm-1 pr-1">
+            <ng-select formControlName="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time">
+                <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option>
+            </ng-select>
+        </div>
+        <div class="col col-sm-auto p-0 text-center">:</div>
+        <div class="col-auto pl-1">
+            <ng-select formControlName="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time">
+                <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option>
+            </ng-select>
         </div>
     </div>
-</form>
\ No newline at end of file
+</form>
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 4b627ac6599797d1a5edddabef980fc023e66cd3..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { NgSelectModule } from '@ng-select/ng-select';
-
-import { TimeComponent } from './time.component';
-import { FieldCriterion } from '../../../../store/models/criterion';
-
-describe('[Instance][Search][Component][Criteria][SearchType] TimeComponent', () => {
-    @Component({
-        selector: `app-host`,
-        template: `
-            <app-time 
-                    [id]="id"
-                    [label]="label"
-                    [operator]="operator"
-                    [criterion]="criterion"
-                    [advancedForm]="advancedForm">
-            </app-time>`
-    })
-    class TestHostComponent {
-        @ViewChild(TimeComponent, { static: false })
-        public testedComponent: TimeComponent;
-        public id: number = undefined;
-        public label: string = undefined;
-        public operator: string = undefined;
-        public criterion: FieldCriterion = undefined;
-        public advancedForm: boolean = false;
-    }
-
-    @Component({ selector: 'app-operator', template: '' })
-    class OperatorStubComponent {
-        @Input() operator: string;
-        @Input() searchType: string;
-        @Input() advancedForm: boolean;
-        @Input() disabled: boolean;
-    }
-
-    let testHostComponent: TestHostComponent;
-    let testHostFixture: ComponentFixture<TestHostComponent>;
-    let testedComponent: TimeComponent;
-
-    beforeEach(() => {
-        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('should call ngOnChanges and apply changes', () => {
-        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
-        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: '15:47' } as FieldCriterion;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.hh.value).toEqual('15');
-        expect(testedComponent.form.controls.mm.value).toEqual('47');
-        expect(testedComponent.form.disabled).toBeTruthy();
-        testHostComponent.criterion = undefined;
-        testHostFixture.detectChanges();
-        expect(testedComponent.form.controls.hh.value).toBeNull();
-        expect(testedComponent.form.controls.hh.value).toBeNull();
-        expect(testedComponent.form.enabled).toBeTruthy();
-        expect(spy).toHaveBeenCalledTimes(2);
-    });
-
-    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.form.controls.hh.setValue('15');
-        testedComponent.form.controls.mm.setValue('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('#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');
-    });
-});
diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.ts b/client/src/app/instance/search/components/criteria/search-type/time.component.ts
index 9b3cc6585f5203c46a1cddac2dcb0943af21f8ca..6c74117c9421134763d72a70b56fd4eac5d906ff 100644
--- a/client/src/app/instance/search/components/criteria/search-type/time.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/time.component.ts
@@ -1,68 +1,36 @@
-/**
- * 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, OnChanges, SimpleChanges } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators } from '@angular/forms';
 
+import { AbstractSearchTypeComponent } from './abstract-search-type.component';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
-import { Attribute } from 'src/app/metamodel/models';
-import { searchTypeOperators } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Time search type component.
- *
- * @implements OnChanges
- */
 @Component({
     selector: 'app-time',
-    templateUrl: 'time.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
+    templateUrl: 'time.component.html'
 })
-
-export class TimeComponent implements OnInit, OnChanges {
-    @Input() attribute: Attribute;
-    @Input() criterion: Criterion;
-    @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter();
-    @Output() deleteCriterion: EventEmitter<number> = new EventEmitter();
-
-    public form = new FormGroup({
-        operator: new FormControl(''),
-        hh: new FormControl('', [Validators.required]),
-        mm: new FormControl('', [Validators.required])
-    });
-
+export class TimeComponent extends AbstractSearchTypeComponent {
     hours: string[] = this.initTime(24);
     minutes: string[] = this.initTime(60);
 
-    operators = searchTypeOperators;
-
-    ngOnInit() {
-        if (!this.attribute.dynamic_operator) {
-            this.form.controls.operator.disable();
-        }
+    constructor() {
+        super();
+        this.form = new FormGroup({
+            operator: new FormControl(''),
+            hh: new FormControl('', [Validators.required]),
+            mm: new FormControl('', [Validators.required])
+        });
     }
 
-    ngOnChanges(changes: SimpleChanges): void {
-        if (changes.criterion && changes.criterion.currentValue) {
-            const criterion = changes.criterion.currentValue as FieldCriterion;
-            this.form.controls.operator.setValue(criterion.operator);
-            if (criterion.operator != 'nl' && criterion.operator != 'nnl') {
-                this.form.controls.hh.setValue(criterion.value.slice(0, 2));
-                this.form.controls.mm.setValue(criterion.value.slice(3, 5));
+    setCriterion(criterion: Criterion) {
+        super.setCriterion(criterion);
+        if (criterion) {
+            const fieldCriterion = criterion as FieldCriterion;
+            this.form.controls.operator.setValue(fieldCriterion.operator);
+            if (fieldCriterion.operator != 'nl' && fieldCriterion.operator != 'nnl') {
+                this.form.controls.hh.setValue(fieldCriterion.value.slice(0, 2));
+                this.form.controls.mm.setValue(fieldCriterion.value.slice(3, 5));
             }
-            this.form.disable();
-        }
-
-        if (changes.criterion && !changes.criterion.currentValue) {
-            this.form.enable();
-            this.form.reset();
+        } else {
             this.form.controls.operator.setValue(this.attribute.operator);
             if (!this.attribute.dynamic_operator) {
                 this.form.controls.operator.disable();
@@ -70,6 +38,29 @@ export class TimeComponent implements OnInit, OnChanges {
             this.operatorOnChange();
         }
     }
+    
+    /**
+     * Return new criterion
+     *
+     * @return Criterion
+     */
+    getCriterion(): Criterion {
+        let value = null;
+        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
+            value = `${this.form.value.hh}:${this.form.value.mm}`
+        }
+
+        return {
+            id: this.attribute.id,
+            type: 'field',
+            operator: this.form.controls.operator.value,
+            value
+        } as FieldCriterion;
+    }
+
+    isValid(): boolean {
+        return this.form.valid || this.form.controls.operator.value === 'nl' || this.form.controls.operator.value === 'nnl';
+    }
 
     /**
      * Modifies operator with the given one.
@@ -84,20 +75,6 @@ export class TimeComponent implements OnInit, OnChanges {
         }
     }
 
-    /**
-     * Emits event to add criterion to the criteria list.
-     *
-     * @fires EventEmitter<FieldCriterion>
-     */
-    emitAdd(): void {
-        let value = null;
-        if (this.form.controls.operator.value != 'nl' && this.form.controls.operator.value != 'nnl') {
-            value = `${this.form.value.hh}:${this.form.value.mm}`
-        }
-        const time = {id: this.attribute.id, type: 'field', operator: this.form.controls.operator.value, value };
-        this.addCriterion.emit(time);
-    }
-
     /**
      * Returns string array to represent the given time.
      *
@@ -113,4 +90,5 @@ export class TimeComponent implements OnInit, OnChanges {
         }
         return array;
     }
+
 }
diff --git a/client/src/app/instance/search/components/criteria/test-search-type.component.ts b/client/src/app/instance/search/components/criteria/test-search-type.component.ts
deleted file mode 100644
index 72d3aefa2813cdab0f9a0223c9adcede0245cb18..0000000000000000000000000000000000000000
--- a/client/src/app/instance/search/components/criteria/test-search-type.component.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { AbstractSearchTypeComponent } from './abstract-search-type.component';
-
-@Component({
-    selector: 'app-test-search-type',
-    template: '<p>{{ data.text }}</p>'
-})
-export class TestSearchTypeComponent implements AbstractSearchTypeComponent {
-    @Input() data: any;
-}
diff --git a/client/src/styles.scss b/client/src/styles.scss
index dfcc0815a1435d9e54d35ec964131da9877fb1c1..2f276ed780c804c7a33845a14cafaff9d99f1680 100644
--- a/client/src/styles.scss
+++ b/client/src/styles.scss
@@ -95,3 +95,17 @@ input.ng-invalid, select.ng-invalid, .ng-select.ng-invalid div.ng-select-contain
 .disabled {
     cursor: not-allowed !important;
 }
+
+.operator_readonly {
+    background-color: #e9ecef;
+    border: 1px solid #ced4da;
+    border-radius: .25rem;
+    display: block;
+    width: 100%;
+    height: calc(1.5em + .75rem + 2px);
+    padding: .375rem .75rem;
+    font-size: 1rem;
+    font-weight: 400;
+    line-height: 1.5;
+    color:#495057;
+}