From c67698a9a2cbe07912b3882aeffbe9bde8a37272 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Fri, 3 Sep 2021 16:56:01 +0200
Subject: [PATCH] Search multiple overivew + count => done

---
 .../datasets/dataset-list.component.html      |  13 ++
 .../datasets/dataset-list.component.ts        |  82 ++++++++++
 .../datasets-by-family.component.html         |  43 +++++
 .../datasets-by-family.component.scss         |  27 +++
 .../datasets/datasets-by-family.component.ts  |  96 +++++++++++
 .../components/datasets/index.ts              |   7 +
 .../search-multiple/components/index.ts       |   6 +-
 .../components/result/index.ts                |   5 +
 .../components/result/overview.component.html |  69 ++++++++
 .../components/result/overview.component.ts   |  87 ++++++++++
 .../abstract-search-multiple.component.ts     |  14 +-
 .../containers/datasets.component.html        |  59 +++----
 .../containers/datasets.component.ts          |  89 ++--------
 .../containers/position.component.html        |  25 ++-
 .../containers/position.component.ts          |  29 +++-
 .../containers/result-multiple.component.html |  23 +--
 .../containers/result-multiple.component.ts   | 154 +++++-------------
 .../search-multiple-routing.module.ts         |  10 +-
 .../criteria/cone-search-tab.component.html   |   2 -
 .../search/containers/result.component.ts     |   2 +-
 .../cone-search/cone-search.component.ts      |   2 -
 .../store/actions/search-multiple.actions.ts  |  11 +-
 .../store/effects/search-multiple.effects.ts  |  91 +++++++++--
 client/src/app/instance/store/models/index.ts |   2 +
 .../models/search-multiple-dataset-data.ts}   |   5 +-
 .../models/search-multiple-dataset-length.ts  |  13 ++
 .../store/reducers/search-multiple.reducer.ts |  59 ++++++-
 .../selectors/search-multiple.selector.ts     |  35 ++++
 28 files changed, 770 insertions(+), 290 deletions(-)
 create mode 100644 client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html
 create mode 100644 client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts
 create mode 100644 client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html
 create mode 100644 client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss
 create mode 100644 client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts
 create mode 100644 client/src/app/instance/search-multiple/components/datasets/index.ts
 create mode 100644 client/src/app/instance/search-multiple/components/result/index.ts
 create mode 100644 client/src/app/instance/search-multiple/components/result/overview.component.html
 create mode 100644 client/src/app/instance/search-multiple/components/result/overview.component.ts
 rename client/src/app/instance/{search-multiple/containers/position.component.scss => store/models/search-multiple-dataset-data.ts} (73%)
 create mode 100644 client/src/app/instance/store/models/search-multiple-dataset-length.ts

diff --git a/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html
new file mode 100644
index 00000000..4395a45c
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html
@@ -0,0 +1,13 @@
+<div class="row">
+    <div *ngFor="let datasetFamily of getDatasetFamilyList()" class="col-12 col-lg-6 col-xl-4 my-3 text-center">
+        <app-datasets-by-family
+            [datasetFamily]="datasetFamily"
+            [datasetList]="getDatasetsByFamily(datasetFamily.id)"
+            [surveyList]="surveyList"
+            [selectedDatasets]="selectedDatasets"
+            [isAllSelected]="getIsAllSelected(datasetFamily.id)"
+            [isAllUnselected]="getIsAllUnselected(datasetFamily.id)"
+            (updateSelectedDatasets)="updateSelectedDatasets.emit($event)">
+        </app-datasets-by-family>
+    </div>
+</div>
diff --git a/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts
new file mode 100644
index 00000000..f01569ab
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts
@@ -0,0 +1,82 @@
+/**
+ * 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 } from '@angular/core';
+
+import { Dataset, DatasetFamily, Survey } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-dataset-list',
+    templateUrl: 'dataset-list.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+/**
+ * @class
+ * @classdesc Search multiple dataset list component.
+ */
+export class DatasetListComponent {
+    @Input() datasetFamilyList: DatasetFamily[];
+    @Input() datasetList: Dataset[];
+    @Input() surveyList: Survey[];
+    @Input() selectedDatasets: string[];
+    @Output() updateSelectedDatasets: EventEmitter<string[]> = new EventEmitter();
+
+    /**
+     * Returns dataset family list sorted by display, that contains datasets with cone search enabled.
+     *
+     * @return Family[]
+     */
+    getDatasetFamilyList(): DatasetFamily[] {
+        const familyId: number[] = [];
+        this.datasetList.forEach(d => {
+            if (!familyId.includes(d.id_dataset_family)) {
+                familyId.push(d.id_dataset_family);
+            }
+        });
+        return this.datasetFamilyList
+            .filter(f => familyId.includes(f.id));
+    }
+
+    /**
+     * Returns dataset list that belongs to the given ID family.
+     *
+     * @param  {number} familyId - The dataset family ID.
+     *
+     * @return Dataset[]
+     */
+    getDatasetsByFamily(familyId: number): Dataset[] {
+        return this.datasetList.filter(d => d.id_dataset_family === familyId);
+    }
+
+    /**
+     * Checks if all datasets that belongs to the given dataset family ID are selected.
+     *
+     * @param  {number} familyId - The dataset family ID.
+     *
+     * @return boolean
+     */
+    getIsAllSelected(familyId: number): boolean {
+        const datasetListName = this.getDatasetsByFamily(familyId).map(d => d.name);
+        const filteredSelectedDatasets = this.selectedDatasets.filter(name => datasetListName.indexOf(name) > -1);
+        return datasetListName.length === filteredSelectedDatasets.length;
+    }
+
+    /**
+     * Checks if none of datasets that belongs to the given dataset family ID are selected.
+     *
+     * @param  {number} familyId - The dataset family ID.
+     *
+     * @return boolean
+     */
+    getIsAllUnselected(familyId: number): boolean {
+        const datasetListName = this.getDatasetsByFamily(familyId).map(d => d.name);
+        const filteredSelectedDatasets = this.selectedDatasets.filter(name => datasetListName.indexOf(name) > -1);
+        return filteredSelectedDatasets.length === 0;
+    }
+}
diff --git a/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html
new file mode 100644
index 00000000..c0ab965e
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html
@@ -0,0 +1,43 @@
+<p class="mb-3"><em>{{ datasetFamily.label }}</em></p>
+<div class="row mb-1">
+    <div class="col pr-1">
+        <button (click)="selectAll()" [disabled]="isAllSelected"
+            class="btn btn-sm btn-block btn-outline-secondary letter-spacing">
+            Select All
+        </button>
+    </div>
+    <div class="col pl-1">
+        <button (click)="unselectAll()" [disabled]="isAllUnselected"
+            class="btn btn-sm btn-block btn-outline-secondary letter-spacing">
+            Unselect All
+        </button>
+    </div>
+</div>
+<div class="selectbox p-0">
+    <div *ngFor="let dataset of datasetList">
+        <div *ngIf="isSelected(dataset.name)">
+            <button class="btn btn-block text-left py-1 m-0 rounded-0" (click)="toggleSelection(dataset.name)">
+                <span class="fas fa-fw fa-check-square theme-color"></span>
+                {{ dataset.label }}
+                <span [tooltip]="datasetInfo" placement="right" containerClass="custom-tooltip right-tooltip">
+                    <span class="far fa-question-circle fa-xs"></span>
+                </span>
+            </button>
+        </div>
+        <div *ngIf="!isSelected(dataset.name)">
+            <button class="btn btn-block text-left py-1 m-0 rounded-0" (click)="toggleSelection(dataset.name)">
+                <span class="far fa-fw fa-square text-secondary"></span>
+                {{ dataset.label }}
+                <span [tooltip]="datasetInfo" placement="right" containerClass="custom-tooltip right-tooltip">
+                    <span class="far fa-question-circle fa-xs"></span>
+                </span>
+            </button>
+        </div>
+
+        <ng-template #datasetInfo class="text-left">
+            {{ dataset.description }}
+            <br><br>
+            {{ getSurveyDescription(dataset.survey_name) }}
+        </ng-template>
+    </div>
+</div>
diff --git a/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss
new file mode 100644
index 00000000..14afb86a
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss
@@ -0,0 +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.
+ */
+
+.selectbox {
+    height: 200px;
+    overflow-y: auto;
+    border: 1px solid #ced4da;
+    border-radius: .25rem;
+}
+
+.letter-spacing {
+    letter-spacing: 2px;
+}
+
+.selectbox button:hover {
+    background-color: #ECECEC;
+}
+
+.selectbox button:focus {
+    box-shadow: none;
+}
diff --git a/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts
new file mode 100644
index 00000000..8a1a260c
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts
@@ -0,0 +1,96 @@
+/**
+ * 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, ViewEncapsulation } from '@angular/core';
+
+import { Dataset, DatasetFamily, Survey } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-datasets-by-family',
+    templateUrl: 'datasets-by-family.component.html',
+    styleUrls: ['datasets-by-family.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+    encapsulation: ViewEncapsulation.None
+})
+/**
+ * @class
+ * @classdesc Search multiple datasets by family component.
+ */
+export class DatasetsByFamilyComponent {
+    @Input() datasetFamily: DatasetFamily;
+    @Input() datasetList: Dataset[];
+    @Input() surveyList: Survey[];
+    @Input() selectedDatasets: string[];
+    @Input() isAllSelected: boolean;
+    @Input() isAllUnselected: boolean;
+    @Output() updateSelectedDatasets: EventEmitter<string[]> = new EventEmitter();
+
+    /**
+     * Checks if dataset is selected fot the given dataset name.
+     *
+     * @param  {string} dname - The dataset name.
+     *
+     * @return boolean
+     */
+    isSelected(dname: string): boolean {
+        return this.selectedDatasets.filter(i => i === dname).length > 0;
+    }
+
+    /**
+     * Emits event to update dataset list selection with the given updated selected dataset name list.
+     *
+     * @param  {string} dname - The dataset name.
+     */
+    toggleSelection(dname: string): void {
+        const clonedSelectedDatasets = [...this.selectedDatasets];
+        const index = clonedSelectedDatasets.indexOf(dname);
+        if (index > -1) {
+            clonedSelectedDatasets.splice(index, 1);
+        } else {
+            clonedSelectedDatasets.push(dname);
+        }
+        this.updateSelectedDatasets.emit(clonedSelectedDatasets);
+    }
+
+    /**
+     * Emits event to update dataset list selection with all datasets names.
+     */
+    selectAll(): void {
+        const clonedSelectedDatasets = [...this.selectedDatasets];
+        const datasetListName = this.datasetList.map(d => d.name);
+        datasetListName.filter(name => clonedSelectedDatasets.indexOf(name) === -1).forEach(name => {
+            clonedSelectedDatasets.push(name);
+        });
+        this.updateSelectedDatasets.emit(clonedSelectedDatasets);
+    }
+
+    /**
+     * Emits event to update dataset list selection with no datasets names.
+     */
+    unselectAll(): void {
+        const clonedSelectedDatasets = [...this.selectedDatasets];
+        const datasetListName = this.datasetList.map(d => d.name);
+        datasetListName.filter(name => clonedSelectedDatasets.indexOf(name) > -1).forEach(name => {
+            const index = clonedSelectedDatasets.indexOf(name);
+            clonedSelectedDatasets.splice(index, 1);
+        });
+        this.updateSelectedDatasets.emit(clonedSelectedDatasets);
+    }
+
+    /**
+     * Returns survey description of the given survey name.
+     *
+     * @param  {string} surveyName - The survey name.
+     *
+     * @return string
+     */
+    getSurveyDescription(surveyName: string): string {
+        return this.surveyList.find(p => p.name === surveyName).description;
+    }
+}
diff --git a/client/src/app/instance/search-multiple/components/datasets/index.ts b/client/src/app/instance/search-multiple/components/datasets/index.ts
new file mode 100644
index 00000000..730e87c6
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/datasets/index.ts
@@ -0,0 +1,7 @@
+import { DatasetListComponent } from './dataset-list.component';
+import { DatasetsByFamilyComponent } from './datasets-by-family.component';
+
+export const datasetsComponents = [
+    DatasetListComponent,
+    DatasetsByFamilyComponent
+];
diff --git a/client/src/app/instance/search-multiple/components/index.ts b/client/src/app/instance/search-multiple/components/index.ts
index d3d2236e..53355e40 100644
--- a/client/src/app/instance/search-multiple/components/index.ts
+++ b/client/src/app/instance/search-multiple/components/index.ts
@@ -1,7 +1,11 @@
 import { ProgressBarMultipleComponent } from './progress-bar-multiple.component';
 import { SummaryMultipleComponent } from './summary-multiple.component';
+import { datasetsComponents } from './datasets';
+import { resultComponents } from './result';
 
 export const dummiesComponents = [
     ProgressBarMultipleComponent,
-    SummaryMultipleComponent
+    SummaryMultipleComponent,
+    datasetsComponents,
+    resultComponents
 ];
diff --git a/client/src/app/instance/search-multiple/components/result/index.ts b/client/src/app/instance/search-multiple/components/result/index.ts
new file mode 100644
index 00000000..73ec1010
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/result/index.ts
@@ -0,0 +1,5 @@
+import { OverviewComponent } from './overview.component';
+
+export const resultComponents = [
+    OverviewComponent
+];
diff --git a/client/src/app/instance/search-multiple/components/result/overview.component.html b/client/src/app/instance/search-multiple/components/result/overview.component.html
new file mode 100644
index 00000000..d1414ccf
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/result/overview.component.html
@@ -0,0 +1,69 @@
+<div class="jumbotron mb-5 py-4">
+    <div *ngIf="getTotalObject() === 0">
+        <div class="">
+            <h2 class="font-weight-bold">No results.</h2>
+        </div>
+        <hr class="my-4">
+        <div class="row justify-content-around">
+            <div class="col-auto border-right">
+                <span class="title">Cone search</span>
+                <ul class="list-unstyled pl-3">
+                    <li>RA = {{ coneSearch.ra }}°</li>
+                    <li>DEC = {{ coneSearch.dec }}°</li>
+                    <li>radius = {{ coneSearch.radius }} arcsecond</li>
+                </ul>
+            </div>
+            <div *ngFor="let datasetFamily of getSortedDatasetFamilyList()" class="col-auto">
+                <span class="title">{{ datasetFamily.label }}</span>
+                <ul class="list-unstyled pl-3">
+                    <li *ngFor="let dataset of getSelectedDatasetsByFamily(datasetFamily.id)" >
+                        {{ dataset.label }} <span class="badge badge-pill badge-light text-danger">0</span>
+                    </li>
+                </ul>
+            </div>
+        </div>
+        <hr class="my-4">
+        <div class="text-center">
+            <a routerLink="/search-multiple/position" [queryParams]="queryParams" class="btn btn-lg btn-outline-primary">
+                <span class="fas fa-undo"></span> Try something else
+            </a>
+        </div>
+    </div>
+
+    <div *ngIf="getTotalObject() > 0">
+        <div class="lead">
+            Found
+            <span class="font-weight-bold">{{ getTotalObject() }}</span>
+            <span *ngIf="getTotalObject() > 1; else object"> objects</span>
+            <ng-template #object> object</ng-template>
+            in
+            <span class="font-weight-bold">{{ getTotalDatasets() }}</span>
+            <span *ngIf="getTotalDatasets() > 1; else dataset"> datasets</span>
+            <ng-template #dataset> dataset</ng-template>.
+        </div>
+        <hr class="my-4">
+        <div class="row justify-content-around">
+            <div class="col-auto border-right">
+                <span class="title">Cone search</span>
+                <ul class="list-unstyled pl-3">
+                    <li>RA = {{ coneSearch.ra }}°</li>
+                    <li>DEC = {{ coneSearch.dec }}°</li>
+                    <li>radius = {{ coneSearch.radius }} arcsecond</li>
+                </ul>
+            </div>
+            <div *ngFor="let datasetFamily of getSortedDatasetFamilyList()" class="col-auto">
+                <span class="title">{{ datasetFamily.label }}</span>
+                <ul class="list-unstyled pl-3">
+                    <li *ngFor="let dataset of getSelectedDatasetsByFamily(datasetFamily.id)" >
+                        {{ dataset.label }}
+                        <span class="badge badge-pill badge-light" [ngClass]="{'text-primary': getCountByDataset(dataset.name) !== 0}">
+                            {{ getCountByDataset(dataset.name) }}
+                        </span>
+                        <span *ngIf="getCountByDataset(dataset.name) < 2"> object found</span>
+                        <span *ngIf="getCountByDataset(dataset.name) > 1"> objects found</span>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/client/src/app/instance/search-multiple/components/result/overview.component.ts b/client/src/app/instance/search-multiple/components/result/overview.component.ts
new file mode 100644
index 00000000..a2f64652
--- /dev/null
+++ b/client/src/app/instance/search-multiple/components/result/overview.component.ts
@@ -0,0 +1,87 @@
+/**
+ * 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 } from '@angular/core';
+
+import { DatasetFamily, Dataset } from 'src/app/metamodel/models';
+import { ConeSearch, SearchMultipleDatasetLength, SearchMultipleQueryParams } from 'src/app/instance/store/models';
+
+@Component({
+    selector: 'app-overview',
+    templateUrl: 'overview.component.html'
+})
+export class OverviewComponent {
+    @Input() datasetFamilyList: DatasetFamily[];
+    @Input() datasetList: Dataset[];
+    @Input() coneSearch: ConeSearch;
+    @Input() selectedDatasets: string[];
+    @Input() dataLength: SearchMultipleDatasetLength[];
+    @Input() queryParams: SearchMultipleQueryParams;
+
+    /**
+     * Returns total amount of results for all datasets.
+     *
+     * @return number
+     */
+    getTotalObject(): number {
+        return this.dataLength
+            .filter(datasetLength => datasetLength.length > 0)
+            .reduce((sum, datasetLength) => sum + datasetLength.length, 0);
+    }
+
+    /**
+     * Returns total number of datasets with results.
+     *
+     * @return number
+     */
+    getTotalDatasets(): number {
+        return this.dataLength.filter(datasetLength => datasetLength.length > 0).length;
+    }
+
+    /**
+     * Returns dataset families sorted by display, that contains selected datasets.
+     *
+     * @return Family[]
+     */
+    getSortedDatasetFamilyList(): DatasetFamily[] {
+        let datasetFamiliesWithSelectedDataset: DatasetFamily[] = [];
+        this.selectedDatasets.forEach(dname => {
+            const dataset: Dataset = this.datasetList.find(d => d.name === dname);
+            const datasetFamily: DatasetFamily = this.datasetFamilyList.find(f => f.id === dataset.id_dataset_family);
+            if (!datasetFamiliesWithSelectedDataset.includes(datasetFamily)) {
+                datasetFamiliesWithSelectedDataset.push(datasetFamily);
+            }
+        });
+        return datasetFamiliesWithSelectedDataset;
+    }
+
+    /**
+     * Returns selected dataset list for the given dataset family ID.
+     *
+     * @param  {number} familyId - The family ID.
+     *
+     * @return Dataset[]
+     */
+    getSelectedDatasetsByFamily(familyId: number): Dataset[] {
+        return this.datasetList
+            .filter(d => d.id_dataset_family === familyId)
+            .filter(d => this.selectedDatasets.includes(d.name));
+    }
+
+    /**
+     * Returns the result number for the given dataset name.
+     *
+     * @param  {string} dname - The dataset name.
+     *
+     * @return number
+     */
+    getCountByDataset(dname: string): number {
+        return this.dataLength.find(datasetLength => datasetLength.datasetName === dname).length;
+    }
+}
diff --git a/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts b/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts
index f83b809a..31963675 100644
--- a/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts
+++ b/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts
@@ -1,4 +1,4 @@
-import { Directive } from '@angular/core';
+import { Directive, OnDestroy, OnInit } from '@angular/core';
 
 import { Store } from '@ngrx/store';
 import { Observable } from 'rxjs';
@@ -8,11 +8,12 @@ import { ConeSearch, SearchMultipleQueryParams } from '../../store/models';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
 import * as datasetFamilySelector from 'src/app/metamodel/selectors/dataset-family.selector';
 import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
+import * as searchMultipleActions from '../../store/actions/search-multiple.actions';
 import * as searchMultipleSelector from 'src/app/instance/store/selectors/search-multiple.selector';
 import * as coneSearchSelector from 'src/app/instance/store/selectors/cone-search.selector';
 
 @Directive()
-export abstract class AbstractSearchMultipleComponent {
+export abstract class AbstractSearchMultipleComponent implements OnInit, OnDestroy {
     public instanceSelected: Observable<string>;
     public datasetFamilyListIsLoading: Observable<boolean>;
     public datasetFamilyListIsLoaded: Observable<boolean>;
@@ -20,7 +21,6 @@ export abstract class AbstractSearchMultipleComponent {
     public datasetListIsLoading: Observable<boolean>;
     public datasetListIsLoaded: Observable<boolean>;
     public datasetList: Observable<Dataset[]>;
-    public pristine: Observable<boolean>;
     public currentStep: Observable<string>;
     public selectedDatasets: Observable<string[]>;
     public coneSearch: Observable<ConeSearch>;
@@ -34,13 +34,15 @@ export abstract class AbstractSearchMultipleComponent {
         this.datasetListIsLoading = this.store.select(datasetSelector.selectDatasetListIsLoading);
         this.datasetListIsLoaded = this.store.select(datasetSelector.selectDatasetListIsLoaded);
         this.datasetList = this.store.select(datasetSelector.selectAllDatasets);
-        this.pristine = this.store.select(searchMultipleSelector.selectPristine);
         this.currentStep = this.store.select(searchMultipleSelector.selectCurrentStep)
         this.selectedDatasets = this.store.select(searchMultipleSelector.selectSelectedDatasets);
         this.coneSearch = this.store.select(coneSearchSelector.selectConeSearch);
         this.queryParams = this.store.select(searchMultipleSelector.selectQueryParams);
     }
-
+    
     ngOnInit() {
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.initSearch()));
     }
-}
\ No newline at end of file
+
+    ngOnDestroy() { }
+}
diff --git a/client/src/app/instance/search-multiple/containers/datasets.component.html b/client/src/app/instance/search-multiple/containers/datasets.component.html
index 4f05f1ef..6c030e4a 100644
--- a/client/src/app/instance/search-multiple/containers/datasets.component.html
+++ b/client/src/app/instance/search-multiple/containers/datasets.component.html
@@ -1,61 +1,44 @@
-<div class="row mt-4">
+<app-spinner *ngIf="(datasetFamilyListIsLoading | async) || (datasetListIsLoading | async) || (surveyListIsLoading | async)">
+</app-spinner>
+
+<div *ngIf="(datasetFamilyListIsLoaded | async) && (datasetListIsLoaded | async) && (surveyListIsLoaded | async)" class="row mt-4">
     <div class="col-12 col-md-8 col-lg-9">
         <div class="border rounded my-2">
             <p class="border-bottom bg-light text-primary mb-0 py-4 pl-4">Datasets</p>
             <div class="px-3">
-                <div *ngIf="datasetSearchMetaIsLoading | async" class="text-center my-5">
-                    <span class="fas fa-circle-notch fa-spin fa-3x"></span>
-                    <span class="sr-only">Loading...</span>
-                </div>
-                    <app-dataset-list *ngIf="datasetSearchMetaIsLoaded | async"
-                        [surveyList]="surveyList | async"
-                        [datasetFamilyList]="datasetFamilyList | async"
-                        [datasetList]="datasetList | async"
-                        [selectedDatasets]="selectedDatasets | async"
-                        (updateSelectedDatasets)="updateSelectedDatasets($event)">
-                    </app-dataset-list>
+                <app-dataset-list
+                    [surveyList]="surveyList | async"
+                    [datasetFamilyList]="datasetFamilyList | async"
+                    [datasetList]="datasetList | async"
+                    [selectedDatasets]="selectedDatasets | async"
+                    (updateSelectedDatasets)="updateSelectedDatasets($event)">
+                </app-dataset-list>
             </div>
         </div>
     </div>
     <div class="col-12 col-md-4 col-lg-3 pt-2">
         <app-summary-multiple
             [currentStep]="currentStep | async"
-            [isValidConeSearch]="isValidConeSearch | async"
             [coneSearch]="coneSearch | async"
-            [datasetSearchMetaIsLoading]="datasetSearchMetaIsLoading | async"
-            [datasetSearchMetaIsLoaded]="datasetSearchMetaIsLoaded | async"
-            [datasetFamilyList]="datasetFamilyList | async"
-            [datasetList]="datasetList | async"
             [selectedDatasets]="selectedDatasets | async"
-            [noSelectedDatasets]="noSelectedDatasets | async"
-            [queryParams]="queryParams | async">
+            [queryParams]="queryParams | async"
+            [datasetFamilyList]="datasetFamilyList | async"
+            [datasetList]="datasetList | async">
         </app-summary-multiple>
     </div>
 </div>
 
 <div class="row mt-5 justify-content-between">
     <div class="col">
-        <button *ngIf="noSelectedDatasets | async" class="btn btn-outline-secondary disabled not-allowed" title="At least one dataset required!">
-            <span class="fas fa-arrow-left"></span> Previous
-        </button>
-        <a *ngIf="!(noSelectedDatasets | async)"
-           routerLink="/search-multiple/position"
-           [queryParams]="queryParams | async"
-           (click)="checkStep()"
-           class="btn btn-outline-secondary">
-            <span class="fas fa-arrow-left"></span> Previous
+        <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/position" [queryParams]="queryParams | async" 
+            class="btn btn-outline-secondary">
+            <span class="fas fa-arrow-left"></span> Position
         </a>
     </div>
-    <div class="col col-auto">
-        <button *ngIf="noSelectedDatasets | async" class="btn btn-outline-primary disabled not-allowed" title="At least one dataset required!">
-            Next <span class="fas fa-arrow-right"></span>
-        </button>
-        <a *ngIf="!(noSelectedDatasets | async)"
-           routerLink="/search-multiple/result"
-           [queryParams]="queryParams | async"
-           (click)="checkStep()"
-           class="btn btn-outline-primary">
-            Next <span class="fas fa-arrow-right"></span>
+    <div *ngIf="(selectedDatasets | async).length > 0" class="col col-auto">
+        <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/result" [queryParams]="queryParams | async"
+            class="btn btn-outline-primary">
+            Result <span class="fas fa-arrow-right"></span>
         </a>
     </div>
 </div>
diff --git a/client/src/app/instance/search-multiple/containers/datasets.component.ts b/client/src/app/instance/search-multiple/containers/datasets.component.ts
index 4ffa2ea0..5b990465 100644
--- a/client/src/app/instance/search-multiple/containers/datasets.component.ts
+++ b/client/src/app/instance/search-multiple/containers/datasets.component.ts
@@ -7,92 +7,39 @@
  * file that was distributed with this source code.
  */
 
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 
 import { Store } from '@ngrx/store';
 import { Observable } from 'rxjs';
 
-import * as fromSearchMultiple from '../store/search-multiple.reducer';
-import * as searchMultipleActions from '../store/search-multiple.action';
-import * as searchMultipleSelector from '../store/search-multiple.selector';
-import { SearchMultipleQueryParams } from '../store/model';
-import * as fromMetamodel from '../../metamodel/reducers';
-import * as datasetActions from '../../metamodel/action/dataset.action';
-import { Dataset, Family, Survey } from '../../metamodel/model';
-import * as metamodelSelector from '../../metamodel/selectors';
-import * as coneSearchSelector from '../../shared/cone-search/store/cone-search.selector';
-import { ConeSearch } from '../../shared/cone-search/store/model';
-import { ScrollTopService } from '../../shared/service/sroll-top.service';
-
-/**
- * Interface for store state.
- *
- * @interface StoreState
- */
-interface StoreState {
-    searchMultiple: fromSearchMultiple.State;
-    metamodel: fromMetamodel.State;
-}
+import { Survey } from 'src/app/metamodel/models';
+import { AbstractSearchMultipleComponent } from './abstract-search-multiple.component';
+import * as searchMultipleActions from '../../store/actions/search-multiple.actions';
+import * as surveySelector from 'src/app/metamodel/selectors/survey.selector';
 
 @Component({
     selector: 'app-datasets',
     templateUrl: 'datasets.component.html'
 })
-/**
- * @class
- * @classdesc Search multiple datasets container.
- *
- * @implements OnInit
- */
-export class DatasetsComponent implements OnInit {
-    public datasetSearchMetaIsLoading: Observable<boolean>;
-    public datasetSearchMetaIsLoaded: Observable<boolean>;
-    public currentStep: Observable<string>;
+export class DatasetsComponent extends AbstractSearchMultipleComponent {
+    public surveyListIsLoading: Observable<boolean>;
+    public surveyListIsLoaded: Observable<boolean>;
     public surveyList: Observable<Survey[]>;
-    public datasetFamilyList: Observable<Family[]>;
-    public datasetList: Observable<Dataset[]>;
-    public isValidConeSearch: Observable<boolean>;
-    public coneSearch: Observable<ConeSearch>;
-    public selectedDatasets: Observable<string[]>;
-    public noSelectedDatasets: Observable<boolean>;
-    public queryParams: Observable<SearchMultipleQueryParams>;
 
-    constructor(private store: Store<StoreState>, private scrollTopService: ScrollTopService) {
-        this.datasetSearchMetaIsLoading = store.select(metamodelSelector.getDatasetSearchMetaIsLoading);
-        this.datasetSearchMetaIsLoaded = store.select(metamodelSelector.getDatasetSearchMetaIsLoaded);
-        this.currentStep = store.select(searchMultipleSelector.getCurrentStep);
-        this.surveyList = store.select(metamodelSelector.getSurveyList);
-        this.datasetFamilyList = store.select(metamodelSelector.getDatasetFamilyList);
-        this.datasetList = store.select(metamodelSelector.getDatasetWithConeSearchList);
-        this.isValidConeSearch = this.store.select(coneSearchSelector.getIsValidConeSearch);
-        this.selectedDatasets = this.store.select(searchMultipleSelector.getSelectedDatasets);
-        this.noSelectedDatasets = this.store.select(searchMultipleSelector.getNoSelectedDatasets);
-        this.coneSearch = this.store.select(coneSearchSelector.getConeSearch);
-        this.queryParams = this.store.select(searchMultipleSelector.getQueryParams);
+    constructor(protected store: Store<{ }>) {
+        super(store);
+        this.surveyListIsLoading = store.select(surveySelector.selectSurveyListIsLoading);
+        this.surveyListIsLoaded = store.select(surveySelector.selectSurveyListIsLoaded);
+        this.surveyList = store.select(surveySelector.selectAllSurveys);
     }
 
     ngOnInit() {
-        // Create a micro task that is processed after the current synchronous code
-        // This micro task prevent the expression has changed after view init error
-        Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.InitSearchByUrlAction()));
-        Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.ChangeStepAction('datasets')));
-        this.store.dispatch(new datasetActions.LoadDatasetSearchMetaAction());
-        this.scrollTopService.setScrollTop();
-    }
-
-    /**
-     * Dispatches action to check datasets step.
-     */
-    checkStep(): void {
-        this.store.dispatch(new searchMultipleActions.DatasetsCheckedAction());
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'datasets' })));
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkDatasets()));
+        super.ngOnInit();
     }
 
-    /**
-     * Dispatches action to update dataset list selection with the given updated selected dataset name list.
-     *
-     * @param  {string[]} selectedDatasets - The updated dataset name list.
-     */
-    updateSelectedDatasets(selectedDatasets: string[]): void {
-        this.store.dispatch(new searchMultipleActions.UpdateSelectedDatasetsAction(selectedDatasets));
+    updateSelectedDatasets(selectedDatasets: string[]) {
+        this.store.dispatch(searchMultipleActions.updateSelectedDatasets({ selectedDatasets }));
     }
 }
diff --git a/client/src/app/instance/search-multiple/containers/position.component.html b/client/src/app/instance/search-multiple/containers/position.component.html
index e7c2138b..cb9ef95a 100644
--- a/client/src/app/instance/search-multiple/containers/position.component.html
+++ b/client/src/app/instance/search-multiple/containers/position.component.html
@@ -5,11 +5,24 @@
     <div class="col-12 col-md-8 col-lg-9">
         <div class="border rounded my-2">
             <p class="border-bottom bg-light text-primary mb-0 py-4 pl-4">Cone Search</p>
-            <div class="p-4">
-                <!-- <app-cone-search></app-cone-search>
-                <div class=" pt-3 text-center">
-                    <button class="btn btn-outline-secondary" (click)="resetConeSearch()">Reset cone search</button>
-                </div> -->
+            <div class="row p-4">
+                <div class="col">
+                    <app-cone-search
+                        [coneSearch]="coneSearch | async"
+                        [resolver]="resolver | async"
+                        [resolverIsLoading]="resolverIsLoading | async"
+                        [resolverIsLoaded]="resolverIsLoaded | async"
+                        (retrieveCoordinates)="retrieveCoordinates($event)" #cs>
+                    </app-cone-search>
+                </div>
+                <div class="col-2 text-center align-self-end">
+                    <button class="btn btn-outline-success" *ngIf="!(coneSearch | async)" [hidden]="cs.form.invalid" (click)="addConeSearch(cs.getConeSearch())">
+                        <span class="fas fa-plus fa-fw"></span>
+                    </button>
+                    <button class="btn btn-outline-danger" *ngIf="coneSearch | async" (click)="deleteConeSearch()">
+                        <span class="fa fa-times fa-fw"></span>
+                    </button>
+                </div>
             </div>
         </div>
     </div>
@@ -27,7 +40,7 @@
 
 <div *ngIf="coneSearch | async" class="row mt-5 justify-content-end">
     <div class="col col-auto">
-        <a routerLink="/instance/{{ instanceSelected | async}}/search-multiple/datasets}" [queryParams]="queryParams | async"
+        <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/datasets" [queryParams]="queryParams | async"
             class="btn btn-outline-primary">Datasets <span class="fas fa-arrow-right"></span>
         </a>
     </div>
diff --git a/client/src/app/instance/search-multiple/containers/position.component.ts b/client/src/app/instance/search-multiple/containers/position.component.ts
index a7aa89f9..dea59795 100644
--- a/client/src/app/instance/search-multiple/containers/position.component.ts
+++ b/client/src/app/instance/search-multiple/containers/position.component.ts
@@ -10,23 +10,44 @@
 import { Component } from '@angular/core';
 
 import { Store } from '@ngrx/store';
+import { Observable } from 'rxjs';
 
 import { AbstractSearchMultipleComponent } from './abstract-search-multiple.component';
+import { Resolver, ConeSearch } from '../../store/models';
 import * as searchMultipleActions from '../../store/actions/search-multiple.actions';
+import * as coneSearchActions from '../../store/actions/cone-search.actions';
+import * as coneSearchSelector from '../../store/selectors/cone-search.selector';
 
 @Component({
     selector: 'app-position',
-    templateUrl: 'position.component.html',
-    styleUrls: ['position.component.scss']
+    templateUrl: 'position.component.html'
 })
 export class PositionComponent extends AbstractSearchMultipleComponent {
+    public resolver: Observable<Resolver>;
+    public resolverIsLoading: Observable<boolean>;
+    public resolverIsLoaded: Observable<boolean>;
 
     constructor(protected store: Store<{ }>) {
         super(store);
-        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'position' })));
+        this.resolver = this.store.select(coneSearchSelector.selectResolver);
+        this.resolverIsLoading = this.store.select(coneSearchSelector.selectResolverIsLoading);
+        this.resolverIsLoaded = this.store.select(coneSearchSelector.selectResolverIsLoaded);
     }
 
     ngOnInit() {
-        
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'position' })));
+        super.ngOnInit();
+    }
+
+    addConeSearch(coneSearch: ConeSearch): void {
+        this.store.dispatch(coneSearchActions.addConeSearch({ coneSearch }));
+    }
+
+    deleteConeSearch(): void {
+        this.store.dispatch(coneSearchActions.deleteConeSearch());
+    }
+
+    retrieveCoordinates(name: string): void {
+        this.store.dispatch(coneSearchActions.retrieveCoordinates({ name }));
     }
 }
diff --git a/client/src/app/instance/search-multiple/containers/result-multiple.component.html b/client/src/app/instance/search-multiple/containers/result-multiple.component.html
index f1d2d75d..84f8d63b 100644
--- a/client/src/app/instance/search-multiple/containers/result-multiple.component.html
+++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.html
@@ -1,16 +1,17 @@
-<div class="row mt-4">
+<app-spinner *ngIf="(datasetFamilyListIsLoading | async) || (datasetListIsLoading | async) || (dataLengthIsLoading | async)">
+</app-spinner>
+
+<div *ngIf="(datasetFamilyListIsLoaded | async) && (datasetListIsLoaded | async) || (dataLengthIsLoaded | async)" class="row mt-4">
     <div class="col-12">
         <app-overview
-            [datasetSearchMetaIsLoaded]="datasetSearchMetaIsLoaded | async"
-            [coneSearch]="coneSearch | async"
             [datasetFamilyList]="datasetFamilyList | async"
             [datasetList]="datasetList | async"
+            [coneSearch]="coneSearch | async"
             [selectedDatasets]="selectedDatasets | async"
-            [queryParams]="queryParams | async"
-            [datasetsCountIsLoaded]="datasetsCountIsLoaded | async"
-            [datasetsCount]="datasetsCount | async"
-            (getDatasetCount)="getDatasetsCount()">
+            [dataLength]="dataLength | async"
+            [queryParams]="queryParams | async">
         </app-overview>
+        <!--
         <app-datasets-result
             [datasetsCountIsLoaded]="datasetsCountIsLoaded | async"
             [datasetFamilyList]="datasetFamilyList | async"
@@ -26,14 +27,14 @@
             (retrieveMeta)="retrieveMeta($event)"
             (retrieveData)="retrieveData($event)"
             (updateSelectedData)="updateSelectedData($event)">
-        </app-datasets-result>
+        </app-datasets-result> -->
     </div>
 </div>
-<div *ngIf="(datasetSearchMetaIsLoaded | async) && (datasetsCountIsLoaded | async)" class="row mt-5 justify-content-between">
+<div class="row mt-5 justify-content-between">
     <div class="col">
-        <a routerLink="/search-multiple/datasets" [queryParams]="queryParams | async"
+        <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/datasets" [queryParams]="queryParams | async" 
             class="btn btn-outline-secondary">
-            <span class="fas fa-arrow-left"></span> Previous
+            <span class="fas fa-arrow-left"></span> Datasets
         </a>
     </div>
 </div>
diff --git a/client/src/app/instance/search-multiple/containers/result-multiple.component.ts b/client/src/app/instance/search-multiple/containers/result-multiple.component.ts
index 64b5ed34..d6118271 100644
--- a/client/src/app/instance/search-multiple/containers/result-multiple.component.ts
+++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.ts
@@ -7,136 +7,56 @@
  * file that was distributed with this source code.
  */
 
-import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Component } from '@angular/core';
 
+import { Observable, Subscription } from 'rxjs';
 import { Store } from '@ngrx/store';
-import { Dictionary } from '@ngrx/entity';
-import { Observable } from 'rxjs';
 
-import * as fromSearchMultiple from '../store/search-multiple.reducer';
-import * as searchMultipleActions from '../store/search-multiple.action';
-import * as searchMultipleSelector from '../store/search-multiple.selector';
-import { DataByDataset, DatasetCount, SearchMultipleQueryParams } from '../store/model';
-import * as fromMetamodel from '../../metamodel/reducers';
-import * as datasetActions from '../../metamodel/action/dataset.action';
-import * as attributeActions from '../../metamodel/action/attribute.action';
-import * as metamodelSelector from '../../metamodel/selectors';
-import { AttributesByDataset, Dataset, Family } from '../../metamodel/model';
-import * as coneSearchSelector from '../../shared/cone-search/store/cone-search.selector';
-import { ConeSearch } from '../../shared/cone-search/store/model';
-import { Pagination } from '../../shared/datatable/model';
-import { ScrollTopService } from '../../shared/service/sroll-top.service';
-
-/**
- * Interface for store state.
- *
- * @interface StoreState
- */
-interface StoreState {
-    searchMultiple: fromSearchMultiple.State;
-    metamodel: fromMetamodel.State;
-}
+import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../../store/models';
+import { AbstractSearchMultipleComponent } from './abstract-search-multiple.component';
+import * as searchMultipleActions from '../../store/actions/search-multiple.actions';
+import * as searchMultipleSelector from 'src/app/instance/store/selectors/search-multiple.selector';
 
 @Component({
     selector: 'app-result-multiple',
     templateUrl: 'result-multiple.component.html'
 })
-/**
- * @class
- * @classdesc Search multiple result container.
- *
- * @implements OnInit
- * @implements OnDestroy
- */
-export class ResultMultipleComponent implements OnInit, OnDestroy {
-    public datasetSearchMetaIsLoading: Observable<boolean>;
-    public datasetSearchMetaIsLoaded: Observable<boolean>;
-    public datasetFamilyList: Observable<Family[]>;
-    public datasetList: Observable<Dataset[]>;
-    public currentStep: Observable<string>;
-    public coneSearch: Observable<ConeSearch>;
-    public selectedDatasets: Observable<string[]>;
-    public queryParams: Observable<SearchMultipleQueryParams>;
-    public datasetsCountIsLoading: Observable<boolean>;
-    public datasetsCountIsLoaded: Observable<boolean>;
-    public datasetsCount: Observable<DatasetCount[]>;
-    public datasetsWithAttributeList: Observable<string[] | number[]>;
-    public allAttributeList: Observable<Dictionary<AttributesByDataset>>;
-    public data: Observable<{ dname: string, data: any[]}[]>;
-    public datasetsWithData: Observable<string[] | number[]>;
-    public allData: Observable<Dictionary<DataByDataset>>;
-    public selectedData: Observable<{ dname: string, data: (number | string)[] }[]>;
-
-    constructor(private store: Store<StoreState>, private scrollTopService: ScrollTopService) {
-        this.datasetSearchMetaIsLoading = store.select(metamodelSelector.getDatasetSearchMetaIsLoading);
-        this.datasetSearchMetaIsLoaded = store.select(metamodelSelector.getDatasetSearchMetaIsLoaded);
-        this.currentStep = store.select(searchMultipleSelector.getCurrentStep);
-        this.datasetFamilyList = store.select(metamodelSelector.getDatasetFamilyList);
-        this.datasetList = store.select(metamodelSelector.getDatasetList);
-        this.coneSearch = this.store.select(coneSearchSelector.getConeSearch);
-        this.selectedDatasets = this.store.select(searchMultipleSelector.getSelectedDatasets);
-        this.queryParams = this.store.select(searchMultipleSelector.getQueryParams);
-        this.datasetsCountIsLoading = this.store.select(searchMultipleSelector.getDatasetsCountIsLoading);
-        this.datasetsCountIsLoaded = this.store.select(searchMultipleSelector.getDatasetsCountIsLoaded);
-        this.datasetsCount = this.store.select(searchMultipleSelector.getDatasetsCount);
-        this.datasetsWithAttributeList = this.store.select(metamodelSelector.getDatasetsWithAttributeList);
-        this.allAttributeList = this.store.select(metamodelSelector.getAllAttributeList);
-        this.datasetsWithData = this.store.select(searchMultipleSelector.getDatasetsWithData);
-        this.allData = this.store.select(searchMultipleSelector.getAllData);
-        this.selectedData = this.store.select(searchMultipleSelector.getSelectedData);
+export class ResultMultipleComponent extends AbstractSearchMultipleComponent {
+    public pristine: Observable<boolean>;
+    public dataLength: Observable<SearchMultipleDatasetLength[]>;
+    public dataLengthIsLoading: Observable<boolean>;
+    public dataLengthIsLoaded: Observable<boolean>;
+    public data: Observable<SearchMultipleDatasetData[]>;
+    public dataIsLoading: Observable<boolean>;
+    public dataIsLoaded: Observable<boolean>;
+
+    private pristineSubscription: Subscription;
+
+    constructor(protected store: Store<{ }>) {
+        super(store);
+        this.pristine = this.store.select(searchMultipleSelector.selectPristine);
+        this.dataLength = this.store.select(searchMultipleSelector.selectDataLength);
+        this.dataLengthIsLoading = this.store.select(searchMultipleSelector.selectDataLengthIsLoading);
+        this.dataLengthIsLoaded = this.store.select(searchMultipleSelector.selectDataLengthIsLoaded);
+        this.data = this.store.select(searchMultipleSelector.selectData);
+        this.dataIsLoading = this.store.select(searchMultipleSelector.selectDataIsLoading);
+        this.dataIsLoaded = this.store.select(searchMultipleSelector.selectDataIsLoaded);
     }
 
     ngOnInit() {
-        // Create a micro task that is processed after the current synchronous code
-        // This micro task prevent the expression has changed after view init error
-        Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.ChangeStepAction('result')));
-        Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.InitSearchByUrlAction()));
-        this.store.dispatch(new datasetActions.LoadDatasetSearchMetaAction());
-        this.scrollTopService.setScrollTop();
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'result' })));
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkDatasets()));
+        Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkResult()));
+        super.ngOnInit();
+        this.pristineSubscription = this.pristine.subscribe(pristine => {
+            if (!pristine) {
+                Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.retrieveDataLength()));
+            }
+        });
     }
 
-    /**
-     * Dispatches action to retrieve dataset result number.
-     */
-    getDatasetsCount(): void {
-        this.store.dispatch(new searchMultipleActions.RetrieveDatasetsCountAction());
-    }
-
-    /**
-     * Dispatches action to retrieve metadata of a given dataset name.
-     *
-     * @param  {string} dname - The dataset name.
-     */
-    retrieveMeta(dname: string): void {
-        this.store.dispatch(new attributeActions.LoadAttributeListAction(dname));
-    }
-
-    /**
-     * Dispatches action to retrieve data with the given pagination parameters.
-     *
-     * @param  {Pagination} params - The pagination parameters.
-     */
-    retrieveData(params: Pagination): void {
-        this.store.dispatch(new searchMultipleActions.RetrieveDataAction(params));
-    }
-
-    /**
-     * Dispatches action to update data selection with the given updated selected data.
-     *
-     * @param  {{ dname: string, data: (string | number)[] }[]} data - The updated selected data.
-     */
-    updateSelectedData(data: { dname: string, data: (string | number)[] }[]): void {
-        this.store.dispatch(new searchMultipleActions.UpdateSelectedDataAction(data));
-    }
-
-    // executeProcess(typeProcess: string): void {
-    //     this.store.dispatch(new searchActions.ExecuteProcessAction(typeProcess));
-    // }
-
-    /**
-     * Dispatches action to destroy search multiple results.
-     */
     ngOnDestroy() {
-        this.store.dispatch(new searchMultipleActions.DestroyResultsAction());
+        this.pristineSubscription.unsubscribe();
+        super.ngOnDestroy();
     }
 }
diff --git a/client/src/app/instance/search-multiple/search-multiple-routing.module.ts b/client/src/app/instance/search-multiple/search-multiple-routing.module.ts
index ca77e734..a3590eb2 100644
--- a/client/src/app/instance/search-multiple/search-multiple-routing.module.ts
+++ b/client/src/app/instance/search-multiple/search-multiple-routing.module.ts
@@ -12,12 +12,16 @@ import { Routes, RouterModule } from '@angular/router';
 
 import { SearchMultipleComponent } from './search-multiple.component';
 import { PositionComponent } from './containers/position.component';
+import { DatasetsComponent } from './containers/datasets.component';
+import { ResultMultipleComponent } from './containers/result-multiple.component';
 
 const routes: Routes = [
     {
         path: '', component: SearchMultipleComponent, children: [
             { path: '', redirectTo: 'position', pathMatch: 'full' },
-            { path: 'position', component: PositionComponent }
+            { path: 'position', component: PositionComponent },
+            { path: 'datasets', component: DatasetsComponent },
+            { path: 'result', component: ResultMultipleComponent }
         ]
     }
 ];
@@ -30,5 +34,7 @@ export class SearchMultipleRoutingModule { }
 
 export const routedComponents = [
     SearchMultipleComponent,
-    PositionComponent
+    PositionComponent,
+    DatasetsComponent,
+    ResultMultipleComponent
 ];
diff --git a/client/src/app/instance/search/components/criteria/cone-search-tab.component.html b/client/src/app/instance/search/components/criteria/cone-search-tab.component.html
index ea951c32..f3ec7c16 100644
--- a/client/src/app/instance/search/components/criteria/cone-search-tab.component.html
+++ b/client/src/app/instance/search/components/criteria/cone-search-tab.component.html
@@ -19,8 +19,6 @@
                     [resolver]="resolver"
                     [resolverIsLoading]="resolverIsLoading"
                     [resolverIsLoaded]="resolverIsLoaded"
-                    (addConeSearch)="addConeSearch.emit($event)"
-                    (deleteConeSearch)="deleteConeSearch.emit()"
                     (retrieveCoordinates)="retrieveCoordinates.emit($event)" #cs>
                 </app-cone-search>
             </div>
diff --git a/client/src/app/instance/search/containers/result.component.ts b/client/src/app/instance/search/containers/result.component.ts
index 82f65348..f62ca538 100644
--- a/client/src/app/instance/search/containers/result.component.ts
+++ b/client/src/app/instance/search/containers/result.component.ts
@@ -68,7 +68,7 @@ export class ResultComponent extends AbstractSearchComponent {
             if (!pristine) {
                 Promise.resolve(null).then(() => this.store.dispatch(searchActions.retrieveDataLength()));
             }
-        })
+        });
     }
 
     sampRegister() {
diff --git a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts
index 59de49ad..0bd90e12 100644
--- a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts
+++ b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts
@@ -26,8 +26,6 @@ export class ConeSearchComponent implements OnChanges {
     @Input() resolver: Resolver;
     @Input() resolverIsLoading: boolean;
     @Input() resolverIsLoaded: boolean;
-    @Output() addConeSearch: EventEmitter<ConeSearch> = new EventEmitter();
-    @Output() deleteConeSearch: EventEmitter<{ }> = new EventEmitter();
     @Output() retrieveCoordinates: EventEmitter<string> = new EventEmitter();
 
     public form = new FormGroup({
diff --git a/client/src/app/instance/store/actions/search-multiple.actions.ts b/client/src/app/instance/store/actions/search-multiple.actions.ts
index 8559feb4..173a56ec 100644
--- a/client/src/app/instance/store/actions/search-multiple.actions.ts
+++ b/client/src/app/instance/store/actions/search-multiple.actions.ts
@@ -9,20 +9,17 @@
 
 import { createAction, props } from '@ngrx/store';
 
+import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../models';
+
 export const initSearch = createAction('[Search Multiple] Init Search');
-export const restartSearch = createAction('[Search Multiple] Restart Search');
-export const resetSearch = createAction('[Search Multiple] Reset Search');
-export const destroyResults = createAction('[Search Multiple] Destroy Results');
-export const loadDefaultFormParameters = createAction('[Search Multiple] Load Default Form Parameters');
 export const markAsDirty = createAction('[Search Multiple] Mark As Dirty');
 export const changeStep = createAction('[Search Multiple] Change Step', props<{ step: string }>());
-export const checkPosition = createAction('[Search Multiple] Check Position');
 export const checkDatasets = createAction('[Search Multiple] Check Datasets');
 export const checkResult = createAction('[Search Multiple] Check Result');
 export const updateSelectedDatasets = createAction('[Search Multiple] Update Selected Datasets', props<{ selectedDatasets: string[] }>());
 export const retrieveDataLength = createAction('[Search Multiple] Retrieve Data Length');
-export const retrieveDataLengthSuccess = createAction('[Search Multiple] Retrieve Data Length Success', props<{ length: number }>());
+export const retrieveDataLengthSuccess = createAction('[Search Multiple] Retrieve Data Length Success', props<{ dataLength: SearchMultipleDatasetLength[] }>());
 export const retrieveDataLengthFail = createAction('[Search Multiple] Retrieve Data Length Fail');
 export const retrieveData = createAction('[Search Multiple] Retrieve Data');
-export const retrieveDataSuccess = createAction('[Search Multiple] Retrieve Data Success', props<{ data: any[] }>());
+export const retrieveDataSuccess = createAction('[Search Multiple] Retrieve Data Success', props<{ data: SearchMultipleDatasetData[] }>());
 export const retrieveDataFail = createAction('[Search Multiple] Retrieve Data Fail');
diff --git a/client/src/app/instance/store/effects/search-multiple.effects.ts b/client/src/app/instance/store/effects/search-multiple.effects.ts
index e6c26c9c..27078901 100644
--- a/client/src/app/instance/store/effects/search-multiple.effects.ts
+++ b/client/src/app/instance/store/effects/search-multiple.effects.ts
@@ -10,26 +10,95 @@
 import { Injectable } from '@angular/core';
 
 import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
-import { Store, Action  } from '@ngrx/store';
-import { of } from 'rxjs';
+import { Store, Action } from '@ngrx/store';
+import { forkJoin, of } from 'rxjs';
 import { map, tap, mergeMap, catchError } from 'rxjs/operators';
 import { ToastrService } from 'ngx-toastr';
 
-import { ConeSearch, criterionToString, stringToCriterion } from '../models';
+import { SearchMultipleDatasetLength } from '../models';
 import { SearchService } from '../services/search.service';
-import * as searchActions from '../actions/search.actions';
-import * as attributeActions from 'src/app/metamodel/actions/attribute.actions';
-import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector';
-import * as criteriaFamilyActions from 'src/app/metamodel/actions/criteria-family.actions';
-import * as outputFamilyActions from 'src/app/metamodel/actions/output-family.actions';
-import * as outputCategoryActions from 'src/app/metamodel/actions/output-category.actions';
-import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
-import * as searchSelector from '../selectors/search.selector';
+import * as searchMultipleActions from '../actions/search-multiple.actions';
+import * as searchMultipleSelector from '../selectors/search-multiple.selector';
 import * as coneSearchActions from '../actions/cone-search.actions';
 import * as coneSearchSelector from '../selectors/cone-search.selector';
 
 @Injectable()
 export class SearchMultipleEffects {
+    initSearch$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(searchMultipleActions.initSearch),
+            concatLatestFrom(() => [
+                this.store.select(searchMultipleSelector.selectPristine),
+                this.store.select(coneSearchSelector.selectConeSearchByRoute),
+                this.store.select(searchMultipleSelector.selectSelectedDatasetsByRoute)
+            ]),
+            mergeMap(([action, pristine, coneSearchByRoute, selectedDatasetsByRoute]) => {
+                if (!pristine) {
+                    // Default form parameters already loaded or no dataset selected
+                    return of({ type: '[No Action] Load Default Form Parameters' });
+                }
+
+                let actions: Action[] = [
+                    searchMultipleActions.markAsDirty()
+                ];
+
+                // Update cone search
+                if (coneSearchByRoute) {
+                    const params = coneSearchByRoute.split(':');
+                    const coneSearch = {
+                        ra: +params[0],
+                        dec: +params[1],
+                        radius: +params[2]
+                    };
+                    actions.push(coneSearchActions.addConeSearch({ coneSearch }));
+                }
+
+                // Update selected datasets
+                if (selectedDatasetsByRoute) {
+                    // Build output list with the URL query parameters (a)
+                    const selectedDatasets = selectedDatasetsByRoute.split(';');
+                    actions.push(
+                        searchMultipleActions.updateSelectedDatasets({ selectedDatasets }),
+                        searchMultipleActions.checkDatasets()
+                    );
+                }
+
+                // Returns actions and mark the form as dirty
+                return actions;
+            })
+        )
+    );
+
+    retrieveDataLength$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(searchMultipleActions.retrieveDataLength),
+            concatLatestFrom(() => [
+                this.store.select(searchMultipleSelector.selectSelectedDatasets),
+                this.store.select(coneSearchSelector.selectConeSearch)
+            ]),
+            mergeMap(([action, selectedDatasets, coneSearch]) => {
+                const queries = selectedDatasets.map(datasetName => this.searchService.retrieveDataLength(
+                    `${datasetName}?a=count&cs=${coneSearch.ra}:${coneSearch.dec}:${coneSearch.radius}`
+                ).pipe(
+                    map((response: { nb: number }[]) => ({ datasetName, length: response[0].nb }))
+                ));
+
+                return forkJoin(queries)
+                    .pipe(
+                        map((response: SearchMultipleDatasetLength[]) => searchMultipleActions.retrieveDataLengthSuccess({ dataLength: response })),
+                        catchError(() => of(searchMultipleActions.retrieveDataLengthFail()))
+                    )
+            })
+        )
+    );
+
+    retrieveDataLengthFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(searchMultipleActions.retrieveDataLengthFail),
+            tap(() => this.toastr.error('Loading Failed', 'The search multiple data length loading failed'))
+        ), { dispatch: false}
+    );
+
     constructor(
         private actions$: Actions,
         private searchService: SearchService,
diff --git a/client/src/app/instance/store/models/index.ts b/client/src/app/instance/store/models/index.ts
index 0f5c77df..962eede1 100644
--- a/client/src/app/instance/store/models/index.ts
+++ b/client/src/app/instance/store/models/index.ts
@@ -5,3 +5,5 @@ export * from './criterion';
 export * from './pagination.model';
 export * from './cone-search.model';
 export * from './resolver.model';
+export * from './search-multiple-dataset-length';
+export * from './search-multiple-dataset-data';
diff --git a/client/src/app/instance/search-multiple/containers/position.component.scss b/client/src/app/instance/store/models/search-multiple-dataset-data.ts
similarity index 73%
rename from client/src/app/instance/search-multiple/containers/position.component.scss
rename to client/src/app/instance/store/models/search-multiple-dataset-data.ts
index b2357fc4..fa25ed88 100644
--- a/client/src/app/instance/search-multiple/containers/position.component.scss
+++ b/client/src/app/instance/store/models/search-multiple-dataset-data.ts
@@ -7,6 +7,7 @@
  * file that was distributed with this source code.
  */
 
-.disabled {
-    cursor: not-allowed;
+export interface SearchMultipleDatasetData {
+    datasetName: string;
+    data: any[];
 }
diff --git a/client/src/app/instance/store/models/search-multiple-dataset-length.ts b/client/src/app/instance/store/models/search-multiple-dataset-length.ts
new file mode 100644
index 00000000..75961a0d
--- /dev/null
+++ b/client/src/app/instance/store/models/search-multiple-dataset-length.ts
@@ -0,0 +1,13 @@
+/**
+ * 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.
+ */
+
+export interface SearchMultipleDatasetLength {
+    datasetName: string;
+    length: number;
+}
diff --git a/client/src/app/instance/store/reducers/search-multiple.reducer.ts b/client/src/app/instance/store/reducers/search-multiple.reducer.ts
index 25dcaf67..af05dbda 100644
--- a/client/src/app/instance/store/reducers/search-multiple.reducer.ts
+++ b/client/src/app/instance/store/reducers/search-multiple.reducer.ts
@@ -9,6 +9,7 @@
 
 import { createReducer, on } from '@ngrx/store';
 
+import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../models';
 import * as searchMultipleActions from '../actions/search-multiple.actions';
 
 export interface State {
@@ -18,6 +19,12 @@ export interface State {
     datasetsStepChecked: boolean;
     resultStepChecked: boolean;
     selectedDatasets: string[];
+    dataLengthIsLoading: boolean;
+    dataLengthIsLoaded: boolean;
+    dataLength: SearchMultipleDatasetLength[];
+    dataIsLoading: boolean;
+    dataIsLoaded: boolean;
+    data: SearchMultipleDatasetData[];
 }
 
 export const initialState: State = {
@@ -26,15 +33,17 @@ export const initialState: State = {
     positionStepChecked: false,
     datasetsStepChecked: false,
     resultStepChecked: false,
-    selectedDatasets: []
+    selectedDatasets: [],
+    dataLengthIsLoading: false,
+    dataLengthIsLoaded: false,
+    dataLength: [],
+    dataIsLoading: false,
+    dataIsLoaded: false,
+    data: []
 };
 
 export const searchMultipleReducer = createReducer(
     initialState,
-    on(searchMultipleActions.restartSearch, () => ({
-        ...initialState,
-        currentStep: 'position'
-    })),
     on(searchMultipleActions.changeStep, (state, { step }) => ({
         ...state,
         currentStep: step
@@ -43,10 +52,6 @@ export const searchMultipleReducer = createReducer(
         ...state,
         pristine: false
     })),
-    on(searchMultipleActions.checkPosition, state => ({
-        ...state,
-        positionStepChecked: true
-    })),
     on(searchMultipleActions.checkDatasets, state => ({
         ...state,
         datasetsStepChecked: true
@@ -58,6 +63,36 @@ export const searchMultipleReducer = createReducer(
     on(searchMultipleActions.updateSelectedDatasets, (state, { selectedDatasets }) => ({
         ...state,
         selectedDatasets
+    })),
+    on(searchMultipleActions.retrieveDataLength, state => ({
+        ...state,
+        dataLengthIsLoading: true,
+        dataLengthIsLoaded: false
+    })),
+    on(searchMultipleActions.retrieveDataLengthSuccess, (state, { dataLength }) => ({
+        ...state,
+        dataLength,
+        dataLengthIsLoading: false,
+        dataLengthIsLoaded: true
+    })),
+    on(searchMultipleActions.retrieveDataLengthFail, state => ({
+        ...state,
+        dataLengthIsLoading: false
+    })),
+    on(searchMultipleActions.retrieveData, state => ({
+        ...state,
+        dataIsLoading: true,
+        dataIsLoaded: false
+    })),
+    on(searchMultipleActions.retrieveDataSuccess, (state, { data }) => ({
+        ...state,
+        data,
+        dataIsLoading: false,
+        dataIsLoaded: true
+    })),
+    on(searchMultipleActions.retrieveDataFail, state => ({
+        ...state,
+        dataIsLoading: false
     }))
 );
 
@@ -67,3 +102,9 @@ export const selectPositionStepChecked = (state: State) => state.positionStepChe
 export const selectDatasetsStepChecked = (state: State) => state.datasetsStepChecked;
 export const selectResultStepChecked = (state: State) => state.resultStepChecked;
 export const selectSelectedDatasets = (state: State) => state.selectedDatasets;
+export const selectDataLengthIsLoading = (state: State) => state.dataLengthIsLoading;
+export const selectDataLengthIsLoaded = (state: State) => state.dataLengthIsLoaded;
+export const selectDataLength = (state: State) => state.dataLength;
+export const selectDataIsLoading = (state: State) => state.dataIsLoading;
+export const selectDataIsLoaded = (state: State) => state.dataIsLoaded;
+export const selectData = (state: State) => state.data;
diff --git a/client/src/app/instance/store/selectors/search-multiple.selector.ts b/client/src/app/instance/store/selectors/search-multiple.selector.ts
index 9e88a69b..61ae2e99 100644
--- a/client/src/app/instance/store/selectors/search-multiple.selector.ts
+++ b/client/src/app/instance/store/selectors/search-multiple.selector.ts
@@ -49,6 +49,36 @@ export const selectSelectedDatasets = createSelector(
     fromSearchMultiple.selectSelectedDatasets
 );
 
+export const selectDataLengthIsLoading = createSelector(
+    selectSearchMultipleState,
+    fromSearchMultiple.selectDataLengthIsLoading
+);
+
+export const selectDataLengthIsLoaded = createSelector(
+    selectSearchMultipleState,
+    fromSearchMultiple.selectDataLengthIsLoaded
+);
+
+export const selectDataLength = createSelector(
+    selectSearchMultipleState,
+    fromSearchMultiple.selectDataLength
+);
+
+export const selectDataIsLoading = createSelector(
+    selectSearchMultipleState,
+    fromSearchMultiple.selectDataIsLoading
+);
+
+export const selectDataIsLoaded = createSelector(
+    selectSearchMultipleState,
+    fromSearchMultiple.selectDataIsLoaded
+);
+
+export const selectData = createSelector(
+    selectSearchMultipleState,
+    fromSearchMultiple.selectData
+);
+
 export const selectQueryParams = createSelector(
     coneSearchSelector.selectConeSearch,
     selectSelectedDatasets,
@@ -71,3 +101,8 @@ export const selectQueryParams = createSelector(
         return queryParams;
     }
 );
+
+export const selectSelectedDatasetsByRoute = createSelector(
+    reducer.selectRouterState,
+    router => router.state.queryParams.d as string
+);
-- 
GitLab