[ADF-4745] memory leak fixes (#4931)

* fix app-layout component

* fix card-view component

* fix cloud-layout service

* code fixes

* code fixes

* even more fixes

* even more fixes

* lint fixes

* test fixes

* fix code

* remove useless pipes

* fix code owners

* enable spellcheck for cloud components

* update test

* update test
This commit is contained in:
Denys Vuika
2019-07-16 15:56:00 +01:00
committed by Eugenio Romano
parent e2311ab045
commit 1abb9bfc89
98 changed files with 1581 additions and 1066 deletions

View File

@@ -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');

View File

@@ -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<boolean>();
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();
}
}

View File

@@ -57,7 +57,7 @@ describe('DropdownBreadcrumb', () => {
function triggerComponentChange(fakeNodeData) {
component.folderNode = fakeNodeData;
component.ngOnChanges(null);
component.ngOnChanges();
fixture.detectChanges();
}

View File

@@ -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<boolean>();
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;

View File

@@ -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<boolean>();
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() {

View File

@@ -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<boolean>();
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);
}
}
}

View File

@@ -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<PaginationModel> = new BehaviorSubject<PaginationModel>(this.DEFAULT_PAGINATION);
private layoutPresets = {};
private subscriptions: Subscription[] = [];
private rowMenuCache: { [key: string]: ContentActionModel[] } = {};
private loadingTimeout;
private onDestroy$ = new Subject<boolean>();
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) {

View File

@@ -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<string>('');
node: NodeEntry;
private sub: Subscription;
private onDestroy$ = new Subject<boolean>();
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();
}
}

View File

@@ -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<string>('');
private sub: Subscription;
private onDestroy$ = new Subject<boolean>();
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();
}
}

View File

@@ -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<string>('');
private sub: Subscription;
private onDestroy$ = new Subject<boolean>();
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();
}
}

View File

@@ -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<string>('');
node: NodeEntry;
private sub: Subscription;
private onDestroy$ = new Subject<boolean>();
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();
}
}

View File

@@ -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)
);
}
}

View File

@@ -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<FocusEvent> = this.focusSubject
.asObservable()
.pipe(
debounceTime(50),
filter(($event: any) => {
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout');
})
}),
takeUntil(this.onDestroy$)
);
focusEvents.subscribe(() => {

View File

@@ -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<boolean>();
constructor(private dateAdapter: DateAdapter<Moment>,
private userPreferencesService: UserPreferencesService) {
}
@@ -82,9 +86,10 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit {
const theCustomDateAdapter = <MomentDateAdapter> <any> 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();

View File

@@ -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<boolean>();
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() {

View File

@@ -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<string> = new Subject();
keyPressedStream = new Subject<string>();
_classList: { [key: string]: boolean } = {};
private onDestroy$ = new Subject<boolean>();
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();

View File

@@ -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<boolean>();
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;

View File

@@ -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<boolean>();
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() {

View File

@@ -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<boolean>();
/**
* 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) {

View File

@@ -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<boolean>();
/**
* 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() {

View File

@@ -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<UploadFilesEvent>();
protected subscriptions: Subscription[] = [];
protected onDestroy$ = new Subject<boolean>();
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();
}
/**

View File

@@ -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<boolean> = new Subject<boolean>();
private onDestroy$ = new Subject<boolean>();
@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$))