diff --git a/scilog/src/app/core/search-scroll.service.spec.ts b/scilog/src/app/core/search-scroll.service.spec.ts index 1ee38938..b5fd690c 100644 --- a/scilog/src/app/core/search-scroll.service.spec.ts +++ b/scilog/src/app/core/search-scroll.service.spec.ts @@ -2,6 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { AppConfigService } from '../app-config.service'; import { SearchScrollService } from './search-scroll.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; const getConfig = () => ({}); @@ -10,9 +11,10 @@ describe('SearchScrollService', () => { beforeEach(() => { TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], providers: [ { provide: AppConfigService, useValue: { getConfig } }, - { provide: SearchScrollService, useValue: {} }, + SearchScrollService ], }); service = TestBed.inject(SearchScrollService); @@ -21,4 +23,11 @@ describe('SearchScrollService', () => { it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should reset', () => { + const setSearchSpy = spyOn(service, 'setSearchString'); + service.reset('some'); + expect(setSearchSpy).toHaveBeenCalledOnceWith('some'); + }); + }); diff --git a/scilog/src/app/core/search-scroll.service.ts b/scilog/src/app/core/search-scroll.service.ts index 5c4fc498..7a094355 100644 --- a/scilog/src/app/core/search-scroll.service.ts +++ b/scilog/src/app/core/search-scroll.service.ts @@ -1,22 +1,34 @@ import { Injectable } from '@angular/core'; -import { Subscription } from 'rxjs'; -import { SearchDataService } from '@shared/remote-data.service'; +import { SearchDataService, LogbookDataService } from '@shared/remote-data.service'; import { ScrollBaseService } from '@shared/scroll-base.service'; -@Injectable() -export class SearchScrollService extends ScrollBaseService { - - currentViewSubscription: Subscription = null; +export class SearchScrollBaseService extends ScrollBaseService { + + protected dataService: SearchDataService | LogbookDataService + + private setSearchString(searchString: string) { + this.dataService.searchString = searchString; + } + + reset(searchString?: string) { + this.setSearchString(searchString); + super.reset(); + } + +} + +@Injectable({ providedIn: 'root' }) +export class SearchScrollService extends SearchScrollBaseService { constructor( - private searchDataService: SearchDataService, - ) { + protected dataService: SearchDataService, + ) { super(); } getDataBuffer(index:number, count:number, config:any){ - return this.searchDataService.getDataBuffer(index, count, config); + return this.dataService.getDataBuffer(index, count, config); } } diff --git a/scilog/src/app/core/toolbar/toolbar.component.html b/scilog/src/app/core/toolbar/toolbar.component.html index 5197c5d6..f032fa52 100644 --- a/scilog/src/app/core/toolbar/toolbar.component.html +++ b/scilog/src/app/core/toolbar/toolbar.component.html @@ -12,18 +12,22 @@
- +
-
+ {{ versionInfo }} diff --git a/scilog/src/app/core/toolbar/toolbar.component.spec.ts b/scilog/src/app/core/toolbar/toolbar.component.spec.ts index 29b03cc5..de832239 100644 --- a/scilog/src/app/core/toolbar/toolbar.component.spec.ts +++ b/scilog/src/app/core/toolbar/toolbar.component.spec.ts @@ -6,6 +6,7 @@ import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-m import { ToolbarComponent } from './toolbar.component'; import { AppConfigService } from 'src/app/app-config.service'; +import { By } from '@angular/platform-browser'; const getConfig = () => ({}); @@ -31,4 +32,14 @@ describe('ToolbarComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should overviewSearch', () => { + component.showSearch = true; + fixture.detectChanges(); + fixture.debugElement.query( + By.css('search-window') + ).triggerEventHandler('overviewSearch', 'searched'); + expect(component.searched).toEqual('searched'); + }); + }); diff --git a/scilog/src/app/core/toolbar/toolbar.component.ts b/scilog/src/app/core/toolbar/toolbar.component.ts index 07a8e937..8c1a2970 100644 --- a/scilog/src/app/core/toolbar/toolbar.component.ts +++ b/scilog/src/app/core/toolbar/toolbar.component.ts @@ -24,6 +24,7 @@ export class ToolbarComponent implements OnInit { @Input() showMenuIcon: boolean; + searched: string; subscriptions: Subscription[] = []; logbookName: string; isLogbookOpen = false; @@ -46,8 +47,6 @@ export class ToolbarComponent implements OnInit { } } - @ViewChild('searchWindow') searchWindow: SearchWindowComponent; - views: Views[] = []; appConfig: AppConfig = this.appConfigService.getConfig(); @@ -92,9 +91,11 @@ export class ToolbarComponent implements OnInit { })); this.showVersionInfo = !this.showMenuIcon; console.log(this.showVersionInfo) - } + overviewSearch($event) { + this.searched = $event + } logout() { console.log("logout"); @@ -137,11 +138,6 @@ export class ToolbarComponent implements OnInit { closeSearch(){ this.showSearch = false; - this.searchWindow.reset(); - } - - selectedSearchEntry($event){ - } loadView(index: number){ diff --git a/scilog/src/app/logbook/core/search-window/search-window.component.css b/scilog/src/app/logbook/core/search-window/search-window.component.css index 4edbc05d..96af2218 100644 --- a/scilog/src/app/logbook/core/search-window/search-window.component.css +++ b/scilog/src/app/logbook/core/search-window/search-window.component.css @@ -57,52 +57,3 @@ width: 50vw !important; } -.searchResults { - height: calc(50vh - 95px); - overflow: scroll; -} - -.searchResults .header { - font-size: 20px; - color: var(--header-color); - padding-bottom: 20px; - padding-top: 10px; - padding: 15px; -} - -.searchContainer { - overflow: scroll; - height: calc(50vh - 140px); -} - -.statusMessage { - position: absolute; - bottom: 0px; - color: black; - padding: 10px; - font-size: 12px; - width: calc(100% - 20px); -} - -.statusMessage > div { - padding-left:20px; -} - -.statusMessage > div > span { - border-radius: 5px; - background-color: #004c63; - color: white; - box-shadow: 0 1px 4px 0 var(--default-shadow-0), 0 2px 8px 0 var(--default-shadow-1), - 0 4px 8px -1px var(--default-shadow-2); - border: unset; - padding: 4px; - margin-left: 4px; -} - -.spinner { - position: fixed; - z-index: 1031; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} diff --git a/scilog/src/app/logbook/core/search-window/search-window.component.html b/scilog/src/app/logbook/core/search-window/search-window.component.html index 885b8ec3..9ee94ad5 100644 --- a/scilog/src/app/logbook/core/search-window/search-window.component.html +++ b/scilog/src/app/logbook/core/search-window/search-window.component.html @@ -13,7 +13,7 @@ - -
+
Tag:
-
-
- Logbook view: - -
-
- - -
-
-
- - - -
- -
- -
- Showing results for: - - {{searchedString}} - -
- -
\ No newline at end of file diff --git a/scilog/src/app/logbook/core/search-window/search-window.component.spec.ts b/scilog/src/app/logbook/core/search-window/search-window.component.spec.ts index 0903320b..af5395b0 100644 --- a/scilog/src/app/logbook/core/search-window/search-window.component.spec.ts +++ b/scilog/src/app/logbook/core/search-window/search-window.component.spec.ts @@ -1,5 +1,5 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { LogbookInfoService } from '@shared/logbook-info.service'; import { AppConfigService } from 'src/app/app-config.service'; @@ -14,7 +14,6 @@ describe('SearchWindowComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ SearchWindowComponent ], providers: [ { provide: AppConfigService, useValue: { getConfig } }, { provide: MatDialog, useValue: {} }, @@ -50,20 +49,26 @@ describe('SearchWindowComponent', () => { expect(component.searchString).toEqual('someSearch'); }); - [ - ['some', false, true, 1], - ['', true, false, 0] - ].forEach((t, i) => { - it(`should submitSearch ${i}`, () => { - component.searchString = 'someSearch'; - const resetSpy = spyOn(component.searchScrollService, 'reset'); - const prepareConfigSpy = spyOn(component, '_prepareConfig'); - component.submitSearch(t[0] as string); - expect(component.showHelp).toEqual(t[1] as boolean); - expect(component.showResults).toEqual(t[2] as boolean); - expect(resetSpy).toHaveBeenCalledTimes(t[3] as number); - expect(prepareConfigSpy).toHaveBeenCalledTimes(t[3] as number); - }); + it('should submitSearch logbook', () => { + const resetSpy = spyOn(component['searchScrollService'], 'reset'); + const search = 'some'; + component.searchString = search; + component.submitSearch(); + expect(resetSpy).toHaveBeenCalledOnceWith(search); + expect(component.searched).toEqual(search); + }); + + it('should submitSearch overview', () => { + component.logbookId = undefined; + const search = 'some'; + component.searchString = search; + const resetSpy = spyOn(component['logbookIconScrollService'], 'reset'); + const emitSpy = spyOn(component.overviewSearch, 'emit'); + const closeSearchSpy = spyOn(component, 'closeSearch'); + component.submitSearch(); + expect(resetSpy).toHaveBeenCalledOnceWith(search); + expect(emitSpy).toHaveBeenCalledOnceWith(search); + expect(closeSearchSpy).toHaveBeenCalled(); }); }); diff --git a/scilog/src/app/logbook/core/search-window/search-window.component.ts b/scilog/src/app/logbook/core/search-window/search-window.component.ts index b3b5e63d..0a91be9c 100644 --- a/scilog/src/app/logbook/core/search-window/search-window.component.ts +++ b/scilog/src/app/logbook/core/search-window/search-window.component.ts @@ -1,14 +1,12 @@ import { Component, OnInit, Output, EventEmitter, Input, ElementRef, ViewChild } from '@angular/core'; -import { SearchScrollService } from '@shared/search-scroll.service'; import { WidgetConfig, WidgetItemConfig } from '@model/config'; import { Subscription } from 'rxjs'; -import { SearchDataService } from '@shared/remote-data.service'; import { UserPreferencesService } from '@shared/user-preferences.service'; import { LogbookInfoService } from '@shared/logbook-info.service'; import { TagService } from '@shared/tag.service'; -import { ScrollToElementService } from '@shared/scroll-to-element.service'; import { Hotkeys } from '@shared/hotkeys.service'; -import { animate, style, transition, trigger } from '@angular/animations'; +import { LogbookIconScrollService } from 'src/app/overview/logbook-icon-scroll-service.service'; +import { SearchScrollService } from 'src/app/core/search-scroll.service'; interface SearchResult { location: string[], @@ -23,60 +21,49 @@ interface SearchResult { selector: 'search-window', templateUrl: './search-window.component.html', styleUrls: ['./search-window.component.css'], - providers: [SearchScrollService], - animations: [ - trigger('spinner', [ - transition(':enter', [ - style({opacity: 0}), - animate('1ms 0.2s ease-out', style({opacity: 1})) - ]) - ]), - ] }) export class SearchWindowComponent implements OnInit { - @Input() configsArray: WidgetConfig[]; + @Input() + searched: string; + config: WidgetItemConfig; @Output() close = new EventEmitter(); + @Output() overviewSearch = new EventEmitter(); @ViewChild('searchSnippets') searchSnippets: ElementRef; - searchString: string = ''; + _searchString: string = ''; searchSnippetIndex: string = ''; - showResults = false; - showHelp = true; tags: string[] = []; _sample_user: string = ""; subscriptions: Subscription[] = []; - searchedString: string; + logbookId?: string; constructor( - public searchScrollService: SearchScrollService, - private searchDataservice: SearchDataService, public userPreferences: UserPreferencesService, private logbookInfo: LogbookInfoService, private tagService: TagService, - private scrollToElementService: ScrollToElementService, private hotkeys: Hotkeys, + private logbookIconScrollService: LogbookIconScrollService, + private searchScrollService: SearchScrollService, ) { } async ngOnInit(): Promise { - // console.log(this.configsArray[1].config); + this.searchString = this.searched; + this.logbookId = this.logbookInfo?.logbookInfo?.id; this.config = this._prepareConfig(); - this.searchScrollService.initialize(this.config); - - this._initialize_help(); + await this._initialize_help(); this.subscriptions.push(this.hotkeys.addShortcut({ keys: 'esc', description: { label: 'Close search', group: "General" } }).subscribe(() => { this.closeSearch(); })); this.subscriptions.push(this.hotkeys.addShortcut({ keys: 'enter', description: { label: 'Submit search', group: "General" } }).subscribe(() => { - if (this.searchString) - this.submitSearch(this.searchString); + this.submitSearch(); })); } @@ -87,11 +74,18 @@ export class SearchWindowComponent implements OnInit { this.searchSnippets.nativeElement.focus(); } - private async _initialize_help() { - this.tags = await this.tagService.getTags(); - if (this.tags.length == 0) { - this.tags = ["alignment"]; + submitSearch() { + this.searched = this.searchString; + if (this.logbookId) { + this.searchScrollService.reset(this.searchString); + return } + this.logbookIconScrollService.reset(this.searchString); + this.overviewSearch.emit(this.searchString); + this.closeSearch(); + } + + private async _initialize_help() { this._sample_user = this.userPreferences.userInfo.username; // roles.find((val)=>{ @@ -103,11 +97,17 @@ export class SearchWindowComponent implements OnInit { if (typeof this._sample_user == 'undefined') { this._sample_user = "p12345"; } - + if (!this.logbookId) return + this.tags = await this.tagService?.getTags(); + if (this.tags?.length == 0) { + this.tags = ["alignment"]; + } } + reset() { this.searchString = ""; } + addToSearch(val: string) { let _stringParts = val.split(" "); _stringParts.forEach((subVal) => { @@ -119,15 +119,18 @@ export class SearchWindowComponent implements OnInit { } closeSearch() { + this.reset(); this.close.emit(); } - selectedSnippet($event) { - console.log($event); - this.scrollToElementService.selectedItem = $event; - this.closeSearch(); + set searchString(searchString: string) { + this._searchString = searchString; + if (!searchString) this.searched = searchString; } + get searchString() { + return this._searchString; + } private _parseSearchString(): SearchResult { let searchResult: SearchResult = { @@ -139,10 +142,9 @@ export class SearchWindowComponent implements OnInit { endDate: "", } console.log("local search") - searchResult.location.push(this.logbookInfo.logbookInfo.id); + if (this.logbookId) searchResult.location.push(this.logbookId); console.log("Search value: ", this.searchString); console.log("Search config: ", searchResult) - this.searchDataservice.searchString = this.searchString; return searchResult; } @@ -169,19 +171,6 @@ export class SearchWindowComponent implements OnInit { return _config; } - submitSearch(searchText: string = '') { - this.searchedString = searchText; - if (searchText) { - this.showHelp = false; - this.showResults = true; - this.searchScrollService.config = this._prepareConfig(); - this.searchScrollService.reset(); - } else { - this.showHelp = true; - this.showResults = false; - } - }; - ngOnDestroy(): void { //Called once, before the instance is destroyed. //Add 'implements OnDestroy' to the class. diff --git a/scilog/src/app/logbook/core/search/search.component.css b/scilog/src/app/logbook/core/search/search.component.css index e69de29b..a68e5223 100644 --- a/scilog/src/app/logbook/core/search/search.component.css +++ b/scilog/src/app/logbook/core/search/search.component.css @@ -0,0 +1,25 @@ +.searchResults { + height: calc(50vh - 95px); + overflow: scroll; +} + +.searchResults .header { + font-size: 20px; + color: var(--header-color); + padding-bottom: 20px; + padding-top: 10px; + padding: 15px; +} + +.searchContainer { + overflow: scroll; + height: calc(50vh - 140px); +} + +.spinner { + position: fixed; + z-index: 1031; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/scilog/src/app/logbook/core/search/search.component.html b/scilog/src/app/logbook/core/search/search.component.html index 78ca5f94..a9b652f5 100644 --- a/scilog/src/app/logbook/core/search/search.component.html +++ b/scilog/src/app/logbook/core/search/search.component.html @@ -1,24 +1,13 @@ - - - Search search - - - - -
-
-
- I'm looking for... -
+
+
+ Logbook view: + +
+
+ +
-
-
- Recent searches -
-
-
-
- +
+
diff --git a/scilog/src/app/logbook/core/search/search.component.spec.ts b/scilog/src/app/logbook/core/search/search.component.spec.ts index be8e387b..7560a002 100644 --- a/scilog/src/app/logbook/core/search/search.component.spec.ts +++ b/scilog/src/app/logbook/core/search/search.component.spec.ts @@ -1,6 +1,11 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SearchComponent } from './search.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { AppConfigService } from 'src/app/app-config.service'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +const getConfig = () => ({}); describe('SearchComponent', () => { let component: SearchComponent; @@ -8,7 +13,11 @@ describe('SearchComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ SearchComponent ] + declarations: [ SearchComponent ], + providers: [ + { provide: AppConfigService, useValue: { getConfig } }, + ], + imports: [HttpClientTestingModule, BrowserAnimationsModule], }) .compileComponents(); })); @@ -22,4 +31,11 @@ describe('SearchComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should selectedSnippet', () => { + const emitSpy = spyOn(component.close, 'emit'); + component.selectedSnippet(''); + expect(emitSpy).toHaveBeenCalled(); + }); + }); diff --git a/scilog/src/app/logbook/core/search/search.component.ts b/scilog/src/app/logbook/core/search/search.component.ts index 6004c514..18f1b844 100644 --- a/scilog/src/app/logbook/core/search/search.component.ts +++ b/scilog/src/app/logbook/core/search/search.component.ts @@ -1,16 +1,42 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { SearchScrollService } from 'src/app/core/search-scroll.service'; +import { ScrollToElementService } from '../scroll-to-element.service'; +import { WidgetItemConfig } from 'src/app/core/model/config'; +import { animate, style, transition, trigger } from '@angular/animations'; @Component({ - selector: 'app-search', + selector: 'search', templateUrl: './search.component.html', - styleUrls: ['./search.component.css'] + styleUrls: ['./search.component.css'], + animations: [ + trigger('spinner', [ + transition(':enter', [ + style({ opacity: 0 }), + animate('1ms 0.2s ease-out', style({ opacity: 1 })) + ]) + ]), + ] }) export class SearchComponent implements OnInit { - searchString: string = ""; - constructor() { } + @Input() + config: WidgetItemConfig; - ngOnInit(): void { + @Output() close = new EventEmitter(); + + constructor( + public searchScrollService: SearchScrollService, + private scrollToElementService: ScrollToElementService, + ) { } + + async ngOnInit(): Promise { + this.searchScrollService.initialize(this.config); + } + + selectedSnippet($event) { + console.log($event); + this.scrollToElementService.selectedItem = $event; + this.close.emit(); } } diff --git a/scilog/src/app/overview/logbook-icon-scroll-service.service.ts b/scilog/src/app/overview/logbook-icon-scroll-service.service.ts index cc667d7e..f51a06db 100644 --- a/scilog/src/app/overview/logbook-icon-scroll-service.service.ts +++ b/scilog/src/app/overview/logbook-icon-scroll-service.service.ts @@ -1,15 +1,15 @@ import { Injectable } from '@angular/core'; import { LogbookDataService } from '@shared/remote-data.service'; -import { ScrollBaseService } from '@shared/scroll-base.service'; +import { SearchScrollBaseService } from '@shared/search-scroll.service'; import { Datasource } from 'ngx-ui-scroll'; @Injectable({ providedIn: 'root' }) -export class LogbookIconScrollService extends ScrollBaseService { +export class LogbookIconScrollService extends SearchScrollBaseService { groupSize = 3; - constructor(private logbookDataService: LogbookDataService) { + constructor(protected dataService: LogbookDataService) { super(); } @@ -49,7 +49,7 @@ export class LogbookIconScrollService extends ScrollBaseService { } async getData(index: number, count: number, config: any) { - const buffer = await this.logbookDataService.getDataBuffer(index, count, config); + const buffer = await this.dataService.getDataBuffer(index, count, config); this.datasource.adapter.relax(); const groupedBuffer = []; while (buffer.length) groupedBuffer.push(buffer.splice(0, this.groupSize)); diff --git a/scilog/src/app/overview/overview.component.css b/scilog/src/app/overview/overview.component.css index 84358b44..1f186ac7 100644 --- a/scilog/src/app/overview/overview.component.css +++ b/scilog/src/app/overview/overview.component.css @@ -46,12 +46,3 @@ mat-button-toggle-group { left: 50%; transform: translate(-50%, -50%); } - -.search { - width: 180px; -} - -.expanded-search { - width: 540px; -} - diff --git a/scilog/src/app/overview/overview.component.html b/scilog/src/app/overview/overview.component.html index e396fd83..31af0cb4 100644 --- a/scilog/src/app/overview/overview.component.html +++ b/scilog/src/app/overview/overview.component.html @@ -1,18 +1,5 @@
- - - Search search - - - - -

Logbooks

{ beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ OverviewComponent, LogbookSearchMockPipe], + declarations: [ OverviewComponent], imports: [MatDialogModule, RouterTestingModule, BrowserAnimationsModule], providers: [ { provide: MAT_DIALOG_DATA, useValue: {} }, @@ -154,14 +145,4 @@ describe('OverviewComponent', () => { expect(onResizedSpy).toHaveBeenCalled(); }); - it('should submitSearch', () => { - component.searchString = 'someSearch'; - const resetSpy = spyOn(component.logbookIconScrollService, 'reset'); - const prepareConfigSpy = spyOn(component, '_prepareConfig'); - component.submitSearch(); - expect(resetSpy).toHaveBeenCalledTimes(1); - expect(prepareConfigSpy).toHaveBeenCalledTimes(1); - }); - - }); diff --git a/scilog/src/app/overview/overview.component.ts b/scilog/src/app/overview/overview.component.ts index 47df5d5f..61f066bb 100644 --- a/scilog/src/app/overview/overview.component.ts +++ b/scilog/src/app/overview/overview.component.ts @@ -24,7 +24,6 @@ export type MatCardType = 'logbook-module' | 'logbook-headline'; selector: 'app-overview', templateUrl: './overview.component.html', styleUrls: ['./overview.component.css'], - providers: [LogbookIconScrollService], animations: [ trigger('spinner', [ transition(':enter', [ @@ -46,11 +45,9 @@ export class OverviewComponent implements OnInit { logbookSubscription: Subscription = null; subscriptions: Subscription[] = []; - private _searchString: string = ''; _matCardSide = { 'logbook-module': 352, 'logbook-headline': 47 }; @ViewChild('logbookContainer', { static: true }) logbookContainer: ElementRef; matCardType: MatCardType = 'logbook-module'; - searchClass = 'search'; constructor( @@ -132,15 +129,6 @@ export class OverviewComponent implements OnInit { } - public get searchString(): string { - return this._searchString; - } - public set searchString(value: string) { - this.searchClass = 'expanded-search'; - this._searchString = value; - this.dataService.searchString = this._searchString; - } - logbookSelected(logbookID: string) { this.cookie.lastLogbook = logbookID; console.log("selected logbook: ", logbookID); @@ -216,13 +204,6 @@ export class OverviewComponent implements OnInit { return _config; } - submitSearch(searchString: string='') { - this.searchString = searchString; - this.searchClass = 'search'; - this.logbookIconScrollService.config = this._prepareConfig(); - this.logbookIconScrollService.reset(); - } - ngOnDestroy(): void { this.subscriptions.forEach( (subscription) => subscription.unsubscribe());