diff --git a/.github/CODEOWNERS.md b/.github/CODEOWNERS similarity index 95% rename from .github/CODEOWNERS.md rename to .github/CODEOWNERS index c256931223..825503bf96 100644 --- a/.github/CODEOWNERS.md +++ b/.github/CODEOWNERS @@ -1,4 +1,3 @@ - # This is a comment. # Each line is a file pattern followed by one or more owners. @@ -12,7 +11,7 @@ # precedence. When someone opens a pull request that only # modifies JS files, only @js-owner and not the global # owner(s) will be requested for a review. -e2e/* @eromano @cristinaj @gmandakini @marouanbentaleb +e2e/* @eromano @cristinaj @gmandakini @marouanbentaleb # You can also use email addresses if you prefer. They'll be # used to look up users just like we do for commit author diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts index 686a5fd604..80e5cbf1a2 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts @@ -15,9 +15,11 @@ * limitations under the License. */ -import { Component, ViewEncapsulation, OnInit } from '@angular/core'; +import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core'; import { UserPreferencesService, AppConfigService, AlfrescoApiService, UserPreferenceValues } from '@alfresco/adf-core'; import { HeaderDataService } from '../header-data/header-data.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ templateUrl: 'app-layout.component.html', @@ -27,8 +29,8 @@ import { HeaderDataService } from '../header-data/header-data.service'; }, encapsulation: ViewEncapsulation.None }) - -export class AppLayoutComponent implements OnInit { +export class AppLayoutComponent implements OnInit, OnDestroy { + private onDestroy$ = new Subject(); links: Array = [ { href: '/home', icon: 'home', title: 'APP_LAYOUT.HOME' }, @@ -108,14 +110,42 @@ export class AppLayoutComponent implements OnInit { this.expandedSidenav = expand; } - this.headerService.hideMenu.subscribe((show) => this.showMenu = show); - this.headerService.color.subscribe((color) => this.color = color); - this.headerService.title.subscribe((title) => this.title = title); - this.headerService.logo.subscribe((path) => this.logo = path); - this.headerService.redirectUrl.subscribe((redirectUrl) => this.redirectUrl = redirectUrl); - this.headerService.tooltip.subscribe((tooltip) => this.tooltip = tooltip); - this.headerService.position.subscribe((position) => this.position = position); - this.headerService.hideSidenav.subscribe((hideSidenav) => this.hideSidenav = hideSidenav); + this.headerService.hideMenu + .pipe(takeUntil(this.onDestroy$)) + .subscribe(show => this.showMenu = show); + + this.headerService.color + .pipe(takeUntil(this.onDestroy$)) + .subscribe(color => this.color = color); + + this.headerService.title + .pipe(takeUntil(this.onDestroy$)) + .subscribe(title => this.title = title); + + this.headerService.logo + .pipe(takeUntil(this.onDestroy$)) + .subscribe(path => this.logo = path); + + this.headerService.redirectUrl + .pipe(takeUntil(this.onDestroy$)) + .subscribe(redirectUrl => this.redirectUrl = redirectUrl); + + this.headerService.tooltip + .pipe(takeUntil(this.onDestroy$)) + .subscribe(tooltip => this.tooltip = tooltip); + + this.headerService.position + .pipe(takeUntil(this.onDestroy$)) + .subscribe(position => this.position = position); + + this.headerService.hideSidenav + .pipe(takeUntil(this.onDestroy$)) + .subscribe(hideSidenav => this.hideSidenav = hideSidenav); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } constructor( diff --git a/demo-shell/src/app/components/card-view/card-view.component.ts b/demo-shell/src/app/components/card-view/card-view.component.ts index 0f4a283367..66291c863c 100644 --- a/demo-shell/src/app/components/card-view/card-view.component.ts +++ b/demo-shell/src/app/components/card-view/card-view.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, OnInit, ElementRef, ViewChild } from '@angular/core'; +import { Component, OnInit, ElementRef, ViewChild, OnDestroy } from '@angular/core'; import { CardViewTextItemModel, CardViewDateItemModel, @@ -29,13 +29,14 @@ import { CardViewMapItemModel, UpdateNotification } from '@alfresco/adf-core'; -import { of } from 'rxjs'; +import { of, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ templateUrl: './card-view.component.html', styleUrls: ['./card-view.component.scss'] }) -export class CardViewComponent implements OnInit { +export class CardViewComponent implements OnInit, OnDestroy { @ViewChild('console') console: ElementRef; @@ -43,6 +44,8 @@ export class CardViewComponent implements OnInit { properties: any; logs: string[]; + private onDestroy$ = new Subject(); + constructor(private cardViewUpdateService: CardViewUpdateService) { this.logs = []; this.createCard(); @@ -53,7 +56,14 @@ export class CardViewComponent implements OnInit { } ngOnInit() { - this.cardViewUpdateService.itemUpdated$.subscribe(this.onItemChange.bind(this)); + this.cardViewUpdateService.itemUpdated$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(this.onItemChange.bind(this)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } createCard() { diff --git a/demo-shell/src/app/components/cloud/cloud-breadcrumb-component.ts b/demo-shell/src/app/components/cloud/cloud-breadcrumb-component.ts index d3edf12944..90816e16bd 100644 --- a/demo-shell/src/app/components/cloud/cloud-breadcrumb-component.ts +++ b/demo-shell/src/app/components/cloud/cloud-breadcrumb-component.ts @@ -19,26 +19,24 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ - selector: 'app-cloud-breadcrumbs', - templateUrl: './cloud-breadcrumb-component.html', - styleUrls: ['cloud-breadcrumb-component.scss'] + selector: 'app-cloud-breadcrumbs', + templateUrl: './cloud-breadcrumb-component.html', + styleUrls: ['cloud-breadcrumb-component.scss'] }) export class CloudBreadcrumbsComponent implements OnInit { + appName: string; + filterName: string; - appName: string; - filterName: string; + constructor(private route: ActivatedRoute) {} - constructor(private route: ActivatedRoute) { } - - ngOnInit() { - this.route.parent.params.subscribe(( - params) => { - this.appName = params.appName; - }); - this.route.queryParams.subscribe((params) => { - if (params.filterName) { - this.filterName = params.filterName; - } - }); - } + ngOnInit() { + this.route.parent.params.subscribe(params => { + this.appName = params.appName; + }); + this.route.queryParams.subscribe(params => { + if (params.filterName) { + this.filterName = params.filterName; + } + }); + } } diff --git a/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts b/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts index a97919d5f6..2a193bfdbe 100644 --- a/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts +++ b/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts @@ -46,8 +46,9 @@ export class CloudFiltersDemoComponent implements OnInit { ) {} ngOnInit() { - this.currentTaskFilter$ = this.cloudLayoutService.getCurrentTaskFilterParam(); - this.currentProcessFilter$ = this.cloudLayoutService.getCurrentProcessFilterParam(); + this.currentTaskFilter$ = this.cloudLayoutService.taskFilter$; + this.currentProcessFilter$ = this.cloudLayoutService.processFilter$; + let root = ''; if (this.route.snapshot && this.route.snapshot.firstChild) { root = this.route.snapshot.firstChild.url[0].path; diff --git a/demo-shell/src/app/components/cloud/cloud-settings.component.ts b/demo-shell/src/app/components/cloud/cloud-settings.component.ts index cdb7a31fb8..46d9d2c0fb 100644 --- a/demo-shell/src/app/components/cloud/cloud-settings.component.ts +++ b/demo-shell/src/app/components/cloud/cloud-settings.component.ts @@ -15,15 +15,18 @@ * limitations under the License. */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { CloudLayoutService } from './services/cloud-layout.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-cloud-settings', templateUrl: './cloud-settings.component.html', styleUrls: ['./cloud-settings.component.scss'] }) -export class CloudSettingsComponent implements OnInit { +export class CloudSettingsComponent implements OnInit, OnDestroy { + private onDestroy$ = new Subject(); multiselect: boolean; selectionMode: string; @@ -40,8 +43,15 @@ export class CloudSettingsComponent implements OnInit { constructor(private cloudLayoutService: CloudLayoutService) { } ngOnInit() { - this.cloudLayoutService.getCurrentSettings() - .subscribe((settings) => this.setCurrentSettings(settings)); + this.cloudLayoutService + .settings$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(settings => this.setCurrentSettings(settings)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } setCurrentSettings(settings) { diff --git a/demo-shell/src/app/components/cloud/cloud-viewer.component.ts b/demo-shell/src/app/components/cloud/cloud-viewer.component.ts index a65bfe0b5c..14623f5330 100644 --- a/demo-shell/src/app/components/cloud/cloud-viewer.component.ts +++ b/demo-shell/src/app/components/cloud/cloud-viewer.component.ts @@ -15,32 +15,24 @@ * limitations under the License. */ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; import { Params } from '@angular/router/src/shared'; @Component({ selector: 'app-cloud-viewer', templateUrl: './cloud-viewer.component.html' }) -export class CloudViewerComponent implements OnInit, OnDestroy { +export class CloudViewerComponent implements OnInit { nodeId: string; - private sub: Subscription; - constructor(private route: ActivatedRoute) { } ngOnInit() { - this.sub = this.route.params.subscribe((params: Params) => { + this.route.params.subscribe((params: Params) => { this.nodeId = params['nodeId']; }); } - - ngOnDestroy() { - this.sub.unsubscribe(); - } - } diff --git a/demo-shell/src/app/components/cloud/community/community-filters.component.ts b/demo-shell/src/app/components/cloud/community/community-filters.component.ts index 39e64d288f..f3cf42d129 100644 --- a/demo-shell/src/app/components/cloud/community/community-filters.component.ts +++ b/demo-shell/src/app/components/cloud/community/community-filters.component.ts @@ -45,8 +45,9 @@ export class CommunityCloudFiltersDemoComponent implements OnInit { ) {} ngOnInit() { - this.currentTaskFilter$ = this.cloudLayoutService.getCurrentTaskFilterParam(); - this.currentProcessFilter$ = this.cloudLayoutService.getCurrentProcessFilterParam(); + this.currentTaskFilter$ = this.cloudLayoutService.taskFilter$; + this.currentProcessFilter$ = this.cloudLayoutService.processFilter$; + let root = ''; if (this.route.snapshot && this.route.snapshot.firstChild) { root = this.route.snapshot.firstChild.url[0].path; diff --git a/demo-shell/src/app/components/cloud/community/community-processes-cloud.component.ts b/demo-shell/src/app/components/cloud/community/community-processes-cloud.component.ts index c7d658b9b1..4092e9a573 100644 --- a/demo-shell/src/app/components/cloud/community/community-processes-cloud.component.ts +++ b/demo-shell/src/app/components/cloud/community/community-processes-cloud.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, ViewChild, OnInit } from '@angular/core'; +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { ProcessListCloudComponent, ProcessFilterCloudModel, @@ -27,12 +27,14 @@ import { import { ActivatedRoute, Router } from '@angular/router'; import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; import { CloudLayoutService } from '../services/cloud-layout.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Pagination } from '@alfresco/js-api'; @Component({ templateUrl: './community-processes-cloud.component.html' }) -export class CommunityProcessesCloudDemoComponent implements OnInit { - +export class CommunityProcessesCloudDemoComponent implements OnInit, OnDestroy { public static ACTION_SAVE_AS = 'saveAs'; static PROCESS_FILTER_PROPERTY_KEYS = 'adf-edit-process-filter'; @@ -56,6 +58,8 @@ export class CommunityProcessesCloudDemoComponent implements OnInit { editedFilter: ProcessFilterCloudModel; + private onDestroy$ = new Subject(); + constructor( private route: ActivatedRoute, private router: Router, @@ -63,7 +67,10 @@ export class CommunityProcessesCloudDemoComponent implements OnInit { private userPreference: UserPreferencesService, private processFilterCloudService: ProcessFilterCloudService, private appConfig: AppConfigService) { - const properties = this.appConfig.get>(CommunityProcessesCloudDemoComponent.PROCESS_FILTER_PROPERTY_KEYS); + const properties = this.appConfig.get>( + CommunityProcessesCloudDemoComponent.PROCESS_FILTER_PROPERTY_KEYS + ); + if (properties) { this.processFilterProperties = properties; } @@ -71,6 +78,7 @@ export class CommunityProcessesCloudDemoComponent implements OnInit { ngOnInit() { this.isFilterLoaded = false; + this.route.parent.params.subscribe((params) => { this.appName = params.appName; }); @@ -85,14 +93,23 @@ export class CommunityProcessesCloudDemoComponent implements OnInit { } }); - this.cloudLayoutService.getCurrentSettings() - .subscribe((settings) => this.setCurrentSettings(settings)); + this.cloudLayoutService + .settings$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(settings => this.setCurrentSettings(settings)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } loadDefaultFilters() { - this.processFilterCloudService.getProcessFilters('community').subscribe( (filters: ProcessFilterCloudModel[]) => { - this.onFilterChange(filters[0]); - }); + this.processFilterCloudService + .getProcessFilters('community') + .subscribe((filters: ProcessFilterCloudModel[]) => { + this.onFilterChange(filters[0]); + }); } setCurrentSettings(settings) { @@ -103,7 +120,7 @@ export class CommunityProcessesCloudDemoComponent implements OnInit { } } - onChangePageSize(event) { + onChangePageSize(event: Pagination) { this.userPreference.paginationSize = event.maxItems; } @@ -111,13 +128,18 @@ export class CommunityProcessesCloudDemoComponent implements OnInit { this.selectedRows = []; } - onRowClick(processInstanceId) { + onRowClick(processInstanceId: string) { this.router.navigate([`/cloud/community/process-details/${processInstanceId}`]); } onFilterChange(query: any) { this.editedFilter = Object.assign({}, query); - this.sortArray = [new ProcessListCloudSortingModel({ orderBy: this.editedFilter.sort, direction: this.editedFilter.order })]; + this.sortArray = [ + new ProcessListCloudSortingModel({ + orderBy: this.editedFilter.sort, + direction: this.editedFilter.order + }) + ]; } onProcessFilterAction(filterAction: any) { diff --git a/demo-shell/src/app/components/cloud/community/community-task-cloud.component.ts b/demo-shell/src/app/components/cloud/community/community-task-cloud.component.ts index 1e77a51372..94c38ddb1c 100644 --- a/demo-shell/src/app/components/cloud/community/community-task-cloud.component.ts +++ b/demo-shell/src/app/components/cloud/community/community-task-cloud.component.ts @@ -15,11 +15,14 @@ * limitations under the License. */ -import { Component, ViewChild, OnInit } from '@angular/core'; +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { TaskListCloudComponent, TaskListCloudSortingModel, TaskFilterCloudModel, TaskFilterCloudService } from '@alfresco/adf-process-services-cloud'; import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; import { ActivatedRoute, Router } from '@angular/router'; import { CloudLayoutService } from '../services/cloud-layout.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Pagination } from '@alfresco/js-api'; @Component({ templateUrl: './community-task-cloud.component.html', @@ -28,8 +31,7 @@ import { CloudLayoutService } from '../services/cloud-layout.service'; } `] }) -export class CommunityTasksCloudDemoComponent implements OnInit { - +export class CommunityTasksCloudDemoComponent implements OnInit, OnDestroy { public static ACTION_SAVE_AS = 'saveAs'; static TASK_FILTER_PROPERTY_KEYS = 'adf-edit-task-filter'; @@ -51,6 +53,8 @@ export class CommunityTasksCloudDemoComponent implements OnInit { selectionMode: string; taskDetailsRedirection: boolean; + private onDestroy$ = new Subject(); + constructor( private cloudLayoutService: CloudLayoutService, private route: ActivatedRoute, @@ -79,14 +83,23 @@ export class CommunityTasksCloudDemoComponent implements OnInit { } }); - this.cloudLayoutService.getCurrentSettings() - .subscribe((settings) => this.setCurrentSettings(settings)); + this.cloudLayoutService + .settings$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(settings => this.setCurrentSettings(settings)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } loadDefaultFilters() { - this.taskFilterCloudService.getTaskListFilters('community').subscribe( (filters: TaskFilterCloudModel[]) => { - this.onFilterChange(filters[0]); - }); + this.taskFilterCloudService + .getTaskListFilters('community') + .subscribe((filters: TaskFilterCloudModel[]) => { + this.onFilterChange(filters[0]); + }); } setCurrentSettings(settings) { @@ -98,7 +111,7 @@ export class CommunityTasksCloudDemoComponent implements OnInit { } } - onChangePageSize(event) { + onChangePageSize(event: Pagination) { this.userPreference.paginationSize = event.maxItems; } @@ -106,7 +119,7 @@ export class CommunityTasksCloudDemoComponent implements OnInit { this.selectedRows = []; } - onRowClick(taskId) { + onRowClick(taskId: string) { if (!this.multiselect && this.selectionMode !== 'multiple' && this.taskDetailsRedirection) { this.router.navigate([`/cloud/community/task-details/${taskId}`]); } diff --git a/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts index 31a417e206..40455ff8c2 100644 --- a/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts +++ b/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, ViewChild, OnInit } from '@angular/core'; +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { ProcessListCloudComponent, ProcessFilterCloudModel, @@ -25,13 +25,16 @@ import { import { ActivatedRoute, Router } from '@angular/router'; import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; -import { CloudLayoutService } from './services/cloud-layout.service'; +import { CloudLayoutService, CloudServiceSettings } from './services/cloud-layout.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Pagination } from '@alfresco/js-api'; @Component({ templateUrl: './processes-cloud-demo.component.html', styleUrls: ['./processes-cloud-demo.component.scss'] }) -export class ProcessesCloudDemoComponent implements OnInit { +export class ProcessesCloudDemoComponent implements OnInit, OnDestroy { public static ACTION_SAVE_AS = 'saveAs'; static PROCESS_FILTER_PROPERTY_KEYS = 'adf-edit-process-filter'; @@ -57,6 +60,8 @@ export class ProcessesCloudDemoComponent implements OnInit { editedFilter: ProcessFilterCloudModel; + private onDestroy$ = new Subject(); + constructor( private route: ActivatedRoute, private router: Router, @@ -81,11 +86,17 @@ export class ProcessesCloudDemoComponent implements OnInit { this.filterId = params.id; }); - this.cloudLayoutService.getCurrentSettings() - .subscribe((settings) => this.setCurrentSettings(settings)); + this.cloudLayoutService.settings$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(settings => this.setCurrentSettings(settings)); } - setCurrentSettings(settings) { + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + + setCurrentSettings(settings: CloudServiceSettings) { if (settings) { this.multiselect = settings.multiselect; this.testingMode = settings.testingMode; @@ -94,7 +105,7 @@ export class ProcessesCloudDemoComponent implements OnInit { } } - onChangePageSize(event) { + onChangePageSize(event: Pagination) { this.userPreference.paginationSize = event.maxItems; } @@ -102,7 +113,7 @@ export class ProcessesCloudDemoComponent implements OnInit { this.selectedRows = []; } - onRowClick(processInstanceId) { + onRowClick(processInstanceId: string) { if (!this.multiselect && this.selectionMode !== 'multiple' && this.processDetailsRedirection) { this.router.navigate([`/cloud/${this.appName}/process-details/${processInstanceId}`]); } @@ -110,7 +121,12 @@ export class ProcessesCloudDemoComponent implements OnInit { onFilterChange(query: any) { this.editedFilter = Object.assign({}, query); - this.sortArray = [new ProcessListCloudSortingModel({ orderBy: this.editedFilter.sort, direction: this.editedFilter.order })]; + this.sortArray = [ + new ProcessListCloudSortingModel({ + orderBy: this.editedFilter.sort, + direction: this.editedFilter.order + }) + ]; } onProcessFilterAction(filterAction: any) { diff --git a/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts b/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts index 7bf710169c..d3041063d3 100644 --- a/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts +++ b/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts @@ -16,14 +16,28 @@ */ import { Injectable } from '@angular/core'; -import { Observable, BehaviorSubject } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; + +export interface CloudServiceSettings { + multiselect: boolean; + testingMode: boolean; + taskDetailsRedirection: boolean; + processDetailsRedirection: boolean; + selectionMode: string; +} + +export interface FilterSettings { + id?: string; + index?: number; + key?: string; +} @Injectable({ providedIn: 'root' }) export class CloudLayoutService { - private settings = { + private settings: CloudServiceSettings = { multiselect: false, testingMode: false, taskDetailsRedirection: true, @@ -31,40 +45,19 @@ export class CloudLayoutService { selectionMode: 'single' }; - private filterTaskSubject: BehaviorSubject = new BehaviorSubject({index: 0}); - private filterTask$: Observable; - private filterProcessSubject: BehaviorSubject = new BehaviorSubject({index: 0}); - private filterProcess$: Observable; - private settingsSubject: BehaviorSubject = new BehaviorSubject(this.settings); - private settings$: Observable; + taskFilter$ = new BehaviorSubject({index: 0}); + processFilter$ = new BehaviorSubject({index: 0}); + settings$ = new BehaviorSubject(this.settings); - constructor() { - this.filterTask$ = this.filterTaskSubject.asObservable(); - this.filterProcess$ = this.filterProcessSubject.asObservable(); - this.settings$ = this.settingsSubject.asObservable(); + setCurrentTaskFilterParam(param: FilterSettings) { + this.taskFilter$.next(param); } - getCurrentTaskFilterParam() { - return this.filterTask$; + setCurrentProcessFilterParam(param: FilterSettings) { + this.processFilter$.next(param); } - setCurrentTaskFilterParam(param) { - this.filterTaskSubject.next(param); - } - - getCurrentProcessFilterParam() { - return this.filterProcess$; - } - - setCurrentProcessFilterParam(param) { - this.filterProcessSubject.next(param); - } - - getCurrentSettings() { - return this.settings$; - } - - setCurrentSettings(param) { - this.settingsSubject.next(param); + setCurrentSettings(param: CloudServiceSettings) { + this.settings$.next(param); } } diff --git a/demo-shell/src/app/components/cloud/tasks-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/tasks-cloud-demo.component.ts index 4cc0058347..daa4aeed93 100644 --- a/demo-shell/src/app/components/cloud/tasks-cloud-demo.component.ts +++ b/demo-shell/src/app/components/cloud/tasks-cloud-demo.component.ts @@ -15,17 +15,19 @@ * limitations under the License. */ -import { Component, ViewChild, OnInit } from '@angular/core'; +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { TaskListCloudComponent, TaskListCloudSortingModel, TaskFilterCloudModel } from '@alfresco/adf-process-services-cloud'; import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; import { ActivatedRoute, Router } from '@angular/router'; import { CloudLayoutService } from './services/cloud-layout.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ templateUrl: 'tasks-cloud-demo.component.html', styleUrls: ['tasks-cloud-demo.component.scss'] }) -export class TasksCloudDemoComponent implements OnInit { +export class TasksCloudDemoComponent implements OnInit, OnDestroy { public static ACTION_SAVE_AS = 'saveAs'; static TASK_FILTER_PROPERTY_KEYS = 'adf-edit-task-filter'; @@ -50,6 +52,8 @@ export class TasksCloudDemoComponent implements OnInit { selectionMode: string; taskDetailsRedirection: boolean; + private onDestroy$ = new Subject(); + constructor( private cloudLayoutService: CloudLayoutService, private route: ActivatedRoute, @@ -75,8 +79,14 @@ export class TasksCloudDemoComponent implements OnInit { this.filterId = params.id; }); - this.cloudLayoutService.getCurrentSettings() - .subscribe((settings) => this.setCurrentSettings(settings)); + this.cloudLayoutService.settings$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(settings => this.setCurrentSettings(settings)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } setCurrentSettings(settings) { diff --git a/demo-shell/src/app/components/config-editor/config-editor.component.ts b/demo-shell/src/app/components/config-editor/config-editor.component.ts index d133897b49..32bcc6d893 100644 --- a/demo-shell/src/app/components/config-editor/config-editor.component.ts +++ b/demo-shell/src/app/components/config-editor/config-editor.component.ts @@ -15,20 +15,24 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { AppConfigService, NotificationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-config-editor', templateUrl: 'config-editor.component.html', styleUrls: ['./config-editor.component.scss'] }) -export class ConfigEditorComponent { +export class ConfigEditorComponent implements OnDestroy { + + private onDestroy$ = new Subject(); editor: any; code: any; @@ -83,15 +87,23 @@ export class ConfigEditorComponent { this.indentCode(); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + textOrientationClick() { this.isUserPreference = true; this.userPreferenceProperty = 'textOrientation'; - this.userPreferencesService.select(this.userPreferenceProperty).subscribe((textOrientation: number) => { - this.code = JSON.stringify(textOrientation); - this.field = 'textOrientation'; - this.indentCode(); - }); + this.userPreferencesService + .select(this.userPreferenceProperty) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((textOrientation: number) => { + this.code = JSON.stringify(textOrientation); + this.field = 'textOrientation'; + this.indentCode(); + }); this.indentCode(); } @@ -99,21 +111,27 @@ export class ConfigEditorComponent { infinitePaginationConfClick() { this.isUserPreference = true; this.userPreferenceProperty = UserPreferenceValues.PaginationSize; - this.userPreferencesService.select(this.userPreferenceProperty).subscribe((pageSize: number) => { - this.code = JSON.stringify(pageSize); - this.field = 'adf-infinite-pagination'; - this.indentCode(); - }); + this.userPreferencesService + .select(this.userPreferenceProperty) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((pageSize: number) => { + this.code = JSON.stringify(pageSize); + this.field = 'adf-infinite-pagination'; + this.indentCode(); + }); } supportedPageSizesClick() { this.isUserPreference = true; this.userPreferenceProperty = UserPreferenceValues.SupportedPageSizes; - this.userPreferencesService.select(this.userPreferenceProperty).subscribe((supportedPageSizes: number) => { - this.code = JSON.stringify(supportedPageSizes); - this.field = 'adf-supported-page-size'; - this.indentCode(); - }); + this.userPreferencesService + .select(this.userPreferenceProperty) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((supportedPageSizes: number) => { + this.code = JSON.stringify(supportedPageSizes); + this.field = 'adf-supported-page-size'; + this.indentCode(); + }); } indentCode() { diff --git a/demo-shell/src/app/components/document-list/extension-presets/name-column/name-column.component.ts b/demo-shell/src/app/components/document-list/extension-presets/name-column/name-column.component.ts index 54c43bce43..a4fa1189a1 100644 --- a/demo-shell/src/app/components/document-list/extension-presets/name-column/name-column.component.ts +++ b/demo-shell/src/app/components/document-list/extension-presets/name-column/name-column.component.ts @@ -25,9 +25,10 @@ import { OnDestroy } from '@angular/core'; import { NodeEntry } from '@alfresco/js-api'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { Node } from '@alfresco/js-api'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-name-column', @@ -47,24 +48,26 @@ export class NameColumnComponent implements OnInit, OnDestroy { displayText$ = new BehaviorSubject(''); node: NodeEntry; - private sub: Subscription; + private onDestroy$ = new Subject(); constructor(private element: ElementRef, private alfrescoApiService: AlfrescoApiService) {} ngOnInit() { this.updateValue(); - this.sub = this.alfrescoApiService.nodeUpdated.subscribe((node: Node) => { - const row = this.context.row; - if (row) { - const { entry } = row.node; + this.alfrescoApiService.nodeUpdated + .pipe(takeUntil(this.onDestroy$)) + .subscribe((node: Node) => { + const row = this.context.row; + if (row) { + const { entry } = row.node; - if (entry === node) { - row.node = { entry }; - this.updateValue(); + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } } - } - }); + }); } protected updateValue() { @@ -87,9 +90,7 @@ export class NameColumnComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - this.sub = null; - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/demo-shell/src/app/components/files/files.component.ts b/demo-shell/src/app/components/files/files.component.ts index bb9cdc512c..731ea24524 100644 --- a/demo-shell/src/app/components/files/files.component.ts +++ b/demo-shell/src/app/components/files/files.component.ts @@ -45,7 +45,7 @@ import { SelectAppsDialogComponent } from '@alfresco/adf-process-services'; import { VersionManagerDialogAdapterComponent } from './version-manager-dialog-adapter.component'; import { MetadataDialogAdapterComponent } from './metadata-dialog-adapter.component'; -import { Subscription, Subject } from 'rxjs'; +import { Subject } from 'rxjs'; import { PreviewService } from '../../services/preview.service'; import { debounceTime, takeUntil } from 'rxjs/operators'; import { SearchEntry } from '@alfresco/js-api'; @@ -201,9 +201,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy { displayEmptyMetadata = false; hyperlinkNavigation = false; - private onCreateFolder: Subscription; - private onEditFolder: Subscription; - constructor(private notificationService: NotificationService, private uploadService: UploadService, private contentService: ContentService, @@ -264,13 +261,28 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy { }); } - this.uploadService.fileUploadComplete.asObservable() - .pipe(debounceTime(300)) - .subscribe((value) => this.onFileUploadEvent(value)); - this.uploadService.fileUploadDeleted.subscribe((value) => this.onFileUploadEvent(value)); - this.contentService.folderCreated.subscribe((value) => this.onFolderCreated(value)); - this.onCreateFolder = this.contentService.folderCreate.subscribe((value) => this.onFolderAction(value)); - this.onEditFolder = this.contentService.folderEdit.subscribe((value) => this.onFolderAction(value)); + this.uploadService.fileUploadComplete + .pipe( + debounceTime(300), + takeUntil(this.onDestroy$) + ) + .subscribe(value => this.onFileUploadEvent(value)); + + this.uploadService.fileUploadDeleted + .pipe(takeUntil(this.onDestroy$)) + .subscribe(value => this.onFileUploadEvent(value)); + + this.contentService.folderCreated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(value => this.onFolderCreated(value)); + + this.contentService.folderCreate + .pipe(takeUntil(this.onDestroy$)) + .subscribe(value => this.onFolderAction(value)); + + this.contentService.folderEdit + .pipe(takeUntil(this.onDestroy$)) + .subscribe(value => this.onFolderAction(value)); this.contentMetadataService.error .pipe(takeUntil(this.onDestroy$)) @@ -286,9 +298,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy { } ngOnDestroy() { - this.onCreateFolder.unsubscribe(); - this.onEditFolder.unsubscribe(); - this.onDestroy$.next(true); this.onDestroy$.complete(); } @@ -592,7 +601,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy { width: '400px' }); - dialogInstance.componentInstance.error.subscribe((message) => { + dialogInstance.componentInstance.error.subscribe((message: string) => { this.notificationService.openSnackMessage(message); }); } diff --git a/demo-shell/src/app/components/form/form-list.component.ts b/demo-shell/src/app/components/form/form-list.component.ts index 4ad54895e7..bf4e2934ef 100644 --- a/demo-shell/src/app/components/form/form-list.component.ts +++ b/demo-shell/src/app/components/form/form-list.component.ts @@ -15,16 +15,18 @@ * limitations under the License. */ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core'; import { FormModel, FormService, LogService, FormOutcomeEvent } from '@alfresco/adf-core'; import { FormComponent } from '@alfresco/adf-process-services'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-form-list', templateUrl: 'form-list.component.html', styleUrls: ['form-list.component.scss'] }) -export class FormListComponent { +export class FormListComponent implements OnInit, OnDestroy { @ViewChild('adfForm') activitiForm: FormComponent; @@ -38,13 +40,24 @@ export class FormListComponent { restoredData: any = {}; showValidationIcon = false; + private onDestroy$ = new Subject(); constructor(private formService: FormService, private logService: LogService) { + } + + ngOnInit() { // Prevent default outcome actions - formService.executeOutcome.subscribe((formOutcomeEvent: FormOutcomeEvent) => { - formOutcomeEvent.preventDefault(); - this.logService.log(formOutcomeEvent.outcome); - }); + this.formService.executeOutcome + .pipe(takeUntil(this.onDestroy$)) + .subscribe((formOutcomeEvent: FormOutcomeEvent) => { + formOutcomeEvent.preventDefault(); + this.logService.log(formOutcomeEvent.outcome); + }); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onRowDblClick(event: CustomEvent) { diff --git a/demo-shell/src/app/components/form/form-loading.component.ts b/demo-shell/src/app/components/form/form-loading.component.ts index ff9e71ecce..42e4227a8c 100644 --- a/demo-shell/src/app/components/form/form-loading.component.ts +++ b/demo-shell/src/app/components/form/form-loading.component.ts @@ -15,46 +15,60 @@ * limitations under the License. */ -import { Component, Inject, OnInit } from '@angular/core'; -import { FormModel, FormService, FormOutcomeEvent, CoreAutomationService } from '@alfresco/adf-core'; +import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; +import { + FormModel, + FormService, + FormOutcomeEvent, + CoreAutomationService +} from '@alfresco/adf-core'; import { InMemoryFormService } from '../../services/in-memory-form.service'; import { FakeFormService } from './fake-form.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-form-loading', templateUrl: 'form-loading.component.html', styleUrls: ['form-loading.component.scss'], - providers: [ - { provide: FormService, useClass: FakeFormService } - ] + providers: [{ provide: FormService, useClass: FakeFormService }] }) -export class FormLoadingComponent implements OnInit { - +export class FormLoadingComponent implements OnInit, OnDestroy { form: FormModel; typeaheadFieldValue = ''; selectFieldValue = ''; radioButtonFieldValue = ''; formattedData = {}; - constructor(@Inject(FormService) private formService: InMemoryFormService, - private automationService: CoreAutomationService) { - formService.executeOutcome.subscribe((formOutcomeEvent: FormOutcomeEvent) => { - formOutcomeEvent.preventDefault(); - }); - } + private onDestroy$ = new Subject(); + + constructor( + @Inject(FormService) private formService: InMemoryFormService, + private automationService: CoreAutomationService + ) {} ngOnInit() { + this.formService.executeOutcome + .pipe(takeUntil(this.onDestroy$)) + .subscribe((formOutcomeEvent: FormOutcomeEvent) => { + formOutcomeEvent.preventDefault(); + }); + this.formattedData = {}; const formDefinitionJSON: any = this.automationService.forms.getSimpleFormDefinition(); this.form = this.formService.parseForm(formDefinitionJSON); } - onLoadButtonClicked() { - this.formattedData = { - 'typeaheadField': this.typeaheadFieldValue, - 'selectBox': this.selectFieldValue, - 'radioButton': this.radioButtonFieldValue - }; + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } + onLoadButtonClicked() { + this.formattedData = { + typeaheadField: this.typeaheadFieldValue, + selectBox: this.selectFieldValue, + radioButton: this.radioButtonFieldValue + }; + } } diff --git a/demo-shell/src/app/components/form/form.component.ts b/demo-shell/src/app/components/form/form.component.ts index 33ba5fec89..8559e4d031 100644 --- a/demo-shell/src/app/components/form/form.component.ts +++ b/demo-shell/src/app/components/form/form.component.ts @@ -18,7 +18,8 @@ import { Component, Inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { FormModel, FormFieldModel, FormService, FormOutcomeEvent, NotificationService, CoreAutomationService } from '@alfresco/adf-core'; import { InMemoryFormService } from '../../services/in-memory-form.service'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-form', @@ -35,7 +36,6 @@ export class FormComponent implements OnInit, OnDestroy { errorFields: FormFieldModel[] = []; formConfig: string; editor: any; - private subscriptions: Subscription[] = []; editorOptions = { theme: 'vs-dark', @@ -46,15 +46,11 @@ export class FormComponent implements OnInit, OnDestroy { automaticLayout: true }; + private onDestroy$ = new Subject(); + constructor(@Inject(FormService) private formService: InMemoryFormService, private notificationService: NotificationService, private automationService: CoreAutomationService) { - - this.subscriptions.push( - formService.executeOutcome.subscribe((formOutcomeEvent: FormOutcomeEvent) => { - formOutcomeEvent.preventDefault(); - }) - ); } logErrors(errorFields: FormFieldModel[]) { @@ -62,13 +58,21 @@ export class FormComponent implements OnInit, OnDestroy { } ngOnInit() { - this.formConfig = JSON.stringify(this.automationService.forms.getFormDefinition()); + this.formService.executeOutcome + .pipe(takeUntil(this.onDestroy$)) + .subscribe((formOutcomeEvent: FormOutcomeEvent) => { + formOutcomeEvent.preventDefault(); + }); + + this.formConfig = JSON.stringify( + this.automationService.forms.getFormDefinition() + ); this.parseForm(); } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onInitFormEditor(editor) { diff --git a/demo-shell/src/app/components/header-data/header-data.service.ts b/demo-shell/src/app/components/header-data/header-data.service.ts index 33eda6d066..6977549479 100644 --- a/demo-shell/src/app/components/header-data/header-data.service.ts +++ b/demo-shell/src/app/components/header-data/header-data.service.ts @@ -24,14 +24,14 @@ export class HeaderDataService { show = true; - @Output() hideMenu: EventEmitter = new EventEmitter(); - @Output() color: EventEmitter = new EventEmitter(); - @Output() title: EventEmitter = new EventEmitter(); - @Output() logo: EventEmitter = new EventEmitter(); - @Output() redirectUrl: EventEmitter = new EventEmitter(); - @Output() tooltip: EventEmitter = new EventEmitter(); - @Output() position: EventEmitter = new EventEmitter(); - @Output() hideSidenav: EventEmitter = new EventEmitter(); + @Output() hideMenu = new EventEmitter(); + @Output() color = new EventEmitter(); + @Output() title = new EventEmitter(); + @Output() logo = new EventEmitter(); + @Output() redirectUrl = new EventEmitter(); + @Output() tooltip = new EventEmitter(); + @Output() position = new EventEmitter(); + @Output() hideSidenav = new EventEmitter(); hideMenuButton() { this.show = !this.show; @@ -59,11 +59,11 @@ export class HeaderDataService { this.tooltip.emit(tooltip); } - changePosition(position) { + changePosition(position: string) { this.position.emit(position); } - changeSidenavVisibility(hideSidenav) { + changeSidenavVisibility(hideSidenav: boolean) { this.hideSidenav.emit(hideSidenav); } } diff --git a/demo-shell/src/app/components/log/log.component.ts b/demo-shell/src/app/components/log/log.component.ts index 5ea521fe35..a86105c665 100644 --- a/demo-shell/src/app/components/log/log.component.ts +++ b/demo-shell/src/app/components/log/log.component.ts @@ -15,36 +15,57 @@ * limitations under the License. */ -import { Component, HostListener } from '@angular/core'; +import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { LogService, ObjectDataTableAdapter } from '@alfresco/adf-core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-log', templateUrl: './log.component.html', styleUrls: ['./log.component.css'] }) -export class LogComponent { +export class LogComponent implements OnInit, OnDestroy { logs: any[] = []; show = false; ctrlLKey = 12; logsData: ObjectDataTableAdapter; - constructor(public logService: LogService) { + private onDestroy$ = new Subject(); - logService.onMessage.subscribe((message) => { - let contentMessage = ''; - try { - contentMessage = JSON.stringify(message.text); - } catch (error) { - return; - } - this.logs.push({ type: message.type, text: contentMessage}); - this.logsData = new ObjectDataTableAdapter(this.logs, [ - { type: 'text', key: 'type', title: 'Log level', sortable: true }, - { type: 'text', key: 'text', title: 'Message', sortable: false } - ]); + constructor(public logService: LogService) {} - }); + ngOnInit() { + this.logService.onMessage + .pipe(takeUntil(this.onDestroy$)) + .subscribe(message => { + let contentMessage = ''; + try { + contentMessage = JSON.stringify(message.text); + } catch (error) { + return; + } + this.logs.push({ type: message.type, text: contentMessage }); + this.logsData = new ObjectDataTableAdapter(this.logs, [ + { + type: 'text', + key: 'type', + title: 'Log level', + sortable: true + }, + { + type: 'text', + key: 'text', + title: 'Message', + sortable: false + } + ]); + }); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } @HostListener('document:keypress', ['$event']) @@ -54,6 +75,5 @@ export class LogComponent { if (key === this.ctrlLKey) { this.show = !this.show; } - } } diff --git a/demo-shell/src/app/components/notifications/notifications.component.ts b/demo-shell/src/app/components/notifications/notifications.component.ts index 2c2074bd8d..44fee12a70 100644 --- a/demo-shell/src/app/components/notifications/notifications.component.ts +++ b/demo-shell/src/app/components/notifications/notifications.component.ts @@ -15,16 +15,18 @@ * limitations under the License. */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { NotificationService } from '@alfresco/adf-core'; import { MatSnackBarConfig } from '@angular/material'; import { FormBuilder, FormGroup, FormControl } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ templateUrl: './notifications.component.html', styleUrls: ['./notifications.component.scss'] }) -export class NotificationsComponent implements OnInit { +export class NotificationsComponent implements OnInit, OnDestroy { message = 'I ♥️ ADF'; withAction = false; @@ -55,6 +57,8 @@ export class NotificationsComponent implements OnInit { defaultDuration = 20000; + private onDestroy$ = new Subject(); + constructor(private notificationService: NotificationService, private formBuilder: FormBuilder) { this.snackBarConfig.duration = this.defaultDuration; @@ -69,10 +73,15 @@ export class NotificationsComponent implements OnInit { }); this.configForm.valueChanges - .subscribe((configFormValues) => + .pipe(takeUntil(this.onDestroy$)) + .subscribe(configFormValues => this.setSnackBarConfig(configFormValues) ); + } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } setSnackBarConfig(configFormValues: any) { diff --git a/demo-shell/src/app/components/permissions/demo-permissions.component.ts b/demo-shell/src/app/components/permissions/demo-permissions.component.ts index d40bc426e0..321357dc3d 100644 --- a/demo-shell/src/app/components/permissions/demo-permissions.component.ts +++ b/demo-shell/src/app/components/permissions/demo-permissions.component.ts @@ -48,9 +48,11 @@ export class DemoPermissionComponent implements OnInit { } }); } - this.nodeService.getNode(this.nodeId, {include: ['permissions'] }).subscribe( (currentNode: MinimalNodeEntryEntity) => { - this.toggleStatus = currentNode.permissions.isInheritanceEnabled; - }); + this.nodeService + .getNode(this.nodeId, {include: ['permissions'] }) + .subscribe( (currentNode: MinimalNodeEntryEntity) => { + this.toggleStatus = currentNode.permissions.isInheritanceEnabled; + }); } onUpdatedPermissions(node: MinimalNodeEntryEntity) { @@ -63,9 +65,12 @@ export class DemoPermissionComponent implements OnInit { } openAddPermissionDialog(event: Event) { - this.nodePermissionDialogService.updateNodePermissionByDialog(this.nodeId).subscribe( - () => this.displayPermissionComponent.reload(), - (error) => this.showErrorMessage(error)); + this.nodePermissionDialogService + .updateNodePermissionByDialog(this.nodeId) + .subscribe( + () => this.displayPermissionComponent.reload(), + (error) => this.showErrorMessage(error) + ); } showErrorMessage(error) { diff --git a/demo-shell/src/app/components/process-list-demo/process-list-demo.component.ts b/demo-shell/src/app/components/process-list-demo/process-list-demo.component.ts index 76d46f6892..518fbca260 100644 --- a/demo-shell/src/app/components/process-list-demo/process-list-demo.component.ts +++ b/demo-shell/src/app/components/process-list-demo/process-list-demo.component.ts @@ -15,17 +15,18 @@ * limitations under the License. */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormGroup, FormBuilder, Validators, FormControl, AbstractControl } from '@angular/forms'; import { ActivatedRoute, Params } from '@angular/router'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; @Component({ templateUrl: './process-list-demo.component.html', styleUrls: [`./process-list-demo.component.scss`] }) -export class ProcessListDemoComponent implements OnInit { +export class ProcessListDemoComponent implements OnInit, OnDestroy { DEFAULT_SIZE = 20; @@ -54,6 +55,8 @@ export class ProcessListDemoComponent implements OnInit { {value: 'created-desc', title: 'Created (desc)'} ]; + private onDestroy$ = new Subject(); + constructor(private route: ActivatedRoute, private formBuilder: FormBuilder) { } @@ -72,6 +75,11 @@ export class ProcessListDemoComponent implements OnInit { this.buildForm(); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + buildForm() { this.processListForm = this.formBuilder.group({ processAppId: new FormControl(this.appId, [Validators.pattern('^[0-9]*$'), Validators.min(this.minValue)]), @@ -84,10 +92,9 @@ export class ProcessListDemoComponent implements OnInit { }); this.processListForm.valueChanges - .pipe( - debounceTime(500) - ) - .subscribe((processFilter) => { + .pipe(takeUntil(this.onDestroy$)) + .pipe(debounceTime(500)) + .subscribe(processFilter => { if (this.isFormValid()) { this.filterProcesses(processFilter); } diff --git a/demo-shell/src/app/components/process-service/form-node-viewer.component.ts b/demo-shell/src/app/components/process-service/form-node-viewer.component.ts index 9311e344cb..245dd835d0 100644 --- a/demo-shell/src/app/components/process-service/form-node-viewer.component.ts +++ b/demo-shell/src/app/components/process-service/form-node-viewer.component.ts @@ -15,32 +15,25 @@ * limitations under the License. */ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; @Component({ selector: 'app-form-node-viewer', templateUrl: './form-node-viewer.component.html', styleUrls: ['./form-node-viewer.component.css'] }) -export class FormNodeViewerComponent implements OnInit, OnDestroy { +export class FormNodeViewerComponent implements OnInit { nodeId: string; - private sub: Subscription; - constructor(private route: ActivatedRoute) { } ngOnInit() { - this.sub = this.route.params.subscribe((params) => { + this.route.params.subscribe((params) => { this.nodeId = params['id']; }); } - ngOnDestroy() { - this.sub.unsubscribe(); - } - } diff --git a/demo-shell/src/app/components/process-service/form-viewer.component.ts b/demo-shell/src/app/components/process-service/form-viewer.component.ts index b2d1e8f61f..496dcb2993 100644 --- a/demo-shell/src/app/components/process-service/form-viewer.component.ts +++ b/demo-shell/src/app/components/process-service/form-viewer.component.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; import { Params } from '@angular/router/src/shared'; @Component({ @@ -25,23 +24,16 @@ import { Params } from '@angular/router/src/shared'; templateUrl: './form-viewer.component.html', styleUrls: ['./form-viewer.component.css'] }) -export class FormViewerComponent implements OnInit, OnDestroy { +export class FormViewerComponent implements OnInit { taskId: string; - private sub: Subscription; - constructor(private route: ActivatedRoute) { } ngOnInit() { - this.sub = this.route.params.subscribe((params: Params) => { + this.route.params.subscribe((params: Params) => { this.taskId = params['id']; }); } - - ngOnDestroy() { - this.sub.unsubscribe(); - } - } diff --git a/demo-shell/src/app/components/process-service/process-attachments.component.ts b/demo-shell/src/app/components/process-service/process-attachments.component.ts index 7012cedf56..f79897f07a 100644 --- a/demo-shell/src/app/components/process-service/process-attachments.component.ts +++ b/demo-shell/src/app/components/process-service/process-attachments.component.ts @@ -22,7 +22,8 @@ import { UploadService } from '@alfresco/adf-core'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { AppConfigService } from '@alfresco/adf-core'; import { PreviewService } from '../../services/preview.service'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; export function processUploadServiceFactory(api: AlfrescoApiService, config: AppConfigService) { return new ProcessUploadService(api, config); @@ -51,7 +52,7 @@ export class ProcessAttachmentsComponent implements OnInit, OnChanges, OnDestroy processInstance: ProcessInstance; - private subscriptions: Subscription[] = []; + private onDestroy$ = new Subject(); constructor( private uploadService: UploadService, @@ -60,11 +61,9 @@ export class ProcessAttachmentsComponent implements OnInit, OnChanges, OnDestroy ) {} ngOnInit() { - this.subscriptions.push( - this.uploadService.fileUploadComplete.subscribe( - (value) => this.onFileUploadComplete(value.data) - ) - ); + this.uploadService.fileUploadComplete + .pipe(takeUntil(this.onDestroy$)) + .subscribe(value => this.onFileUploadComplete(value.data)); } ngOnChanges() { @@ -77,8 +76,8 @@ export class ProcessAttachmentsComponent implements OnInit, OnChanges, OnDestroy } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onFileUploadComplete(content: any) { diff --git a/demo-shell/src/app/components/process-service/process-service.component.ts b/demo-shell/src/app/components/process-service/process-service.component.ts index 1d94f49d57..46c45a065e 100644 --- a/demo-shell/src/app/components/process-service/process-service.component.ts +++ b/demo-shell/src/app/components/process-service/process-service.component.ts @@ -35,7 +35,7 @@ import { UserProcessInstanceFilterRepresentation } from '@alfresco/js-api'; import { - FORM_FIELD_VALIDATORS, FormEvent, FormFieldEvent, FormRenderingService, FormService, + FORM_FIELD_VALIDATORS, FormRenderingService, FormService, DynamicTableRow, ValidateDynamicTableRowEvent, AppConfigService, PaginationComponent, UserPreferenceValues } from '@alfresco/adf-core'; @@ -57,12 +57,13 @@ import { TaskListComponent } from '@alfresco/adf-process-services'; import { LogService } from '@alfresco/adf-core'; -import { AlfrescoApiService, UserPreferencesService, ValidateFormEvent } from '@alfresco/adf-core'; -import { Subscription } from 'rxjs'; +import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; +import { Subject } from 'rxjs'; import { /*CustomEditorComponent*/ CustomStencil01 } from './custom-editor/custom-editor.component'; import { DemoFieldValidator } from './demo-field-validator'; import { PreviewService } from '../../services/preview.service'; import { Location } from '@angular/common'; +import { takeUntil } from 'rxjs/operators'; const currentProcessIdNew = '__NEW__'; const currentTaskIdNew = '__NEW__'; @@ -160,7 +161,7 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit new DemoFieldValidator() ]; - private subscriptions: Subscription[] = []; + private onDestroy$ = new Subject(); constructor(private elementRef: ElementRef, private route: ActivatedRoute, @@ -184,17 +185,28 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit // Uncomment this line to map 'custom_stencil_01' to local editor component formRenderingService.setComponentTypeResolver('custom_stencil_01', () => CustomStencil01, true); - this.subscriptions.push( - formService.formLoaded.subscribe((formEvent: FormEvent) => { + formService.formLoaded + .pipe(takeUntil(this.onDestroy$)) + .subscribe(formEvent => { this.logService.log(`Form loaded: ${formEvent.form.id}`); - }), - formService.formFieldValueChanged.subscribe((formFieldEvent: FormFieldEvent) => { + }); + + formService.formFieldValueChanged + .pipe(takeUntil(this.onDestroy$)) + .subscribe(formFieldEvent => { this.logService.log(`Field value changed. Form: ${formFieldEvent.form.id}, Field: ${formFieldEvent.field.id}, Value: ${formFieldEvent.field.value}`); - }), - this.preferenceService.select(UserPreferenceValues.PaginationSize).subscribe((pageSize) => { + }); + + this.preferenceService + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((pageSize) => { this.paginationPageSize = pageSize; - }), - formService.validateDynamicTableRow.subscribe( + }); + + formService.validateDynamicTableRow + .pipe(takeUntil(this.onDestroy$)) + .subscribe( (validateDynamicTableRowEvent: ValidateDynamicTableRowEvent) => { const row: DynamicTableRow = validateDynamicTableRowEvent.row; if (row && row.value && row.value.name === 'admin') { @@ -203,23 +215,28 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit validateDynamicTableRowEvent.preventDefault(); } } - ), + ); - formService.formContentClicked.subscribe((content) => { + formService.formContentClicked + .pipe(takeUntil(this.onDestroy$)) + .subscribe((content) => { this.showContentPreview(content); - }), + }); - formService.validateForm.subscribe((validateFormEvent: ValidateFormEvent) => { + formService.validateForm + .pipe(takeUntil(this.onDestroy$)) + .subscribe(validateFormEvent => { this.logService.log('Error form:' + validateFormEvent.errorsField); - }) - ); + }); // Uncomment this block to see form event handling in action /* - formService.formEvents.subscribe((event: Event) => { - this.logService.log('Event fired:' + event.type); - this.logService.log('Event Target:' + event.target); - }); + formService.formEvents + .pipe(takeUntil(this.onDestroy$)) + .subscribe((event: Event) => { + this.logService.log('Event fired:' + event.type); + this.logService.log('Event Target:' + event.target); + }); */ } @@ -247,8 +264,8 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onTaskFilterClick(filter: FilterRepresentationModel): void { diff --git a/demo-shell/src/app/components/process-service/task-attachments.component.ts b/demo-shell/src/app/components/process-service/task-attachments.component.ts index f235452b58..47a114d697 100644 --- a/demo-shell/src/app/components/process-service/task-attachments.component.ts +++ b/demo-shell/src/app/components/process-service/task-attachments.component.ts @@ -19,12 +19,13 @@ import { Component, Input, OnChanges, OnInit, ViewChild, OnDestroy } from '@angu import { TaskListService, TaskAttachmentListComponent, - TaskDetailsModel, - TaskUploadService + TaskUploadService, + TaskDetailsModel } from '@alfresco/adf-process-services'; -import { UploadService, AlfrescoApiService, AppConfigService, FileUploadCompleteEvent } from '@alfresco/adf-core'; +import { UploadService, AlfrescoApiService, AppConfigService } from '@alfresco/adf-core'; import { PreviewService } from '../../services/preview.service'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; export function taskUploadServiceFactory(api: AlfrescoApiService, config: AppConfigService) { return new TaskUploadService(api, config); @@ -51,9 +52,9 @@ export class TaskAttachmentsComponent implements OnInit, OnChanges, OnDestroy { @Input() taskId: string; - taskDetails: any; + taskDetails: TaskDetailsModel; - private subscriptions: Subscription[] = []; + private onDestroy$ = new Subject(); constructor( private uploadService: UploadService, @@ -62,25 +63,22 @@ export class TaskAttachmentsComponent implements OnInit, OnChanges, OnDestroy { } ngOnInit() { - this.subscriptions.push( - this.uploadService.fileUploadComplete.subscribe( - (fileUploadCompleteEvent: FileUploadCompleteEvent) => this.onFileUploadComplete(fileUploadCompleteEvent.data) - ) - ); + this.uploadService.fileUploadComplete + .pipe(takeUntil(this.onDestroy$)) + .subscribe(event => this.onFileUploadComplete(event.data)); } ngOnChanges() { if (this.taskId) { - this.activitiTaskList.getTaskDetails(this.taskId) - .subscribe((taskDetails: TaskDetailsModel) => { - this.taskDetails = taskDetails; - }); + this.activitiTaskList + .getTaskDetails(this.taskId) + .subscribe(taskDetails => this.taskDetails = taskDetails); } } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onFileUploadComplete(content: any) { diff --git a/demo-shell/src/app/components/search/search-result.component.ts b/demo-shell/src/app/components/search/search-result.component.ts index 075a9f64a3..13f0870ec4 100644 --- a/demo-shell/src/app/components/search/search-result.component.ts +++ b/demo-shell/src/app/components/search/search-result.component.ts @@ -20,7 +20,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { NodePaging, Pagination, ResultSetPaging } from '@alfresco/js-api'; import { SearchQueryBuilderService } from '@alfresco/adf-content-services'; import { UserPreferencesService, SearchService, AppConfigService } from '@alfresco/adf-core'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-search-result-component', @@ -38,7 +39,7 @@ export class SearchResultComponent implements OnInit, OnDestroy { sorting = ['name', 'asc']; - private subscriptions: Subscription[] = []; + private onDestroy$ = new Subject(); constructor(public router: Router, private config: AppConfigService, @@ -55,19 +56,21 @@ export class SearchResultComponent implements OnInit, OnDestroy { this.sorting = this.getSorting(); - this.subscriptions.push( - this.queryBuilder.updated.subscribe(() => { + this.queryBuilder.updated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { this.sorting = this.getSorting(); this.isLoading = true; - }), + }); - this.queryBuilder.executed.subscribe((resultSetPaging: ResultSetPaging) => { + this.queryBuilder.executed + .pipe(takeUntil(this.onDestroy$)) + .subscribe((resultSetPaging: ResultSetPaging) => { this.queryBuilder.paging.skipCount = 0; this.onSearchResultLoaded(resultSetPaging); this.isLoading = false; - }) - ); + }); if (this.route) { this.route.params.forEach((params: Params) => { @@ -102,8 +105,8 @@ export class SearchResultComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onSearchResultLoaded(resultSetPaging: ResultSetPaging) { diff --git a/demo-shell/src/app/components/task-list-demo/task-list-demo.component.ts b/demo-shell/src/app/components/task-list-demo/task-list-demo.component.ts index d93624ed7f..061bafae13 100644 --- a/demo-shell/src/app/components/task-list-demo/task-list-demo.component.ts +++ b/demo-shell/src/app/components/task-list-demo/task-list-demo.component.ts @@ -15,11 +15,12 @@ * limitations under the License. */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormGroup, FormBuilder, Validators, FormControl, AbstractControl } from '@angular/forms'; import { ActivatedRoute, Params } from '@angular/router'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, takeUntil } from 'rxjs/operators'; import moment from 'moment-es6'; +import { Subject } from 'rxjs'; @Component({ selector: 'app-task-list-demo', @@ -27,7 +28,7 @@ import moment from 'moment-es6'; styleUrls: [`./task-list-demo.component.scss`] }) -export class TaskListDemoComponent implements OnInit { +export class TaskListDemoComponent implements OnInit, OnDestroy { DEFAULT_SIZE = 20; taskListForm: FormGroup; @@ -75,6 +76,8 @@ export class TaskListDemoComponent implements OnInit { {value: 'due-desc', title: 'Due (desc)'} ]; + private onDestroy$ = new Subject(); + constructor(private route: ActivatedRoute, private formBuilder: FormBuilder) { } @@ -94,6 +97,11 @@ export class TaskListDemoComponent implements OnInit { this.buildForm(); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + buildForm() { this.taskListForm = this.formBuilder.group({ taskAppId: new FormControl(this.defaultAppId, [Validators.pattern('^[0-9]*$')]), @@ -114,9 +122,10 @@ export class TaskListDemoComponent implements OnInit { this.taskListForm.valueChanges .pipe( - debounceTime(500) + debounceTime(500), + takeUntil(this.onDestroy$) ) - .subscribe((taskFilter) => { + .subscribe(taskFilter => { if (this.isFormValid()) { this.filterTasks(taskFilter); } diff --git a/demo-shell/src/app/components/trashcan/trashcan.component.ts b/demo-shell/src/app/components/trashcan/trashcan.component.ts index 7183417bb4..e94f3794ea 100644 --- a/demo-shell/src/app/components/trashcan/trashcan.component.ts +++ b/demo-shell/src/app/components/trashcan/trashcan.component.ts @@ -15,32 +15,42 @@ * limitations under the License. */ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { DocumentListComponent } from '@alfresco/adf-content-services'; import { UserPreferencesService, UserPreferenceValues, RestoreMessageModel, NotificationService } from '@alfresco/adf-core'; import { Router } from '@angular/router'; import { PathInfoEntity } from '@alfresco/js-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ templateUrl: './trashcan.component.html', styleUrls: ['trashcan.component.scss'] }) -export class TrashcanComponent { +export class TrashcanComponent implements OnInit, OnDestroy { @ViewChild('documentList') documentList: DocumentListComponent; currentLocale; + private onDestroy$ = new Subject(); + constructor( private preference: UserPreferencesService, private router: Router, - private notificationService: NotificationService - ) { + private notificationService: NotificationService) { + } + + ngOnInit() { this.preference .select(UserPreferenceValues.Locale) - .subscribe((locale) => { - this.currentLocale = locale; - }); + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.currentLocale = locale); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } onRestore(restoreMessage: RestoreMessageModel) { diff --git a/lib/content-services/breadcrumb/breadcrumb.component.spec.ts b/lib/content-services/breadcrumb/breadcrumb.component.spec.ts index 7cf8667b68..42c6096cca 100644 --- a/lib/content-services/breadcrumb/breadcrumb.component.spec.ts +++ b/lib/content-services/breadcrumb/breadcrumb.component.spec.ts @@ -58,7 +58,7 @@ describe('Breadcrumb', () => { it('should root be present as default node if the path is null', () => { component.root = 'default'; component.folderNode = fakeNodeWithCreatePermission; - component.ngOnChanges(null); + component.ngOnChanges(); expect(component.route[0].name).toBe('default'); }); @@ -215,7 +215,7 @@ describe('Breadcrumb', () => { return transformNode; }); component.folderNode = node; - component.ngOnChanges(null); + component.ngOnChanges(); expect(component.route.length).toBe(4); expect(component.route[3].id).toBe('test-id'); expect(component.route[3].name).toBe('test-name'); diff --git a/lib/content-services/breadcrumb/breadcrumb.component.ts b/lib/content-services/breadcrumb/breadcrumb.component.ts index f152ac87b7..e7f7134934 100644 --- a/lib/content-services/breadcrumb/breadcrumb.component.ts +++ b/lib/content-services/breadcrumb/breadcrumb.component.ts @@ -22,13 +22,15 @@ import { OnChanges, OnInit, Output, - SimpleChanges, ViewChild, - ViewEncapsulation + ViewEncapsulation, + OnDestroy } from '@angular/core'; import { MatSelect } from '@angular/material'; import { Node, PathElementEntity } from '@alfresco/js-api'; import { DocumentListComponent } from '../document-list'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-breadcrumb', @@ -39,7 +41,7 @@ import { DocumentListComponent } from '../document-list'; 'class': 'adf-breadcrumb' } }) -export class BreadcrumbComponent implements OnInit, OnChanges { +export class BreadcrumbComponent implements OnInit, OnChanges, OnDestroy { /** Active node, builds UI based on folderNode.path.elements collection. */ @Input() @@ -84,6 +86,8 @@ export class BreadcrumbComponent implements OnInit, OnChanges { route: PathElementEntity[] = []; + private onDestroy$ = new Subject(); + get hasRoot(): boolean { return !!this.root; } @@ -96,14 +100,16 @@ export class BreadcrumbComponent implements OnInit, OnChanges { this.transform = this.transform ? this.transform : null; if (this.target) { - this.target.$folderNode.subscribe((folderNode: Node) => { - this.folderNode = folderNode; - this.recalculateNodes(); - }); + this.target.$folderNode + .pipe(takeUntil(this.onDestroy$)) + .subscribe((folderNode: Node) => { + this.folderNode = folderNode; + this.recalculateNodes(); + }); } } - ngOnChanges(changes: SimpleChanges): void { + ngOnChanges(): void { this.recalculateNodes(); } @@ -184,4 +190,9 @@ export class BreadcrumbComponent implements OnInit, OnChanges { } } } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } } diff --git a/lib/content-services/breadcrumb/dropdown-breadcrumb.component.spec.ts b/lib/content-services/breadcrumb/dropdown-breadcrumb.component.spec.ts index f9c00a58d1..92c228c2ef 100644 --- a/lib/content-services/breadcrumb/dropdown-breadcrumb.component.spec.ts +++ b/lib/content-services/breadcrumb/dropdown-breadcrumb.component.spec.ts @@ -57,7 +57,7 @@ describe('DropdownBreadcrumb', () => { function triggerComponentChange(fakeNodeData) { component.folderNode = fakeNodeData; - component.ngOnChanges(null); + component.ngOnChanges(); fixture.detectChanges(); } diff --git a/lib/content-services/content-node-selector/content-node-selector-panel.component.ts b/lib/content-services/content-node-selector/content-node-selector-panel.component.ts index 857ff162d2..c548752560 100644 --- a/lib/content-services/content-node-selector/content-node-selector-panel.component.ts +++ b/lib/content-services/content-node-selector/content-node-selector-panel.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation, OnDestroy } from '@angular/core'; import { HighlightDirective, UserPreferencesService, @@ -29,9 +29,10 @@ import { DocumentListComponent } from '../document-list/components/document-list import { RowFilter } from '../document-list/data/row-filter.model'; import { ImageResolver } from '../document-list/data/image-resolver.model'; import { ContentNodeSelectorService } from './content-node-selector.service'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, takeUntil } from 'rxjs/operators'; import { CustomResourcesService } from '../document-list/services/custom-resources.service'; import { ShareDataRow } from '../document-list'; +import { Subject } from 'rxjs'; export type ValidationFunction = (entry: Node) => boolean; @@ -44,7 +45,7 @@ const defaultValidation = () => true; encapsulation: ViewEncapsulation.None, host: { 'class': 'adf-content-node-selector-panel' } }) -export class ContentNodeSelectorPanelComponent implements OnInit { +export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { DEFAULT_PAGINATION: Pagination = new Pagination({ maxItems: 25, @@ -166,21 +167,11 @@ export class ContentNodeSelectorPanelComponent implements OnInit { target: PaginatedComponent; + private onDestroy$ = new Subject(); + constructor(private contentNodeSelectorService: ContentNodeSelectorService, private customResourcesService: CustomResourcesService, private userPreferencesService: UserPreferencesService) { - this.searchInput.valueChanges - .pipe( - debounceTime(this.debounceSearch) - ) - .subscribe((searchValue) => { - this.search(searchValue); - }); - - this.userPreferencesService.select(UserPreferenceValues.PaginationSize).subscribe((pagSize) => { - this.pageSize = pagSize; - }); - } set chosenNode(value: Node) { @@ -197,6 +188,18 @@ export class ContentNodeSelectorPanelComponent implements OnInit { } ngOnInit() { + this.searchInput.valueChanges + .pipe( + debounceTime(this.debounceSearch), + takeUntil(this.onDestroy$) + ) + .subscribe(searchValue => this.search(searchValue)); + + this.userPreferencesService + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pagSize => this.pageSize = pagSize); + this.target = this.documentList; this.folderIdToShow = this.currentFolderId; @@ -204,6 +207,11 @@ export class ContentNodeSelectorPanelComponent implements OnInit { this.isSelectionValid = this.isSelectionValid ? this.isSelectionValid : defaultValidation; } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + private createRowFilter(filter?: RowFilter) { if (!filter) { filter = () => true; diff --git a/lib/content-services/content-node-share/content-node-share.dialog.ts b/lib/content-services/content-node-share/content-node-share.dialog.ts index a13ab1464d..ecd853cd92 100644 --- a/lib/content-services/content-node-share/content-node-share.dialog.ts +++ b/lib/content-services/content-node-share/content-node-share.dialog.ts @@ -25,12 +25,13 @@ import { } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef, MatDialog, MatSlideToggleChange } from '@angular/material'; import { FormGroup, FormControl } from '@angular/forms'; -import { Subscription, Observable, throwError } from 'rxjs'; +import { Observable, throwError, Subject } from 'rxjs'; import { skip, distinctUntilChanged, mergeMap, - catchError + catchError, + takeUntil } from 'rxjs/operators'; import { SharedLinksApiService, @@ -52,7 +53,6 @@ import { ContentNodeShareSettings } from './content-node-share.settings'; encapsulation: ViewEncapsulation.None }) export class ShareDialogComponent implements OnInit, OnDestroy { - private subscriptions: Subscription[] = []; minDate = moment().add(1, 'd'); sharedId: string; @@ -75,6 +75,8 @@ export class ShareDialogComponent implements OnInit, OnDestroy { @ViewChild('dateTimePickerInput') dateTimePickerInput; + private onDestroy$ = new Subject(); + constructor( private appConfigService: AppConfigService, private sharedLinksApiService: SharedLinksApiService, @@ -93,21 +95,20 @@ export class ShareDialogComponent implements OnInit, OnDestroy { this.form.controls['time'].disable(); } - this.subscriptions.push( - this.form.controls.time.valueChanges - .pipe( - skip(1), - distinctUntilChanged(), - mergeMap( - (updates) => this.updateNode(updates), - (formUpdates) => formUpdates - ), - catchError((error) => { - return throwError(error); - }) - ) - .subscribe((updates) => this.updateEntryExpiryDate(updates)) - ); + this.form.controls.time.valueChanges + .pipe( + skip(1), + distinctUntilChanged(), + mergeMap( + (updates) => this.updateNode(updates), + (formUpdates) => formUpdates + ), + catchError((error) => { + return throwError(error); + }), + takeUntil(this.onDestroy$) + ) + .subscribe(updates => this.updateEntryExpiryDate(updates)); if (this.data.node && this.data.node.entry) { this.fileName = this.data.node.entry.name; @@ -126,7 +127,8 @@ export class ShareDialogComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe); + this.onDestroy$.next(true); + this.onDestroy$.complete(); } removeShare() { diff --git a/lib/content-services/content-node-share/content-node-share.directive.ts b/lib/content-services/content-node-share/content-node-share.directive.ts index a6284f325c..c5fc53004c 100644 --- a/lib/content-services/content-node-share/content-node-share.directive.ts +++ b/lib/content-services/content-node-share/content-node-share.directive.ts @@ -15,19 +15,20 @@ * limitations under the License. */ -import { Directive, Input, HostListener, OnChanges, NgZone } from '@angular/core'; +import { Directive, Input, HostListener, OnChanges, NgZone, OnDestroy } from '@angular/core'; import { MatDialog } from '@angular/material'; import { NodeEntry, Node } from '@alfresco/js-api'; import { ShareDialogComponent } from './content-node-share.dialog'; -import { Observable, from } from 'rxjs'; +import { Observable, from, Subject } from 'rxjs'; import { AlfrescoApiService } from '@alfresco/adf-core'; +import { takeUntil } from 'rxjs/operators'; @Directive({ selector: '[adf-share]', exportAs: 'adfShare' }) -export class NodeSharedDirective implements OnChanges { +export class NodeSharedDirective implements OnChanges, OnDestroy { isFile: boolean = false; isShared: boolean = false; @@ -41,12 +42,7 @@ export class NodeSharedDirective implements OnChanges { @Input() baseShareUrl: string; - @HostListener('click') - onClick() { - if (this.node) { - this.shareNode(this.node); - } - } + private onDestroy$ = new Subject(); constructor( private dialog: MatDialog, @@ -54,6 +50,11 @@ export class NodeSharedDirective implements OnChanges { private alfrescoApiService: AlfrescoApiService) { } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + shareNode(nodeEntry: NodeEntry) { if (nodeEntry && nodeEntry.entry && nodeEntry.entry.isFile) { // shared and favorite @@ -89,11 +90,20 @@ export class NodeSharedDirective implements OnChanges { } ngOnChanges() { - this.zone.onStable.subscribe(() => { - if (this.node && this.node.entry) { - this.isFile = this.node.entry.isFile; - this.isShared = this.node.entry.properties ? this.node.entry.properties['qshare:sharedId'] : false; - } - }); + this.zone.onStable + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { + if (this.node && this.node.entry) { + this.isFile = this.node.entry.isFile; + this.isShared = this.node.entry.properties ? this.node.entry.properties['qshare:sharedId'] : false; + } + }); + } + + @HostListener('click') + onClick() { + if (this.node) { + this.shareNode(this.node); + } } } diff --git a/lib/content-services/document-list/components/document-list.component.ts b/lib/content-services/document-list/components/document-list.component.ts index de19441c90..2dc54c8b5c 100644 --- a/lib/content-services/document-list/components/document-list.component.ts +++ b/lib/content-services/document-list/components/document-list.component.ts @@ -47,7 +47,7 @@ import { } from '@alfresco/adf-core'; import { Node, NodeEntry, NodePaging, Pagination } from '@alfresco/js-api'; -import { Subject, BehaviorSubject, Subscription, of } from 'rxjs'; +import { Subject, BehaviorSubject, of } from 'rxjs'; import { ShareDataRow } from './../data/share-data-row.model'; import { ShareDataTableAdapter } from './../data/share-datatable-adapter'; import { presetsDefaultModel } from '../models/preset.model'; @@ -58,6 +58,7 @@ import { NavigableComponentInterface } from '../../breadcrumb/navigable-componen import { RowFilter } from '../data/row-filter.model'; import { DocumentListService } from '../services/document-list.service'; import { DocumentLoaderNode } from '../models/document-folder.model'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-document-list', @@ -314,9 +315,9 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte pagination: BehaviorSubject = new BehaviorSubject(this.DEFAULT_PAGINATION); private layoutPresets = {}; - private subscriptions: Subscription[] = []; private rowMenuCache: { [key: string]: ContentActionModel[] } = {}; private loadingTimeout; + private onDestroy$ = new Subject(); constructor(private documentListService: DocumentListService, private ngZone: NgZone, @@ -327,10 +328,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte private thumbnailService: ThumbnailService, private alfrescoApiService: AlfrescoApiService, private lockService: LockService) { - - this.userPreferencesService.select(UserPreferenceValues.PaginationSize).subscribe((pagSize) => { - this.maxItems = this._pagination.maxItems = pagSize; - }); } getContextActions(node: NodeEntry) { @@ -375,6 +372,13 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte } ngOnInit() { + this.userPreferencesService + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pagSize => { + this.maxItems = this._pagination.maxItems = pagSize; + }); + this.rowMenuCache = {}; this.loadLayoutPresets(); this.data = new ShareDataTableAdapter(this.thumbnailService, this.contentService, null, this.getDefaultSorting(), this.sortingMode); @@ -389,20 +393,18 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte this.data.setImageResolver(this.imageResolver); } - this.subscriptions.push( - this.contextActionHandler.subscribe((val) => this.contextActionCallback(val)) - ); + this.contextActionHandler + .pipe(takeUntil(this.onDestroy$)) + .subscribe(val => this.contextActionCallback(val)); this.enforceSingleClickNavigationForMobile(); } ngAfterContentInit() { if (this.columnList) { - this.subscriptions.push( - this.columnList.columns.changes.subscribe(() => { - this.setTableSchema(); - }) - ); + this.columnList.columns.changes + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => this.setTableSchema()); } this.setTableSchema(); } @@ -598,9 +600,9 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte } if (typeof action.execute === 'function' && handlerSub) { - handlerSub.subscribe(() => { - action.execute(node); - }); + handlerSub + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => action.execute(node)); } } } @@ -834,8 +836,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte } ngOnDestroy() { - this.subscriptions.forEach((s) => s.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } private handleError(err: any) { diff --git a/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts b/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts index 1ff690aa88..5b1de0d0a3 100644 --- a/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts +++ b/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts @@ -24,10 +24,11 @@ import { ElementRef, OnDestroy } from '@angular/core'; -import { NodeEntry, Node, Site } from '@alfresco/js-api'; +import { NodeEntry, Site } from '@alfresco/js-api'; import { ShareDataRow } from '../../data/share-data-row.model'; import { AlfrescoApiService } from '@alfresco/adf-core'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-library-name-column', @@ -50,7 +51,7 @@ export class LibraryNameColumnComponent implements OnInit, OnDestroy { displayText$ = new BehaviorSubject(''); node: NodeEntry; - private sub: Subscription; + private onDestroy$ = new Subject(); constructor( private element: ElementRef, @@ -60,8 +61,9 @@ export class LibraryNameColumnComponent implements OnInit, OnDestroy { ngOnInit() { this.updateValue(); - this.sub = this.alfrescoApiService.nodeUpdated.subscribe( - (node: Node) => { + this.alfrescoApiService.nodeUpdated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(node => { const row: ShareDataRow = this.context.row; if (row) { const { entry } = row.node; @@ -71,8 +73,7 @@ export class LibraryNameColumnComponent implements OnInit, OnDestroy { this.updateValue(); } } - } - ); + }); } protected updateValue() { @@ -119,9 +120,7 @@ export class LibraryNameColumnComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - this.sub = null; - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts b/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts index a7087dc218..4afae95c87 100644 --- a/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts +++ b/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts @@ -23,10 +23,11 @@ import { ViewEncapsulation, OnDestroy } from '@angular/core'; -import { Subscription, BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { AlfrescoApiService } from '@alfresco/adf-core'; -import { Node, SiteEntry, Site } from '@alfresco/js-api'; +import { SiteEntry, Site } from '@alfresco/js-api'; import { ShareDataRow } from '../../data/share-data-row.model'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-library-role-column', @@ -45,24 +46,26 @@ export class LibraryRoleColumnComponent implements OnInit, OnDestroy { displayText$ = new BehaviorSubject(''); - private sub: Subscription; + private onDestroy$ = new Subject(); constructor(private api: AlfrescoApiService) {} ngOnInit() { this.updateValue(); - this.sub = this.api.nodeUpdated.subscribe((node: Node) => { - const row: ShareDataRow = this.context.row; - if (row) { - const { entry } = row.node; + this.api.nodeUpdated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(node => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; - if (entry === node) { - row.node = { entry }; - this.updateValue(); + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } } - } - }); + }); } protected updateValue() { @@ -90,9 +93,7 @@ export class LibraryRoleColumnComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - this.sub = null; - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts b/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts index b645884b29..935bb0ac0a 100644 --- a/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts +++ b/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts @@ -17,9 +17,10 @@ import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { AlfrescoApiService } from '@alfresco/adf-core'; -import { Subscription, BehaviorSubject } from 'rxjs'; -import { Node, Site, SiteEntry } from '@alfresco/js-api'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { Site, SiteEntry } from '@alfresco/js-api'; import { ShareDataRow } from '../../data/share-data-row.model'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-library-status-column', @@ -36,24 +37,26 @@ export class LibraryStatusColumnComponent implements OnInit, OnDestroy { displayText$ = new BehaviorSubject(''); - private sub: Subscription; + private onDestroy$ = new Subject(); constructor(private api: AlfrescoApiService) {} ngOnInit() { this.updateValue(); - this.sub = this.api.nodeUpdated.subscribe((node: Node) => { - const row: ShareDataRow = this.context.row; - if (row) { - const { entry } = row.node; + this.api.nodeUpdated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(node => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; - if (entry === node) { - row.node = { entry }; - this.updateValue(); + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } } - } - }); + }); } protected updateValue() { @@ -79,9 +82,7 @@ export class LibraryStatusColumnComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - this.sub = null; - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/content-services/document-list/components/name-column/name-column.component.ts b/lib/content-services/document-list/components/name-column/name-column.component.ts index 144543afa5..67419b66e3 100644 --- a/lib/content-services/document-list/components/name-column/name-column.component.ts +++ b/lib/content-services/document-list/components/name-column/name-column.component.ts @@ -25,10 +25,10 @@ import { OnDestroy } from '@angular/core'; import { NodeEntry } from '@alfresco/js-api'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { AlfrescoApiService } from '@alfresco/adf-core'; -import { Node } from '@alfresco/js-api'; import { ShareDataRow } from '../../data/share-data-row.model'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-name-column', @@ -48,24 +48,26 @@ export class NameColumnComponent implements OnInit, OnDestroy { displayText$ = new BehaviorSubject(''); node: NodeEntry; - private sub: Subscription; + private onDestroy$ = new Subject(); constructor(private element: ElementRef, private alfrescoApiService: AlfrescoApiService) {} ngOnInit() { this.updateValue(); - this.sub = this.alfrescoApiService.nodeUpdated.subscribe((node: Node) => { - const row: ShareDataRow = this.context.row; - if (row) { - const { entry } = row.node; + this.alfrescoApiService.nodeUpdated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(node => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; - if (entry === node) { - row.node = { entry }; - this.updateValue(); + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } } - } - }); + }); } protected updateValue() { @@ -88,9 +90,7 @@ export class NameColumnComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - this.sub = null; - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts b/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts index fc3f0380cc..527b0ef831 100644 --- a/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts +++ b/lib/content-services/permission-manager/components/permission-list/permission-list.component.ts @@ -89,7 +89,7 @@ export class PermissionListComponent implements OnInit { saveNewRole(event: any, permissionRow: PermissionDisplayModel) { const updatedPermissionRole: PermissionElement = this.buildUpdatedPermission(event.value, permissionRow); this.nodePermissionService.updatePermissionRole(this.actualNode, updatedPermissionRole) - .subscribe((node: Node) => { + .subscribe(() => { this.update.emit(updatedPermissionRole); }); } @@ -103,9 +103,12 @@ export class PermissionListComponent implements OnInit { } removePermission(permissionRow: PermissionDisplayModel) { - this.nodePermissionService.removePermission(this.actualNode, permissionRow).subscribe((node) => { - this.update.emit(node); - }, (error) => this.error.emit(error)); + this.nodePermissionService + .removePermission(this.actualNode, permissionRow) + .subscribe( + node => this.update.emit(node), + error => this.error.emit(error) + ); } } diff --git a/lib/content-services/search/components/search-control.component.ts b/lib/content-services/search/components/search-control.component.ts index c5b8a00268..2c26c9e5be 100644 --- a/lib/content-services/search/components/search-control.component.ts +++ b/lib/content-services/search/components/search-control.component.ts @@ -108,19 +108,24 @@ export class SearchControlComponent implements OnInit, OnDestroy { private userPreferencesService: UserPreferencesService ) { - this.toggleSearch.asObservable().pipe(debounceTime(200)).subscribe(() => { - if (this.expandable) { - this.subscriptAnimationState = this.toggleAnimation(); + this.toggleSearch + .pipe( + debounceTime(200), + takeUntil(this.onDestroy$) + ) + .subscribe(() => { + if (this.expandable) { + this.subscriptAnimationState = this.toggleAnimation(); - if (this.subscriptAnimationState.value === 'inactive') { - this.searchTerm = ''; - this.searchAutocomplete.resetResults(); - if ( document.activeElement.id === this.searchInput.nativeElement.id) { - this.searchInput.nativeElement.blur(); + if (this.subscriptAnimationState.value === 'inactive') { + this.searchTerm = ''; + this.searchAutocomplete.resetResults(); + if ( document.activeElement.id === this.searchInput.nativeElement.id) { + this.searchInput.nativeElement.blur(); + } } } - } - }); + }); } applySearchFocus(animationDoneEvent) { @@ -252,12 +257,12 @@ export class SearchControlComponent implements OnInit, OnDestroy { private setupFocusEventHandlers() { const focusEvents: Observable = this.focusSubject - .asObservable() .pipe( debounceTime(50), filter(($event: any) => { return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout'); - }) + }), + takeUntil(this.onDestroy$) ); focusEvents.subscribe(() => { diff --git a/lib/content-services/search/components/search-date-range/search-date-range.component.ts b/lib/content-services/search/components/search-date-range/search-date-range.component.ts index dc76ec6a3f..4753873498 100644 --- a/lib/content-services/search/components/search-date-range/search-date-range.component.ts +++ b/lib/content-services/search/components/search-date-range/search-date-range.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { OnInit, Component, ViewEncapsulation } from '@angular/core'; +import { OnInit, Component, ViewEncapsulation, OnDestroy } from '@angular/core'; import { FormControl, Validators, FormGroup } from '@angular/forms'; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; import { MomentDateAdapter, MOMENT_DATE_FORMATS } from '@alfresco/adf-core'; @@ -26,6 +26,8 @@ import { SearchQueryBuilderService } from '../../search-query-builder.service'; import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher'; import { Moment } from 'moment'; import { UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; declare let moment: any; @@ -42,7 +44,7 @@ const DEFAULT_FORMAT_DATE: string = 'DD/MM/YYYY'; encapsulation: ViewEncapsulation.None, host: { class: 'adf-search-date-range' } }) -export class SearchDateRangeComponent implements SearchWidget, OnInit { +export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy { from: FormControl; to: FormControl; @@ -56,6 +58,8 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit { maxDate: any; datePickerDateFormat = DEFAULT_FORMAT_DATE; + private onDestroy$ = new Subject(); + constructor(private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) { } @@ -82,9 +86,10 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit { const theCustomDateAdapter = this.dateAdapter; theCustomDateAdapter.overrideDisplayFormat = this.datePickerDateFormat; - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.setLocale(locale)); const validators = Validators.compose([ Validators.required @@ -101,6 +106,11 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit { this.maxDate = this.dateAdapter.today().startOf('day'); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + apply(model: { from: string, to: string }, isValid: boolean) { if (isValid && this.id && this.context && this.settings && this.settings.field) { const start = moment(model.from).startOf('day').format(); diff --git a/lib/content-services/search/components/search-filter/search-filter.component.ts b/lib/content-services/search/components/search-filter/search-filter.component.ts index b41762b6ff..46d0bd3d76 100644 --- a/lib/content-services/search/components/search-filter/search-filter.component.ts +++ b/lib/content-services/search/components/search-filter/search-filter.component.ts @@ -22,8 +22,9 @@ import { SearchQueryBuilderService } from '../../search-query-builder.service'; import { FacetFieldBucket } from '../../facet-field-bucket.interface'; import { FacetField } from '../../facet-field.interface'; import { SearchFilterList } from './models/search-filter-list.model'; -import { takeWhile } from 'rxjs/operators'; -import { ResultSetPaging, GenericBucket, GenericFacetResponse, ResultSetContext } from '@alfresco/js-api'; +import { takeUntil } from 'rxjs/operators'; +import { GenericBucket, GenericFacetResponse, ResultSetContext } from '@alfresco/js-api'; +import { Subject } from 'rxjs'; @Component({ selector: 'adf-search-filter', @@ -36,8 +37,6 @@ export class SearchFilterComponent implements OnInit, OnDestroy { private DEFAULT_PAGE_SIZE = 5; - isAlive = true; - /** All facet field items to be displayed in the component. These are updated according to the response. * When a new search is performed, the already existing items are updated with the new bucket count values and * the newly received items are added to the responseFacets. @@ -52,6 +51,8 @@ export class SearchFilterComponent implements OnInit, OnDestroy { displayResetButton: boolean; selectedBuckets: Array<{ field: FacetField, bucket: FacetFieldBucket }> = []; + private onDestroy$ = new Subject(); + constructor(public queryBuilder: SearchQueryBuilderService, private searchService: SearchService, private translationService: TranslationService) { @@ -68,26 +69,25 @@ export class SearchFilterComponent implements OnInit, OnDestroy { } this.displayResetButton = this.queryBuilder.config && !!this.queryBuilder.config.resetButton; - this.queryBuilder.updated.pipe( - takeWhile(() => this.isAlive) - ).subscribe(() => { - this.queryBuilder.execute(); - }); + this.queryBuilder.updated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => this.queryBuilder.execute()); } ngOnInit() { if (this.queryBuilder) { - this.queryBuilder.executed.pipe( - takeWhile(() => this.isAlive) - ).subscribe((resultSetPaging: ResultSetPaging) => { - this.onDataLoaded(resultSetPaging); - this.searchService.dataLoaded.next(resultSetPaging); - }); + this.queryBuilder.executed + .pipe(takeUntil(this.onDestroy$)) + .subscribe(resultSetPaging => { + this.onDataLoaded(resultSetPaging); + this.searchService.dataLoaded.next(resultSetPaging); + }); } } ngOnDestroy() { - this.isAlive = false; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } private updateSelectedBuckets() { diff --git a/lib/content-services/search/components/search.component.ts b/lib/content-services/search/components/search.component.ts index f9bed56d0f..c8f11138f6 100644 --- a/lib/content-services/search/components/search.component.ts +++ b/lib/content-services/search/components/search.component.ts @@ -27,11 +27,12 @@ import { Output, TemplateRef, ViewChild, - ViewEncapsulation + ViewEncapsulation, + OnDestroy } from '@angular/core'; import { NodePaging } from '@alfresco/js-api'; import { Subject } from 'rxjs'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-search', @@ -44,7 +45,7 @@ import { debounceTime } from 'rxjs/operators'; 'class': 'adf-search' } }) -export class SearchComponent implements AfterContentInit, OnChanges { +export class SearchComponent implements AfterContentInit, OnChanges, OnDestroy { @ViewChild('panel') panel: ElementRef; @@ -99,25 +100,27 @@ export class SearchComponent implements AfterContentInit, OnChanges { } _isOpen: boolean = false; - - keyPressedStream: Subject = new Subject(); - + keyPressedStream = new Subject(); _classList: { [key: string]: boolean } = {}; + private onDestroy$ = new Subject(); constructor(private searchService: SearchService, private _elementRef: ElementRef) { - this.keyPressedStream.asObservable() + this.keyPressedStream .pipe( - debounceTime(200) + debounceTime(200), + takeUntil(this.onDestroy$) ) - .subscribe((searchedWord: string) => { + .subscribe(searchedWord => { this.loadSearchResults(searchedWord); }); - searchService.dataLoaded.subscribe( - (nodePaging: NodePaging) => this.onSearchDataLoaded(nodePaging), - (error) => this.onSearchDataError(error) - ); + searchService.dataLoaded + .pipe(takeUntil(this.onDestroy$)) + .subscribe( + nodePaging => this.onSearchDataLoaded(nodePaging), + error => this.onSearchDataError(error) + ); } ngAfterContentInit() { @@ -130,6 +133,11 @@ export class SearchComponent implements AfterContentInit, OnChanges { } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + resetResults() { this.cleanResults(); this.setVisibility(); diff --git a/lib/content-services/site-dropdown/sites-dropdown.component.ts b/lib/content-services/site-dropdown/sites-dropdown.component.ts index fbc0954949..38b4b06ed4 100644 --- a/lib/content-services/site-dropdown/sites-dropdown.component.ts +++ b/lib/content-services/site-dropdown/sites-dropdown.component.ts @@ -15,10 +15,12 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation, OnDestroy } from '@angular/core'; import { SitesService, LogService } from '@alfresco/adf-core'; import { SitePaging, SiteEntry } from '@alfresco/js-api'; import { MatSelect } from '@angular/material'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; export enum Relations { Members = 'members', @@ -32,7 +34,7 @@ export enum Relations { encapsulation: ViewEncapsulation.None, host: { 'class': 'adf-sites-dropdown' } }) -export class DropdownSitesComponent implements OnInit { +export class DropdownSitesComponent implements OnInit, OnDestroy { /** Hide the "My Files" option. */ @Input() @@ -77,27 +79,34 @@ export class DropdownSitesComponent implements OnInit { private readonly MAX_ITEMS = 50; private readonly ITEM_HEIGHT = 45; private readonly ITEM_HEIGHT_TO_WAIT_BEFORE_LOAD_NEXT = (this.ITEM_HEIGHT * (this.MAX_ITEMS / 2)); + private onDestroy$ = new Subject(); selected: SiteEntry = null; - - public MY_FILES_VALUE = '-my-'; + MY_FILES_VALUE = '-my-'; constructor(private sitesService: SitesService, private logService: LogService) { } ngOnInit() { - this.siteSelect.openedChange.subscribe(() => { - if (this.siteSelect.panelOpen) { - this.siteSelect.panel.nativeElement.addEventListener('scroll', (event) => this.loadAllOnScroll(event)); - } - }); + this.siteSelect.openedChange + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { + if (this.siteSelect.panelOpen) { + this.siteSelect.panel.nativeElement.addEventListener('scroll', (event) => this.loadAllOnScroll(event)); + } + }); if (!this.siteList) { this.loadSiteList(); } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + loadAllOnScroll(event) { if (this.isInfiniteScrollingEnabled() && this.isScrollInNextFetchArea(event)) { this.loading = true; diff --git a/lib/content-services/tag/tag-actions.component.ts b/lib/content-services/tag/tag-actions.component.ts index 881f936f37..0b906201f2 100644 --- a/lib/content-services/tag/tag-actions.component.ts +++ b/lib/content-services/tag/tag-actions.component.ts @@ -18,8 +18,9 @@ import { TranslationService } from '@alfresco/adf-core'; import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core'; import { TagService } from './services/tag.service'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { TagPaging } from '@alfresco/js-api'; +import { takeUntil } from 'rxjs/operators'; /** * @@ -55,17 +56,15 @@ export class TagActionsComponent implements OnChanges, OnInit, OnDestroy { errorMsg: string; disableAddTag: boolean = true; - private subscriptions: Subscription[] = []; + private onDestroy$ = new Subject(); constructor(private tagService: TagService, private translateService: TranslationService) { } ngOnInit() { - this.subscriptions.push( - this.tagService.refresh.subscribe(() => { - this.refreshTag(); - }) - ); + this.tagService.refresh + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => this.refreshTag()); } ngOnChanges() { @@ -73,8 +72,8 @@ export class TagActionsComponent implements OnChanges, OnInit, OnDestroy { } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } refreshTag() { diff --git a/lib/content-services/tag/tag-list.component.ts b/lib/content-services/tag/tag-list.component.ts index a21ad80b8b..fcd5006cc3 100644 --- a/lib/content-services/tag/tag-list.component.ts +++ b/lib/content-services/tag/tag-list.component.ts @@ -15,9 +15,11 @@ * limitations under the License. */ -import { Component, EventEmitter, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output, ViewEncapsulation, OnDestroy } from '@angular/core'; import { TagService } from './services/tag.service'; import { PaginationModel } from '@alfresco/adf-core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; /** * This component provide a list of all the tag inside the ECM @@ -28,7 +30,7 @@ import { PaginationModel } from '@alfresco/adf-core'; styleUrls: ['./tag-list.component.scss'], encapsulation: ViewEncapsulation.None }) -export class TagListComponent implements OnInit { +export class TagListComponent implements OnInit, OnDestroy { /** Emitted when a tag is selected. */ @Output() @@ -50,6 +52,8 @@ export class TagListComponent implements OnInit { isLoading = false; isSizeMinimum = true; + private onDestroy$ = new Subject(); + /** * Constructor * @param tagService @@ -64,14 +68,21 @@ export class TagListComponent implements OnInit { this.pagination = this.defaultPagination; - this.tagService.refresh.subscribe(() => { - this.tagsEntries = []; - this.refreshTag(this.defaultPagination); - }); + this.tagService.refresh + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { + this.tagsEntries = []; + this.refreshTag(this.defaultPagination); + }); } ngOnInit() { - return this.refreshTag(this.defaultPagination); + this.refreshTag(this.defaultPagination); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } refreshTag(opts?: any) { diff --git a/lib/content-services/tag/tag-node-list.component.ts b/lib/content-services/tag/tag-node-list.component.ts index 5de1492d7b..6d0d41eb7a 100644 --- a/lib/content-services/tag/tag-node-list.component.ts +++ b/lib/content-services/tag/tag-node-list.component.ts @@ -15,9 +15,11 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core'; import { TagService } from './services/tag.service'; import { TagPaging } from '@alfresco/js-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; /** * @@ -30,7 +32,7 @@ import { TagPaging } from '@alfresco/js-api'; styleUrls: ['./tag-node-list.component.scss'], encapsulation: ViewEncapsulation.None }) -export class TagNodeListComponent implements OnChanges { +export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit { /** The identifier of a node. */ @Input() nodeId: string; @@ -45,18 +47,28 @@ export class TagNodeListComponent implements OnChanges { @Output() results = new EventEmitter(); + private onDestroy$ = new Subject(); + /** * Constructor * @param tagService */ constructor(private tagService: TagService) { - this.tagService.refresh.subscribe(() => { - this.refreshTag(); - }); } ngOnChanges() { - return this.refreshTag(); + this.refreshTag(); + } + + ngOnInit() { + this.tagService.refresh + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => this.refreshTag()); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } refreshTag() { diff --git a/lib/content-services/upload/components/base-upload/upload-base.ts b/lib/content-services/upload/components/base-upload/upload-base.ts index 737bc077c4..fdb0e392fa 100644 --- a/lib/content-services/upload/components/base-upload/upload-base.ts +++ b/lib/content-services/upload/components/base-upload/upload-base.ts @@ -18,8 +18,9 @@ import { FileModel, FileInfo } from '@alfresco/adf-core'; import { EventEmitter, Input, Output, OnInit, OnDestroy, NgZone } from '@angular/core'; import { UploadService, TranslationService } from '@alfresco/adf-core'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { UploadFilesEvent } from '../upload-files.event'; +import { takeUntil } from 'rxjs/operators'; export abstract class UploadBase implements OnInit, OnDestroy { @@ -71,7 +72,7 @@ export abstract class UploadBase implements OnInit, OnDestroy { @Output() beginUpload = new EventEmitter(); - protected subscriptions: Subscription[] = []; + protected onDestroy$ = new Subject(); constructor(protected uploadService: UploadService, protected translationService: TranslationService, @@ -79,17 +80,14 @@ export abstract class UploadBase implements OnInit, OnDestroy { } ngOnInit() { - this.subscriptions.push( - this.uploadService.fileUploadError.subscribe((error) => { - this.error.emit(error); - }) - ); - + this.uploadService.fileUploadError + .pipe(takeUntil(this.onDestroy$)) + .subscribe(error => this.error.emit(error)); } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } /** diff --git a/lib/content-services/upload/components/file-uploading-dialog.component.ts b/lib/content-services/upload/components/file-uploading-dialog.component.ts index 202d759700..bab4cc839d 100644 --- a/lib/content-services/upload/components/file-uploading-dialog.component.ts +++ b/lib/content-services/upload/components/file-uploading-dialog.component.ts @@ -15,10 +15,7 @@ * limitations under the License. */ -import { - FileModel, FileUploadCompleteEvent, FileUploadDeleteEvent, - FileUploadErrorEvent, FileUploadStatus, UploadService, UserPreferencesService -} from '@alfresco/adf-core'; +import { FileModel, FileUploadStatus, UploadService, UserPreferencesService } from '@alfresco/adf-core'; import { ChangeDetectorRef, Component, Input, Output, EventEmitter, OnDestroy, OnInit, ViewChild, HostBinding } from '@angular/core'; import { Subscription, merge, Subject } from 'rxjs'; import { FileUploadingListComponent } from './file-uploading-list.component'; @@ -33,7 +30,7 @@ import { takeUntil } from 'rxjs/operators'; export class FileUploadingDialogComponent implements OnInit, OnDestroy { /** Dialog direction. Can be 'ltr' or 'rtl. */ private direction: Direction = 'ltr'; - private onDestroy$: Subject = new Subject(); + private onDestroy$ = new Subject(); @ViewChild('uploadList') uploadList: FileUploadingListComponent; @@ -79,8 +76,9 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { } ngOnInit() { - this.listSubscription = this.uploadService - .queueChanged.subscribe((fileList: FileModel[]) => { + this.listSubscription = this.uploadService.queueChanged + .pipe(takeUntil(this.onDestroy$)) + .subscribe(fileList => { this.filesUploadingList = fileList; if (this.filesUploadingList.length) { @@ -92,33 +90,38 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { this.uploadService.fileUploadComplete, this.uploadService.fileUploadDeleted ) - .subscribe((event: (FileUploadDeleteEvent | FileUploadCompleteEvent)) => { + .pipe(takeUntil(this.onDestroy$)) + .subscribe(event => { this.totalCompleted = event.totalComplete; this.changeDetector.detectChanges(); }); this.errorSubscription = this.uploadService.fileUploadError - .subscribe((event: FileUploadErrorEvent) => { + .pipe(takeUntil(this.onDestroy$)) + .subscribe(event => { this.totalErrors = event.totalError; this.changeDetector.detectChanges(); }); - this.fileUploadSubscription = this.uploadService - .fileUpload.subscribe(() => { + this.fileUploadSubscription = this.uploadService.fileUpload + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { this.changeDetector.detectChanges(); }); - this.uploadService.fileDeleted.subscribe((objId) => { - if (this.filesUploadingList) { - const file = this.filesUploadingList.find((item) => { - return item.data.entry.id === objId; - }); - if (file) { - file.status = FileUploadStatus.Cancelled; - this.changeDetector.detectChanges(); + this.uploadService.fileDeleted + .pipe(takeUntil(this.onDestroy$)) + .subscribe(objId => { + if (this.filesUploadingList) { + const file = this.filesUploadingList.find((item) => { + return item.data.entry.id === objId; + }); + if (file) { + file.status = FileUploadStatus.Cancelled; + this.changeDetector.detectChanges(); + } } - } - }); + }); this.userPreferencesService.select('textOrientation') .pipe(takeUntil(this.onDestroy$)) diff --git a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts index f7fde2fe35..df019170ed 100644 --- a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, ViewChild, OnDestroy } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; import { MatDatetimepicker, DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; @@ -27,6 +27,8 @@ import { UserPreferencesService, UserPreferenceValues } from '../../../services/ import { MomentDateAdapter } from '../../../utils/momentDateAdapter'; import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model'; import { AppConfigService } from '../../../app-config/app-config.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ providers: [ @@ -39,7 +41,7 @@ import { AppConfigService } from '../../../app-config/app-config.service'; templateUrl: './card-view-dateitem.component.html', styleUrls: ['./card-view-dateitem.component.scss'] }) -export class CardViewDateItemComponent implements OnInit { +export class CardViewDateItemComponent implements OnInit, OnDestroy { @Input() property: CardViewDateItemModel; @@ -56,6 +58,8 @@ export class CardViewDateItemComponent implements OnInit { valueDate: Moment; dateFormat: string; + private onDestroy$ = new Subject(); + constructor(private cardViewUpdateService: CardViewUpdateService, private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService, @@ -64,9 +68,10 @@ export class CardViewDateItemComponent implements OnInit { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); ( this.dateAdapter).overrideDisplayFormat = 'MMM DD'; @@ -75,6 +80,11 @@ export class CardViewDateItemComponent implements OnInit { } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + showProperty() { return this.displayEmpty || !this.property.isEmpty(); } diff --git a/lib/core/comments/comment-list.component.ts b/lib/core/comments/comment-list.component.ts index f0148a8b81..7541964175 100644 --- a/lib/core/comments/comment-list.component.ts +++ b/lib/core/comments/comment-list.component.ts @@ -15,11 +15,13 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, Output, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core'; import { CommentModel } from '../models/comment.model'; import { EcmUserService } from '../userinfo/services/ecm-user.service'; import { PeopleProcessService } from '../services/people-process.service'; import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-comment-list', @@ -28,7 +30,7 @@ import { UserPreferencesService, UserPreferenceValues } from '../services/user-p encapsulation: ViewEncapsulation.None }) -export class CommentListComponent { +export class CommentListComponent implements OnInit, OnDestroy { /** The comments data used to populate the list. */ @Input() @@ -39,15 +41,24 @@ export class CommentListComponent { clickRow: EventEmitter = new EventEmitter(); selectedComment: CommentModel; - currentLocale; + private onDestroy$ = new Subject(); constructor(public peopleProcessService: PeopleProcessService, public ecmUserService: EcmUserService, public userPreferenceService: UserPreferencesService) { - userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.currentLocale = locale; - }); + } + + ngOnInit() { + this.userPreferenceService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.currentLocale = locale); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } selectComment(comment: CommentModel): void { diff --git a/lib/core/datatable/components/datatable/datatable-cell.component.ts b/lib/core/datatable/components/datatable/datatable-cell.component.ts index bac6b1c12d..aa866091a8 100644 --- a/lib/core/datatable/components/datatable/datatable-cell.component.ts +++ b/lib/core/datatable/components/datatable/datatable-cell.component.ts @@ -27,8 +27,8 @@ import { DataColumn } from '../../data/data-column.model'; import { DataRow } from '../../data/data-row.model'; import { DataTableAdapter } from '../../data/datatable-adapter'; import { AlfrescoApiService } from '../../../services/alfresco-api.service'; -import { Subscription, BehaviorSubject } from 'rxjs'; -import { Node } from '@alfresco/js-api'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-datatable-cell', @@ -77,21 +77,23 @@ export class DataTableCellComponent implements OnInit, OnDestroy { @Input() tooltip: string; - private sub: Subscription; + protected onDestroy$ = new Subject(); constructor(protected alfrescoApiService: AlfrescoApiService) {} ngOnInit() { this.updateValue(); - this.sub = this.alfrescoApiService.nodeUpdated.subscribe((node: Node) => { - if (this.row) { - if (this.row['node'].entry.id === node.id) { - this.row['node'].entry = node; - this.row['cache'][this.column.key] = this.column.key.split('.').reduce((source, key) => source[key], node); - this.updateValue(); + this.alfrescoApiService.nodeUpdated + .pipe(takeUntil(this.onDestroy$)) + .subscribe(node => { + if (this.row) { + if (this.row['node'].entry.id === node.id) { + this.row['node'].entry = node; + this.row['cache'][this.column.key] = this.column.key.split('.').reduce((source, key) => source[key], node); + this.updateValue(); + } } - } - }); + }); } protected updateValue() { @@ -107,9 +109,7 @@ export class DataTableCellComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - this.sub = null; - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/core/datatable/components/datatable/date-cell.component.ts b/lib/core/datatable/components/datatable/date-cell.component.ts index 9fb4d8f09f..091671cdaf 100644 --- a/lib/core/datatable/components/datatable/date-cell.component.ts +++ b/lib/core/datatable/components/datatable/date-cell.component.ts @@ -23,6 +23,7 @@ import { } from '../../../services/user-preferences.service'; import { AlfrescoApiService } from '../../../services/alfresco-api.service'; import { AppConfigService } from '../../../app-config/app-config.service'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-date-cell', @@ -75,9 +76,8 @@ export class DateCellComponent extends DataTableCellComponent { if (userPreferenceService) { userPreferenceService .select(UserPreferenceValues.Locale) - .subscribe((locale) => { - this.currentLocale = locale; - }); + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.currentLocale = locale); } } } diff --git a/lib/core/form/components/widgets/date-time/date-time.widget.ts b/lib/core/form/components/widgets/date-time/date-time.widget.ts index d0c9d9e806..93003aa924 100644 --- a/lib/core/form/components/widgets/date-time/date-time.widget.ts +++ b/lib/core/form/components/widgets/date-time/date-time.widget.ts @@ -17,7 +17,7 @@ /* tslint:disable:component-selector */ -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; @@ -28,6 +28,8 @@ import { MomentDateAdapter } from '../../../../utils/momentDateAdapter'; import { MOMENT_DATE_FORMATS } from '../../../../utils/moment-date-formats.model'; import { FormService } from './../../../services/form.service'; import { WidgetComponent } from './../widget.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ providers: [ @@ -41,12 +43,14 @@ import { WidgetComponent } from './../widget.component'; styleUrls: ['./date-time.widget.scss'], encapsulation: ViewEncapsulation.None }) -export class DateTimeWidgetComponent extends WidgetComponent implements OnInit { +export class DateTimeWidgetComponent extends WidgetComponent implements OnInit, OnDestroy { minDate: Moment; maxDate: Moment; displayDate: Moment; + private onDestroy$ = new Subject(); + constructor(public formService: FormService, private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) { @@ -54,9 +58,10 @@ export class DateTimeWidgetComponent extends WidgetComponent implements OnInit { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); const momentDateAdapter = this.dateAdapter; momentDateAdapter.overrideDisplayFormat = this.field.dateDisplayFormat; @@ -73,6 +78,11 @@ export class DateTimeWidgetComponent extends WidgetComponent implements OnInit { this.displayDate = moment(this.field.value); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + onDateChanged(newDateValue) { if (newDateValue && newDateValue.value) { this.field.value = newDateValue.value.format(this.field.dateDisplayFormat); diff --git a/lib/core/form/components/widgets/date/date.widget.ts b/lib/core/form/components/widgets/date/date.widget.ts index fdd85ddd09..5de5b0927f 100644 --- a/lib/core/form/components/widgets/date/date.widget.ts +++ b/lib/core/form/components/widgets/date/date.widget.ts @@ -20,12 +20,14 @@ import { UserPreferencesService, UserPreferenceValues } from '../../../../services/user-preferences.service'; import { MomentDateAdapter } from '../../../../utils/momentDateAdapter'; import { MOMENT_DATE_FORMATS } from '../../../../utils/moment-date-formats.model'; -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; import moment from 'moment-es6'; import { Moment } from 'moment'; import { FormService } from './../../../services/form.service'; import { baseHost, WidgetComponent } from './../widget.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'date-widget', @@ -37,7 +39,7 @@ import { baseHost, WidgetComponent } from './../widget.component'; host: baseHost, encapsulation: ViewEncapsulation.None }) -export class DateWidgetComponent extends WidgetComponent implements OnInit { +export class DateWidgetComponent extends WidgetComponent implements OnInit, OnDestroy { DATE_FORMAT = 'DD/MM/YYYY'; @@ -45,6 +47,8 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit { maxDate: Moment; displayDate: Moment; + private onDestroy$ = new Subject(); + constructor(public formService: FormService, private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) { @@ -52,9 +56,10 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); const momentDateAdapter = this.dateAdapter; momentDateAdapter.overrideDisplayFormat = this.field.dateDisplayFormat; @@ -71,6 +76,11 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit { this.displayDate = moment(this.field.value); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + onDateChanged(newDateValue) { if (newDateValue && newDateValue.value) { this.field.value = newDateValue.value.format(this.field.dateDisplayFormat); diff --git a/lib/core/form/components/widgets/dynamic-table/editors/date/date.editor.ts b/lib/core/form/components/widgets/dynamic-table/editors/date/date.editor.ts index b6b58e3c1c..d0ab7bf034 100644 --- a/lib/core/form/components/widgets/dynamic-table/editors/date/date.editor.ts +++ b/lib/core/form/components/widgets/dynamic-table/editors/date/date.editor.ts @@ -21,13 +21,15 @@ import { UserPreferencesService, UserPreferenceValues } from '../../../../../../ import { MomentDateAdapter } from '../../../../../../utils/momentDateAdapter'; import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS, MatDatepickerInputEvent } from '@angular/material'; import moment from 'moment-es6'; import { Moment } from 'moment'; import { DynamicTableColumn } from './../../dynamic-table-column.model'; import { DynamicTableRow } from './../../dynamic-table-row.model'; import { DynamicTableModel } from './../../dynamic-table.widget.model'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-date-editor', @@ -37,7 +39,7 @@ import { DynamicTableModel } from './../../dynamic-table.widget.model'; {provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS}], styleUrls: ['./date.editor.scss'] }) -export class DateEditorComponent implements OnInit { +export class DateEditorComponent implements OnInit, OnDestroy { DATE_FORMAT: string = 'DD-MM-YYYY'; @@ -55,14 +57,17 @@ export class DateEditorComponent implements OnInit { minDate: Moment; maxDate: Moment; + private onDestroy$ = new Subject(); + constructor(private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); const momentDateAdapter = this.dateAdapter; momentDateAdapter.overrideDisplayFormat = this.DATE_FORMAT; @@ -70,6 +75,11 @@ export class DateEditorComponent implements OnInit { this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + onDateChanged(newDateValue: MatDatepickerInputEvent | HTMLInputElement) { if (newDateValue && newDateValue.value) { /* validates the user inputs */ diff --git a/lib/core/form/components/widgets/dynamic-table/editors/datetime/datetime.editor.ts b/lib/core/form/components/widgets/dynamic-table/editors/datetime/datetime.editor.ts index 7aa712a0cf..3fb5ece2ae 100644 --- a/lib/core/form/components/widgets/dynamic-table/editors/datetime/datetime.editor.ts +++ b/lib/core/form/components/widgets/dynamic-table/editors/datetime/datetime.editor.ts @@ -20,7 +20,7 @@ import { UserPreferencesService, UserPreferenceValues } from '../../../../../../services/user-preferences.service'; import { MomentDateAdapter } from '../../../../../../utils/momentDateAdapter'; import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; import moment from 'moment-es6'; import { Moment } from 'moment'; @@ -29,6 +29,8 @@ import { DynamicTableRow } from './../../dynamic-table-row.model'; import { DynamicTableModel } from './../../dynamic-table.widget.model'; import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-datetime-editor', @@ -41,7 +43,7 @@ import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetim ], styleUrls: ['./datetime.editor.scss'] }) -export class DateTimeEditorComponent implements OnInit { +export class DateTimeEditorComponent implements OnInit, OnDestroy { DATE_TIME_FORMAT: string = 'DD/MM/YYYY HH:mm'; @@ -59,14 +61,17 @@ export class DateTimeEditorComponent implements OnInit { minDate: Moment; maxDate: Moment; + private onDestroy$ = new Subject(); + constructor(private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); const momentDateAdapter = this.dateAdapter; momentDateAdapter.overrideDisplayFormat = this.DATE_TIME_FORMAT; @@ -74,6 +79,11 @@ export class DateTimeEditorComponent implements OnInit { this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_TIME_FORMAT); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + onDateChanged(newDateValue) { if (newDateValue && newDateValue.value) { const newValue = moment(newDateValue.value, this.DATE_TIME_FORMAT); diff --git a/lib/core/form/components/widgets/typeahead/typeahead.widget.ts b/lib/core/form/components/widgets/typeahead/typeahead.widget.ts index e24153db3c..b3c5096b94 100644 --- a/lib/core/form/components/widgets/typeahead/typeahead.widget.ts +++ b/lib/core/form/components/widgets/typeahead/typeahead.widget.ts @@ -57,50 +57,50 @@ export class TypeaheadWidgetComponent extends WidgetComponent implements OnInit getValuesByTaskId() { this.formService .getRestFieldValues( - this.field.form.taskId, - this.field.id + this.field.form.taskId, + this.field.id ) .subscribe( - (formFieldOption: FormFieldOption[]) => { - const options = formFieldOption || []; - this.field.options = options; + (formFieldOption: FormFieldOption[]) => { + const options = formFieldOption || []; + this.field.options = options; - const fieldValue = this.field.value; - if (fieldValue) { - const toSelect = options.find((item) => item.id === fieldValue || item.name.toLocaleLowerCase() === fieldValue.toLocaleLowerCase()); - if (toSelect) { - this.value = toSelect.name; + const fieldValue = this.field.value; + if (fieldValue) { + const toSelect = options.find((item) => item.id === fieldValue || item.name.toLocaleLowerCase() === fieldValue.toLocaleLowerCase()); + if (toSelect) { + this.value = toSelect.name; + } } - } - this.onFieldChanged(this.field); - this.field.updateForm(); - }, - (err) => this.handleError(err) + this.onFieldChanged(this.field); + this.field.updateForm(); + }, + (err) => this.handleError(err) ); } getValuesByProcessDefinitionId() { this.formService .getRestFieldValuesByProcessId( - this.field.form.processDefinitionId, - this.field.id + this.field.form.processDefinitionId, + this.field.id ) .subscribe( - (formFieldOption: FormFieldOption[]) => { - const options = formFieldOption || []; - this.field.options = options; + (formFieldOption: FormFieldOption[]) => { + const options = formFieldOption || []; + this.field.options = options; - const fieldValue = this.field.value; - if (fieldValue) { - const toSelect = options.find((item) => item.id === fieldValue); - if (toSelect) { - this.value = toSelect.name; + const fieldValue = this.field.value; + if (fieldValue) { + const toSelect = options.find((item) => item.id === fieldValue); + if (toSelect) { + this.value = toSelect.name; + } } - } - this.onFieldChanged(this.field); - this.field.updateForm(); - }, - (err) => this.handleError(err) + this.onFieldChanged(this.field); + this.field.updateForm(); + }, + (err) => this.handleError(err) ); } diff --git a/lib/core/login/components/login.component.ts b/lib/core/login/components/login.component.ts index be8d1ee084..7fe06a4d9b 100644 --- a/lib/core/login/components/login.component.ts +++ b/lib/core/login/components/login.component.ts @@ -17,7 +17,7 @@ import { Component, EventEmitter, - Input, OnInit, Output, TemplateRef, ViewEncapsulation + Input, OnInit, Output, TemplateRef, ViewEncapsulation, OnDestroy } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router, ActivatedRoute, Params } from '@angular/router'; @@ -36,6 +36,8 @@ import { } from '../../app-config/app-config.service'; import { OauthConfigModel } from '../../models/oauth-config.model'; import { DomSanitizer } from '@angular/platform-browser'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; enum LoginSteps { Landing = 0, @@ -57,7 +59,7 @@ interface ValidationMessage { class: 'adf-login' } }) -export class LoginComponent implements OnInit { +export class LoginComponent implements OnInit, OnDestroy { isPasswordShow: boolean = false; /** @@ -127,6 +129,7 @@ export class LoginComponent implements OnInit { data: any; private _message: { [id: string]: { [id: string]: ValidationMessage } }; + private onDestroy$ = new Subject(); /** * Constructor @@ -175,7 +178,14 @@ export class LoginComponent implements OnInit { this.initFormFieldsDefault(); this.initFormFieldsMessagesDefault(); } - this.form.valueChanges.subscribe((data) => this.onValueChanged(data)); + this.form.valueChanges + .pipe(takeUntil(this.onDestroy$)) + .subscribe(data => this.onValueChanged(data)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } submit() { diff --git a/lib/core/pagination/infinite-pagination.component.ts b/lib/core/pagination/infinite-pagination.component.ts index 85b6bb4df3..e2765e5a7f 100644 --- a/lib/core/pagination/infinite-pagination.component.ts +++ b/lib/core/pagination/infinite-pagination.component.ts @@ -24,12 +24,13 @@ import { } from '@angular/core'; import { PaginatedComponent } from './paginated-component.interface'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { PaginationComponentInterface } from './pagination-component.interface'; import { PaginationModel } from '../models/pagination.model'; import { RequestPaginationModel } from '../models/request-pagination.model'; import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service'; import { Pagination } from '@alfresco/js-api'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-infinite-pagination', @@ -48,22 +49,25 @@ export class InfinitePaginationComponent implements OnInit, OnDestroy, Paginatio }); _target: PaginatedComponent; + private onDestroy$ = new Subject(); /** Component that provides custom pagination support. */ @Input() set target(target: PaginatedComponent) { if (target) { this._target = target; - this.paginationSubscription = target.pagination.subscribe((pagination: PaginationModel) => { - this.isLoading = false; - this.pagination = pagination; + target.pagination + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pagination => { + this.isLoading = false; + this.pagination = pagination; - if (!this.pagination.hasMoreItems) { - this.pagination.hasMoreItems = false; - } + if (!this.pagination.hasMoreItems) { + this.pagination.hasMoreItems = false; + } - this.cdr.detectChanges(); - }); + this.cdr.detectChanges(); + }); } } @@ -90,17 +94,18 @@ export class InfinitePaginationComponent implements OnInit, OnDestroy, Paginatio merge: true }; - private paginationSubscription: Subscription; - constructor(private cdr: ChangeDetectorRef, private userPreferencesService: UserPreferencesService) { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.PaginationSize).subscribe((pageSize: number) => { - this.pageSize = this.pageSize || pageSize; - this.requestPaginationModel.maxItems = this.pageSize; - }); + this.userPreferencesService + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((pageSize: number) => { + this.pageSize = this.pageSize || pageSize; + this.requestPaginationModel.maxItems = this.pageSize; + }); } onLoadMore() { @@ -127,8 +132,7 @@ export class InfinitePaginationComponent implements OnInit, OnDestroy, Paginatio } ngOnDestroy() { - if (this.paginationSubscription) { - this.paginationSubscription.unsubscribe(); - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/core/pagination/pagination.component.ts b/lib/core/pagination/pagination.component.ts index 675567ca5b..c808795638 100644 --- a/lib/core/pagination/pagination.component.ts +++ b/lib/core/pagination/pagination.component.ts @@ -23,9 +23,10 @@ import { import { Pagination } from '@alfresco/js-api'; import { PaginatedComponent } from './paginated-component.interface'; import { PaginationComponentInterface } from './pagination-component.interface'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { PaginationModel } from '../models/pagination.model'; import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-pagination', @@ -82,29 +83,32 @@ export class PaginationComponent implements OnInit, OnDestroy, PaginationCompone @Output() prevPage: EventEmitter = new EventEmitter(); - private paginationSubscription: Subscription; + private onDestroy$ = new Subject(); constructor(private cdr: ChangeDetectorRef, private userPreferencesService: UserPreferencesService) { - this.userPreferencesService.select(UserPreferenceValues.PaginationSize).subscribe((pagSize) => { - this.pagination.maxItems = pagSize; - }); } ngOnInit() { + this.userPreferencesService + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pagSize => this.pagination.maxItems = pagSize); + if (!this.supportedPageSizes) { this.supportedPageSizes = this.userPreferencesService.supportedPageSizes; } if (this.target) { - this.paginationSubscription = this.target.pagination.subscribe((pagination: PaginationModel) => { + this.target.pagination + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pagination => { + if (pagination.count === 0 && !this.isFirstPage) { + this.goPrevious(); + } - if (pagination.count === 0 && !this.isFirstPage) { - this.goPrevious(); - } - - this.pagination = pagination; - this.cdr.detectChanges(); - }); + this.pagination = pagination; + this.cdr.detectChanges(); + }); } if (!this.pagination) { @@ -217,6 +221,11 @@ export class PaginationComponent implements OnInit, OnDestroy, PaginationCompone }); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + handlePaginationEvent(action: string, params: PaginationModel) { const { NEXT_PAGE, @@ -258,10 +267,4 @@ export class PaginationComponent implements OnInit, OnDestroy, PaginationCompone this.target.updatePagination(params); } } - - ngOnDestroy() { - if (this.paginationSubscription) { - this.paginationSubscription.unsubscribe(); - } - } } diff --git a/lib/core/pipes/localized-date.pipe.ts b/lib/core/pipes/localized-date.pipe.ts index 02593f8cd9..83695935a9 100644 --- a/lib/core/pipes/localized-date.pipe.ts +++ b/lib/core/pipes/localized-date.pipe.ts @@ -16,15 +16,17 @@ */ import { DatePipe } from '@angular/common'; -import { Pipe, PipeTransform } from '@angular/core'; +import { Pipe, PipeTransform, OnDestroy } from '@angular/core'; import { AppConfigService } from '../app-config/app-config.service'; import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Pipe({ name: 'adfLocalizedDate', pure: false }) -export class LocalizedDatePipe implements PipeTransform { +export class LocalizedDatePipe implements PipeTransform, OnDestroy { static DEFAULT_LOCALE = 'en-US'; static DEFAULT_DATE_FORMAT = 'mediumDate'; @@ -32,15 +34,20 @@ export class LocalizedDatePipe implements PipeTransform { defaultLocale: string = LocalizedDatePipe.DEFAULT_LOCALE; defaultFormat: string = LocalizedDatePipe.DEFAULT_DATE_FORMAT; + private onDestroy$ = new Subject(); + constructor(public userPreferenceService?: UserPreferencesService, public appConfig?: AppConfigService) { if (this.userPreferenceService) { - this.userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => { - if (locale) { - this.defaultLocale = locale; - } - }); + this.userPreferenceService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => { + if (locale) { + this.defaultLocale = locale; + } + }); } if (this.appConfig) { @@ -55,4 +62,9 @@ export class LocalizedDatePipe implements PipeTransform { return datePipe.transform(value, actualFormat); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + } diff --git a/lib/core/pipes/time-ago.pipe.ts b/lib/core/pipes/time-ago.pipe.ts index 38694141f5..70d7ec92fc 100644 --- a/lib/core/pipes/time-ago.pipe.ts +++ b/lib/core/pipes/time-ago.pipe.ts @@ -16,15 +16,17 @@ */ import moment from 'moment-es6'; -import { Pipe, PipeTransform } from '@angular/core'; +import { Pipe, PipeTransform, OnDestroy } from '@angular/core'; import { AppConfigService } from '../app-config/app-config.service'; import { UserPreferenceValues, UserPreferencesService } from '../services/user-preferences.service'; import { DatePipe } from '@angular/common'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Pipe({ name: 'adfTimeAgo' }) -export class TimeAgoPipe implements PipeTransform { +export class TimeAgoPipe implements PipeTransform, OnDestroy { static DEFAULT_LOCALE = 'en-US'; static DEFAULT_DATE_TIME_FORMAT = 'dd/MM/yyyy HH:mm'; @@ -32,11 +34,16 @@ export class TimeAgoPipe implements PipeTransform { defaultLocale: string; defaultDateTimeFormat: string; + private onDestroy$ = new Subject(); + constructor(public userPreferenceService: UserPreferencesService, public appConfig: AppConfigService) { - this.userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.defaultLocale = locale || TimeAgoPipe.DEFAULT_LOCALE; - }); + this.userPreferenceService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => { + this.defaultLocale = locale || TimeAgoPipe.DEFAULT_LOCALE; + }); this.defaultDateTimeFormat = this.appConfig.get('dateValues.defaultDateTimeFormat', TimeAgoPipe.DEFAULT_DATE_TIME_FORMAT); } @@ -54,4 +61,9 @@ export class TimeAgoPipe implements PipeTransform { } return ''; } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } } diff --git a/lib/core/viewer/directives/viewer-extension.directive.ts b/lib/core/viewer/directives/viewer-extension.directive.ts index 5d1866fd0f..321301e70c 100644 --- a/lib/core/viewer/directives/viewer-extension.directive.ts +++ b/lib/core/viewer/directives/viewer-extension.directive.ts @@ -15,13 +15,15 @@ * limitations under the License. */ -import { AfterContentInit, ContentChild, Directive, Input, TemplateRef } from '@angular/core'; +import { AfterContentInit, ContentChild, Directive, Input, TemplateRef, OnDestroy } from '@angular/core'; import { ViewerComponent } from '../components/viewer.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Directive({ selector: 'adf-viewer-extension' }) -export class ViewerExtensionDirective implements AfterContentInit { +export class ViewerExtensionDirective implements AfterContentInit, OnDestroy { @ContentChild(TemplateRef) template: any; @@ -37,6 +39,8 @@ export class ViewerExtensionDirective implements AfterContentInit { templateModel: any; + private onDestroy$ = new Subject(); + constructor(private viewerComponent: ViewerComponent) { } @@ -45,9 +49,11 @@ export class ViewerExtensionDirective implements AfterContentInit { this.viewerComponent.extensionTemplates.push(this.templateModel); - this.viewerComponent.extensionChange.subscribe((fileExtension) => { - this.templateModel.isVisible = this.isVisible(fileExtension); - }); + this.viewerComponent.extensionChange + .pipe(takeUntil(this.onDestroy$)) + .subscribe(fileExtension => { + this.templateModel.isVisible = this.isVisible(fileExtension); + }); if (this.supportedExtensions instanceof Array) { this.supportedExtensions.forEach((extension) => { @@ -56,6 +62,11 @@ export class ViewerExtensionDirective implements AfterContentInit { } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + /** * check if the current extension in the viewer is compatible with this extension checking against supportedExtensions */ diff --git a/lib/insights/analytics-process/components/analytics-report-parameters.component.ts b/lib/insights/analytics-process/components/analytics-report-parameters.component.ts index e4c3673baf..9d01269f9d 100644 --- a/lib/insights/analytics-process/components/analytics-report-parameters.component.ts +++ b/lib/insights/analytics-process/components/analytics-report-parameters.component.ts @@ -37,6 +37,8 @@ import { ReportParameterDetailsModel } from '../../diagram/models/report/reportP import { ReportParametersModel } from '../../diagram/models/report/reportParameters.model'; import { ReportQuery } from '../../diagram/models/report/reportQuery.model'; import { AnalyticsService } from '../services/analytics.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-analytics-report-parameters', @@ -80,7 +82,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On onDropdownChanged = new EventEmitter(); - successReportParams = new EventEmitter(); + successReportParams = new EventEmitter(); successParamOpt = new EventEmitter(); @@ -94,12 +96,10 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On reportName: string; - private dropDownSub; - private reportParamsSub; - private paramOpts; reportParamQuery: ReportQuery; private hideParameters: boolean = true; formValidState: boolean = false; + private onDestroy$ = new Subject(); constructor(private analyticsService: AnalyticsService, private formBuilder: FormBuilder, @@ -110,19 +110,27 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On } ngOnInit() { - this.dropDownSub = this.onDropdownChanged.subscribe((field) => { - const paramDependOn: ReportParameterDetailsModel = this.reportParameters.definition.parameters.find((p) => p.dependsOn === field.id); - if (paramDependOn) { - this.retrieveParameterOptions(this.reportParameters.definition.parameters, this.appId, this.reportId, field.value); - } - }); + this.onDropdownChanged + .pipe(takeUntil(this.onDestroy$)) + .subscribe((field: any) => { + const paramDependOn = this.reportParameters.definition.parameters.find( + (param) => param.dependsOn === field.id + ); + if (paramDependOn) { + this.retrieveParameterOptions( + this.reportParameters.definition.parameters, this.appId, this.reportId, field.value + ); + } + }); - this.paramOpts = this.successReportParams.subscribe((report: ReportParametersModel) => { - if (report.hasParameters()) { - this.retrieveParameterOptions(report.definition.parameters, this.appId); - this.generateFormGroupFromParameter(report.definition.parameters); - } - }); + this.successReportParams + .pipe(takeUntil(this.onDestroy$)) + .subscribe(report => { + if (report.hasParameters()) { + this.retrieveParameterOptions(report.definition.parameters, this.appId); + this.generateFormGroupFromParameter(report.definition.parameters); + } + }); } ngOnChanges(changes: SimpleChanges) { @@ -195,7 +203,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On } public getReportParams(reportId: string) { - this.reportParamsSub = this.analyticsService.getReportParams(reportId).subscribe( + this.analyticsService.getReportParams(reportId).subscribe( (res: ReportParametersModel) => { this.reportParameters = res; if (this.reportParameters.hasParameters()) { @@ -293,11 +301,8 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On } ngOnDestroy() { - this.dropDownSub.unsubscribe(); - this.paramOpts.unsubscribe(); - if (this.reportParamsSub) { - this.reportParamsSub.unsubscribe(); - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } public editEnable() { @@ -309,15 +314,17 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On } public editTitle() { - this.reportParamsSub = this.analyticsService.updateReport(this.reportParameters.id, this.reportParameters.name).subscribe( - (res: ReportParametersModel) => { - this.editDisable(); - this.edit.emit(this.reportParameters.name); - }, - (err: any) => { - this.error.emit(err); - } - ); + this.analyticsService + .updateReport(this.reportParameters.id, this.reportParameters.name) + .subscribe( + () => { + this.editDisable(); + this.edit.emit(this.reportParameters.name); + }, + err => { + this.error.emit(err); + } + ); } public showDialog(event: string) { diff --git a/lib/insights/analytics-process/components/widgets/date-range/date-range.widget.ts b/lib/insights/analytics-process/components/widgets/date-range/date-range.widget.ts index 80e64b4633..96c868922e 100644 --- a/lib/insights/analytics-process/components/widgets/date-range/date-range.widget.ts +++ b/lib/insights/analytics-process/components/widgets/date-range/date-range.widget.ts @@ -18,11 +18,13 @@ /* tslint:disable:no-input-rename */ import { MOMENT_DATE_FORMATS, MomentDateAdapter, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; -import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation, OnDestroy } from '@angular/core'; import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; import moment from 'moment-es6'; import { Moment } from 'moment'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-date-range-widget', @@ -33,7 +35,7 @@ import { Moment } from 'moment'; styleUrls: ['./date-range.widget.scss'], encapsulation: ViewEncapsulation.None }) -export class DateRangeWidgetComponent implements OnInit { +export class DateRangeWidgetComponent implements OnInit, OnDestroy { public FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD'; public SHOW_FORMAT: string = 'DD/MM/YYYY'; @@ -52,15 +54,18 @@ export class DateRangeWidgetComponent implements OnInit { startDatePicker: Moment = moment(); endDatePicker: Moment = moment(); + private onDestroy$ = new Subject(); + constructor( private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); const momentDateAdapter = this.dateAdapter; momentDateAdapter.overrideDisplayFormat = this.SHOW_FORMAT; @@ -87,6 +92,11 @@ export class DateRangeWidgetComponent implements OnInit { this.dateRange.valueChanges.subscribe(() => this.onGroupValueChanged()); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + onGroupValueChanged() { if (this.dateRange.valid) { const dateStart = this.convertToMomentDateWithTime(this.dateRange.controls.startDate.value); diff --git a/lib/process-services-cloud/src/lib/form/components/attach-file-cloud-widget/attach-file-cloud-widget.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/attach-file-cloud-widget/attach-file-cloud-widget.component.spec.ts index 90fb438628..42bf7af75d 100644 --- a/lib/process-services-cloud/src/lib/form/components/attach-file-cloud-widget/attach-file-cloud-widget.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/attach-file-cloud-widget/attach-file-cloud-widget.component.spec.ts @@ -56,7 +56,7 @@ describe('AttachFileCloudWidgetComponent', () => { } }; - const contentSourceparam = { + const contentSourceParam = { fileSource: { name: 'mock-alf-content', serviceId: 'alfresco-content' @@ -130,7 +130,7 @@ describe('AttachFileCloudWidgetComponent', () => { value: [] }); widget.field.id = 'attach-file-alfresco'; - widget.field.params = contentSourceparam; + widget.field.params = contentSourceParam; fixture.detectChanges(); fixture.whenStable().then(() => { expect(element.querySelector('.adf-attach-widget__menu-upload')).not.toBeNull(); @@ -144,7 +144,7 @@ describe('AttachFileCloudWidgetComponent', () => { value: [] }); widget.field.id = 'attach-file-alfresco'; - widget.field.params = contentSourceparam; + widget.field.params = contentSourceParam; fixture.detectChanges(); fixture.whenStable().then(() => { const attachButton: HTMLButtonElement = element.querySelector('#attach-file-alfresco'); diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts index 1ac66263bf..15133efc2a 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts @@ -88,8 +88,8 @@ describe('EditProcessFilterCloudComponent', () => { }); it('should fetch process instance filter by id', async(() => { - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -103,8 +103,8 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should display filter name as title', async(() => { - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-title-id'); const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-sub-title-id'); @@ -118,8 +118,8 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should not display mat-spinner if isloading set to false', async(() => { - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-title-id'); const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-sub-title-id'); @@ -136,8 +136,8 @@ describe('EditProcessFilterCloudComponent', () => { it('should display mat-spinner if isloading set to true', async(() => { component.isLoading = true; - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-process-filter-loading-margin'); @@ -150,8 +150,8 @@ describe('EditProcessFilterCloudComponent', () => { describe('EditProcessFilter form', () => { beforeEach(() => { - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); }); @@ -245,14 +245,17 @@ describe('EditProcessFilterCloudComponent', () => { it('should display state drop down', async(() => { fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-process-property-status"] .mat-select-trigger'); stateElement.click(); + fixture.detectChanges(); fixture.whenStable().then(() => { const statusOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); - expect(statusOptions.length).toEqual(3); + expect(statusOptions.length).toEqual(6); }); })); @@ -287,8 +290,8 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); component.filterProperties = ['appName', 'processName']; fixture.detectChanges(); - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -302,8 +305,8 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); component.filterProperties = []; fixture.detectChanges(); - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); fixture.whenStable().then(() => { const stateController = component.editProcessFilterForm.get('status'); @@ -330,8 +333,8 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); component.filterProperties = ['appName', 'processName']; fixture.detectChanges(); - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); const appController = component.editProcessFilterForm.get('appName'); fixture.detectChanges(); @@ -343,8 +346,8 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should display default sort properties', async(() => { - const processFilterIdchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIdchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -373,8 +376,8 @@ describe('EditProcessFilterCloudComponent', () => { })); component.sortProperties = ['id', 'processName', 'processDefinitionId']; fixture.detectChanges(); - const processFilterIdchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIdchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -396,8 +399,8 @@ describe('EditProcessFilterCloudComponent', () => { describe('edit filter actions', () => { beforeEach(() => { - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); getProcessFilterByIdSpy.and.returnValue(of(fakeFilter)); fixture.detectChanges(); }); @@ -425,9 +428,9 @@ describe('EditProcessFilterCloudComponent', () => { }); })); - it('should emit delete event and delete the filter on click of delete button', async(() => { + it('should emit delete event and delete the filter on click of delete button', (done) => { component.toggleFilterActions = true; - const deleteFilterSpy = spyOn(service, 'deleteFilter').and.returnValue(of()); + const deleteFilterSpy = spyOn(service, 'deleteFilter').and.returnValue(of({})); const deleteSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); @@ -442,9 +445,14 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); fixture.whenStable().then(() => { expect(deleteFilterSpy).toHaveBeenCalled(); - expect(deleteSpy).toHaveBeenCalled(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(deleteSpy).toHaveBeenCalled(); + done(); + }); + }); - })); + }); it('should emit saveAs event and add filter on click saveAs button', async(() => { component.toggleFilterActions = true; @@ -497,8 +505,8 @@ describe('EditProcessFilterCloudComponent', () => { fixture.detectChanges(); component.actions = ['save']; fixture.detectChanges(); - const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); - component.ngOnChanges({ 'id': processFilterIDchange }); + const processFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIdChange }); fixture.detectChanges(); fixture.whenStable().then(() => { expect(component.processFilterActions).toBeDefined(); @@ -534,8 +542,8 @@ describe('EditProcessFilterCloudComponent', () => { it('should set the correct lastModifiedTo date', (done) => { component.appName = 'fake'; component.filterProperties = ['appName', 'processInstanceId', 'priority', 'lastModified']; - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange }); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); fixture.detectChanges(); const lastModifiedToControl: AbstractControl = component.editProcessFilterForm.get('lastModifiedTo'); diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts index 6835c3b6f3..ab0c2bec93 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts @@ -18,12 +18,11 @@ import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, OnDestroy } from '@angular/core'; import { FormGroup, FormBuilder, AbstractControl } from '@angular/forms'; import { MatDialog, DateAdapter } from '@angular/material'; -import { debounceTime, filter, takeUntil } from 'rxjs/operators'; +import { debounceTime, filter, takeUntil, finalize } from 'rxjs/operators'; import { Subject } from 'rxjs'; import moment from 'moment-es6'; import { Moment } from 'moment'; -import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; import { ProcessFilterCloudModel, ProcessFilterProperties, ProcessFilterAction, ProcessFilterOptions } from '../models/process-filter-cloud.model'; import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; @@ -120,9 +119,10 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes private appsProcessCloudService: AppsProcessCloudService) { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); } ngOnChanges(changes: SimpleChanges) { @@ -153,16 +153,15 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes */ retrieveProcessFilterAndBuildForm() { this.isLoading = true; - this.processFilterCloudService.getFilterById(this.appName, this.id) - .pipe(takeUntil(this.onDestroy$)).subscribe((response) => { - this.isLoading = false; - this.processFilter = new ProcessFilterCloudModel(response); - this.processFilterProperties = this.createAndFilterProperties(); - this.processFilterActions = this.createAndFilterActions(); - this.buildForm(this.processFilterProperties); - }, (error) => { - this.isLoading = false; - }); + this.processFilterCloudService + .getFilterById(this.appName, this.id) + .pipe(finalize(() => this.isLoading = false)) + .subscribe(response => { + this.processFilter = new ProcessFilterCloudModel(response); + this.processFilterProperties = this.createAndFilterProperties(); + this.processFilterActions = this.createAndFilterActions(); + this.buildForm(this.processFilterProperties); + }); } /** @@ -170,7 +169,11 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes */ onFilterChange() { this.editProcessFilterForm.valueChanges - .pipe(debounceTime(500), filter(() => this.isFormValid())) + .pipe( + debounceTime(500), + filter(() => this.isFormValid()), + takeUntil(this.onDestroy$) + ) .subscribe((formValues: ProcessFilterCloudModel) => { this.setLastModifiedToFilter(formValues); this.changedProcessFilter = new ProcessFilterCloudModel(Object.assign({}, this.processFilter, formValues)); @@ -292,8 +295,9 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes } getRunningApplications() { - this.appsProcessCloudService.getDeployedApplicationsByStatus(EditProcessFilterCloudComponent.APP_RUNNING_STATUS) - .pipe(takeUntil(this.onDestroy$)).subscribe((applications: ApplicationInstanceModel[]) => { + this.appsProcessCloudService + .getDeployedApplicationsByStatus(EditProcessFilterCloudComponent.APP_RUNNING_STATUS) + .subscribe(applications => { if (applications && applications.length > 0) { applications.map((application) => { this.applicationNames.push({ label: application.name, value: application.name }); @@ -316,23 +320,25 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes * Save a process instance filter */ save(saveAction: ProcessFilterAction) { - this.processFilterCloudService.updateFilter(this.changedProcessFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { - saveAction.filter = this.changedProcessFilter; - this.action.emit(saveAction); - this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter); - }); + this.processFilterCloudService + .updateFilter(this.changedProcessFilter) + .subscribe(() => { + saveAction.filter = this.changedProcessFilter; + this.action.emit(saveAction); + this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter); + }); } /** * Delete a process instance filter */ delete(deleteAction: ProcessFilterAction) { - this.processFilterCloudService.deleteFilter(this.processFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { - deleteAction.filter = this.processFilter; - this.action.emit(deleteAction); - }); + this.processFilterCloudService + .deleteFilter(this.processFilter) + .subscribe(() => { + deleteAction.filter = this.processFilter; + this.action.emit(deleteAction); + }); } /** @@ -357,11 +363,12 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes key: 'custom-' + filterKey }; const resultFilter: ProcessFilterCloudModel = Object.assign({}, this.changedProcessFilter, newFilter); - this.processFilterCloudService.addFilter(resultFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { - saveAsAction.filter = resultFilter; - this.action.emit(saveAsAction); - }); + this.processFilterCloudService + .addFilter(resultFilter) + .subscribe(() => { + saveAsAction.filter = resultFilter; + this.action.emit(saveAsAction); + }); } }); } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts index e6db30639a..23a69e2d7c 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts @@ -50,7 +50,7 @@ export class ProcessFilterCloudService { } else if (!this.hasProcessFilters(preferences, key)) { return this.createProcessFilters(appName, key, this.defaultProcessFilters(appName)); } else { - return of(this.findFiltersByKeyInPrefrences(preferences, key)); + return of(this.findFiltersByKeyInPreferences(preferences, key)); } }), catchError((err) => this.handleProcessError(err)) @@ -97,7 +97,7 @@ export class ProcessFilterCloudService { /** * Adds a new process instance filter * @param filter The new filter to add - * @returns Obervable of process instance filters with newly added filter + * @returns Observable of process instance filters with newly added filter */ addFilter(newFilter: ProcessFilterCloudModel): Observable { const key: string = this.prepareKey(newFilter.appName); @@ -233,7 +233,7 @@ export class ProcessFilterCloudService { * @param appName Name of the target app * @returns Array of ProcessFilterCloudModel */ - private findFiltersByKeyInPrefrences(preferences: any, key: string): ProcessFilterCloudModel[] { + private findFiltersByKeyInPreferences(preferences: any, key: string): ProcessFilterCloudModel[] { const result = preferences.find((filter: any) => { return filter.entry.key === key; }); return result && result.entry ? JSON.parse(result.entry.value) : []; } diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts index 47f6705a0e..d86474d1ba 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts @@ -25,7 +25,7 @@ describe('Activiti ProcessList Cloud Service', () => { let service: ProcessListCloudService; let alfrescoApiMock: AlfrescoApiServiceMock; - function returFakeProcessListResults() { + function returnFakeProcessListResults() { return { oauth2Auth: { callCustomApi: () => { @@ -70,7 +70,7 @@ describe('Activiti ProcessList Cloud Service', () => { it('should return the processes', (done) => { const processRequest: ProcessQueryCloudRequestModel = { appName: 'fakeName' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returFakeProcessListResults); + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeProcessListResults); service.getProcessByRequest(processRequest).subscribe((res) => { expect(res).toBeDefined(); expect(res).not.toBeNull(); diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts index 418954ec6d..dd0311e897 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts @@ -82,10 +82,10 @@ export class ProcessListCloudService extends BaseCloudService { return property === 'appName' || property === 'sorting'; } - private buildSortingParam(sortings: ProcessListCloudSortingModel[]): string { + private buildSortingParam(models: ProcessListCloudSortingModel[]): string { let finalSorting: string = ''; - if (sortings) { - for (const sort of sortings) { + if (models) { + for (const sort of models) { if (!finalSorting) { finalSorting = `${sort.orderBy},${sort.direction}`; } else { diff --git a/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts b/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts index 79a8dc89c5..28bc8d0fb2 100644 --- a/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts @@ -16,9 +16,9 @@ */ import { FormControl } from '@angular/forms'; -import { Component, OnInit, Output, EventEmitter, ViewEncapsulation, Input, ViewChild, ElementRef, SimpleChanges, OnChanges } from '@angular/core'; -import { Observable, of, BehaviorSubject } from 'rxjs'; -import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map } from 'rxjs/operators'; +import { Component, OnInit, Output, EventEmitter, ViewEncapsulation, Input, ViewChild, ElementRef, SimpleChanges, OnChanges, OnDestroy } from '@angular/core'; +import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; +import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators'; import { FullNamePipe, IdentityUserModel, IdentityUserService, LogService } from '@alfresco/adf-core'; import { trigger, state, style, transition, animate } from '@angular/animations'; @@ -39,7 +39,7 @@ import { trigger, state, style, transition, animate } from '@angular/animations' encapsulation: ViewEncapsulation.None }) -export class PeopleCloudComponent implements OnInit, OnChanges { +export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { static MODE_SINGLE = 'single'; static MODE_MULTIPLE = 'multiple'; @@ -98,15 +98,13 @@ export class PeopleCloudComponent implements OnInit, OnChanges { private _searchUsers: IdentityUserModel[] = []; private selectedUsersSubject: BehaviorSubject; private searchUsersSubject: BehaviorSubject; + private onDestroy$ = new Subject(); + selectedUsers$: Observable; searchUsers$: Observable; - _subscriptAnimationState: string = 'enter'; - clientId: string; - isFocused: boolean; - invalidUsers: IdentityUserModel[] = []; constructor(private identityUserService: IdentityUserService, private logService: LogService) { @@ -141,6 +139,11 @@ export class PeopleCloudComponent implements OnInit, OnChanges { } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + initSubjects() { if (this.selectedUsersSubject === undefined) { this.selectedUsersSubject = new BehaviorSubject(this.preSelectUsers); @@ -239,11 +242,11 @@ export class PeopleCloudComponent implements OnInit, OnChanges { }); } - public userExists(result: any): boolean { + public userExists(result: IdentityUserModel): boolean { return result && (result.id !== undefined || result.username !== undefined - || result.amil !== undefined); + || result.email !== undefined); } private initSearch() { @@ -285,8 +288,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges { } else { return of(user); } - }) - ).subscribe((user) => { + }), + takeUntil(this.onDestroy$) + ).subscribe((user: any) => { this._searchUsers.push(user); this.searchUsersSubject.next(this._searchUsers); }); diff --git a/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts b/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts index 340d698abc..7331f0833e 100644 --- a/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts @@ -19,7 +19,7 @@ import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; import { MOMENT_DATE_FORMATS, MomentDateAdapter } from '@alfresco/adf-core'; import moment from 'moment-es6'; import { Moment } from 'moment'; -import { Observable, Subscription } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { FormBuilder, AbstractControl, Validators, FormGroup, FormControl } from '@angular/forms'; import { LogService, @@ -32,6 +32,7 @@ import { PeopleCloudComponent } from './people-cloud/people-cloud.component'; import { GroupCloudComponent } from '../../../../lib/group/components/group-cloud.component'; import { TaskCloudService } from '../../services/task-cloud.service'; import { StartTaskCloudRequestModel } from '../models/start-task-cloud-request.model'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-cloud-start-task', @@ -101,9 +102,7 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy { private assigneeForm: AbstractControl = new FormControl(''); private groupForm: AbstractControl = new FormControl(''); - - private localeSub: Subscription; - private createTaskSub: Subscription; + private onDestroy$ = new Subject(); constructor(private taskService: TaskCloudService, private dateAdapter: DateAdapter, @@ -114,21 +113,17 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy { } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); this.loadCurrentUser(); this.buildForm(); } ngOnDestroy() { - if (this.localeSub) { - this.localeSub.unsubscribe(); - } - - if (this.createTaskSub) { - this.createTaskSub.unsubscribe(); - } + this.onDestroy$.next(true); + this.onDestroy$.complete(); } buildForm() { @@ -162,7 +157,7 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy { } private createNewTask(newTask: StartTaskCloudRequestModel) { - this.createTaskSub = this.taskService.createNewTask(newTask, this.appName) + this.taskService.createNewTask(newTask, this.appName) .subscribe( (res: any) => { this.submitted = false; diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts index 721d6a0b7a..95b057dec4 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts @@ -70,8 +70,8 @@ describe('EditTaskFilterCloudComponent', () => { }); it('should fetch task filter by taskId', () => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); fixture.whenStable().then(() => { expect(getTaskFilterSpy).toHaveBeenCalled(); @@ -84,8 +84,8 @@ describe('EditTaskFilterCloudComponent', () => { }); it('should display filter name as title', async(() => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-sub-title-id'); @@ -96,8 +96,8 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should not display mat-spinner if isloading set to false', async(() => { - const taskFilterIDchange = new SimpleChange(null, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange }); + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); fixture.detectChanges(); const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-sub-title-id'); @@ -114,8 +114,8 @@ describe('EditTaskFilterCloudComponent', () => { it('should display mat-spinner if isloading set to true', async(() => { component.isLoading = true; - const taskFilterIDchange = new SimpleChange(null, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange }); + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); fixture.detectChanges(); const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-task-filter-loading-margin'); @@ -128,8 +128,8 @@ describe('EditTaskFilterCloudComponent', () => { describe('EditTaskFilter form', () => { beforeEach(() => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({'id': taskFilterIdChange}); fixture.detectChanges(); }); @@ -229,8 +229,8 @@ describe('EditTaskFilterCloudComponent', () => { getTaskFilterSpy.and.returnValue(of(fakeAllTaskFilter)); - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); @@ -275,8 +275,8 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should able to build a editTaskFilter form with default properties if input is empty', async(() => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); component.filterProperties = []; fixture.detectChanges(); const stateController = component.editTaskFilterForm.get('status'); @@ -299,8 +299,8 @@ describe('EditTaskFilterCloudComponent', () => { it('should able to fetch running applications when appName property defined in the input', async(() => { component.filterProperties = ['appName', 'processInstanceId', 'priority']; fixture.detectChanges(); - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); const appController = component.editTaskFilterForm.get('appName'); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -314,8 +314,8 @@ describe('EditTaskFilterCloudComponent', () => { describe('sort properties', () => { it('should display default sort properties', async(() => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -340,8 +340,8 @@ describe('EditTaskFilterCloudComponent', () => { priority: '12' })); fixture.detectChanges(); - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -361,8 +361,8 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should display default sort properties if input is empty', async(() => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); component.sortProperties = []; fixture.detectChanges(); @@ -386,8 +386,8 @@ describe('EditTaskFilterCloudComponent', () => { it('should display default filter actions', async(() => { component.toggleFilterActions = true; - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -410,8 +410,8 @@ describe('EditTaskFilterCloudComponent', () => { it('should display filter actions when input actions are specified', async(() => { component.actions = ['save']; fixture.detectChanges(); - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); component.toggleFilterActions = true; fixture.detectChanges(); @@ -430,8 +430,8 @@ describe('EditTaskFilterCloudComponent', () => { it('should display default filter actions if input is empty', async(() => { component.toggleFilterActions = true; component.actions = []; - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); @@ -454,8 +454,8 @@ describe('EditTaskFilterCloudComponent', () => { it('should set the correct lastModifiedTo date', (done) => { component.appName = 'fake'; component.filterProperties = ['appName', 'processInstanceId', 'priority', 'lastModified']; - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); const lastModifiedToControl: AbstractControl = component.editTaskFilterForm.get('lastModifiedTo'); @@ -478,8 +478,8 @@ describe('EditTaskFilterCloudComponent', () => { describe('edit filter actions', () => { beforeEach(() => { - const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); - component.ngOnChanges({ 'id': taskFilterIDchange}); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange}); fixture.detectChanges(); }); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts index 2261df355f..fa883d01b0 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts @@ -124,9 +124,10 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestro } ngOnInit() { - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); } ngOnChanges(changes: SimpleChanges) { @@ -159,8 +160,11 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestro */ onFilterChange() { this.editTaskFilterForm.valueChanges - .pipe(debounceTime(500), - filter(() => this.isFormValid())) + .pipe( + debounceTime(500), + filter(() => this.isFormValid()), + takeUntil(this.onDestroy$) + ) .subscribe((formValues: TaskFilterCloudModel) => { this.setLastModifiedToFilter(formValues); this.changedTaskFilter = new TaskFilterCloudModel(Object.assign({}, this.taskFilter, formValues)); @@ -334,7 +338,7 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestro save(saveAction: TaskFilterAction) { this.taskFilterCloudService.updateFilter(this.changedTaskFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + .pipe(takeUntil(this.onDestroy$)).subscribe(() => { saveAction.filter = this.changedTaskFilter; this.action.emit(saveAction); this.formHasBeenChanged = this.compareFilters(this.changedTaskFilter, this.taskFilter); @@ -343,7 +347,7 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestro delete(deleteAction: TaskFilterAction) { this.taskFilterCloudService.deleteFilter(this.taskFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + .pipe(takeUntil(this.onDestroy$)).subscribe(() => { deleteAction.filter = this.taskFilter; this.action.emit(deleteAction); }); @@ -369,7 +373,7 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestro }; const resultFilter: TaskFilterCloudModel = Object.assign({}, this.changedTaskFilter, newFilter); this.taskFilterCloudService.addFilter(resultFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + .pipe(takeUntil(this.onDestroy$)).subscribe(() => { saveAsAction.filter = resultFilter; this.action.emit(saveAsAction); }); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts index e60837181c..b66e572876 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts @@ -49,7 +49,7 @@ export class TaskFilterCloudService { } else if (!this.hasTaskFilters(preferences, key)) { return this.createTaskFilters(appName, key, this.defaultTaskFilters(appName)); } else { - return of(this.findFiltersByKeyInPrefrences(preferences, key)); + return of(this.findFiltersByKeyInPreferences(preferences, key)); } }), catchError((err) => this.handleTaskError(err)) @@ -138,7 +138,7 @@ export class TaskFilterCloudService { /** * Adds a new task filter. * @param filter The new filter to add - * @returns Obervable of task instance filters with newly added filter + * @returns Observable of task instance filters with newly added filter */ addFilter(newFilter: TaskFilterCloudModel) { const key: string = this.prepareKey(newFilter.appName); @@ -244,7 +244,7 @@ export class TaskFilterCloudService { * @param appName Name of the target app * @returns Array of TaskFilterCloudModel */ - private findFiltersByKeyInPrefrences(preferences: any, key: string): TaskFilterCloudModel[] { + private findFiltersByKeyInPreferences(preferences: any, key: string): TaskFilterCloudModel[] { const result = preferences.find((filter: any) => { return filter.entry.key === key; }); return result && result.entry ? JSON.parse(result.entry.value) : []; } diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts index 4b81303f31..b3e67a567f 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts @@ -29,8 +29,9 @@ import { import { TaskDetailsCloudModel, TaskStatusEnum } from '../../start-task/models/task-details-cloud.model'; import { Router } from '@angular/router'; import { TaskCloudService } from '../../services/task-cloud.service'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { NumericFieldValidator } from '../../../validators/numeric-field.validator'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-cloud-task-header', @@ -62,7 +63,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy { dateFormat: string; dateLocale: string; - private subscriptions: Subscription[] = []; + private onDestroy$ = new Subject(); constructor( private taskCloudService: TaskCloudService, @@ -80,19 +81,21 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy { this.loadTaskDetailsById(this.appName, this.taskId); } - this.subscriptions.push(this.cardViewUpdateService.itemUpdated$.subscribe(this.updateTaskDetails.bind(this))); + this.cardViewUpdateService.itemUpdated$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(this.updateTaskDetails.bind(this)); } loadTaskDetailsById(appName: string, taskId: string): any { - this.subscriptions.push(this.taskCloudService.getTaskById(appName, taskId).subscribe( + this.taskCloudService.getTaskById(appName, taskId).subscribe( (taskDetails) => { this.taskDetails = taskDetails; if (this.taskDetails.parentTaskId) { - this.loadParentName(this.taskDetails.parentTaskId); + this.loadParentName(`${this.taskDetails.parentTaskId}`); } else { this.refreshData(); } - })); + }); } private initDefaultProperties() { @@ -220,7 +223,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy { ); } - private loadParentName(taskId) { + private loadParentName(taskId: string) { this.taskCloudService.getTaskById(this.appName, taskId) .subscribe( (taskDetails) => { @@ -263,7 +266,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts index 034844cd3f..92acdd26dc 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts @@ -15,16 +15,17 @@ * limitations under the License. */ -import { Component, ViewEncapsulation, OnChanges, Input, SimpleChanges, Output, EventEmitter, ContentChild, AfterContentInit } from '@angular/core'; +import { Component, ViewEncapsulation, OnChanges, Input, SimpleChanges, Output, EventEmitter, ContentChild, AfterContentInit, OnDestroy, OnInit } from '@angular/core'; import { AppConfigService, UserPreferencesService, DataTableSchema, UserPreferenceValues, PaginatedComponent, PaginationModel, DataRowEvent, CustomEmptyContentTemplateDirective } from '@alfresco/adf-core'; import { taskPresetsCloudDefaultModel } from '../models/task-preset-cloud.model'; import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { TaskListCloudService } from '../services/task-list-cloud.service'; import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-cloud-task-list', @@ -33,7 +34,7 @@ import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; encapsulation: ViewEncapsulation.None }) -export class TaskListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent { +export class TaskListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy, OnInit { static PRESET_KEY = 'adf-cloud-task-list.presets'; @@ -148,14 +149,13 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges isLoading = false; selectedInstances: any[]; + private onDestroy$ = new Subject(); + constructor(private taskListCloudService: TaskListCloudService, appConfigService: AppConfigService, private userPreferences: UserPreferencesService) { super(appConfigService, TaskListCloudComponent.PRESET_KEY, taskPresetsCloudDefaultModel); this.size = userPreferences.paginationSize; - this.userPreferences.select(UserPreferenceValues.PaginationSize).subscribe((pageSize) => { - this.size = pageSize; - }); this.pagination = new BehaviorSubject( { maxItems: this.size, @@ -165,12 +165,24 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges } + ngOnInit() { + this.userPreferences + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pageSize => this.size = pageSize); + } + ngOnChanges(changes: SimpleChanges) { if (this.isPropertyChanged(changes)) { this.reload(); } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + ngAfterContentInit() { this.createDatatableSchema(); } diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts index 8cfcf69794..1c53233056 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts @@ -85,10 +85,10 @@ export class TaskListCloudService extends BaseCloudService { return requestNode[property] !== '' && requestNode[property] !== null && requestNode[property] !== undefined; } - private buildSortingParam(sortings: TaskListCloudSortingModel[]): string { + private buildSortingParam(models: TaskListCloudSortingModel[]): string { let finalSorting: string = ''; - if (sortings) { - for (const sort of sortings) { + if (models) { + for (const sort of models) { if (!finalSorting) { finalSorting = `${sort.orderBy},${sort.direction}`; } else { diff --git a/lib/process-services/app-list/apps-list.component.ts b/lib/process-services/app-list/apps-list.component.ts index 5b86ff045e..77ecdae653 100644 --- a/lib/process-services/app-list/apps-list.component.ts +++ b/lib/process-services/app-list/apps-list.component.ts @@ -16,18 +16,18 @@ */ import { AppsProcessService, TranslationService, CustomEmptyContentTemplateDirective } from '@alfresco/adf-core'; -import { AfterContentInit, Component, EventEmitter, Input, OnInit, Output, ContentChild } from '@angular/core'; -import { Observable, Observer, of } from 'rxjs'; +import { AfterContentInit, Component, EventEmitter, Input, OnInit, Output, ContentChild, OnDestroy } from '@angular/core'; +import { Observable, Observer, of, Subject } from 'rxjs'; import { AppDefinitionRepresentationModel } from '../task-list'; import { IconModel } from './icon.model'; -import { share } from 'rxjs/operators'; +import { share, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-apps', templateUrl: 'apps-list.component.html', styleUrls: ['./apps-list.component.scss'] }) -export class AppsListComponent implements OnInit, AfterContentInit { +export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy { public static LAYOUT_LIST: string = 'LIST'; public static LAYOUT_GRID: string = 'GRID'; @@ -60,17 +60,16 @@ export class AppsListComponent implements OnInit, AfterContentInit { private appsObserver: Observer; apps$: Observable; - currentApp: AppDefinitionRepresentationModel; - appList: AppDefinitionRepresentationModel [] = []; private iconsMDL: IconModel; loading: boolean = false; - hasEmptyCustomContentTemplate: boolean = false; + private onDestroy$ = new Subject(); + constructor( private appsProcessService: AppsProcessService, private translationService: TranslationService) { @@ -83,13 +82,19 @@ export class AppsListComponent implements OnInit, AfterContentInit { this.setDefaultLayoutType(); } - this.apps$.subscribe((app: any) => { - this.appList.push(app); - }); + this.apps$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe((app: any) => this.appList.push(app)); + this.iconsMDL = new IconModel(); this.load(); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + ngAfterContentInit() { if (this.emptyCustomContent) { this.hasEmptyCustomContentTemplate = true; diff --git a/lib/process-services/content-widget/attach-file-widget.component.ts b/lib/process-services/content-widget/attach-file-widget.component.ts index 1864a0f199..68ea8df0fd 100644 --- a/lib/process-services/content-widget/attach-file-widget.component.ts +++ b/lib/process-services/content-widget/attach-file-widget.component.ts @@ -17,7 +17,7 @@ /* tslint:disable:component-selector */ -import { Component, ViewEncapsulation, OnInit } from '@angular/core'; +import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core'; import { UploadWidgetComponent, FormService, @@ -26,14 +26,13 @@ import { ProcessContentService, ActivitiContentService, ContentService, - FormEvent, AppConfigValues, AppConfigService } from '@alfresco/adf-core'; import { ContentNodeDialogService } from '@alfresco/adf-content-services'; import { Node, RelatedContentRepresentation } from '@alfresco/js-api'; -import { from, zip, of } from 'rxjs'; -import { mergeMap } from 'rxjs/operators'; +import { from, zip, of, Subject } from 'rxjs'; +import { mergeMap, takeUntil } from 'rxjs/operators'; import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service'; @Component({ @@ -53,10 +52,11 @@ import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.servi }, encapsulation: ViewEncapsulation.None }) -export class AttachFileWidgetComponent extends UploadWidgetComponent implements OnInit { +export class AttachFileWidgetComponent extends UploadWidgetComponent implements OnInit, OnDestroy { repositoryList = []; private tempFilesList = []; + private onDestroy$ = new Subject(); constructor(public formService: FormService, private logger: LogService, @@ -82,11 +82,18 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements this.repositoryList = repoList; }); - this.formService.taskSaved.subscribe((formSaved: FormEvent) => { - if (formSaved.form.id === this.field.form.id) { - this.tempFilesList = []; - } - }); + this.formService.taskSaved + .pipe(takeUntil(this.onDestroy$)) + .subscribe(formSaved => { + if (formSaved.form.id === this.field.form.id) { + this.tempFilesList = []; + } + }); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } isFileSourceConfigured(): boolean { diff --git a/lib/process-services/form/form.component.ts b/lib/process-services/form/form.component.ts index 8b391fdfb0..5c281df16e 100644 --- a/lib/process-services/form/form.component.ts +++ b/lib/process-services/form/form.component.ts @@ -15,17 +15,14 @@ * limitations under the License. */ -import { - Component, EventEmitter, Input, Output, ViewEncapsulation, SimpleChanges, OnInit, OnDestroy, OnChanges -} from '@angular/core'; +import { Component, EventEmitter, Input, Output, ViewEncapsulation, SimpleChanges, OnInit, OnDestroy, OnChanges } from '@angular/core'; import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../content-widget'; import { EcmModelService, NodeService, WidgetVisibilityService, FormService, FormRenderingService, FormBaseComponent, FormOutcomeModel, - ValidateFormEvent, FormEvent, FormErrorEvent, FormFieldModel, + FormEvent, FormErrorEvent, FormFieldModel, FormModel, FormOutcomeEvent, FormValues, ContentLinkModel } from '@alfresco/adf-core'; - -import { Observable, of, Subscription } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { Observable, of, Subject } from 'rxjs'; +import { switchMap, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-form', @@ -84,7 +81,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro debugMode: boolean = false; - protected subscriptions: Subscription[] = []; + protected onDestroy$ = new Subject(); constructor(protected formService: FormService, protected visibilityService: WidgetVisibilityService, @@ -97,21 +94,22 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro } ngOnInit() { - this.subscriptions.push( - this.formService.formContentClicked.subscribe((content: ContentLinkModel) => { - this.formContentClicked.emit(content); - }), - this.formService.validateForm.subscribe((validateFormEvent: ValidateFormEvent) => { + this.formService.formContentClicked + .pipe(takeUntil(this.onDestroy$)) + .subscribe(content => this.formContentClicked.emit(content)); + + this.formService.validateForm + .pipe(takeUntil(this.onDestroy$)) + .subscribe(validateFormEvent => { if (validateFormEvent.errorsField.length > 0) { this.formError.next(validateFormEvent.errorsField); } - }) - ); + }); } ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } ngOnChanges(changes: SimpleChanges) { @@ -187,8 +185,8 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro } getFormByTaskId(taskId: string): Promise { - return new Promise((resolve, reject) => { - this.findProcessVariablesByTaskId(taskId).subscribe((processVariables) => { + return new Promise(resolve => { + this.findProcessVariablesByTaskId(taskId).subscribe(() => { this.formService .getTaskForm(taskId) .subscribe( diff --git a/lib/process-services/form/start-form.component.ts b/lib/process-services/form/start-form.component.ts index 71db078234..0949df4a90 100644 --- a/lib/process-services/form/start-form.component.ts +++ b/lib/process-services/form/start-form.component.ts @@ -29,7 +29,7 @@ import { OnDestroy } from '@angular/core'; import { FormComponent } from './form.component'; -import { ContentLinkModel, FormService, WidgetVisibilityService, FormRenderingService, ValidateFormEvent, FormOutcomeModel } from '@alfresco/adf-core'; +import { ContentLinkModel, FormService, WidgetVisibilityService, FormRenderingService, FormOutcomeModel } from '@alfresco/adf-core'; @Component({ selector: 'adf-start-form', @@ -77,24 +77,6 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn this.showTitle = false; } - ngOnInit() { - this.subscriptions.push( - this.formService.formContentClicked.subscribe((content) => { - this.formContentClicked.emit(content); - }), - this.formService.validateForm.subscribe((validateFormEvent: ValidateFormEvent) => { - if (validateFormEvent.errorsField.length > 0) { - this.formError.next(validateFormEvent.errorsField); - } - }) - ); - } - - ngOnDestroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions = []; - } - ngOnChanges(changes: SimpleChanges) { const processDefinitionId = changes['processDefinitionId']; if (processDefinitionId && processDefinitionId.currentValue) { diff --git a/lib/process-services/process-comments/process-comments.component.ts b/lib/process-services/process-comments/process-comments.component.ts index a797ecc24c..c4c1a0259e 100644 --- a/lib/process-services/process-comments/process-comments.component.ts +++ b/lib/process-services/process-comments/process-comments.component.ts @@ -16,16 +16,16 @@ */ import { CommentModel, CommentProcessService } from '@alfresco/adf-core'; -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { Observable, Observer } from 'rxjs'; -import { share } from 'rxjs/operators'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy } from '@angular/core'; +import { Observable, Observer, Subject } from 'rxjs'; +import { share, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-process-instance-comments', templateUrl: './process-comments.component.html', styleUrls: ['./process-comments.component.scss'] }) -export class ProcessCommentsComponent implements OnChanges { +export class ProcessCommentsComponent implements OnChanges, OnDestroy { /** (**required**) The numeric ID of the process instance to display comments for. */ @Input() @@ -44,16 +44,22 @@ export class ProcessCommentsComponent implements OnChanges { private commentObserver: Observer; comment$: Observable; + private onDestroy$ = new Subject(); + message: string; beingAdded: boolean = false; constructor(private commentProcessService: CommentProcessService) { - this.comment$ = new Observable((observer) => this.commentObserver = observer) - .pipe(share()); - this.comment$.subscribe((comment: CommentModel) => { - this.comments.push(comment); - }); + this.comment$ = new Observable(observer => this.commentObserver = observer).pipe(share()); + this.comment$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(comment => this.comments.push(comment)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } ngOnChanges(changes: SimpleChanges) { diff --git a/lib/process-services/process-list/components/process-instance-tasks.component.ts b/lib/process-services/process-list/components/process-instance-tasks.component.ts index 59c5e08b91..50f9928120 100644 --- a/lib/process-services/process-list/components/process-instance-tasks.component.ts +++ b/lib/process-services/process-list/components/process-instance-tasks.component.ts @@ -17,20 +17,20 @@ import { LogService } from '@alfresco/adf-core'; import { DatePipe } from '@angular/common'; -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, OnDestroy } from '@angular/core'; import { MatDialog } from '@angular/material'; -import { Observable, Observer } from 'rxjs'; +import { Observable, Observer, Subject } from 'rxjs'; import { TaskDetailsEvent, TaskDetailsModel } from '../../task-list'; import { ProcessInstance } from '../models/process-instance.model'; import { ProcessService } from './../services/process.service'; -import { share } from 'rxjs/operators'; +import { share, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-process-instance-tasks', templateUrl: './process-instance-tasks.component.html', styleUrls: ['./process-instance-tasks.component.css'] }) -export class ProcessInstanceTasksComponent implements OnInit, OnChanges { +export class ProcessInstanceTasksComponent implements OnInit, OnChanges, OnDestroy { /** (**required**) The ID of the process instance to display tasks for. */ @Input() @@ -51,10 +51,10 @@ export class ProcessInstanceTasksComponent implements OnInit, OnChanges { private taskObserver: Observer; private completedTaskObserver: Observer; + private onDestroy$ = new Subject(); task$: Observable; completedTask$: Observable; - message: string; processId: string; @@ -78,12 +78,18 @@ export class ProcessInstanceTasksComponent implements OnInit, OnChanges { } ngOnInit() { - this.task$.subscribe((task: TaskDetailsModel) => { - this.activeTasks.push(task); - }); - this.completedTask$.subscribe((task: TaskDetailsModel) => { - this.completedTasks.push(task); - }); + this.task$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(task => this.activeTasks.push(task)); + + this.completedTask$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(task => this.completedTasks.push(task)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } ngOnChanges(changes: SimpleChanges) { diff --git a/lib/process-services/process-list/components/start-process.component.ts b/lib/process-services/process-list/components/start-process.component.ts index d30431d0d5..6f867872c4 100644 --- a/lib/process-services/process-list/components/start-process.component.ts +++ b/lib/process-services/process-list/components/start-process.component.ts @@ -17,7 +17,7 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, - Output, SimpleChanges, ViewChild, ViewEncapsulation + Output, SimpleChanges, ViewChild, ViewEncapsulation, OnDestroy } from '@angular/core'; import { ActivitiContentService, AppConfigService, AppConfigValues, @@ -28,8 +28,8 @@ import { ProcessDefinitionRepresentation } from './../models/process-definition. import { ProcessInstance } from './../models/process-instance.model'; import { ProcessService } from './../services/process.service'; import { FormControl, Validators, AbstractControl } from '@angular/forms'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Observable, Subject } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; import { MatAutocompleteTrigger } from '@angular/material'; import { StartFormComponent } from '../../form'; @@ -39,7 +39,7 @@ import { StartFormComponent } from '../../form'; styleUrls: ['./start-process.component.scss'], encapsulation: ViewEncapsulation.None }) -export class StartProcessInstanceComponent implements OnChanges, OnInit { +export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestroy { MAX_LENGTH: number = 255; @@ -101,6 +101,8 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit { filteredProcesses: Observable; maxProcessNameLength: number = this.MAX_LENGTH; + private onDestroy$ = new Subject(); + constructor(private activitiProcess: ProcessService, private activitiContentService: ActivitiContentService, private appConfig: AppConfigService) { @@ -112,13 +114,22 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit { this.loadStartProcess(); - this.processNameInput.valueChanges.subscribe((name) => this.name = name); + this.processNameInput.valueChanges + .pipe(takeUntil(this.onDestroy$)) + .subscribe(name => this.name = name); + this.filteredProcesses = this.processDefinitionInput.valueChanges .pipe( - map((value) => this._filter(value)) + map((value) => this._filter(value)), + takeUntil(this.onDestroy$) ); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + ngOnChanges(changes: SimpleChanges) { if (changes['values'] && changes['values'].currentValue) { this.moveNodeFromCStoPS(); diff --git a/lib/process-services/task-list/components/start-task.component.ts b/lib/process-services/task-list/components/start-task.component.ts index 1ffbfbdbf9..40b82ee475 100644 --- a/lib/process-services/task-list/components/start-task.component.ts +++ b/lib/process-services/task-list/components/start-task.component.ts @@ -16,16 +16,16 @@ */ import { LogService, UserPreferencesService, UserPreferenceValues, UserProcessModel, FormFieldModel, FormModel } from '@alfresco/adf-core'; -import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation, OnDestroy } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; import { MOMENT_DATE_FORMATS, MomentDateAdapter } from '@alfresco/adf-core'; import moment from 'moment-es6'; import { Moment } from 'moment'; -import { Observable, of } from 'rxjs'; +import { Observable, of, Subject } from 'rxjs'; import { Form } from '../models/form.model'; import { TaskDetailsModel } from '../models/task-details.model'; import { TaskListService } from './../services/tasklist.service'; -import { switchMap, defaultIfEmpty } from 'rxjs/operators'; +import { switchMap, defaultIfEmpty, takeUntil } from 'rxjs/operators'; import { FormBuilder, AbstractControl, Validators, FormGroup, FormControl } from '@angular/forms'; @Component({ @@ -37,7 +37,7 @@ import { FormBuilder, AbstractControl, Validators, FormGroup, FormControl } from { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }], encapsulation: ViewEncapsulation.None }) -export class StartTaskComponent implements OnInit { +export class StartTaskComponent implements OnInit, OnDestroy { public FORMAT_DATE: string = 'DD/MM/YYYY'; MAX_LENGTH: number = 255; @@ -71,6 +71,8 @@ export class StartTaskComponent implements OnInit { maxTaskNameLength: number = this.MAX_LENGTH; loading = false; + private onDestroy$ = new Subject(); + /** * Constructor * @param auth @@ -92,14 +94,21 @@ export class StartTaskComponent implements OnInit { this.validateMaxTaskNameLength(); this.field = new FormFieldModel(new FormModel(), { id: this.assigneeId, value: this.assigneeId, placeholder: 'Assignee' }); - this.userPreferencesService.select(UserPreferenceValues.Locale).subscribe((locale) => { - this.dateAdapter.setLocale(locale); - }); + + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); this.loadFormsTask(); this.buildForm(); } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + buildForm() { this.taskForm = this.formBuilder.group({ name: new FormControl(this.taskDetailsModel.name, [Validators.required, Validators.maxLength(this.maxTaskNameLength), this.whitespaceValidator]), @@ -107,7 +116,9 @@ export class StartTaskComponent implements OnInit { formKey: new FormControl('') }); - this.taskForm.valueChanges.subscribe((taskFormValues) => this.setTaskDetails(taskFormValues)); + this.taskForm.valueChanges + .pipe(takeUntil(this.onDestroy$)) + .subscribe(taskFormValues => this.setTaskDetails(taskFormValues)); } public whitespaceValidator(control: FormControl) { diff --git a/lib/process-services/task-list/components/task-details.component.ts b/lib/process-services/task-list/components/task-details.component.ts index 55e19baa1a..ecf06fb37c 100644 --- a/lib/process-services/task-list/components/task-details.component.ts +++ b/lib/process-services/task-list/components/task-details.component.ts @@ -33,23 +33,24 @@ import { Output, SimpleChanges, TemplateRef, - ViewChild + ViewChild, + OnDestroy } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material'; -import { Observable, Observer } from 'rxjs'; +import { Observable, Observer, Subject } from 'rxjs'; import { ContentLinkModel, FormFieldValidator, FormModel, FormOutcomeEvent } from '@alfresco/adf-core'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskDetailsModel } from '../models/task-details.model'; import { TaskListService } from './../services/tasklist.service'; import { UserRepresentation } from '@alfresco/js-api'; -import { share } from 'rxjs/operators'; +import { share, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-task-details', templateUrl: './task-details.component.html', styleUrls: ['./task-details.component.scss'] }) -export class TaskDetailsComponent implements OnInit, OnChanges { +export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { @ViewChild('activitiComments') activitiComments: CommentsComponent; @@ -166,17 +167,15 @@ export class TaskDetailsComponent implements OnInit, OnChanges { taskDetails: TaskDetailsModel; taskFormName: string = null; - taskPeople: UserProcessModel[] = []; - noTaskDetailsTemplateComponent: TemplateRef; - showAssignee: boolean = false; showAttachForm: boolean = false; internalReadOnlyForm: boolean = false; private peopleSearchObserver: Observer; public errorDialogRef: MatDialogRef>; + private onDestroy$ = new Subject(); peopleSearch: Observable; @@ -189,21 +188,30 @@ export class TaskDetailsComponent implements OnInit, OnChanges { private logService: LogService, private cardViewUpdateService: CardViewUpdateService, private dialog: MatDialog) { - - this.peopleSearch = new Observable((observer) => this.peopleSearchObserver = observer) - .pipe(share()); - this.authService.getBpmLoggedUser().subscribe((user: UserRepresentation) => { - this.currentLoggedUser = user; - }); } ngOnInit() { + this.peopleSearch = new Observable((observer) => this.peopleSearchObserver = observer).pipe(share()); + this.authService.getBpmLoggedUser().subscribe(user => { + this.currentLoggedUser = user; + }); + if (this.taskId) { this.loadDetails(this.taskId); } - this.cardViewUpdateService.itemUpdated$.subscribe(this.updateTaskDetails.bind(this)); - this.cardViewUpdateService.itemClicked$.subscribe(this.clickTaskDetails.bind(this)); + this.cardViewUpdateService.itemUpdated$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(this.updateTaskDetails.bind(this)); + + this.cardViewUpdateService.itemClicked$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(this.clickTaskDetails.bind(this)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } ngOnChanges(changes: SimpleChanges): void { @@ -265,12 +273,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges { * @param updateNotification */ private updateTaskDetails(updateNotification: UpdateNotification) { - this.taskListService.updateTask(this.taskId, updateNotification.changed) - .subscribe( - () => { - this.loadDetails(this.taskId); - } - ); + this.taskListService + .updateTask(this.taskId, updateNotification.changed) + .subscribe(() => this.loadDetails(this.taskId)); } private clickTaskDetails(clickNotification: ClickNotification) { @@ -386,9 +391,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges { * Complete button clicked */ onComplete(): void { - this.taskListService.completeTask(this.taskId).subscribe( - (res) => this.onFormCompleted(null) - ); + this.taskListService + .completeTask(this.taskId) + .subscribe(() => this.onFormCompleted(null)); } onShowAttachForm() { @@ -461,10 +466,13 @@ export class TaskDetailsComponent implements OnInit, OnChanges { searchUser(searchedWord: string) { this.peopleProcessService.getWorkflowUsers(null, searchedWord) - .subscribe((users) => { - users = users.filter((user) => user.id !== this.taskDetails.assignee.id); - this.peopleSearchObserver.next(users); - }, (error) => this.logService.error('Could not load users')); + .subscribe( + users => { + users = users.filter((user) => user.id !== this.taskDetails.assignee.id); + this.peopleSearchObserver.next(users); + }, + () => this.logService.error('Could not load users') + ); } onCloseSearch() { @@ -472,8 +480,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges { } assignTaskToUser(selectedUser: UserProcessModel) { - this.taskListService.assignTask(this.taskDetails.id, selectedUser).subscribe( - (res: any) => { + this.taskListService + .assignTask(this.taskDetails.id, selectedUser) + .subscribe(() => { this.logService.info('Task Assigned to ' + selectedUser.email); this.assignTask.emit(); }); diff --git a/lib/process-services/task-list/components/task-header.component.ts b/lib/process-services/task-list/components/task-header.component.ts index 751bacb046..7bd74af996 100644 --- a/lib/process-services/task-list/components/task-header.component.ts +++ b/lib/process-services/task-list/components/task-header.component.ts @@ -285,11 +285,10 @@ export class TaskHeaderComponent implements OnChanges, OnInit { * @param taskId */ claimTask(taskId: string) { - this.activitiTaskService.claimTask(taskId).subscribe( - (res: any) => { - this.logService.info('Task claimed'); - this.claim.emit(taskId); - }); + this.activitiTaskService.claimTask(taskId).subscribe(() => { + this.logService.info('Task claimed'); + this.claim.emit(taskId); + }); } /** @@ -298,11 +297,10 @@ export class TaskHeaderComponent implements OnChanges, OnInit { * @param taskId */ unclaimTask(taskId: string) { - this.activitiTaskService.unclaimTask(taskId).subscribe( - (res: any) => { - this.logService.info('Task unclaimed'); - this.unclaim.emit(taskId); - }); + this.activitiTaskService.unclaimTask(taskId).subscribe(() => { + this.logService.info('Task unclaimed'); + this.unclaim.emit(taskId); + }); } /** diff --git a/lib/process-services/task-list/components/task-list.component.ts b/lib/process-services/task-list/components/task-list.component.ts index fb0b238cbd..4d5d72ed9a 100644 --- a/lib/process-services/task-list/components/task-list.component.ts +++ b/lib/process-services/task-list/components/task-list.component.ts @@ -21,21 +21,22 @@ import { UserPreferencesService, UserPreferenceValues, PaginationModel } from '@alfresco/adf-core'; import { AfterContentInit, Component, ContentChild, EventEmitter, - Input, OnChanges, Output, SimpleChanges } from '@angular/core'; + Input, OnChanges, Output, SimpleChanges, OnDestroy, OnInit } from '@angular/core'; -import { Observable, BehaviorSubject } from 'rxjs'; +import { Observable, BehaviorSubject, Subject } from 'rxjs'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskListModel } from '../models/task-list.model'; import { taskPresetsDefaultModel } from '../models/task-preset.model'; import { TaskListService } from './../services/tasklist.service'; import moment from 'moment-es6'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-tasklist', templateUrl: './task-list.component.html', styleUrls: ['./task-list.component.css'] }) -export class TaskListComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent { +export class TaskListComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy, OnInit { static PRESET_KEY = 'adf-task-list.presets'; @@ -170,13 +171,12 @@ export class TaskListComponent extends DataTableSchema implements OnChanges, Aft */ hasCustomDataSource: boolean = false; + private onDestroy$ = new Subject(); + constructor(private taskListService: TaskListService, appConfigService: AppConfigService, private userPreferences: UserPreferencesService) { super(appConfigService, TaskListComponent.PRESET_KEY, taskPresetsDefaultModel); - this.userPreferences.select(UserPreferenceValues.PaginationSize).subscribe((pageSize) => { - this.size = pageSize; - }); this.pagination = new BehaviorSubject( { maxItems: this.size, @@ -196,6 +196,18 @@ export class TaskListComponent extends DataTableSchema implements OnChanges, Aft } } + ngOnInit() { + this.userPreferences + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pageSize => this.size = pageSize); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + setCustomDataSource(rows: any[]): void { if (rows) { this.rows = rows; diff --git a/package.json b/package.json index e3b78cd99b..72257d35d1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "lint-lib": "./node_modules/.bin/tslint -p ./lib/tsconfig.json -c ./lib/tslint.json", "lint-e2e": "tsc -p ./e2e/tsconfig.e2e.json && ./node_modules/.bin/tslint -p ./e2e/tsconfig.e2e.json -c ./tslint.json", "validate-config": "ajv validate -s ./lib/core/app-config/schema.json -d ./demo-shell/src/app.config.json --errors=text --verbose", - "spellcheck": "cspell 'demo-shell/{src,e2e}/**/*.ts' 'e2e/**/*.ts' 'lib/{content-services,core,extensions,insights,process-services}/**/*.ts'", + "spellcheck": "cspell 'demo-shell/{src,e2e}/**/*.ts' 'e2e/**/*.ts' 'lib/{content-services,core,extensions,insights,process-services,process-services-cloud}/**/*.ts'", "stylelint": "stylelint ./**/*.scss --config stylelint-config.json", "04": "echo -------------------------------------------- Demo Shell -----------------------------------------------", "04s": "", diff --git a/tslint.json b/tslint.json index 0d8ea983e0..fc0e304ecc 100644 --- a/tslint.json +++ b/tslint.json @@ -175,7 +175,6 @@ "format": "PascalCase" } ], - "arrow-parens": true, "no-input-prefix": true, "ordered-imports": false, "template-conditional-complexity": [