diff --git a/package.json b/package.json index 47bc7a419c..b3e16e29e8 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "ng2-nouislider": "^1.7.11", "ngx-bootstrap": "^3.0.1", "ngx-infinite-scroll": "6.0.1", + "ngx-moment": "^3.1.0", "ngx-pagination": "3.0.3", "nouislider": "^11.0.0", "pem": "1.12.3", @@ -154,7 +155,7 @@ "css-loader": "0.28.9", "deep-freeze": "0.0.1", "exports-loader": "^0.7.0", - "html-webpack-plugin": "3.2.0", + "html-webpack-plugin": "^4.0.0-alpha", "imports-loader": "0.7.1", "istanbul-instrumenter-loader": "3.0.1", "jasmine-core": "^3.2.1", @@ -188,7 +189,7 @@ "protractor": "^5.3.0", "protractor-istanbul-plugin": "2.0.0", "raw-loader": "0.5.1", - "resolve-url-loader": "2.2.1", + "resolve-url-loader": "^2.3.0", "rimraf": "2.6.2", "rollup": "^0.56.0", "rollup-plugin-commonjs": "^8.3.0", diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 127fd8ce98..59cf83777f 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Observable , Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { CollectionDataService } from '../core/data/collection-data.service'; import { PaginatedList } from '../core/data/paginated-list'; diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 69738733a8..a6e1d5376c 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -1,7 +1,8 @@ +import {mergeMap, filter, map} from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Subscription , Observable } from 'rxjs'; +import { Subscription, Observable } from 'rxjs'; import { CommunityDataService } from '../core/data/community-data.service'; import { RemoteData } from '../core/data/remote-data'; import { Bitstream } from '../core/shared/bitstream.model'; @@ -34,11 +35,11 @@ export class CommunityPageComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.communityRD$ = this.route.data.pipe((data) => data.community); - this.logoRD$ = this.communityRD$ - .map((rd: RemoteData) => rd.payload) - .filter((community: Community) => hasValue(community)) - .flatMap((community: Community) => community.logo); + this.communityRD$ = this.route.data.pipe(map((data) => data.community)); + this.logoRD$ = this.communityRD$.pipe( + map((rd: RemoteData) => rd.payload), + filter((community: Community) => hasValue(community)), + mergeMap((community: Community) => community.logo)); } ngOnDestroy(): void { diff --git a/src/app/+item-page/field-components/collections/collections.component.ts b/src/app/+item-page/field-components/collections/collections.component.ts index 4665d60caf..b33c5fd41b 100644 --- a/src/app/+item-page/field-components/collections/collections.component.ts +++ b/src/app/+item-page/field-components/collections/collections.component.ts @@ -1,3 +1,5 @@ + +import {map} from 'rxjs/operators'; import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; @@ -35,11 +37,11 @@ export class CollectionsComponent implements OnInit { // TODO: this should use parents, but the collections // for an Item aren't returned by the REST API yet, // only the owning collection - this.collections = this.item.owner.map((rd: RemoteData) => [rd.payload]); + this.collections = this.item.owner.pipe(map((rd: RemoteData) => [rd.payload])); } hasSucceeded() { - return this.item.owner.map((rd: RemoteData) => rd.hasSucceeded); + return this.item.owner.pipe(map((rd: RemoteData) => rd.hasSucceeded)); } } diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts index 5dc5bfdf6c..db3c30aea2 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.ts @@ -1,5 +1,6 @@ + +import {combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Component, Input, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Item } from '../../../../core/shared/item.model'; @@ -33,7 +34,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On initialize(): void { const originals = this.item.getFiles(); const licenses = this.item.getBitstreamsByBundleName('LICENSE'); - this.bitstreamsObs = Observable.combineLatest(originals, licenses, (o, l) => [...o, ...l]); + this.bitstreamsObs = observableCombineLatest(originals, licenses, (o, l) => [...o, ...l]); this.bitstreamsObs.subscribe( (files) => files.forEach( diff --git a/src/app/+item-page/full/full-item-page.component.ts b/src/app/+item-page/full/full-item-page.component.ts index 4d27b9d8c7..d09ac268ec 100644 --- a/src/app/+item-page/full/full-item-page.component.ts +++ b/src/app/+item-page/full/full-item-page.component.ts @@ -1,3 +1,5 @@ + +import {filter, map} from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -41,9 +43,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit { /*** AoT inheritance fix, will hopefully be resolved in the near future **/ ngOnInit(): void { super.ngOnInit(); - this.metadata$ = this.itemRD$ - .map((rd: RemoteData) => rd.payload) - .filter((item: Item) => hasValue(item)) - .map((item: Item) => item.metadata); + this.metadata$ = this.itemRD$.pipe( + map((rd: RemoteData) => rd.payload), + filter((item: Item) => hasValue(item)), + map((item: Item) => item.metadata),); } } diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index 6f01a30613..35162b011f 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -1,3 +1,5 @@ + +import {mergeMap, filter, map} from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -44,11 +46,11 @@ export class ItemPageComponent implements OnInit { } ngOnInit(): void { - this.itemRD$ = this.route.data.map((data) => data.item); + this.itemRD$ = this.route.data.pipe(map((data) => data.item)); this.metadataService.processRemoteData(this.itemRD$); - this.thumbnail$ = this.itemRD$ - .map((rd: RemoteData) => rd.payload) - .filter((item: Item) => hasValue(item)) - .flatMap((item: Item) => item.getThumbnail()); + this.thumbnail$ = this.itemRD$.pipe( + map((rd: RemoteData) => rd.payload), + filter((item: Item) => hasValue(item)), + mergeMap((item: Item) => item.getThumbnail()),); } } diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 00cf6bdc00..154eb21600 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -1,20 +1,19 @@ +import {combineLatest as observableCombineLatest, of as observableOf, BehaviorSubject , Observable , Subject , Subscription } from 'rxjs'; +import {switchMap, distinctUntilChanged, first, map } from 'rxjs/operators'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { BehaviorSubject , Observable , Subject , Subscription } from 'rxjs'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { PaginatedList } from '../../../../core/data/paginated-list'; import { RemoteData } from '../../../../core/data/remote-data'; import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util'; import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe'; -import { SearchOptions } from '../../../search-options.model'; import { FacetValue } from '../../../search-service/facet-value.model'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { SearchService } from '../../../search-service/search.service'; import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; import { getSucceededRemoteData } from '../../../../core/shared/operators'; -import { map } from 'rxjs/operators'; @Component({ selector: 'ds-search-facet-filter', @@ -53,7 +52,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { /** * Emits the result values for this filter found by the current filter query */ - filterSearchResults: Observable = Observable.of([]); + filterSearchResults: Observable = observableOf([]); /** * Emits the active values for this filter @@ -79,25 +78,25 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined)); - this.currentPage = this.getCurrentPage().distinctUntilChanged(); + this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged()); this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig); const searchOptions = this.searchConfigService.searchOptions; this.subs.push(this.searchConfigService.searchOptions.subscribe(() => this.updateFilterValueList())); - const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => { + const facetValues = observableCombineLatest(searchOptions, this.currentPage, (options, page) => { return { options, page } - }).switchMap(({ options, page }) => { + }).pipe(switchMap(({ options, page }) => { return this.searchService.getFacetValuesFor(this.filterConfig, page, options) .pipe( getSucceededRemoteData(), map((results) => { return { - values: Observable.of(results), + values: observableOf(results), page: page }; } ) ) - }); + })); let filterValues = []; this.subs.push(facetValues.subscribe((facetOutcome) => { const newValues$ = facetOutcome.values; @@ -117,7 +116,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.animationState = 'ready'; this.filterValues$.next(rd); })); - this.subs.push(newValues$.first().subscribe((rd) => { + this.subs.push(newValues$.pipe(first()).subscribe((rd) => { this.isLastPage$.next(hasNoValue(rd.payload.next)) })); })); @@ -180,7 +179,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { * @param data The string from the input field */ onSubmit(data: any) { - this.selectedValues.first().subscribe((selectedValues) => { + this.selectedValues.pipe(first()).subscribe((selectedValues) => { if (isNotEmpty(data)) { this.router.navigate([this.getSearchLink()], { queryParams: @@ -189,7 +188,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { }); this.filter = ''; } - this.filterSearchResults = Observable.of([]); + this.filterSearchResults = observableOf([]); } ) } @@ -211,12 +210,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { * @returns {Observable} The changed filter parameters */ getRemoveParams(value: string): Observable { - return this.selectedValues.map((selectedValues) => { + return this.selectedValues.pipe(map((selectedValues) => { return { [this.filterConfig.paramName]: selectedValues.filter((v) => v !== value), page: 1 }; - }); + })); } /** @@ -225,12 +224,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { * @returns {Observable} The changed filter parameters */ getAddParams(value: string): Observable { - return this.selectedValues.map((selectedValues) => { + return this.selectedValues.pipe(map((selectedValues) => { return { [this.filterConfig.paramName]: [...selectedValues, value], page: 1 }; - }); + })); } /** @@ -249,7 +248,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { */ findSuggestions(data): void { if (isNotEmpty(data)) { - this.searchConfigService.searchOptions.first().subscribe( + this.searchConfigService.searchOptions.pipe(first()).subscribe( (options) => { this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase()) .pipe( @@ -264,7 +263,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { } ) } else { - this.filterSearchResults = Observable.of([]); + this.filterSearchResults = observableOf([]); } } diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts index 8d20378910..87f8edc1ea 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts @@ -1,3 +1,5 @@ + +import {first} from 'rxjs/operators'; import { Component, Input, OnInit } from '@angular/core'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchFilterService } from './search-filter.service'; @@ -35,7 +37,7 @@ export class SearchFilterComponent implements OnInit { * Else, the filter should initially be collapsed */ ngOnInit() { - this.getSelectedValues().first().subscribe((isActive) => { + this.getSelectedValues().pipe(first()).subscribe((isActive) => { if (this.filter.isOpenByDefault || isNotEmpty(isActive)) { this.initialExpand(); } else { diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 3f3f14b9e6..35e6558375 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -1,8 +1,8 @@ +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable, InjectionToken } from '@angular/core'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { SearchFiltersState, SearchFilterState } from './search-filter.reducer'; -import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { SearchFilterCollapseAction, SearchFilterDecrementPageAction, @@ -63,8 +63,8 @@ export class SearchFilterService { */ getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable { const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName); - const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').map((params: Params) => [].concat(...Object.values(params))); - return Observable.combineLatest(values$, prefixValues$, (values, prefixValues) => { + const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(map((params: Params) => [].concat(...Object.values(params)))); + return observableCombineLatest(values$, prefixValues$, (values, prefixValues) => { if (isNotEmpty(values)) { return values; } @@ -78,14 +78,16 @@ export class SearchFilterService { * @returns {Observable} Emits the current collapsed state of the given filter, if it's unavailable, return false */ isCollapsed(filterName: string): Observable { - return this.store.select(filterByNameSelector(filterName)) - .map((object: SearchFilterState) => { + return this.store.pipe( + select(filterByNameSelector(filterName)), + map((object: SearchFilterState) => { if (object) { return object.filterCollapsed; } else { return false; } - }); + }) + ); } /** @@ -94,14 +96,15 @@ export class SearchFilterService { * @returns {Observable} Emits the current page state of the given filter, if it's unavailable, return 1 */ getPage(filterName: string): Observable { - return this.store.select(filterByNameSelector(filterName)) - .map((object: SearchFilterState) => { + return this.store.pipe( + select(filterByNameSelector(filterName)), + map((object: SearchFilterState) => { if (object) { return object.page; } else { return 1; } - }); + })); } /** @@ -159,6 +162,7 @@ export class SearchFilterService { public incrementPage(filterName: string): void { this.store.dispatch(new SearchFilterIncrementPageAction(filterName)); } + /** * Dispatches a reset page action to the store for a given filter * @param {string} filterName The filter for which the action is dispatched diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 0f3832a226..3c76d4391b 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -1,3 +1,5 @@ +import {of as observableOf, combineLatest as observableCombineLatest, Observable , Subscription } from 'rxjs'; +import {startWith} from 'rxjs/operators'; import { isPlatformBrowser } from '@angular/common'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; @@ -12,7 +14,6 @@ import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service'; import { SearchService } from '../../../search-service/search.service'; import { Router } from '@angular/router'; import * as moment from 'moment'; -import { Observable , Subscription } from 'rxjs'; import { RouteService } from '../../../../shared/services/route.service'; import { hasValue } from '../../../../shared/empty.util'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; @@ -79,9 +80,9 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple super.ngOnInit(); this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min; this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max; - const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).startWith(undefined); - const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).startWith(undefined); - this.sub = Observable.combineLatest(iniMin, iniMax, (min, max) => { + const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).pipe(startWith(undefined)); + const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).pipe(startWith(undefined)); + this.sub = observableCombineLatest(iniMin, iniMax, (min, max) => { const minimum = hasValue(min) ? min : this.min; const maximum = hasValue(max) ? max : this.max; return [minimum, maximum] @@ -97,7 +98,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple const parts = value.split(rangeDelimiter); const min = parts.length > 1 ? parts[0].trim() : value; const max = parts.length > 1 ? parts[1].trim() : value; - return Observable.of( + return observableOf( { [this.filterConfig.paramName + minSuffix]: [min], [this.filterConfig.paramName + maxSuffix]: [max], diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index f65c3b4b5b..7f1eb513ea 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -1,8 +1,10 @@ +import { Observable, of as observableOf } from 'rxjs'; + +import { filter, map, mergeMap, startWith, switchMap } from 'rxjs/operators'; import { Component } from '@angular/core'; import { SearchService } from '../search-service/search.service'; import { RemoteData } from '../../core/data/remote-data'; import { SearchFilterConfig } from '../search-service/search-filter-config.model'; -import { Observable } from 'rxjs'; import { SearchConfigurationService } from '../search-service/search-configuration.service'; import { isNotEmpty } from '../../shared/empty.util'; import { SearchFilterService } from './search-filter/search-filter.service'; @@ -37,10 +39,10 @@ export class SearchFiltersComponent { */ constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService, private filterService: SearchFilterService) { this.filters = searchService.getConfig().pipe(getSucceededRemoteData()); - this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => { + this.clearParams = searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => { Object.keys(filters).forEach((f) => filters[f] = null); return filters; - }); + })); } /** @@ -55,23 +57,23 @@ export class SearchFiltersComponent { * @param {SearchFilterConfig} filter The filter to check for * @returns {Observable} Emits true whenever a given filter config should be shown */ - isActive(filter: SearchFilterConfig): Observable { + isActive(filterConfig: SearchFilterConfig): Observable { // console.log(filter.name); - return this.filterService.getSelectedValuesForFilter(filter) - .flatMap((isActive) => { + return this.filterService.getSelectedValuesForFilter(filterConfig).pipe( + mergeMap((isActive) => { if (isNotEmpty(isActive)) { - return Observable.of(true); + return observableOf(true); } else { - return this.searchConfigService.searchOptions - .switchMap((options) => { - return this.searchService.getFacetValuesFor(filter, 1, options) - .filter((RD) => !RD.isLoading) - .map((valuesRD) => { + return this.searchConfigService.searchOptions.pipe( + switchMap((options) => { + return this.searchService.getFacetValuesFor(filterConfig, 1, options).pipe( + filter((RD) => !RD.isLoading), + map((valuesRD) => { return valuesRD.payload.totalElements > 0 - }) + }),) } - ) + )) } - }).startWith(true); + }),startWith(true),); } } diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index a305193cf6..816e3d67bf 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { Observable , Subscription , BehaviorSubject } from 'rxjs'; -import { flatMap, switchMap, } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { switchMap, } from 'rxjs/operators'; import { PaginatedList } from '../core/data/paginated-list'; import { RemoteData } from '../core/data/remote-data'; import { DSpaceObject } from '../core/shared/dspace-object.model'; @@ -76,8 +76,8 @@ export class SearchPageComponent implements OnInit { */ ngOnInit(): void { this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; - this.sub = this.searchOptions$ - .switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData())) + this.sub = this.searchOptions$.pipe( + switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData()))) .subscribe((results) => { this.resultsRD$.next(results); }); diff --git a/src/app/+search-page/search-service/search-configuration.service.ts b/src/app/+search-page/search-service/search-configuration.service.ts index b509a13274..39389e6065 100644 --- a/src/app/+search-page/search-service/search-configuration.service.ts +++ b/src/app/+search-page/search-service/search-configuration.service.ts @@ -1,7 +1,8 @@ +import {of as observableOf, merge as observableMerge, combineLatest as observableCombineLatest, Observable , BehaviorSubject , Subscription } from 'rxjs'; +import {filter, map} from 'rxjs/operators'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SearchOptions } from '../search-options.model'; -import { Observable , BehaviorSubject , Subscription } from 'rxjs'; import { ActivatedRoute, Params } from '@angular/router'; import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { Injectable, OnDestroy } from '@angular/core'; @@ -85,27 +86,27 @@ export class SearchConfigurationService implements OnDestroy { * @returns {Observable} Emits the current scope's identifier */ getCurrentScope(defaultScope: string) { - return this.routeService.getQueryParameterValue('scope').map((scope) => { + return this.routeService.getQueryParameterValue('scope').pipe(map((scope) => { return scope || defaultScope; - }); + })); } /** * @returns {Observable} Emits the current query string */ getCurrentQuery(defaultQuery: string) { - return this.routeService.getQueryParameterValue('query').map((query) => { + return this.routeService.getQueryParameterValue('query').pipe(map((query) => { return query || defaultQuery; - }); + })); } /** * @returns {Observable} Emits the current DSpaceObject type as a number */ getCurrentDSOType(): Observable { - return this.routeService.getQueryParameterValue('dsoType') - .filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()])) - .map((type) => DSpaceObjectType[type.toUpperCase()]); + return this.routeService.getQueryParameterValue('dsoType').pipe( + filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()])), + map((type) => DSpaceObjectType[type.toUpperCase()]),); } /** @@ -114,7 +115,7 @@ export class SearchConfigurationService implements OnDestroy { getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable { const page$ = this.routeService.getQueryParameterValue('page'); const size$ = this.routeService.getQueryParameterValue('pageSize'); - return Observable.combineLatest(page$, size$, (page, size) => { + return observableCombineLatest(page$, size$, (page, size) => { return Object.assign(new PaginationComponentOptions(), defaultPagination, { currentPage: page || defaultPagination.currentPage, pageSize: size || defaultPagination.pageSize @@ -128,7 +129,7 @@ export class SearchConfigurationService implements OnDestroy { getCurrentSort(defaultSort: SortOptions): Observable { const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); const sortField$ = this.routeService.getQueryParameterValue('sortField'); - return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => { + return observableCombineLatest(sortDirection$, sortField$, (sortDirection, sortField) => { // Dirty fix because sometimes the observable value is null somehow sortField = this.route.snapshot.queryParamMap.get('sortField'); @@ -143,7 +144,7 @@ export class SearchConfigurationService implements OnDestroy { * @returns {Observable} Emits the current active filters with their values as they are sent to the backend */ getCurrentFilters(): Observable { - return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => { + return this.routeService.getQueryParamsWithPrefix('f.').pipe(map((filterParams) => { if (isNotEmpty(filterParams)) { const filters = []; Object.keys(filterParams).forEach((key) => { @@ -161,7 +162,7 @@ export class SearchConfigurationService implements OnDestroy { return filters; } return []; - }); + })); } /** @@ -177,7 +178,7 @@ export class SearchConfigurationService implements OnDestroy { * @returns {Subscription} The subscription to unsubscribe from */ subscribeToSearchOptions(defaults: SearchOptions): Subscription { - return Observable.merge( + return observableMerge( this.getScopePart(defaults.scope), this.getQueryPart(defaults.query), this.getDSOTypePart(), @@ -195,7 +196,7 @@ export class SearchConfigurationService implements OnDestroy { * @returns {Subscription} The subscription to unsubscribe from */ subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription { - return Observable.merge( + return observableMerge( this.getPaginationPart(defaults.pagination), this.getSortPart(defaults.sort), this.getScopePart(defaults.scope), @@ -220,7 +221,7 @@ export class SearchConfigurationService implements OnDestroy { scope: this.defaultScope, query: this.defaultQuery }); - this._defaults = Observable.of(new RemoteData(false, false, true, null, options)); + this._defaults = observableOf(new RemoteData(false, false, true, null, options)); } return this._defaults; } @@ -238,53 +239,53 @@ export class SearchConfigurationService implements OnDestroy { * @returns {Observable} Emits the current scope's identifier */ private getScopePart(defaultScope: string): Observable { - return this.getCurrentScope(defaultScope).map((scope) => { + return this.getCurrentScope(defaultScope).pipe(map((scope) => { return { scope } - }); + })); } /** * @returns {Observable} Emits the current query string as a partial SearchOptions object */ private getQueryPart(defaultQuery: string): Observable { - return this.getCurrentQuery(defaultQuery).map((query) => { + return this.getCurrentQuery(defaultQuery).pipe(map((query) => { return { query } - }); + })); } /** * @returns {Observable} Emits the current query string as a partial SearchOptions object */ private getDSOTypePart(): Observable { - return this.getCurrentDSOType().map((dsoType) => { + return this.getCurrentDSOType().pipe(map((dsoType) => { return { dsoType } - }); + })); } /** * @returns {Observable} Emits the current pagination settings as a partial SearchOptions object */ private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable { - return this.getCurrentPagination(defaultPagination).map((pagination) => { + return this.getCurrentPagination(defaultPagination).pipe(map((pagination) => { return { pagination } - }); + })); } /** * @returns {Observable} Emits the current sorting settings as a partial SearchOptions object */ private getSortPart(defaultSort: SortOptions): Observable { - return this.getCurrentSort(defaultSort).map((sort) => { + return this.getCurrentSort(defaultSort).pipe(map((sort) => { return { sort } - }); + })); } /** * @returns {Observable} Emits the current active filters as a partial SearchOptions object */ private getFiltersPart(): Observable { - return this.getCurrentFilters().map((filters) => { + return this.getCurrentFilters().pipe(map((filters) => { return { filters } - }); + })); } } diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 0d30c7f458..12ab6124c1 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -1,3 +1,4 @@ +import {of as observableOf, combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute, @@ -6,7 +7,6 @@ import { Router, UrlSegmentGroup } from '@angular/router'; -import { Observable } from 'rxjs'; import { flatMap, map, switchMap } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { @@ -122,7 +122,7 @@ export class SearchService implements OnDestroy { ); // Create search results again with the correct dso objects linked to each result - const tDomainListObs = Observable.combineLatest(sqrObs, dsoObs, (sqr: SearchQueryResponse, dsos: RemoteData) => { + const tDomainListObs = observableCombineLatest(sqrObs, dsoObs, (sqr: SearchQueryResponse, dsos: RemoteData) => { return sqr.objects.map((object: NormalizedSearchResult, index: number) => { let co = DSpaceObject; @@ -143,7 +143,7 @@ export class SearchService implements OnDestroy { map((response: FacetValueSuccessResponse) => response.pageInfo) ); - const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => { + const payloadObs = observableCombineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => { return new PaginatedList(pageInfo, tDomainList); }); @@ -244,7 +244,7 @@ export class SearchService implements OnDestroy { map((response: FacetValueSuccessResponse) => response.pageInfo) ); - const payloadObs = Observable.combineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => { + const payloadObs = observableCombineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => { return new PaginatedList(pageInfo, facetValue); }); @@ -272,12 +272,12 @@ export class SearchService implements OnDestroy { switchMap((dsoRD: RemoteData) => { if (dsoRD.payload.type === ResourceType.Community) { const community: Community = dsoRD.payload as Community; - return Observable.combineLatest(community.subcommunities, community.collections, (subCommunities, collections) => { + return observableCombineLatest(community.subcommunities, community.collections, (subCommunities, collections) => { /*if this is a community, we also need to show the direct children*/ return [community, ...subCommunities.payload.page, ...collections.payload.page] }) } else { - return Observable.of([dsoRD.payload]); + return observableOf([dsoRD.payload]); } } )); @@ -291,13 +291,13 @@ export class SearchService implements OnDestroy { * @returns {Observable} The current view mode */ getViewMode(): Observable { - return this.route.queryParams.map((params) => { + return this.route.queryParams.pipe(map((params) => { if (isNotEmpty(params.view) && hasValue(params.view)) { return params.view; } else { return ViewMode.List; } - }); + })); } /** diff --git a/src/app/+search-page/search-sidebar/search-sidebar.effects.ts b/src/app/+search-page/search-sidebar/search-sidebar.effects.ts index 758ef2320b..1f5fb0ef60 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.effects.ts +++ b/src/app/+search-page/search-sidebar/search-sidebar.effects.ts @@ -1,5 +1,6 @@ +import { map, tap, filter } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { Effect, Actions } from '@ngrx/effects' +import { Effect, Actions, ofType } from '@ngrx/effects' import * as fromRouter from '@ngrx/router-store'; import { SearchSidebarCollapseAction } from './search-sidebar.actions'; @@ -12,10 +13,14 @@ import { URLBaser } from '../../core/url-baser/url-baser'; export class SearchSidebarEffects { private previousPath: string; @Effect() routeChange$ = this.actions$ - .ofType(fromRouter.ROUTER_NAVIGATION) - .filter((action) => this.previousPath !== this.getBaseUrl(action)) - .do((action) => {this.previousPath = this.getBaseUrl(action)}) - .map(() => new SearchSidebarCollapseAction()); + .pipe( + ofType(fromRouter.ROUTER_NAVIGATION), + filter((action) => this.previousPath !== this.getBaseUrl(action)), + tap((action) => { + this.previousPath = this.getBaseUrl(action) + }), + map(() => new SearchSidebarCollapseAction()) + ); constructor(private actions$: Actions) { diff --git a/src/app/+search-page/search-sidebar/search-sidebar.service.ts b/src/app/+search-page/search-sidebar/search-sidebar.service.ts index c9a7fb57c0..d74fe66348 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.service.ts +++ b/src/app/+search-page/search-sidebar/search-sidebar.service.ts @@ -1,8 +1,8 @@ +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable } from '@angular/core'; import { SearchSidebarState } from './search-sidebar.reducer'; -import { createSelector, Store } from '@ngrx/store'; +import { createSelector, select, Store } from '@ngrx/store'; import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions'; -import { Observable } from 'rxjs'; import { AppState } from '../../app.reducer'; import { HostWindowService } from '../../shared/host-window.service'; @@ -26,7 +26,7 @@ export class SearchSidebarService { constructor(private store: Store, private windowService: HostWindowService) { this.isXsOrSm$ = this.windowService.isXsOrSm(); - this.isCollapsedInStore = this.store.select(sidebarCollapsedSelector); + this.isCollapsedInStore = this.store.pipe(select(sidebarCollapsedSelector)); } /** @@ -34,7 +34,7 @@ export class SearchSidebarService { * @returns {Observable} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed */ get isCollapsed(): Observable { - return Observable.combineLatest( + return observableCombineLatest( this.isXsOrSm$, this.isCollapsedInStore, (mobile, store) => mobile ? store : true); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 092c61bdf9..7d4bfe4f33 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,3 +1,4 @@ +import { filter, first, take } from 'rxjs/operators'; import { AfterViewInit, ChangeDetectionStrategy, @@ -9,7 +10,7 @@ import { } from '@angular/core'; import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; @@ -62,10 +63,10 @@ export class AppComponent implements OnInit, AfterViewInit { this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); // Whether is not authenticathed try to retrieve a possible stored auth token - this.store.select(isAuthenticated) - .take(1) - .filter((authenticated) => !authenticated) - .subscribe((authenticated) => this.authService.checkAuthenticationToken()); + this.store.pipe(select(isAuthenticated), + first(), + filter((authenticated) => !authenticated) + ).subscribe((authenticated) => this.authService.checkAuthenticationToken()); } diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 8204f02556..92af4644c6 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,4 +1,6 @@ -import { Observable, throwError as observableThrowError } from 'rxjs'; +import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; + +import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ResponseCacheService } from '../cache/response-cache.service'; @@ -8,7 +10,7 @@ import { GlobalConfig } from '../../../config/global-config.interface'; import { isNotEmpty } from '../../shared/empty.util'; import { AuthGetRequest, AuthPostRequest, PostRequest, RestRequest } from '../data/request.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { AuthStatusResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { AuthStatusResponse, ErrorResponse } from '../cache/response-cache.models'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; @Injectable() @@ -23,18 +25,18 @@ export class AuthRequestService { } protected fetchRequest(request: RestRequest): Observable { - const [successResponse, errorResponse] = this.responseCache.get(request.href) - .map((entry: ResponseCacheEntry) => entry.response) + return this.responseCache.get(request.href).pipe( + map((entry: ResponseCacheEntry) => entry.response), // TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed - .do(() => this.responseCache.remove(request.href)) - .partition((response: RestResponse) => response.isSuccessful); - return Observable.merge( - errorResponse.flatMap((response: ErrorResponse) => - observableThrowError(new Error(response.errorMessage))), - successResponse - .filter((response: AuthStatusResponse) => isNotEmpty(response)) - .map((response: AuthStatusResponse) => response.response) - .distinctUntilChanged()); + tap(() => this.responseCache.remove(request.href)), + mergeMap((response) => { + if (response.isSuccessful && isNotEmpty(response)) { + return observableOf((response as AuthStatusResponse).response); + } else if (!response.isSuccessful) { + return observableThrowError(new Error((response as ErrorResponse).errorMessage)); + } + }) + ); } protected getEndpointByMethod(endpoint: string, method: string): string { @@ -42,24 +44,24 @@ export class AuthRequestService { } public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable { - return this.halService.getEndpoint(this.linkName) - .filter((href: string) => isNotEmpty(href)) - .map((endpointURL) => this.getEndpointByMethod(endpointURL, method)) - .distinctUntilChanged() - .map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)) - .do((request: PostRequest) => this.requestService.configure(request, true)) - .flatMap((request: PostRequest) => this.fetchRequest(request)) - .distinctUntilChanged(); + return this.halService.getEndpoint(this.linkName).pipe( + filter((href: string) => isNotEmpty(href)), + map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), + distinctUntilChanged(), + map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)), + tap((request: PostRequest) => this.requestService.configure(request, true)), + mergeMap((request: PostRequest) => this.fetchRequest(request)), + distinctUntilChanged()); } public getRequest(method: string, options?: HttpOptions): Observable { - return this.halService.getEndpoint(this.linkName) - .filter((href: string) => isNotEmpty(href)) - .map((endpointURL) => this.getEndpointByMethod(endpointURL, method)) - .distinctUntilChanged() - .map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)) - .do((request: PostRequest) => this.requestService.configure(request, true)) - .flatMap((request: PostRequest) => this.fetchRequest(request)) - .distinctUntilChanged(); + return this.halService.getEndpoint(this.linkName).pipe( + filter((href: string) => isNotEmpty(href)), + map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), + distinctUntilChanged(), + map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)), + tap((request: PostRequest) => this.requestService.configure(request, true)), + mergeMap((request: PostRequest) => this.fetchRequest(request)), + distinctUntilChanged()); } } diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 223100056b..785fee84eb 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -1,11 +1,11 @@ +import { of as observableOf, Observable } from 'rxjs'; + +import { filter, debounceTime, switchMap, take, tap, catchError, map, first } from 'rxjs/operators'; import { Injectable } from '@angular/core'; // import @ngrx -import { Actions, Effect } from '@ngrx/effects'; -import { Action, Store } from '@ngrx/store'; - -// import rxjs -import { Observable } from 'rxjs'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Action, select, Store } from '@ngrx/store'; // import services import { AuthService } from './auth.service'; @@ -43,112 +43,131 @@ export class AuthEffects { * @method authenticate */ @Effect() - public authenticate$: Observable = this.actions$ - .ofType(AuthActionTypes.AUTHENTICATE) - .switchMap((action: AuthenticateAction) => { - return this.authService.authenticate(action.payload.email, action.payload.password) - .first() - .map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)) - .catch((error) => Observable.of(new AuthenticationErrorAction(error))); - }); + public authenticate$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.AUTHENTICATE), + switchMap((action: AuthenticateAction) => { + return this.authService.authenticate(action.payload.email, action.payload.password).pipe( + first(), + map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)), + catchError((error) => observableOf(new AuthenticationErrorAction(error))) + ); + }) + ); @Effect() - public authenticateSuccess$: Observable = this.actions$ - .ofType(AuthActionTypes.AUTHENTICATE_SUCCESS) - .do((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)) - .map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload)); + public authenticateSuccess$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.AUTHENTICATE_SUCCESS), + tap((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)), + map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload)) + ); @Effect() - public authenticated$: Observable = this.actions$ - .ofType(AuthActionTypes.AUTHENTICATED) - .switchMap((action: AuthenticatedAction) => { - return this.authService.authenticatedUser(action.payload) - .map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)) - .catch((error) => Observable.of(new AuthenticatedErrorAction(error))); - }); + public authenticated$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.AUTHENTICATED), + switchMap((action: AuthenticatedAction) => { + return this.authService.authenticatedUser(action.payload).pipe( + map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)), + catchError((error) => observableOf(new AuthenticatedErrorAction(error))),); + }) + ); // It means "reacts to this action but don't send another" - @Effect({dispatch: false}) - public authenticatedError$: Observable = this.actions$ - .ofType(AuthActionTypes.AUTHENTICATED_ERROR) - .do((action: LogOutSuccessAction) => this.authService.removeToken()); + @Effect({ dispatch: false }) + public authenticatedError$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.AUTHENTICATED_ERROR), + tap((action: LogOutSuccessAction) => this.authService.removeToken()) + ); @Effect() - public checkToken$: Observable = this.actions$ - .ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN) - .switchMap(() => { - return this.authService.hasValidAuthenticationToken() - .map((token: AuthTokenInfo) => new AuthenticatedAction(token)) - .catch((error) => Observable.of(new CheckAuthenticationTokenErrorAction())); - }); + public checkToken$: Observable = this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN), + switchMap(() => { + return this.authService.hasValidAuthenticationToken().pipe( + map((token: AuthTokenInfo) => new AuthenticatedAction(token)), + catchError((error) => observableOf(new CheckAuthenticationTokenErrorAction())) + ); + }) + ); @Effect() - public createUser$: Observable = this.actions$ - .ofType(AuthActionTypes.REGISTRATION) - .debounceTime(500) // to remove when functionality is implemented - .switchMap((action: RegistrationAction) => { - return this.authService.create(action.payload) - .map((user: Eperson) => new RegistrationSuccessAction(user)) - .catch((error) => Observable.of(new RegistrationErrorAction(error))); - }); + public createUser$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.REGISTRATION), + debounceTime(500), // to remove when functionality is implemented + switchMap((action: RegistrationAction) => { + return this.authService.create(action.payload).pipe( + map((user: Eperson) => new RegistrationSuccessAction(user)), + catchError((error) => observableOf(new RegistrationErrorAction(error))) + ); + }) + ); @Effect() - public refreshToken$: Observable = this.actions$ - .ofType(AuthActionTypes.REFRESH_TOKEN) - .switchMap((action: RefreshTokenAction) => { - return this.authService.refreshAuthenticationToken(action.payload) - .map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)) - .catch((error) => Observable.of(new RefreshTokenErrorAction())); - }); + public refreshToken$: Observable = this.actions$.pipe(ofType(AuthActionTypes.REFRESH_TOKEN), + switchMap((action: RefreshTokenAction) => { + return this.authService.refreshAuthenticationToken(action.payload).pipe( + map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)), + catchError((error) => observableOf(new RefreshTokenErrorAction())) + ); + }) + ); // It means "reacts to this action but don't send another" - @Effect({dispatch: false}) - public refreshTokenSuccess$: Observable = this.actions$ - .ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS) - .do((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload)); + @Effect({ dispatch: false }) + public refreshTokenSuccess$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS), + tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload)) + ); /** * When the store is rehydrated in the browser, * clear a possible invalid token or authentication errors */ - @Effect({dispatch: false}) - public clearInvalidTokenOnRehydrate$: Observable = this.actions$ - .ofType(StoreActionTypes.REHYDRATE) - .switchMap(() => { - return this.store.select(isAuthenticated) - .take(1) - .filter((authenticated) => !authenticated) - .do(() => this.authService.removeToken()) - .do(() => this.authService.resetAuthenticationError()); - }); + @Effect({ dispatch: false }) + public clearInvalidTokenOnRehydrate$: Observable = this.actions$.pipe( + ofType(StoreActionTypes.REHYDRATE), + switchMap(() => { + return this.store.pipe( + select(isAuthenticated), + first(), + filter((authenticated) => !authenticated), + tap(() => this.authService.removeToken()), + tap(() => this.authService.resetAuthenticationError()) + ); + })); @Effect() public logOut$: Observable = this.actions$ - .ofType(AuthActionTypes.LOG_OUT) - .switchMap(() => { - return this.authService.logout() - .map((value) => new LogOutSuccessAction()) - .catch((error) => Observable.of(new LogOutErrorAction(error))); - }); + .pipe( + ofType(AuthActionTypes.LOG_OUT), + switchMap(() => { + return this.authService.logout().pipe( + map((value) => new LogOutSuccessAction()), + catchError((error) => observableOf(new LogOutErrorAction(error))) + ); + }) + ); - @Effect({dispatch: false}) + @Effect({ dispatch: false }) public logOutSuccess$: Observable = this.actions$ - .ofType(AuthActionTypes.LOG_OUT_SUCCESS) - .do(() => this.authService.removeToken()) - .do(() => this.authService.clearRedirectUrl()) - .do(() => this.authService.refreshAfterLogout()); + .pipe(ofType(AuthActionTypes.LOG_OUT_SUCCESS), + tap(() => this.authService.removeToken()), + tap(() => this.authService.clearRedirectUrl()), + tap(() => this.authService.refreshAfterLogout()) + ); - @Effect({dispatch: false}) + @Effect({ dispatch: false }) public redirectToLogin$: Observable = this.actions$ - .ofType(AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED) - .do(() => this.authService.removeToken()) - .do(() => this.authService.redirectToLogin()); + .pipe(ofType(AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED), + tap(() => this.authService.removeToken()), + tap(() => this.authService.redirectToLogin()) + ); - @Effect({dispatch: false}) + @Effect({ dispatch: false }) public redirectToLoginTokenExpired$: Observable = this.actions$ - .ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED) - .do(() => this.authService.removeToken()) - .do(() => this.authService.redirectToLoginWhenTokenExpired()); + .pipe( + ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED), + tap(() => this.authService.removeToken()), + tap(() => this.authService.redirectToLoginWhenTokenExpired()) + ); /** * @constructor diff --git a/src/app/core/auth/auth.interceptor.ts b/src/app/core/auth/auth.interceptor.ts index 95a93fa0c7..dc751ce19f 100644 --- a/src/app/core/auth/auth.interceptor.ts +++ b/src/app/core/auth/auth.interceptor.ts @@ -1,10 +1,20 @@ +import { of as observableOf, throwError as observableThrowError } from 'rxjs'; -import {throwError as observableThrowError, Observable } from 'rxjs'; +import { catchError, filter, map } from 'rxjs/operators'; import { Injectable, Injector } from '@angular/core'; import { - HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, - HttpErrorResponse, HttpResponseBase + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, + HttpResponseBase } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + + import { find } from 'lodash'; import { AppState } from '../../app.reducer'; @@ -76,11 +86,11 @@ export class AuthInterceptor implements HttpInterceptor { // The access token is expired // Redirect to the login route this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired')); - return Observable.of(null); + return observableOf(null); } else if (!this.isAuthRequest(req) && isNotEmpty(token)) { // Intercept a request that is not to the authentication endpoint - authService.isTokenExpiring() - .filter((isExpiring) => isExpiring) + authService.isTokenExpiring().pipe( + filter((isExpiring) => isExpiring)) .subscribe(() => { // If the current request url is already in the refresh token request list, skip it if (isUndefined(find(this.refreshTokenRequestUrls, req.url))) { @@ -98,8 +108,8 @@ export class AuthInterceptor implements HttpInterceptor { } // Pass on the new request instead of the original request. - return next.handle(newReq) - .map((response) => { + return next.handle(newReq).pipe( + map((response) => { // Intercept a Login/Logout response if (response instanceof HttpResponse && this.isSuccess(response) && (this.isLoginResponse(response) || this.isLogoutResponse(response))) { // It's a success Login/Logout response @@ -119,8 +129,8 @@ export class AuthInterceptor implements HttpInterceptor { } else { return response; } - }) - .catch((error, caught) => { + }), + catchError((error, caught) => { // Intercept an error response if (error instanceof HttpErrorResponse) { // Checks if is a response from a request to an authentication endpoint @@ -135,7 +145,7 @@ export class AuthInterceptor implements HttpInterceptor { statusText: error.statusText, url: error.url }); - return Observable.of(authResponse); + return observableOf(authResponse); } else if (this.isUnauthorized(error)) { // The access token provided is expired, revoked, malformed, or invalid for other reasons // Redirect to the login route @@ -144,7 +154,6 @@ export class AuthInterceptor implements HttpInterceptor { } // Return error response as is. return observableThrowError(error); - }) as any; - + })) as any; } } diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index ff1e0df0be..5ed2483bce 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -1,13 +1,21 @@ +import { of as observableOf, Observable } from 'rxjs'; +import { + take, + filter, + startWith, + first, + distinctUntilChanged, + map, + withLatestFrom +} from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { PRIMARY_OUTLET, Router, UrlSegmentGroup, UrlTree } from '@angular/router'; import { HttpHeaders } from '@angular/common/http'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { RouterReducerState } from '@ngrx/router-store'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { CookieAttributes } from 'js-cookie'; -import { Observable } from 'rxjs'; -import { map, withLatestFrom } from 'rxjs/operators'; import { Eperson } from '../eperson/models/eperson.model'; import { AuthRequestService } from './auth-request.service'; @@ -17,7 +25,12 @@ import { AuthStatus } from './models/auth-status.model'; import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model'; import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; import { CookieService } from '../../shared/services/cookie.service'; -import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; +import { + getAuthenticationToken, + getRedirectUrl, + isAuthenticated, + isTokenRefreshing +} from './selectors'; import { AppState, routerStateSelector } from '../../app.reducer'; import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; @@ -46,21 +59,24 @@ export class AuthService { protected router: Router, protected storage: CookieService, protected store: Store) { - this.store.select(isAuthenticated) - .startWith(false) - .subscribe((authenticated: boolean) => this._authenticated = authenticated); + this.store.pipe( + select(isAuthenticated), + startWith(false) + ).subscribe((authenticated: boolean) => this._authenticated = authenticated); // If current route is different from the one setted in authentication guard // and is not the login route, clear redirect url and messages - const routeUrl$ = this.store.select(routerStateSelector) - .filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state)) - .filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)) - .map((routerState: RouterReducerState) => routerState.state.url); - const redirectUrl$ = this.store.select(getRedirectUrl).distinctUntilChanged(); + const routeUrl$ = this.store.pipe( + select(routerStateSelector), + filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state)), + filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)), + map((routerState: RouterReducerState) => routerState.state.url) + ); + const redirectUrl$ = this.store.pipe(select(getRedirectUrl), distinctUntilChanged()); routeUrl$.pipe( withLatestFrom(redirectUrl$), map(([routeUrl, redirectUrl]) => [routeUrl, redirectUrl]) - ).filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl)) + ).pipe(filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl))) .subscribe(() => { this.clearRedirectUrl(); }); @@ -93,14 +109,14 @@ export class AuthService { let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); options.headers = headers; - return this.authRequestService.postToEndpoint('login', body, options) - .map((status: AuthStatus) => { + return this.authRequestService.postToEndpoint('login', body, options).pipe( + map((status: AuthStatus) => { if (status.authenticated) { return status; } else { throw(new Error('Invalid email or password')); } - }) + })) } @@ -109,7 +125,7 @@ export class AuthService { * @returns {Observable} */ public isAuthenticated(): Observable { - return this.store.select(isAuthenticated); + return this.store.pipe(select(isAuthenticated)); } /** @@ -123,14 +139,14 @@ export class AuthService { headers = headers.append('Accept', 'application/json'); headers = headers.append('Authorization', `Bearer ${token.accessToken}`); options.headers = headers; - return this.authRequestService.getRequest('status', options) - .map((status: AuthStatus) => { + return this.authRequestService.getRequest('status', options).pipe( + map((status: AuthStatus) => { if (status.authenticated) { return status.eperson[0]; } else { throw(new Error('Not authenticated')); } - }); + })); } /** @@ -144,9 +160,10 @@ export class AuthService { * Checks if token is present into storage and is not expired */ public hasValidAuthenticationToken(): Observable { - return this.store.select(getAuthenticationToken) - .take(1) - .map((authTokenInfo: AuthTokenInfo) => { + return this.store.pipe( + select(getAuthenticationToken), + take(1), + map((authTokenInfo: AuthTokenInfo) => { let token: AuthTokenInfo; // Retrieve authentication token info and check if is valid token = isNotEmpty(authTokenInfo) ? authTokenInfo : this.storage.get(TOKENITEM); @@ -155,7 +172,8 @@ export class AuthService { } else { throw false; } - }); + }) + ); } /** @@ -167,14 +185,14 @@ export class AuthService { headers = headers.append('Accept', 'application/json'); headers = headers.append('Authorization', `Bearer ${token.accessToken}`); options.headers = headers; - return this.authRequestService.postToEndpoint('login', {}, options) - .map((status: AuthStatus) => { + return this.authRequestService.postToEndpoint('login', {}, options).pipe( + map((status: AuthStatus) => { if (status.authenticated) { return status.token; } else { throw(new Error('Not authenticated')); } - }); + })); } /** @@ -193,7 +211,7 @@ export class AuthService { // details and then return the new user object // but, let's just return the new user for this example. // this._authenticated = true; - return Observable.of(user); + return observableOf(user); } /** @@ -204,15 +222,15 @@ export class AuthService { // Send a request that sign end the session let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); - const options: HttpOptions = Object.create({headers, responseType: 'text'}); - return this.authRequestService.getRequest('logout', options) - .map((status: AuthStatus) => { + const options: HttpOptions = Object.create({ headers, responseType: 'text' }); + return this.authRequestService.getRequest('logout', options).pipe( + map((status: AuthStatus) => { if (!status.authenticated) { return true; } else { throw(new Error('auth.errors.invalid-user')); } - }) + })) } @@ -233,7 +251,7 @@ export class AuthService { */ public getToken(): AuthTokenInfo { let token: AuthTokenInfo; - this.store.select(getAuthenticationToken) + this.store.pipe(select(getAuthenticationToken)) .subscribe((authTokenInfo: AuthTokenInfo) => { // Retrieve authentication token info and check if is valid token = authTokenInfo || null; @@ -246,9 +264,10 @@ export class AuthService { * @returns {boolean} */ public isTokenExpiring(): Observable { - return this.store.select(isTokenRefreshing) - .first() - .map((isRefreshing: boolean) => { + return this.store.pipe( + select(isTokenRefreshing), + first(), + map((isRefreshing: boolean) => { if (this.isTokenExpired() || isRefreshing) { return false; } else { @@ -256,6 +275,7 @@ export class AuthService { return token.expires - (60 * 5 * 1000) < Date.now(); } }) + ) } /** @@ -279,7 +299,7 @@ export class AuthService { // Set the cookie expire date const expires = new Date(expireDate); - const options: CookieAttributes = {expires: expires}; + const options: CookieAttributes = { expires: expires }; // Save cookie with the token return this.storage.set(TOKENITEM, token, options); @@ -324,8 +344,8 @@ export class AuthService { * Redirect to the route navigated before the login */ public redirectToPreviousUrl() { - this.getRedirectUrl() - .first() + this.getRedirectUrl().pipe( + first()) .subscribe((redirectUrl) => { if (isNotEmpty(redirectUrl)) { this.clearRedirectUrl(); @@ -359,9 +379,9 @@ export class AuthService { getRedirectUrl(): Observable { const redirectUrl = this.storage.get(REDIRECT_COOKIE); if (isNotEmpty(redirectUrl)) { - return Observable.of(redirectUrl); + return observableOf(redirectUrl); } else { - return this.store.select(getRedirectUrl); + return this.store.pipe(select(getRedirectUrl)); } } @@ -374,7 +394,7 @@ export class AuthService { // Set the cookie expire date const expires = new Date(expireDate); - const options: CookieAttributes = {expires: expires}; + const options: CookieAttributes = { expires: expires }; this.storage.set(REDIRECT_COOKIE, url, options); this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : '')); } diff --git a/src/app/core/auth/authenticated.guard.ts b/src/app/core/auth/authenticated.guard.ts index 66fa34992d..b9091a86ad 100644 --- a/src/app/core/auth/authenticated.guard.ts +++ b/src/app/core/auth/authenticated.guard.ts @@ -1,8 +1,10 @@ + +import {take} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; // reducers import { CoreState } from '../core.reducers'; @@ -52,12 +54,12 @@ export class AuthenticatedGuard implements CanActivate, CanLoad { private handleAuth(url: string): Observable { // get observable - const observable = this.store.select(isAuthenticated); + const observable = this.store.pipe(select(isAuthenticated)); // redirect to sign in page if user is not authenticated - observable + observable.pipe( // .filter(() => isEmpty(this.router.routerState.snapshot.url) || this.router.routerState.snapshot.url === url) - .take(1) + take(1)) .subscribe((authenticated) => { if (!authenticated) { this.authService.setRedirectUrl(url); diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index e65452e872..7111eed255 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -1,3 +1,5 @@ + +import {first, map} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; @@ -32,14 +34,14 @@ export class ServerAuthService extends AuthService { headers = headers.append('X-Forwarded-For', clientIp); options.headers = headers; - return this.authRequestService.getRequest('status', options) - .map((status: AuthStatus) => { + return this.authRequestService.getRequest('status', options).pipe( + map((status: AuthStatus) => { if (status.authenticated) { return status.eperson[0]; } else { throw(new Error('Not authenticated')); } - }); + })); } /** @@ -53,8 +55,8 @@ export class ServerAuthService extends AuthService { * Redirect to the route navigated before the login */ public redirectToPreviousUrl() { - this.getRedirectUrl() - .first() + this.getRedirectUrl().pipe( + first()) .subscribe((redirectUrl) => { if (isNotEmpty(redirectUrl)) { // override the route reuse strategy diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 9078bb05da..eb11db3ca4 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -1,5 +1,6 @@ + +import {combineLatest as observableCombineLatest, of as observableOf, Observable, race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; import { distinctUntilChanged, flatMap, map, startWith } from 'rxjs/operators'; import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { PaginatedList } from '../../data/paginated-list'; @@ -32,24 +33,24 @@ export class RemoteDataBuildService { buildSingle(href$: string | Observable): Observable> { if (typeof href$ === 'string') { - href$ = Observable.of(href$); + href$ = observableOf(href$); } const requestHref$ = href$.pipe(flatMap((href: string) => this.objectCache.getRequestHrefBySelfLink(href))); - const requestEntry$ = Observable.race( + const requestEntry$ = observableRace( href$.pipe(getRequestFromSelflink(this.requestService)), requestHref$.pipe(getRequestFromSelflink(this.requestService)) ); - const responseCache$ = Observable.race( + const responseCache$ = observableRace( href$.pipe(getResponseFromSelflink(this.responseCache)), requestHref$.pipe(getResponseFromSelflink(this.responseCache)) ); // always use self link if that is cached, only if it isn't, get it via the response. const payload$ = - Observable.combineLatest( + observableCombineLatest( href$.pipe( flatMap((href: string) => this.objectCache.getBySelfLink(href)), startWith(undefined) @@ -60,7 +61,7 @@ export class RemoteDataBuildService { if (isNotEmpty(resourceSelfLinks)) { return this.objectCache.getBySelfLink(resourceSelfLinks[0]); } else { - return Observable.of(undefined); + return observableOf(undefined); } }), distinctUntilChanged(), @@ -85,7 +86,7 @@ export class RemoteDataBuildService { } toRemoteDataObservable(requestEntry$: Observable, responseCache$: Observable, payload$: Observable) { - return Observable.combineLatest(requestEntry$, responseCache$.startWith(undefined), payload$, + return observableCombineLatest(requestEntry$, responseCache$.pipe(startWith(undefined)), payload$, (reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => { const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; @@ -110,7 +111,7 @@ export class RemoteDataBuildService { buildList(href$: string | Observable): Observable>> { if (typeof href$ === 'string') { - href$ = Observable.of(href$); + href$ = observableOf(href$); } const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); @@ -119,12 +120,12 @@ export class RemoteDataBuildService { const tDomainList$ = responseCache$.pipe( getResourceLinksFromResponse(), flatMap((resourceUUIDs: string[]) => { - return this.objectCache.getList(resourceUUIDs) - .map((normList: TNormalized[]) => { + return this.objectCache.getList(resourceUUIDs).pipe( + map((normList: TNormalized[]) => { return normList.map((normalized: TNormalized) => { return this.build(normalized); }); - }); + })); }), startWith([]), distinctUntilChanged() @@ -144,7 +145,7 @@ export class RemoteDataBuildService { }) ); - const payload$ = Observable.combineLatest(tDomainList$, pageInfo$, (tDomainList, pageInfo) => { + const payload$ = observableCombineLatest(tDomainList$, pageInfo$, (tDomainList, pageInfo) => { return new PaginatedList(pageInfo, tDomainList); }); @@ -204,10 +205,10 @@ export class RemoteDataBuildService { aggregate(input: Array>>): Observable> { if (isEmpty(input)) { - return Observable.of(new RemoteData(false, false, true, null, [])); + return observableOf(new RemoteData(false, false, true, null, [])); } - return Observable.combineLatest( + return observableCombineLatest( ...input, (...arr: Array>) => { const requestPending: boolean = arr @@ -255,7 +256,7 @@ export class RemoteDataBuildService { } aggregatePaginatedList(input: Observable>, pageInfo: PageInfo): Observable>> { - return input.map((rd) => Object.assign(rd, {payload: new PaginatedList(pageInfo, rd.payload)})); + return input.pipe(map((rd) => Object.assign(rd, {payload: new PaginatedList(pageInfo, rd.payload)}))); } } diff --git a/src/app/core/cache/object-cache.effects.ts b/src/app/core/cache/object-cache.effects.ts index 019c792973..2bd8ad0e3c 100644 --- a/src/app/core/cache/object-cache.effects.ts +++ b/src/app/core/cache/object-cache.effects.ts @@ -1,5 +1,6 @@ +import { map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { Actions, Effect } from '@ngrx/effects'; +import { Actions, Effect, ofType } from '@ngrx/effects'; import { StoreActionTypes } from '../../store.actions'; import { ResetObjectCacheTimestampsAction } from './object-cache.actions'; @@ -16,9 +17,11 @@ export class ObjectCacheEffects { * time ago, and will likely need to be revisited later */ @Effect() fixTimestampsOnRehydrate = this.actions$ - .ofType(StoreActionTypes.REHYDRATE) - .map(() => new ResetObjectCacheTimestampsAction(new Date().getTime())); + .pipe(ofType(StoreActionTypes.REHYDRATE), + map(() => new ResetObjectCacheTimestampsAction(new Date().getTime())) + ); - constructor(private actions$: Actions) { } + constructor(private actions$: Actions) { + } } diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index dbeb02cb0a..dbe241ffb3 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -1,16 +1,16 @@ -import { Injectable } from '@angular/core'; -import { MemoizedSelector, Store } from '@ngrx/store'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { Observable } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, mergeMap, take } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { MemoizedSelector, select, Store } from '@ngrx/store'; import { IndexName } from '../index/index.reducer'; -import { ObjectCacheEntry, CacheableObject } from './object-cache.reducer'; +import { CacheableObject, ObjectCacheEntry } from './object-cache.reducer'; import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions'; import { hasNoValue } from '../../shared/empty.util'; import { GenericConstructor } from '../shared/generic-constructor'; import { coreSelector, CoreState } from '../core.reducers'; import { pathSelector } from '../shared/selectors'; -import { Item } from '../shared/item.model'; import { NormalizedObjectFactory } from './models/normalized-object-factory'; import { NormalizedObject } from './models/normalized-object.model'; @@ -73,33 +73,40 @@ export class ObjectCacheService { * An observable of the requested object */ getByUUID(uuid: string): Observable { - return this.store.select(selfLinkFromUuidSelector(uuid)) - .flatMap((selfLink: string) => this.getBySelfLink(selfLink)) + return this.store.pipe( + select(selfLinkFromUuidSelector(uuid)), + mergeMap((selfLink: string) => this.getBySelfLink(selfLink) + ) + ) } getBySelfLink(selfLink: string): Observable { - return this.getEntry(selfLink) - .map((entry: ObjectCacheEntry) => { - const type: GenericConstructor= NormalizedObjectFactory.getConstructor(entry.data.type); + return this.getEntry(selfLink).pipe( + map((entry: ObjectCacheEntry) => { + const type: GenericConstructor = NormalizedObjectFactory.getConstructor(entry.data.type); return Object.assign(new type(), entry.data) as T - }); + })); } private getEntry(selfLink: string): Observable { - return this.store.select(entryFromSelfLinkSelector(selfLink)) - .filter((entry) => this.isValid(entry)) - .distinctUntilChanged(); + return this.store.pipe( + select(entryFromSelfLinkSelector(selfLink)), + filter((entry) => this.isValid(entry)), + distinctUntilChanged() + ); } getRequestHrefBySelfLink(selfLink: string): Observable { - return this.getEntry(selfLink) - .map((entry: ObjectCacheEntry) => entry.requestHref) - .distinctUntilChanged(); + return this.getEntry(selfLink).pipe( + map((entry: ObjectCacheEntry) => entry.requestHref), + distinctUntilChanged(),); } getRequestHrefByUUID(uuid: string): Observable { - return this.store.select(selfLinkFromUuidSelector(uuid)) - .flatMap((selfLink: string) => this.getRequestHrefBySelfLink(selfLink)); + return this.store.pipe( + select(selfLinkFromUuidSelector(uuid)), + mergeMap((selfLink: string) => this.getRequestHrefBySelfLink(selfLink)) + ); } /** @@ -122,7 +129,7 @@ export class ObjectCacheService { * @return Observable> */ getList(selfLinks: string[]): Observable { - return Observable.combineLatest( + return observableCombineLatest( selfLinks.map((selfLink: string) => this.getBySelfLink(selfLink)) ); } @@ -139,9 +146,10 @@ export class ObjectCacheService { hasByUUID(uuid: string): boolean { let result: boolean; - this.store.select(selfLinkFromUuidSelector(uuid)) - .take(1) - .subscribe((selfLink: string) => result = this.hasBySelfLink(selfLink)); + this.store.pipe( + select(selfLinkFromUuidSelector(uuid)), + first() + ).subscribe((selfLink: string) => result = this.hasBySelfLink(selfLink)); return result; } @@ -158,9 +166,9 @@ export class ObjectCacheService { hasBySelfLink(selfLink: string): boolean { let result = false; - this.store.select(entryFromSelfLinkSelector(selfLink)) - .take(1) - .subscribe((entry: ObjectCacheEntry) => result = this.isValid(entry)); + this.store.pipe(select(entryFromSelfLinkSelector(selfLink)), + first() + ).subscribe((entry: ObjectCacheEntry) => result = this.isValid(entry)); return result; } diff --git a/src/app/core/cache/response-cache.effects.ts b/src/app/core/cache/response-cache.effects.ts index d340750797..5a1e53e20c 100644 --- a/src/app/core/cache/response-cache.effects.ts +++ b/src/app/core/cache/response-cache.effects.ts @@ -1,5 +1,6 @@ +import { map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { Actions, Effect } from '@ngrx/effects'; +import { Actions, Effect, ofType } from '@ngrx/effects'; import { ResetResponseCacheTimestampsAction } from './response-cache.actions'; import { StoreActionTypes } from '../../store.actions'; @@ -16,9 +17,11 @@ export class ResponseCacheEffects { * time ago, and will likely need to be revisited later */ @Effect() fixTimestampsOnRehydrate = this.actions$ - .ofType(StoreActionTypes.REHYDRATE) - .map(() => new ResetResponseCacheTimestampsAction(new Date().getTime())); + .pipe(ofType(StoreActionTypes.REHYDRATE), + map(() => new ResetResponseCacheTimestampsAction(new Date().getTime())) + ); - constructor(private actions$: Actions, ) { } + constructor(private actions$: Actions,) { + } } diff --git a/src/app/core/cache/response-cache.service.ts b/src/app/core/cache/response-cache.service.ts index 19e7711168..21430d451c 100644 --- a/src/app/core/cache/response-cache.service.ts +++ b/src/app/core/cache/response-cache.service.ts @@ -1,5 +1,6 @@ +import { filter, take, distinctUntilChanged, first } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { MemoizedSelector, Store } from '@ngrx/store'; +import { MemoizedSelector, select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -21,7 +22,8 @@ function entryFromKeySelector(key: string): MemoizedSelector - ) { } + ) { + } add(key: string, response: RestResponse, msToLive: number): Observable { if (!this.has(key)) { @@ -39,9 +41,11 @@ export class ResponseCacheService { * an observable of the ResponseCacheEntry with the specified key */ get(key: string): Observable { - return this.store.select(entryFromKeySelector(key)) - .filter((entry: ResponseCacheEntry) => this.isValid(entry)) - .distinctUntilChanged() + return this.store.pipe( + select(entryFromKeySelector(key)), + filter((entry: ResponseCacheEntry) => this.isValid(entry)), + distinctUntilChanged() + ) } /** @@ -56,11 +60,11 @@ export class ResponseCacheService { has(key: string): boolean { let result: boolean; - this.store.select(entryFromKeySelector(key)) - .take(1) - .subscribe((entry: ResponseCacheEntry) => { - result = this.isValid(entry); - }); + this.store.pipe(select(entryFromKeySelector(key)), + first() + ).subscribe((entry: ResponseCacheEntry) => { + result = this.isValid(entry); + }); return result; } @@ -70,6 +74,7 @@ export class ResponseCacheService { this.store.dispatch(new ResponseCacheRemoveAction(key)); } } + /** * Check whether a ResponseCacheEntry should still be cached * diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts index d1b042afd3..2199deedcf 100644 --- a/src/app/core/config/config.service.ts +++ b/src/app/core/config/config.service.ts @@ -1,9 +1,8 @@ - -import {throwError as observableThrowError, Observable } from 'rxjs'; - +import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; +import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; -import { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { ConfigSuccessResponse } from '../cache/response-cache.models'; import { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -19,16 +18,18 @@ export abstract class ConfigService { protected abstract halService: HALEndpointService; protected getConfig(request: RestRequest): Observable { - const [successResponse, errorResponse] = this.responseCache.get(request.href) - .map((entry: ResponseCacheEntry) => entry.response) - .partition((response: RestResponse) => response.isSuccessful); - return Observable.merge( - errorResponse.flatMap((response: ErrorResponse) => - observableThrowError(new Error(`Couldn't retrieve the config`))), - successResponse - .filter((response: ConfigSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.configDefinition)) - .map((response: ConfigSuccessResponse) => new ConfigData(response.pageInfo, response.configDefinition)) - .distinctUntilChanged()); + return this.responseCache.get(request.href).pipe( + map((entry: ResponseCacheEntry) => entry.response), + mergeMap((response) => { + if (response.isSuccessful && isNotEmpty(response) && isNotEmpty((response as ConfigSuccessResponse).configDefinition)) { + const configResponse = response as ConfigSuccessResponse; + return observableOf(new ConfigData(configResponse.pageInfo, configResponse.configDefinition)); + } else if (!response.isSuccessful) { + return observableThrowError(new Error(`Couldn't retrieve the config`)); + } + }), + distinctUntilChanged() + ); } protected getConfigByNameHref(endpoint, resourceName): string { @@ -66,13 +67,13 @@ export abstract class ConfigService { } public getConfigAll(): Observable { - return this.halService.getEndpoint(this.linkPath) - .filter((href: string) => isNotEmpty(href)) - .distinctUntilChanged() - .map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)) - .do((request: RestRequest) => this.requestService.configure(request)) - .flatMap((request: RestRequest) => this.getConfig(request)) - .distinctUntilChanged(); + return this.halService.getEndpoint(this.linkPath).pipe( + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)), + tap((request: RestRequest) => this.requestService.configure(request)), + mergeMap((request: RestRequest) => this.getConfig(request)), + distinctUntilChanged(),); } public getConfigByHref(href: string): Observable { @@ -83,25 +84,25 @@ export abstract class ConfigService { } public getConfigByName(name: string): Observable { - return this.halService.getEndpoint(this.linkPath) - .map((endpoint: string) => this.getConfigByNameHref(endpoint, name)) - .filter((href: string) => isNotEmpty(href)) - .distinctUntilChanged() - .map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)) - .do((request: RestRequest) => this.requestService.configure(request)) - .flatMap((request: RestRequest) => this.getConfig(request)) - .distinctUntilChanged(); + return this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getConfigByNameHref(endpoint, name)), + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)), + tap((request: RestRequest) => this.requestService.configure(request)), + mergeMap((request: RestRequest) => this.getConfig(request)), + distinctUntilChanged(),); } public getConfigBySearch(options: FindAllOptions = {}): Observable { - return this.halService.getEndpoint(this.linkPath) - .map((endpoint: string) => this.getConfigSearchHref(endpoint, options)) - .filter((href: string) => isNotEmpty(href)) - .distinctUntilChanged() - .map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)) - .do((request: RestRequest) => this.requestService.configure(request)) - .flatMap((request: RestRequest) => this.getConfig(request)) - .distinctUntilChanged(); + return this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getConfigSearchHref(endpoint, options)), + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)), + tap((request: RestRequest) => this.requestService.configure(request)), + mergeMap((request: RestRequest) => this.getConfig(request)), + distinctUntilChanged(),); } } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index f2c7a743d5..de3955eb14 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -1,10 +1,8 @@ - -import {throwError as observableThrowError, Observable } from 'rxjs'; +import { Observable, throwError as observableThrowError } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, mergeMap, tap } from 'rxjs/operators'; import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; -import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { CommunityDataService } from './community-data.service'; @@ -13,7 +11,7 @@ import { FindByIDRequest } from './request.models'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -export abstract class ComColDataService extends DataService { +export abstract class ComColDataService extends DataService { protected abstract cds: CommunityDataService; protected abstract objectCache: ObjectCacheService; protected abstract halService: HALEndpointService; @@ -32,29 +30,31 @@ export abstract class ComColDataService this.cds.getFindByIDHref(endpoint, scopeID)) - .filter((href: string) => isNotEmpty(href)) - .take(1) - .do((href: string) => { + const scopeCommunityHrefObs = this.cds.getEndpoint().pipe( + mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)), + first((href: string) => isNotEmpty(href)), + tap((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID); this.requestService.configure(request); - }); + }),); - const [successResponse, errorResponse] = scopeCommunityHrefObs - .flatMap((href: string) => this.responseCache.get(href)) - .map((entry: ResponseCacheEntry) => entry.response) - .share() - .partition((response: RestResponse) => response.isSuccessful); - - return Observable.merge( - errorResponse.flatMap((response: ErrorResponse) => - observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))), - successResponse - .flatMap((response: DSOSuccessResponse) => this.objectCache.getByUUID(scopeID)) - .map((nc: NormalizedCommunity) => nc._links[this.linkPath]) - .filter((href) => isNotEmpty(href)) - ).distinctUntilChanged(); + return scopeCommunityHrefObs.pipe( + mergeMap((href: string) => this.responseCache.get(href)), + map((entry: ResponseCacheEntry) => entry.response), + mergeMap((response) => { + if (response.isSuccessful) { + const community$: Observable = this.objectCache.getByUUID(scopeID); + return community$.pipe( + map((community) => community._links[this.linkPath]), + filter((href) => isNotEmpty(href)), + distinctUntilChanged() + ); + } else if (!response.isSuccessful) { + return observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`)) + } + }), + distinctUntilChanged() + ); } } } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 206955ea9f..b8e8bd5ce0 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -1,3 +1,5 @@ + +import {mergeMap, filter, take} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -38,12 +40,12 @@ export class CommunityDataService extends ComColDataService>> { - const hrefObs = this.halService.getEndpoint(this.topLinkPath).filter((href: string) => isNotEmpty(href)) - .flatMap((endpoint: string) => this.getFindAllHref(endpoint, options)); + const hrefObs = this.halService.getEndpoint(this.topLinkPath).pipe(filter((href: string) => isNotEmpty(href)), + mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),); - hrefObs - .filter((href: string) => hasValue(href)) - .take(1) + hrefObs.pipe( + filter((href: string) => hasValue(href)), + take(1),) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 98e21c6ba3..1d0703bb47 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,5 +1,8 @@ + +import {of as observableOf, Observable } from 'rxjs'; + +import {mergeMap, first, take, distinctUntilChanged, map, filter} from 'rxjs/operators'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ResponseCacheService } from '../cache/response-cache.service'; @@ -27,9 +30,9 @@ export abstract class DataService const args = []; if (hasValue(options.scopeID)) { - result = this.getScopedEndpoint(options.scopeID).distinctUntilChanged(); + result = this.getScopedEndpoint(options.scopeID).pipe(distinctUntilChanged()); } else { - result = Observable.of(endpoint); + result = observableOf(endpoint); } if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { @@ -50,19 +53,19 @@ export abstract class DataService } if (isNotEmpty(args)) { - return result.map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString()); + return result.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString())); } else { return result; } } findAll(options: FindAllOptions = {}): Observable>> { - const hrefObs = this.halService.getEndpoint(this.linkPath).filter((href: string) => isNotEmpty(href)) - .flatMap((endpoint: string) => this.getFindAllHref(endpoint, options)); + const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(filter((href: string) => isNotEmpty(href)), + mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),); - hrefObs - .filter((href: string) => hasValue(href)) - .take(1) + hrefObs.pipe( + filter((href: string) => hasValue(href)), + take(1),) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); @@ -76,11 +79,11 @@ export abstract class DataService } findById(id: string): Observable> { - const hrefObs = this.halService.getEndpoint(this.linkPath) - .map((endpoint: string) => this.getFindByIDHref(endpoint, id)); + const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getFindByIDHref(endpoint, id))); - hrefObs - .first((href: string) => hasValue(href)) + hrefObs.pipe( + first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); this.requestService.configure(request); diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 40724b7319..dead4e5f48 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -1,3 +1,5 @@ + +import {distinctUntilChanged, map, filter} from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -34,10 +36,10 @@ export class ItemDataService extends DataService { if (isEmpty(scopeID)) { return this.halService.getEndpoint(this.linkPath); } else { - return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath) - .filter((href: string) => isNotEmpty(href)) - .map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()) - .distinctUntilChanged(); + return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath).pipe( + filter((href: string) => isNotEmpty(href)), + map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()), + distinctUntilChanged(),); } } diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 5fadd316f4..29c9ced472 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -1,9 +1,9 @@ + +import {of as observableOf, Observable } from 'rxjs'; import { Inject, Injectable, Injector } from '@angular/core'; import { Request } from '@angular/http'; import { RequestArgs } from '@angular/http/src/interfaces'; import { Actions, Effect, ofType } from '@ngrx/effects'; -// tslint:disable-next-line:import-blacklist -import { Observable } from 'rxjs'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { isNotEmpty } from '../../shared/empty.util'; @@ -30,7 +30,8 @@ export const addToResponseCacheAndCompleteAction = (request: RestRequest, respon @Injectable() export class RequestEffects { - @Effect() execute = this.actions$.ofType(RequestActionTypes.EXECUTE).pipe( + @Effect() execute = this.actions$.pipe( + ofType(RequestActionTypes.EXECUTE), flatMap((action: RequestExecuteAction) => { return this.requestService.getByUUID(action.payload).pipe( take(1) @@ -46,7 +47,7 @@ export class RequestEffects { return this.restApi.request(request.method, request.href, body, request.options).pipe( map((data: DSpaceRESTV2Response) => this.injector.get(request.getResponseParser()).parse(request, data)), addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig), - catchError((error: RequestError) => Observable.of(new ErrorResponse(error)).pipe( + catchError((error: RequestError) => observableOf(new ErrorResponse(error)).pipe( addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig) )) ); diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 4c9d623941..92dd2c126b 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -1,7 +1,6 @@ import { Store } from '@ngrx/store'; import { cold, hot } from 'jasmine-marbles'; -import { Observable } from 'rxjs'; -import 'rxjs/add/observable/of'; +import { of as observableOf } from 'rxjs'; import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { getMockStore } from '../../shared/mocks/mock-store'; @@ -18,7 +17,8 @@ import { OptionsRequest, PatchRequest, PostRequest, - PutRequest, RestRequest + PutRequest, + RestRequest } from './request.models'; import { RequestService } from './request.service'; @@ -46,12 +46,12 @@ describe('RequestService', () => { responseCache = getMockResponseCacheService(); (responseCache.has as any).and.returnValue(false); - (responseCache.get as any).and.returnValue(Observable.of(undefined)); + (responseCache.get as any).and.returnValue(observableOf(undefined)); uuidService = getMockUUIDService(); store = getMockStore(); - (store.select as any).and.returnValue(Observable.of(undefined)); + (store.pipe as any).and.returnValue(observableOf(undefined)); service = new RequestService( objectCache, @@ -74,7 +74,7 @@ describe('RequestService', () => { describe('isPending', () => { describe('before the request is configured', () => { beforeEach(() => { - spyOn(service, 'getByHref').and.returnValue(Observable.of(undefined)); + spyOn(service, 'getByHref').and.returnValue(observableOf(undefined)); }); it('should return false', () => { @@ -87,7 +87,7 @@ describe('RequestService', () => { describe('when the request has been configured but hasn\'t reached the store yet', () => { beforeEach(() => { - spyOn(service, 'getByHref').and.returnValue(Observable.of(undefined)); + spyOn(service, 'getByHref').and.returnValue(observableOf(undefined)); serviceAsAny.requestsOnTheirWayToTheStore = [testHref]; }); @@ -101,7 +101,7 @@ describe('RequestService', () => { describe('when the request has reached the store, before the server responds', () => { beforeEach(() => { - spyOn(service, 'getByHref').and.returnValue(Observable.of({ + spyOn(service, 'getByHref').and.returnValue(observableOf({ completed: false })) }); @@ -116,7 +116,7 @@ describe('RequestService', () => { describe('after the server responds', () => { beforeEach(() => { - spyOn(service, 'getByHref').and.returnValues(Observable.of({ + spyOn(service, 'getByHref').and.returnValues(observableOf({ completed: true })); }); @@ -134,7 +134,7 @@ describe('RequestService', () => { describe('getByUUID', () => { describe('if the request with the specified UUID exists in the store', () => { beforeEach(() => { - (store.select as any).and.returnValues(hot('a', { + (store.pipe as any).and.returnValues(hot('a', { a: { completed: true } @@ -155,7 +155,7 @@ describe('RequestService', () => { describe('if the request with the specified UUID doesn\'t exist in the store', () => { beforeEach(() => { - (store.select as any).and.returnValues(hot('a', { + (store.pipe as any).and.returnValues(hot('a', { a: undefined })); }); @@ -175,7 +175,7 @@ describe('RequestService', () => { describe('getByHref', () => { describe('when the request with the specified href exists in the store', () => { beforeEach(() => { - (store.select as any).and.returnValues(hot('a', { + (store.pipe as any).and.returnValues(hot('a', { a: testUUID })); spyOn(service, 'getByUUID').and.returnValue(cold('b', { @@ -199,7 +199,7 @@ describe('RequestService', () => { describe('when the request with the specified href doesn\'t exist in the store', () => { beforeEach(() => { - (store.select as any).and.returnValues(hot('a', { + (store.pipe as any).and.returnValues(hot('a', { a: undefined })); spyOn(service, 'getByUUID').and.returnValue(cold('b', { @@ -323,7 +323,7 @@ describe('RequestService', () => { describe('and it\'s a DSOSuccessResponse', () => { beforeEach(() => { - (responseCache.get as any).and.returnValues(Observable.of({ + (responseCache.get as any).and.returnValues(observableOf({ response: { isSuccessful: true, resourceSelfLinks: [ @@ -356,7 +356,7 @@ describe('RequestService', () => { beforeEach(() => { (objectCache.hasBySelfLink as any).and.returnValues(false); (responseCache.has as any).and.returnValues(true); - (responseCache.get as any).and.returnValues(Observable.of({ + (responseCache.get as any).and.returnValues(observableOf({ response: { isSuccessful: true } @@ -428,7 +428,7 @@ describe('RequestService', () => { describe('when the request is added to the store', () => { it('should stop tracking the request', () => { - (store.select as any).and.returnValues(Observable.of({ request })); + (store.pipe as any).and.returnValues(observableOf({ request })); serviceAsAny.trackRequestsOnTheirWayToTheStore(request); expect(serviceAsAny.requestsOnTheirWayToTheStore.includes(request.href)).toBeFalsy(); }); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 9fb12215b7..92eb4b55e7 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -1,12 +1,12 @@ +import { Observable } from 'rxjs'; +import { filter, first, map, mergeMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; - -import { Observable } from 'rxjs'; +import { MemoizedSelector, select, Store } from '@ngrx/store'; import { hasValue } from '../../shared/empty.util'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models'; +import { DSOSuccessResponse } from '../cache/response-cache.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheService } from '../cache/response-cache.service'; import { coreSelector, CoreState } from '../core.reducers'; @@ -16,8 +16,7 @@ import { UUIDService } from '../shared/uuid.service'; import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; import { GetRequest, RestRequest, RestRequestMethod } from './request.models'; -import { RequestEntry, RequestState } from './request.reducer'; -import { ResponseCacheRemoveAction } from '../cache/response-cache.actions'; +import { RequestEntry } from './request.reducer'; @Injectable() export class RequestService { @@ -49,8 +48,8 @@ export class RequestService { // then check the store let isPending = false; - this.getByHref(request.href) - .take(1) + this.getByHref(request.href).pipe( + take(1)) .subscribe((re: RequestEntry) => { isPending = (hasValue(re) && !re.completed) }); @@ -59,12 +58,14 @@ export class RequestService { } getByUUID(uuid: string): Observable { - return this.store.select(this.entryFromUUIDSelector(uuid)); + return this.store.pipe(select(this.entryFromUUIDSelector(uuid))); } getByHref(href: string): Observable { - return this.store.select(this.uuidFromHrefSelector(href)) - .flatMap((uuid: string) => this.getByUUID(uuid)); + return this.store.pipe( + select(this.uuidFromHrefSelector(href)), + mergeMap((uuid: string) => this.getByUUID(uuid)) + ); } // TODO to review "overrideRequest" param when https://github.com/DSpace/dspace-angular/issues/217 will be fixed @@ -81,29 +82,19 @@ export class RequestService { private isCachedOrPending(request: GetRequest) { let isCached = this.objectCache.hasBySelfLink(request.href); if (!isCached && this.responseCache.has(request.href)) { - const [successResponse, errorResponse] = this.responseCache.get(request.href) - .take(1) - .map((entry: ResponseCacheEntry) => entry.response) - .share() - .partition((response: RestResponse) => response.isSuccessful); - - const [dsoSuccessResponse, otherSuccessResponse] = successResponse - .share() - .partition((response: DSOSuccessResponse) => hasValue(response.resourceSelfLinks)); - - Observable.merge( - errorResponse.map(() => true), // TODO add a configurable number of retries in case of an error. - otherSuccessResponse.map(() => true), - dsoSuccessResponse // a DSOSuccessResponse should only be considered cached if all its resources are cached - .map((response: DSOSuccessResponse) => response.resourceSelfLinks) - .map((resourceSelfLinks: string[]) => resourceSelfLinks - .every((selfLink) => this.objectCache.hasBySelfLink(selfLink)) - ) + this.responseCache.get(request.href).pipe( + first(), + map((entry: ResponseCacheEntry) => { + const response = entry.response; + if (response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)) { + return (response as DSOSuccessResponse).resourceSelfLinks.every((selfLink) => this.objectCache.hasBySelfLink(selfLink)) + } else { + return true; + } + }) ).subscribe((c) => isCached = c); } - const isPending = this.isPending(request); - return isCached || isPending; } @@ -121,11 +112,11 @@ export class RequestService { */ private trackRequestsOnTheirWayToTheStore(request: GetRequest) { this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href]; - this.store.select(this.entryFromUUIDSelector(request.href)) - .filter((re: RequestEntry) => hasValue(re)) - .take(1) - .subscribe((re: RequestEntry) => { - this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href) - }); + this.store.pipe(select(this.entryFromUUIDSelector(request.href)), + filter((re: RequestEntry) => hasValue(re)), + take(1) + ).subscribe((re: RequestEntry) => { + this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href) + }); } } diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts index 2402678d06..1570613c17 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts @@ -1,5 +1,5 @@ - import {throwError as observableThrowError, Observable } from 'rxjs'; +import {catchError, map} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Request } from '@angular/http'; import { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http' @@ -37,12 +37,12 @@ export class DSpaceRESTv2Service { * An Observable containing the response from the server */ get(absoluteURL: string): Observable { - return this.http.get(absoluteURL, { observe: 'response' }) - .map((res: HttpResponse) => ({ payload: res.body, statusCode: res.statusText })) - .catch((err) => { + return this.http.get(absoluteURL, { observe: 'response' }).pipe( + map((res: HttpResponse) => ({ payload: res.body, statusCode: res.statusText })), + catchError((err) => { console.log('Error: ', err); return observableThrowError(err); - }); + })); } /** @@ -67,12 +67,12 @@ export class DSpaceRESTv2Service { if (options && options.responseType) { requestOptions.responseType = options.responseType; } - return this.http.request(method, url, requestOptions) - .map((res) => ({ payload: res.body, headers: res.headers, statusCode: res.statusText })) - .catch((err) => { + return this.http.request(method, url, requestOptions).pipe( + map((res) => ({ payload: res.body, headers: res.headers, statusCode: res.statusText })), + catchError((err) => { console.log('Error: ', err); return observableThrowError(err); - }); + })); } } diff --git a/src/app/core/index/index.effects.ts b/src/app/core/index/index.effects.ts index 05ae529c8e..de1ba681a2 100644 --- a/src/app/core/index/index.effects.ts +++ b/src/app/core/index/index.effects.ts @@ -1,5 +1,6 @@ +import { filter, map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { Effect, Actions } from '@ngrx/effects'; +import { Effect, Actions, ofType } from '@ngrx/effects'; import { ObjectCacheActionTypes, AddToObjectCacheAction, @@ -15,44 +16,52 @@ import { IndexName } from './index.reducer'; export class UUIDIndexEffects { @Effect() addObject$ = this.actions$ - .ofType(ObjectCacheActionTypes.ADD) - .filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid)) - .map((action: AddToObjectCacheAction) => { - return new AddToIndexAction( - IndexName.OBJECT, - action.payload.objectToCache.uuid, - action.payload.objectToCache.self - ); - }); + .pipe( + ofType(ObjectCacheActionTypes.ADD), + filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid)), + map((action: AddToObjectCacheAction) => { + return new AddToIndexAction( + IndexName.OBJECT, + action.payload.objectToCache.uuid, + action.payload.objectToCache.self + ); + }) + ); @Effect() removeObject$ = this.actions$ - .ofType(ObjectCacheActionTypes.REMOVE) - .map((action: RemoveFromObjectCacheAction) => { - return new RemoveFromIndexByValueAction( - IndexName.OBJECT, - action.payload - ); - }); + .pipe( + ofType(ObjectCacheActionTypes.REMOVE), + map((action: RemoveFromObjectCacheAction) => { + return new RemoveFromIndexByValueAction( + IndexName.OBJECT, + action.payload + ); + }) + ); @Effect() addRequest$ = this.actions$ - .ofType(RequestActionTypes.CONFIGURE) - .filter((action: RequestConfigureAction) => action.payload.method === RestRequestMethod.Get) - .map((action: RequestConfigureAction) => { - return new AddToIndexAction( - IndexName.REQUEST, - action.payload.href, - action.payload.uuid - ); - }); + .pipe( + ofType(RequestActionTypes.CONFIGURE), + filter((action: RequestConfigureAction) => action.payload.method === RestRequestMethod.Get), + map((action: RequestConfigureAction) => { + return new AddToIndexAction( + IndexName.REQUEST, + action.payload.href, + action.payload.uuid + ); + }) + ); // @Effect() removeRequest$ = this.actions$ - // .ofType(ObjectCacheActionTypes.REMOVE) - // .map((action: RemoveFromObjectCacheAction) => { + // .pipe( + // ofType(ObjectCacheActionTypes.REMOVE), + // map((action: RemoveFromObjectCacheAction) => { // return new RemoveFromIndexByValueAction( // IndexName.OBJECT, // action.payload // ); - // }); + // }) + // ) constructor(private actions$: Actions) { diff --git a/src/app/core/integration/integration.service.ts b/src/app/core/integration/integration.service.ts index 7642848ed6..3c71ca5f3b 100644 --- a/src/app/core/integration/integration.service.ts +++ b/src/app/core/integration/integration.service.ts @@ -1,8 +1,8 @@ - -import {throwError as observableThrowError, Observable } from 'rxjs'; +import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; +import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; -import { ErrorResponse, IntegrationSuccessResponse, RestResponse } from '../cache/response-cache.models'; +import { IntegrationSuccessResponse } from '../cache/response-cache.models'; import { GetRequest, IntegrationRequest } from '../data/request.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -19,16 +19,18 @@ export abstract class IntegrationService { protected abstract halService: HALEndpointService; protected getData(request: GetRequest): Observable { - const [successResponse, errorResponse] = this.responseCache.get(request.href) - .map((entry: ResponseCacheEntry) => entry.response) - .partition((response: RestResponse) => response.isSuccessful); - return Observable.merge( - errorResponse.flatMap((response: ErrorResponse) => - observableThrowError(new Error(`Couldn't retrieve the integration data`))), - successResponse - .filter((response: IntegrationSuccessResponse) => isNotEmpty(response)) - .map((response: IntegrationSuccessResponse) => new IntegrationData(response.pageInfo, response.dataDefinition)) - .distinctUntilChanged()); + return this.responseCache.get(request.href).pipe( + map((entry: ResponseCacheEntry) => entry.response), + mergeMap((response) => { + if (response.isSuccessful && isNotEmpty(response)) { + const dataResponse = response as IntegrationSuccessResponse; + return observableOf(new IntegrationData(dataResponse.pageInfo, dataResponse.dataDefinition)); + } else if (!response.isSuccessful) { + return observableThrowError(new Error(`Couldn't retrieve the integration data`)); + } + }), + distinctUntilChanged() + ); } protected getIntegrationHref(endpoint, options: IntegrationSearchOptions = new IntegrationSearchOptions()): string { @@ -73,14 +75,14 @@ export abstract class IntegrationService { } public getEntriesByName(options: IntegrationSearchOptions): Observable { - return this.halService.getEndpoint(this.linkPath) - .map((endpoint: string) => this.getIntegrationHref(endpoint, options)) - .filter((href: string) => isNotEmpty(href)) - .distinctUntilChanged() - .map((endpointURL: string) => new IntegrationRequest(this.requestService.generateRequestId(), endpointURL)) - .do((request: GetRequest) => this.requestService.configure(request)) - .flatMap((request: GetRequest) => this.getData(request)) - .distinctUntilChanged(); + return this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getIntegrationHref(endpoint, options)), + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + map((endpointURL: string) => new IntegrationRequest(this.requestService.generateRequestId(), endpointURL)), + tap((request: GetRequest) => this.requestService.configure(request)), + mergeMap((request: GetRequest) => this.getData(request)), + distinctUntilChanged()); } } diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 4689b2048c..860dd71ce6 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -1,4 +1,6 @@ +import {distinctUntilKeyChanged, map, filter, first, take} from 'rxjs/operators'; + import { Inject, Injectable } from '@angular/core'; @@ -54,21 +56,21 @@ export class MetadataService { } public listenForRouteChange(): void { - this.router.events - .filter((event) => event instanceof NavigationEnd) - .map(() => this.router.routerState.root) - .map((route: ActivatedRoute) => { + this.router.events.pipe( + filter((event) => event instanceof NavigationEnd), + map(() => this.router.routerState.root), + map((route: ActivatedRoute) => { route = this.getCurrentRoute(route); return { params: route.params, data: route.data }; - }).subscribe((routeInfo: any) => { + }),).subscribe((routeInfo: any) => { this.processRouteChange(routeInfo); }); } public processRemoteData(remoteData: Observable>): void { - remoteData.map((rd: RemoteData) => rd.payload) - .filter((co: CacheableObject) => hasValue(co)) - .take(1) + remoteData.pipe(map((rd: RemoteData) => rd.payload), + filter((co: CacheableObject) => hasValue(co)), + take(1),) .subscribe((dspaceObject: DSpaceObject) => { if (!this.initialized) { this.initialize(dspaceObject); @@ -82,13 +84,13 @@ export class MetadataService { this.clearMetaTags(); } if (routeInfo.data.value.title) { - this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.translate.get(routeInfo.data.value.title).pipe(take(1)).subscribe((translatedTitle: string) => { this.addMetaTag('title', translatedTitle); this.title.setTitle(translatedTitle); }); } if (routeInfo.data.value.description) { - this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.translate.get(routeInfo.data.value.description).pipe(take(1)).subscribe((translatedDescription: string) => { this.addMetaTag('description', translatedDescription); }); } @@ -96,7 +98,7 @@ export class MetadataService { private initialize(dspaceObject: DSpaceObject): void { this.currentObject = new BehaviorSubject(dspaceObject); - this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { + this.currentObject.asObservable().pipe(distinctUntilKeyChanged('uuid')).subscribe(() => { this.setMetaTags(); }); this.initialized = true; @@ -268,11 +270,11 @@ export class MetadataService { private setCitationPdfUrlTag(): void { if (this.currentObject.value instanceof Item) { const item = this.currentObject.value as Item; - item.getFiles().filter((files) => isNotEmpty(files)).first().subscribe((bitstreams: Bitstream[]) => { + item.getFiles().pipe(filter((files) => isNotEmpty(files)),first(),).subscribe((bitstreams: Bitstream[]) => { for (const bitstream of bitstreams) { - bitstream.format.first() - .map((rd: RemoteData) => rd.payload) - .filter((format: BitstreamFormat) => hasValue(format)) + bitstream.format.pipe(first(), + map((rd: RemoteData) => rd.payload), + filter((format: BitstreamFormat) => hasValue(format)),) .subscribe((format: BitstreamFormat) => { if (format.mimetype === 'application/pdf') { this.addMetaTag('citation_pdf_url', bitstream.content); diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts index cc976f37f2..94c137dfd8 100644 --- a/src/app/core/registry/registry.service.ts +++ b/src/app/core/registry/registry.service.ts @@ -1,5 +1,6 @@ + +import {combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; import { RemoteData } from '../data/remote-data'; import { PaginatedList } from '../data/paginated-list'; import { PageInfo } from '../shared/page-info.model'; @@ -70,7 +71,7 @@ export class RegistryService { map((response: RegistryMetadataschemasSuccessResponse) => response.pageInfo) ); - const payloadObs = Observable.combineLatest(metadataschemasObs, pageInfoObs, (metadataschemas, pageInfo) => { + const payloadObs = observableCombineLatest(metadataschemasObs, pageInfoObs, (metadataschemas, pageInfo) => { return new PaginatedList(pageInfo, metadataschemas); }); @@ -132,7 +133,7 @@ export class RegistryService { map((response: RegistryMetadatafieldsSuccessResponse) => response.pageInfo) ); - const payloadObs = Observable.combineLatest(metadatafieldsObs, pageInfoObs, (metadatafields, pageInfo) => { + const payloadObs = observableCombineLatest(metadatafieldsObs, pageInfoObs, (metadatafields, pageInfo) => { return new PaginatedList(pageInfo, metadatafields); }); @@ -164,7 +165,7 @@ export class RegistryService { map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo) ); - const payloadObs = Observable.combineLatest(bitstreamformatsObs, pageInfoObs, (bitstreamformats, pageInfo) => { + const payloadObs = observableCombineLatest(bitstreamformatsObs, pageInfoObs, (bitstreamformats, pageInfo) => { return new PaginatedList(pageInfo, bitstreamformats); }); diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index 8e8d6b73d6..d72b9e9a9f 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -1,5 +1,5 @@ -import { Observable } from 'rxjs'; -import { distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators'; +import {of as observableOf, Observable } from 'rxjs'; +import {filter, distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; import { GlobalConfig } from '../../../config/global-config.interface'; @@ -30,11 +30,11 @@ export class HALEndpointService { private getEndpointMapAt(href): Observable { const request = new EndpointMapRequest(this.requestService.generateRequestId(), href); this.requestService.configure(request); - return this.responseCache.get(request.href) - .map((entry: ResponseCacheEntry) => entry.response) - .filter((response: EndpointMapSuccessResponse) => isNotEmpty(response)) - .map((response: EndpointMapSuccessResponse) => response.endpointMap) - .distinctUntilChanged(); + return this.responseCache.get(request.href).pipe( + map((entry: ResponseCacheEntry) => entry.response), + filter((response: EndpointMapSuccessResponse) => isNotEmpty(response)), + map((response: EndpointMapSuccessResponse) => response.endpointMap), + distinctUntilChanged(),); } public getEndpoint(linkPath: string): Observable { @@ -61,7 +61,7 @@ export class HALEndpointService { }), ]) .reduce((combined, thisElement) => [...combined, ...thisElement], []); - return Observable.of(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged()); + return observableOf(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged()); } public isEnabledOnRestApi(linkPath: string): Observable { diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts index 2d42a189a7..69def7b969 100644 --- a/src/app/core/shared/item.model.ts +++ b/src/app/core/shared/item.model.ts @@ -1,3 +1,4 @@ +import {map, startWith, filter} from 'rxjs/operators'; import { Observable } from 'rxjs'; import { DSpaceObject } from './dspace-object.model'; @@ -58,9 +59,9 @@ export class Item extends DSpaceObject { // TODO: currently this just picks the first thumbnail // should be adjusted when we have a way to determine // the primary thumbnail from rest - return this.getBitstreamsByBundleName('THUMBNAIL') - .filter((thumbnails) => isNotEmpty(thumbnails)) - .map((thumbnails) => thumbnails[0]) + return this.getBitstreamsByBundleName('THUMBNAIL').pipe( + filter((thumbnails) => isNotEmpty(thumbnails)), + map((thumbnails) => thumbnails[0]),) } /** @@ -68,10 +69,10 @@ export class Item extends DSpaceObject { * @returns {Observable} the primaryBitstream of the 'THUMBNAIL' bundle */ getThumbnailForOriginal(original: Bitstream): Observable { - return this.getBitstreamsByBundleName('THUMBNAIL') - .map((files) => { + return this.getBitstreamsByBundleName('THUMBNAIL').pipe( + map((files) => { return files.find((thumbnail) => thumbnail.name.startsWith(original.name)) - }).startWith(undefined); + }),startWith(undefined),); } /** @@ -88,15 +89,15 @@ export class Item extends DSpaceObject { * @returns {Observable} the bitstreams with the given bundleName */ getBitstreamsByBundleName(bundleName: string): Observable { - return this.bitstreams - .map((rd: RemoteData>) => rd.payload.page) - .filter((bitstreams: Bitstream[]) => hasValue(bitstreams)) - .startWith([]) - .map((bitstreams) => { + return this.bitstreams.pipe( + map((rd: RemoteData>) => rd.payload.page), + filter((bitstreams: Bitstream[]) => hasValue(bitstreams)), + startWith([]), + map((bitstreams) => { return bitstreams .filter((bitstream) => hasValue(bitstream)) .filter((bitstream) => bitstream.bundleName === bundleName) - }); + }),); } } diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index 442f60612c..e1f8da0f9d 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { createSelector, Store } from '@ngrx/store'; +import { createSelector, select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { RouterReducerState } from '@ngrx/router-store'; @@ -33,7 +33,7 @@ export class HeaderComponent implements OnInit { ngOnInit(): void { // set loading - this.isNavBarCollapsed = this.store.select(navCollapsedSelector); + this.isNavBarCollapsed = this.store.pipe(select(navCollapsedSelector)); } public toggle(): void { diff --git a/src/app/header/header.effects.ts b/src/app/header/header.effects.ts index e1d281958b..cdc018d2d9 100644 --- a/src/app/header/header.effects.ts +++ b/src/app/header/header.effects.ts @@ -1,5 +1,6 @@ +import { map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { Effect, Actions } from '@ngrx/effects' +import { Effect, Actions, ofType } from '@ngrx/effects' import * as fromRouter from '@ngrx/router-store'; import { HostWindowActionTypes } from '../shared/host-window.actions'; @@ -9,12 +10,16 @@ import { HeaderCollapseAction } from './header.actions'; export class HeaderEffects { @Effect() resize$ = this.actions$ - .ofType(HostWindowActionTypes.RESIZE) - .map(() => new HeaderCollapseAction()); + .pipe( + ofType(HostWindowActionTypes.RESIZE), + map(() => new HeaderCollapseAction()) + ); @Effect() routeChange$ = this.actions$ - .ofType(fromRouter.ROUTER_NAVIGATION) - .map(() => new HeaderCollapseAction()); + .pipe( + ofType(fromRouter.ROUTER_NAVIGATION), + map(() => new HeaderCollapseAction()) + ); constructor(private actions$: Actions) { diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.ts b/src/app/shared/auth-nav-menu/auth-nav-menu.component.ts index 0cc9fb277a..d2924fc2e0 100644 --- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.ts +++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.ts @@ -1,13 +1,19 @@ +import { of as observableOf, Observable } from 'rxjs'; + +import { map, filter } from 'rxjs/operators'; import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; import { RouterReducerState } from '@ngrx/router-store'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { fadeInOut, fadeOut } from '../animations/fade'; import { HostWindowService } from '../host-window.service'; import { AppState, routerStateSelector } from '../../app.reducer'; import { isNotUndefined } from '../empty.util'; -import { getAuthenticatedUser, isAuthenticated, isAuthenticationLoading } from '../../core/auth/selectors'; +import { + getAuthenticatedUser, + isAuthenticated, + isAuthenticationLoading +} from '../../core/auth/selectors'; import { Eperson } from '../../core/eperson/models/eperson.model'; import { LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service'; @@ -32,7 +38,7 @@ export class AuthNavMenuComponent implements OnInit { public isXsOrSm$: Observable; - public showAuth = Observable.of(false); + public showAuth = observableOf(false); public user: Observable; @@ -43,17 +49,19 @@ export class AuthNavMenuComponent implements OnInit { ngOnInit(): void { // set isAuthenticated - this.isAuthenticated = this.store.select(isAuthenticated); + this.isAuthenticated = this.store.pipe(select(isAuthenticated)); // set loading - this.loading = this.store.select(isAuthenticationLoading); + this.loading = this.store.pipe(select(isAuthenticationLoading)); - this.user = this.store.select(getAuthenticatedUser); + this.user = this.store.pipe(select(getAuthenticatedUser)); - this.showAuth = this.store.select(routerStateSelector) - .filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)) - .map((router: RouterReducerState) => { + this.showAuth = this.store.pipe( + select(routerStateSelector), + filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)), + map((router: RouterReducerState) => { return !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE); - }); + }) + ); } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts index 3a39d22bef..432c3959b8 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts @@ -1,24 +1,16 @@ import { - ChangeDetectorRef, Component, + ComponentFactoryResolver, ContentChildren, EventEmitter, Input, OnChanges, Output, QueryList, - SimpleChanges + SimpleChanges, Type } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { - DynamicDatePickerModel, - DynamicFormControlComponent, - DynamicFormControlEvent, - DynamicFormControlModel, - DynamicFormLayout, - DynamicFormLayoutService, - DynamicFormValidationService, - DynamicTemplateDirective, DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX, DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX_GROUP, @@ -29,6 +21,14 @@ import { DYNAMIC_FORM_CONTROL_TYPE_SELECT, DYNAMIC_FORM_CONTROL_TYPE_TEXTAREA, DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER, + DynamicDatePickerModel, DynamicFormControl, + DynamicFormControlContainerComponent, + DynamicFormControlEvent, + DynamicFormControlModel, + DynamicFormLayout, + DynamicFormLayoutService, + DynamicFormValidationService, + DynamicTemplateDirective, } from '@ng-dynamic-forms/core'; import { DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD } from './models/typeahead/dynamic-typeahead.model'; import { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model'; @@ -38,7 +38,6 @@ import { DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER } from './models/date-picker/dat import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP } from './models/lookup/dynamic-lookup.model'; import { DynamicListCheckboxGroupModel } from './models/list/dynamic-list-checkbox-group.model'; import { DynamicListRadioGroupModel } from './models/list/dynamic-list-radio-group.model'; -import { isNotEmpty } from '../../../empty.util'; import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME } from './models/lookup/dynamic-lookup-name.model'; export const enum NGBootstrapFormControlType { @@ -69,11 +68,11 @@ export const enum NGBootstrapFormControlType { styleUrls: ['../../form.component.scss', './ds-dynamic-form.component.scss'], templateUrl: './ds-dynamic-form-control.component.html' }) -export class DsDynamicFormControlComponent extends DynamicFormControlComponent implements OnChanges { +export class DsDynamicFormControlComponent extends DynamicFormControlContainerComponent implements OnChanges { - @ContentChildren(DynamicTemplateDirective) contentTemplateList: QueryList; - // tslint:disable-next-line:no-input-rename - @Input('templates') inputTemplateList: QueryList; + @ContentChildren(DynamicTemplateDirective) contentTemplateList: QueryList; + // tslint:disable-next-line:no-input-rename + @Input('templates') inputTemplateList: QueryList; @Input() formId: string; @Input() asBootstrapFormGroup = true; @@ -90,6 +89,7 @@ export class DsDynamicFormControlComponent extends DynamicFormControlComponent i @Output('dfFocus') focus: EventEmitter = new EventEmitter(); /* tslint:enable:no-output-rename */ + componentType: Type | null; type: NGBootstrapFormControlType | null; static getFormControlType(model: DynamicFormControlModel): NGBootstrapFormControlType | null { @@ -154,10 +154,10 @@ export class DsDynamicFormControlComponent extends DynamicFormControlComponent i } } - constructor(protected changeDetectorRef: ChangeDetectorRef, protected layoutService: DynamicFormLayoutService, + constructor(protected componentFactoryResolver: ComponentFactoryResolver, protected layoutService: DynamicFormLayoutService, protected validationService: DynamicFormValidationService) { - super(changeDetectorRef, layoutService, validationService); + super(componentFactoryResolver, layoutService, validationService); } ngOnChanges(changes: SimpleChanges) { @@ -170,9 +170,4 @@ export class DsDynamicFormControlComponent extends DynamicFormControlComponent i } } - onChangeLanguage(event) { - if (isNotEmpty((this.model as any).value)) { - this.onValueChange(event); - } - } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts index 7789d910a8..c0c207eaff 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts @@ -9,13 +9,13 @@ import { } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { - DynamicFormComponent, + DynamicFormComponent, DynamicFormControlContainerComponent, DynamicFormControlEvent, DynamicFormControlModel, - DynamicFormLayout, - DynamicFormLayoutService, - DynamicFormService, - DynamicTemplateDirective, + DynamicFormLayout, + DynamicFormLayoutService, + DynamicFormService, + DynamicTemplateDirective, } from '@ng-dynamic-forms/core'; import { DsDynamicFormControlComponent } from './ds-dynamic-form-control.component'; import { FormBuilderService } from '../form-builder.service'; @@ -39,9 +39,10 @@ export class DsDynamicFormComponent extends DynamicFormComponent { @ContentChildren(DynamicTemplateDirective) templates: QueryList; - @ViewChildren(DsDynamicFormControlComponent) components: QueryList; + @ViewChildren(DsDynamicFormControlComponent) components: QueryList; + + constructor(protected formService: FormBuilderService, protected layoutService: DynamicFormLayoutService) { + super(formService, layoutService); + } - constructor(protected formService: FormBuilderService, protected layoutService: DynamicFormLayoutService) { - super(formService, layoutService); - } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/dynamic-group/dynamic-group.components.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/dynamic-group/dynamic-group.components.ts index 9387a69107..5aa80bbcd0 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/dynamic-group/dynamic-group.components.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/dynamic-group/dynamic-group.components.ts @@ -1,3 +1,4 @@ +import {of as observableOf, Observable , Subscription } from 'rxjs'; import { ChangeDetectorRef, Component, @@ -9,8 +10,6 @@ import { Output, ViewChild } from '@angular/core'; - -import { Observable , Subscription } from 'rxjs'; import { DynamicFormControlModel, DynamicFormGroupModel, DynamicInputModel } from '@ng-dynamic-forms/core'; import { isEqual } from 'lodash'; @@ -48,7 +47,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit { @Output() focus: EventEmitter = new EventEmitter(); public chips: Chips; - public formCollapsed = Observable.of(false); + public formCollapsed = observableOf(false); public formModel: DynamicFormControlModel[]; public editMode = false; @@ -66,7 +65,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit { ngOnInit() { const config = {rows: this.model.formConfiguration} as SubmissionFormsModel; if (!this.model.isEmpty()) { - this.formCollapsed = Observable.of(true); + this.formCollapsed = observableOf(true); } this.model.valueUpdates.subscribe((value: any[]) => { if ((isNotEmpty(value) && !(value.length === 1 && hasOnlyEmptyProperties(value[0])))) { @@ -151,12 +150,12 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit { } collapseForm() { - this.formCollapsed = Observable.of(true); + this.formCollapsed = observableOf(true); this.clear(); } expandForm() { - this.formCollapsed = Observable.of(false); + this.formCollapsed = observableOf(false); } clear() { @@ -167,7 +166,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit { } this.resetForm(); if (!this.model.isEmpty()) { - this.formCollapsed = Observable.of(true); + this.formCollapsed = observableOf(true); } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.ts index ec0d3e343a..15b60235ce 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.ts @@ -99,7 +99,7 @@ export class DsDynamicListComponent implements OnInit { const value = option.id || option.value; const checked: boolean = isNotEmpty(findKey( this.model.value, - {value: option.value})); + (v) => v.value === option.value)); const item: ListItem = { id: value, diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts index 8928b14ae5..b184b4f4db 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts @@ -1,3 +1,5 @@ + +import {distinctUntilChanged} from 'rxjs/operators'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; @@ -137,8 +139,8 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit { this.searchOptions.query = this.getCurrentValue(); this.loading = true; - this.authorityService.getEntriesByName(this.searchOptions) - .distinctUntilChanged() + this.authorityService.getEntriesByName(this.searchOptions).pipe( + distinctUntilChanged()) .subscribe((object: IntegrationData) => { this.optionsList = object.payload; this.pageInfo = object.pageInfo; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts index 1c8bf15f1a..8a1bf21b64 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts @@ -1,3 +1,5 @@ + +import {tap} from 'rxjs/operators'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { FormGroup } from '@angular/forms'; @@ -66,8 +68,8 @@ export class DsDynamicScrollableDropdownComponent implements OnInit { if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) { this.loading = true; this.searchOptions.currentPage++; - this.authorityService.getEntriesByName(this.searchOptions) - .do(() => this.loading = false) + this.authorityService.getEntriesByName(this.searchOptions).pipe( + tap(() => this.loading = false)) .subscribe((object: IntegrationData) => { this.optionsList = this.optionsList.concat(object.payload); this.pageInfo = object.pageInfo; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts index 318259f792..1ccbd21f5c 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts @@ -1,7 +1,9 @@ + +import {of as observableOf, Observable } from 'rxjs'; + +import {catchError, debounceTime, distinctUntilChanged, tap, switchMap, map, merge} from 'rxjs/operators'; import { ChangeDetectorRef, Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; - -import { Observable } from 'rxjs'; import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { AuthorityService } from '../../../../../../core/integration/authority.service'; @@ -40,33 +42,33 @@ export class DsDynamicTagComponent implements OnInit { formatter = (x: { display: string }) => x.display; search = (text$: Observable) => - text$ - .debounceTime(300) - .distinctUntilChanged() - .do(() => this.changeSearchingStatus(true)) - .switchMap((term) => { + text$.pipe( + debounceTime(300), + distinctUntilChanged(), + tap(() => this.changeSearchingStatus(true)), + switchMap((term) => { if (term === '' || term.length < this.model.minChars) { - return Observable.of({list: []}); + return observableOf({list: []}); } else { this.searchOptions.query = term; - return this.authorityService.getEntriesByName(this.searchOptions) - .map((authorities) => { + return this.authorityService.getEntriesByName(this.searchOptions).pipe( + map((authorities) => { // @TODO Pagination for authority is not working, to refactor when it will be fixed return { list: authorities.payload, pageInfo: authorities.pageInfo }; - }) - .do(() => this.searchFailed = false) - .catch(() => { + }), + tap(() => this.searchFailed = false), + catchError(() => { this.searchFailed = true; - return Observable.of({list: []}); - }); + return observableOf({list: []}); + }),); } - }) - .map((results) => results.list) - .do(() => this.changeSearchingStatus(false)) - .merge(this.hideSearchingWhenUnsubscribed); + }), + map((results) => results.list), + tap(() => this.changeSearchingStatus(false)), + merge(this.hideSearchingWhenUnsubscribed),); constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, private authorityService: AuthorityService, diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts index dbaa8b4fcd..01285e1228 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts @@ -1,7 +1,9 @@ + +import {of as observableOf, Observable } from 'rxjs'; + +import {distinctUntilChanged, switchMap, tap, filter, catchError, debounceTime, merge, map} from 'rxjs/operators'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; - -import { Observable } from 'rxjs'; import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { AuthorityService } from '../../../../../../core/integration/authority.service'; @@ -37,33 +39,33 @@ export class DsDynamicTypeaheadComponent implements OnInit { }; search = (text$: Observable) => - text$ - .debounceTime(300) - .distinctUntilChanged() - .do(() => this.changeSearchingStatus(true)) - .switchMap((term) => { + text$.pipe( + debounceTime(300), + distinctUntilChanged(), + tap(() => this.changeSearchingStatus(true)), + switchMap((term) => { if (term === '' || term.length < this.model.minChars) { - return Observable.of({list: []}); + return observableOf({list: []}); } else { this.searchOptions.query = term; - return this.authorityService.getEntriesByName(this.searchOptions) - .map((authorities) => { + return this.authorityService.getEntriesByName(this.searchOptions).pipe( + map((authorities) => { // @TODO Pagination for authority is not working, to refactor when it will be fixed return { list: authorities.payload, pageInfo: authorities.pageInfo }; - }) - .do(() => this.searchFailed = false) - .catch(() => { + }), + tap(() => this.searchFailed = false), + catchError(() => { this.searchFailed = true; - return Observable.of({list: []}); - }); + return observableOf({list: []}); + }),); } - }) - .map((results) => results.list) - .do(() => this.changeSearchingStatus(false)) - .merge(this.hideSearchingWhenUnsubscribed); + }), + map((results) => results.list), + tap(() => this.changeSearchingStatus(false)), + merge(this.hideSearchingWhenUnsubscribed),); constructor(private authorityService: AuthorityService, private cdr: ChangeDetectorRef) { } @@ -74,8 +76,8 @@ export class DsDynamicTypeaheadComponent implements OnInit { this.model.authorityOptions.scope, this.model.authorityOptions.name, this.model.authorityOptions.metadata); - this.group.get(this.model.id).valueChanges - .filter((value) => this.currentValue !== value) + this.group.get(this.model.id).valueChanges.pipe( + filter((value) => this.currentValue !== value)) .subscribe((value) => { this.currentValue = value; }); diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index c149b51d7b..a8e5ccc499 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -1,3 +1,5 @@ + +import {distinctUntilChanged, map, filter} from 'rxjs/operators'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms'; @@ -8,7 +10,7 @@ import { DynamicFormGroupModel, DynamicFormLayout, } from '@ng-dynamic-forms/core'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { findIndex } from 'lodash'; import { AppState } from '../../app.reducer'; @@ -143,8 +145,8 @@ export class FormComponent implements OnDestroy, OnInit { this.formValid = this.getFormGroupValidStatus(); - this.subs.push(this.formGroup.statusChanges - .filter((currentStatus) => this.formValid !== this.getFormGroupValidStatus()) + this.subs.push(this.formGroup.statusChanges.pipe( + filter((currentStatus) => this.formValid !== this.getFormGroupValidStatus())) .subscribe((currentStatus) => { // Dispatch a FormStatusChangeAction if the form status has changed this.store.dispatch(new FormStatusChangeAction(this.formId, this.getFormGroupValidStatus())); @@ -152,10 +154,11 @@ export class FormComponent implements OnDestroy, OnInit { })); this.subs.push( - this.store.select(formObjectFromIdSelector(this.formId)) - .filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))) - .map((formState) => formState.errors) - .distinctUntilChanged() + this.store.pipe( + select(formObjectFromIdSelector(this.formId)), + filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))), + map((formState) => formState.errors), + distinctUntilChanged(),) // .delay(100) // this terrible delay is here to prevent the detection change error .subscribe((errors: FormError[]) => { const {formGroup, formModel} = this; diff --git a/src/app/shared/form/form.service.ts b/src/app/shared/form/form.service.ts index d9a1402ebd..f17da20db4 100644 --- a/src/app/shared/form/form.service.ts +++ b/src/app/shared/form/form.service.ts @@ -1,7 +1,8 @@ +import { map, distinctUntilChanged, filter } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { AppState } from '../../app.reducer'; import { formObjectFromIdSelector } from './selectors'; @@ -25,39 +26,47 @@ export class FormService { * Method to retrieve form's status from state */ public isValid(formId: string): Observable { - return this.store.select(formObjectFromIdSelector(formId)) - .filter((state) => isNotUndefined(state)) - .map((state) => state.valid) - .distinctUntilChanged(); + return this.store.pipe( + select(formObjectFromIdSelector(formId)), + filter((state) => isNotUndefined(state)), + map((state) => state.valid), + distinctUntilChanged() + ); } /** * Method to retrieve form's data from state */ public getFormData(formId: string): Observable { - return this.store.select(formObjectFromIdSelector(formId)) - .filter((state) => isNotUndefined(state)) - .map((state) => state.data) - .distinctUntilChanged(); + return this.store.pipe( + select(formObjectFromIdSelector(formId)), + filter((state) => isNotUndefined(state)), + map((state) => state.data), + distinctUntilChanged() + ); } /** * Method to retrieve form's errors from state */ public getFormErrors(formId: string): Observable { - return this.store.select(formObjectFromIdSelector(formId)) - .filter((state) => isNotUndefined(state)) - .map((state) => state.errors) - .distinctUntilChanged(); + return this.store.pipe( + select(formObjectFromIdSelector(formId)), + filter((state) => isNotUndefined(state)), + map((state) => state.errors), + distinctUntilChanged() + ); } /** * Method to retrieve form's data from state */ public isFormInitialized(formId: string): Observable { - return this.store.select(formObjectFromIdSelector(formId)) - .distinctUntilChanged() - .map((state) => isNotUndefined(state)); + return this.store.pipe( + select(formObjectFromIdSelector(formId)), + distinctUntilChanged(), + map((state) => isNotUndefined(state)) + ); } public getUniqueId(formId): string { @@ -71,7 +80,7 @@ export class FormService { Object.keys(formGroup.controls).forEach((field) => { const control = formGroup.get(field); if (control instanceof FormControl) { - control.markAsTouched({onlySelf: true}); + control.markAsTouched({ onlySelf: true }); } else if (control instanceof FormGroup) { this.validateAllFormFields(control); } diff --git a/src/app/shared/host-window.service.ts b/src/app/shared/host-window.service.ts index 3c26e717d2..9023a4e719 100644 --- a/src/app/shared/host-window.service.ts +++ b/src/app/shared/host-window.service.ts @@ -1,8 +1,9 @@ -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; + +import { filter, distinctUntilChanged, map } from 'rxjs/operators'; import { HostWindowState } from './host-window.reducer'; import { Injectable } from '@angular/core'; -import { createSelector, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { createSelector, select, Store } from '@ngrx/store'; import { hasValue } from './empty.util'; import { AppState } from '../app.reducer'; @@ -35,8 +36,10 @@ export class HostWindowService { } private getWidthObs(): Observable { - return this.store.select(widthSelector) - .filter((width) => hasValue(width)); + return this.store.pipe( + select(widthSelector), + filter((width) => hasValue(width)) + ); } get widthCategory(): Observable { @@ -94,10 +97,10 @@ export class HostWindowService { } isXsOrSm(): Observable { - return Observable.combineLatest( - this.isXs(), - this.isSm(), - ((isXs, isSm) => isXs || isSm) - ).distinctUntilChanged(); + return observableCombineLatest( + this.isXs(), + this.isSm(), + ((isXs, isSm) => isXs || isSm) + ).pipe(distinctUntilChanged()); } } diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 3b732e4bae..0868a95b86 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,12 +1,15 @@ +import { filter, takeWhile, map } from 'rxjs/operators'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; - -import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../core/auth/auth.actions'; +import { + AuthenticateAction, + ResetAuthenticationMessagesAction +} from '../../core/auth/auth.actions'; import { getAuthenticationError, @@ -99,7 +102,7 @@ export class LogInComponent implements OnDestroy, OnInit { */ public ngOnInit() { // set isAuthenticated - this.isAuthenticated = this.store.select(isAuthenticated); + this.isAuthenticated = this.store.pipe(select(isAuthenticated)); // set formGroup this.form = this.formBuilder.group({ @@ -108,29 +111,35 @@ export class LogInComponent implements OnDestroy, OnInit { }); // set error - this.error = this.store.select(getAuthenticationError) - .map((error) => { + this.error = this.store.pipe(select( + getAuthenticationError), + map((error) => { this.hasError = (isNotEmpty(error)); return error; - }); + }) + ); // set error - this.message = this.store.select(getAuthenticationInfo) - .map((message) => { + this.message = this.store.pipe( + select(getAuthenticationInfo), + map((message) => { this.hasMessage = (isNotEmpty(message)); return message; - }); + }) + ); // set loading - this.loading = this.store.select(isAuthenticationLoading); + this.loading = this.store.pipe(select(isAuthenticationLoading)); // subscribe to success - this.store.select(isAuthenticated) - .takeWhile(() => this.alive) - .filter((authenticated) => authenticated) + this.store.pipe( + select(isAuthenticated), + takeWhile(() => this.alive), + filter((authenticated) => authenticated),) .subscribe(() => { - this.authService.redirectToPreviousUrl(); - }); + this.authService.redirectToPreviousUrl(); + } + ); } /** diff --git a/src/app/shared/log-out/log-out.component.ts b/src/app/shared/log-out/log-out.component.ts index 3b821a0edc..9e8e7f7865 100644 --- a/src/app/shared/log-out/log-out.component.ts +++ b/src/app/shared/log-out/log-out.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; // @ngrx -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; // actions import { LogOutAction } from '../../core/auth/auth.actions'; @@ -48,7 +48,8 @@ export class LogOutComponent implements OnDestroy, OnInit { * @param {Store} store */ constructor(private router: Router, - private store: Store) { } + private store: Store) { + } /** * Lifecycle hook that is called when a directive, pipe or service is destroyed. @@ -62,10 +63,10 @@ export class LogOutComponent implements OnDestroy, OnInit { */ ngOnInit() { // set error - this.error = this.store.select(getLogOutError); + this.error = this.store.pipe(select(getLogOutError)); // set loading - this.loading = this.store.select(isAuthenticationLoading); + this.loading = this.store.pipe(select(isAuthenticationLoading)); } /** diff --git a/src/app/shared/mocks/mock-host-window-service.ts b/src/app/shared/mocks/mock-host-window-service.ts index ac6b58f23a..a9be248240 100644 --- a/src/app/shared/mocks/mock-host-window-service.ts +++ b/src/app/shared/mocks/mock-host-window-service.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; // declare a stub service export class MockHostWindowService { @@ -14,10 +14,10 @@ export class MockHostWindowService { } isXs(): Observable { - return Observable.of(this.width < 576); + return observableOf(this.width < 576); } isSm(): Observable { - return Observable.of(this.width < 768); + return observableOf(this.width < 768); } } diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts index 453b518c87..f3db69a0f2 100644 --- a/src/app/shared/mocks/mock-item.ts +++ b/src/app/shared/mocks/mock-item.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; import { Item } from '../../core/shared/item.model'; @@ -9,7 +9,7 @@ export const MockItem: Item = Object.assign(new Item(), { isArchived: true, isDiscoverable: true, isWithdrawn: false, - bitstreams: Observable.of({ + bitstreams: observableOf({ self: 'dspace-angular://aggregated/object/1507836003548', requestPending: false, responsePending: false, @@ -28,7 +28,7 @@ export const MockItem: Item = Object.assign(new Item(), { { sizeBytes: 10201, content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - format: Observable.of({ + format: observableOf({ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10', requestPending: false, responsePending: false, @@ -63,7 +63,7 @@ export const MockItem: Item = Object.assign(new Item(), { { sizeBytes: 31302, content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content', - format: Observable.of({ + format: observableOf({ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4', requestPending: false, responsePending: false, @@ -195,7 +195,7 @@ export const MockItem: Item = Object.assign(new Item(), { value: 'text' } ], - owningCollection: Observable.of({ + owningCollection: observableOf({ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb', requestPending: false, responsePending: false, diff --git a/src/app/shared/mocks/mock-request.service.ts b/src/app/shared/mocks/mock-request.service.ts index c87ae3d3fe..d46100d56c 100644 --- a/src/app/shared/mocks/mock-request.service.ts +++ b/src/app/shared/mocks/mock-request.service.ts @@ -1,8 +1,8 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; import { RequestService } from '../../core/data/request.service'; import { RequestEntry } from '../../core/data/request.reducer'; -export function getMockRequestService(getByHref$: Observable = Observable.of(new RequestEntry())): RequestService { +export function getMockRequestService(getByHref$: Observable = observableOf(new RequestEntry())): RequestService { return jasmine.createSpyObj('requestService', { configure: false, generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', diff --git a/src/app/shared/mocks/mock-response-cache.service.ts b/src/app/shared/mocks/mock-response-cache.service.ts index 64c8356044..a5a999873d 100644 --- a/src/app/shared/mocks/mock-response-cache.service.ts +++ b/src/app/shared/mocks/mock-response-cache.service.ts @@ -1,10 +1,10 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { ResponseCacheService } from '../../core/cache/response-cache.service'; export function getMockResponseCacheService( - add$: Observable = Observable.of(new ResponseCacheEntry()), - get$: Observable = Observable.of(new ResponseCacheEntry()), + add$: Observable = observableOf(new ResponseCacheEntry()), + get$: Observable = observableOf(new ResponseCacheEntry()), has: boolean = false ): ResponseCacheService { return jasmine.createSpyObj('ResponseCacheService', { diff --git a/src/app/shared/mocks/mock-translate-loader.ts b/src/app/shared/mocks/mock-translate-loader.ts index 06a13ae70a..529a257ec0 100644 --- a/src/app/shared/mocks/mock-translate-loader.ts +++ b/src/app/shared/mocks/mock-translate-loader.ts @@ -1,8 +1,8 @@ +import {of as observableOf, Observable } from 'rxjs'; import { TranslateLoader } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; export class MockTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable { - return Observable.of({}); + return observableOf({}); } } diff --git a/src/app/shared/notifications/notification/notification.component.ts b/src/app/shared/notifications/notification/notification.component.ts index f922ff590d..f3fbe83fde 100644 --- a/src/app/shared/notifications/notification/notification.component.ts +++ b/src/app/shared/notifications/notification/notification.component.ts @@ -1,3 +1,5 @@ + +import {of as observableOf, Observable } from 'rxjs'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -21,7 +23,6 @@ import { fromLeftEnter, fromLeftInState, fromLeftLeave, fromLeftOutState } from import { fromTopEnter, fromTopInState, fromTopLeave, fromTopOutState } from '../../animations/fromTop'; import { fadeInEnter, fadeInState, fadeOutLeave, fadeOutState } from '../../animations/fade'; import { NotificationAnimationsStatus } from '../models/notification-animations-type'; -import { Observable } from 'rxjs'; import { isNotEmpty } from '../../empty.util'; @Component({ @@ -130,14 +131,14 @@ export class NotificationComponent implements OnInit, OnDestroy { let value = null; if (isNotEmpty(item)) { if (typeof item === 'string') { - value = Observable.of(item); + value = observableOf(item); } else if (item instanceof Observable) { value = item; } else if (typeof item === 'object' && isNotEmpty(item.value)) { // when notifications state is transferred from SSR to CSR, // Observables Object loses the instance type and become simply object, // so converts it again to Observable - value = Observable.of(item.value); + value = observableOf(item.value); } } this[key] = value diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.ts index 40ea52949a..829cfadf0f 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.ts @@ -8,7 +8,7 @@ import { ViewEncapsulation } from '@angular/core'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; import { difference } from 'lodash'; @@ -50,7 +50,7 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.sub = this.store.select(notificationsStateSelector) + this.sub = this.store.pipe(select(notificationsStateSelector)) .subscribe((state: NotificationsState) => { if (state.length === 0) { this.notifications = []; diff --git a/src/app/shared/notifications/notifications.effects.ts b/src/app/shared/notifications/notifications.effects.ts index f2627f1806..20f2b618cb 100644 --- a/src/app/shared/notifications/notifications.effects.ts +++ b/src/app/shared/notifications/notifications.effects.ts @@ -12,14 +12,14 @@ export class NotificationsEffects { */ /* @Effect() public timer: Observable = this.actions$ - .ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER) + .pipe(ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER), // .debounceTime((action: any) => action.payload.options.timeOut) - .debounceTime(3000) - .map(() => new RemoveNotificationAction()); + debounceTime(3000), + map(() => new RemoveNotificationAction()); .switchMap((action: NewNotificationWithTimerAction) => Observable .timer(30000) .mapTo(() => new RemoveNotificationAction()) - );*/ + ));*/ /** * @constructor diff --git a/src/app/shared/notifications/notifications.service.ts b/src/app/shared/notifications/notifications.service.ts index 8cf067f4de..d6bb210652 100644 --- a/src/app/shared/notifications/notifications.service.ts +++ b/src/app/shared/notifications/notifications.service.ts @@ -1,3 +1,5 @@ + +import {of as observableOf, Observable } from 'rxjs'; import { Inject, Injectable } from '@angular/core'; import { INotification, Notification } from './models/notification.model'; import { NotificationType } from './models/notification-type'; @@ -5,7 +7,6 @@ import { NotificationOptions } from './models/notification-options.model'; import { uniqueId } from 'lodash'; import { Store } from '@ngrx/store'; import { NewNotificationAction, RemoveAllNotificationsAction, RemoveNotificationAction } from './notifications.actions'; -import { Observable } from 'rxjs'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; @Injectable() @@ -21,8 +22,8 @@ export class NotificationsService { this.store.dispatch(notificationAction); } - success(title: any = Observable.of(''), - content: any = Observable.of(''), + success(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html: boolean = false): INotification { const notification = new Notification(uniqueId(), NotificationType.Success, title, content, options, html); @@ -30,8 +31,8 @@ export class NotificationsService { return notification; } - error(title: any = Observable.of(''), - content: any = Observable.of(''), + error(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html: boolean = false): INotification { const notification = new Notification(uniqueId(), NotificationType.Error, title, content, options, html); @@ -39,8 +40,8 @@ export class NotificationsService { return notification; } - info(title: any = Observable.of(''), - content: any = Observable.of(''), + info(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html: boolean = false): INotification { const notification = new Notification(uniqueId(), NotificationType.Info, title, content, options, html); @@ -48,8 +49,8 @@ export class NotificationsService { return notification; } - warning(title: any = Observable.of(''), - content: any = Observable.of(''), + warning(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html: boolean = false): INotification { const notification = new Notification(uniqueId(), NotificationType.Warning, title, content, options, html); diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index 637b2f3a5f..0018c55c7f 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -1,3 +1,5 @@ + +import {map} from 'rxjs/operators'; import { Component, EventEmitter, Input, OnInit, @@ -88,11 +90,11 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { } getViewMode(): ViewMode { - this.route.queryParams.map((params) => { + this.route.queryParams.pipe(map((params) => { if (isNotEmpty(params.view) && hasValue(params.view)) { this.currentMode = params.view; } - }); + })); return this.currentMode; } diff --git a/src/app/shared/object-grid/object-grid.component.ts b/src/app/shared/object-grid/object-grid.component.ts index db600b48a9..c5ded101b3 100644 --- a/src/app/shared/object-grid/object-grid.component.ts +++ b/src/app/shared/object-grid/object-grid.component.ts @@ -1,3 +1,7 @@ + +import {combineLatest as observableCombineLatest, BehaviorSubject , Observable } from 'rxjs'; + +import {startWith, distinctUntilChanged, map } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, @@ -6,8 +10,6 @@ import { Output, ViewEncapsulation } from '@angular/core'; -import { BehaviorSubject , Observable } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; @@ -105,9 +107,9 @@ export class ObjectGridComponent implements OnInit { } }), distinctUntilChanged() - ).startWith(3); + ).pipe(startWith(3)); - this.columns$ = Observable.combineLatest( + this.columns$ = observableCombineLatest( nbColumns$, this._objects$, (nbColumns, objects) => { diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index cfed30b012..0dc1892e47 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -11,15 +11,14 @@ import { import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription , Observable } from 'rxjs'; -import { isNumeric } from 'rxjs/util'; - +import { Subscription, Observable } from 'rxjs'; import { HostWindowService } from '../host-window.service'; import { HostWindowState } from '../host-window.reducer'; import { PaginationComponentOptions } from './pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { hasValue, isNotEmpty } from '../empty.util'; import { PageInfo } from '../../core/shared/page-info.model'; +import { isNumeric } from 'tslint'; /** * The default pagination controls component. diff --git a/src/app/shared/services/api.service.ts b/src/app/shared/services/api.service.ts index 96e1007f37..4f8474e0c1 100644 --- a/src/app/shared/services/api.service.ts +++ b/src/app/shared/services/api.service.ts @@ -1,4 +1,6 @@ import { throwError as observableThrowError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @@ -12,11 +14,11 @@ export class ApiService { * whatever domain/feature method name */ get(url: string, options?: any) { - return this._http.get(url, options) - .catch((err) => { + return this._http.get(url, options).pipe( + catchError((err) => { console.log('Error: ', err); return observableThrowError(err); - }); + })); } } diff --git a/src/app/shared/services/route.service.ts b/src/app/shared/services/route.service.ts index fcb6f80f29..8565379007 100644 --- a/src/app/shared/services/route.service.ts +++ b/src/app/shared/services/route.service.ts @@ -1,3 +1,5 @@ + +import {distinctUntilChanged, map} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { @@ -13,24 +15,24 @@ export class RouteService { } getQueryParameterValues(paramName: string): Observable { - return this.route.queryParamMap.map((map) => [...map.getAll(paramName)]).distinctUntilChanged(); + return this.route.queryParamMap.pipe(map((map) => [...map.getAll(paramName)]),distinctUntilChanged(),); } getQueryParameterValue(paramName: string): Observable { - return this.route.queryParamMap.map((map) => map.get(paramName)).distinctUntilChanged(); + return this.route.queryParamMap.pipe(map((map) => map.get(paramName)),distinctUntilChanged(),); } hasQueryParam(paramName: string): Observable { - return this.route.queryParamMap.map((map) => map.has(paramName)).distinctUntilChanged(); + return this.route.queryParamMap.pipe(map((map) => map.has(paramName)),distinctUntilChanged(),); } hasQueryParamWithValue(paramName: string, paramValue: string): Observable { - return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1).distinctUntilChanged(); + return this.route.queryParamMap.pipe(map((map) => map.getAll(paramName).indexOf(paramValue) > -1),distinctUntilChanged(),); } getQueryParamsWithPrefix(prefix: string): Observable { - return this.route.queryParamMap - .map((map) => { + return this.route.queryParamMap.pipe( + map((map) => { const params = {}; map.keys .filter((key) => key.startsWith(prefix)) @@ -38,7 +40,7 @@ export class RouteService { params[key] = [...map.getAll(key)]; }); return params; - }) - .distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)); + }), + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),); } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index bc8292d6bb..5cc90d4e07 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -4,7 +4,12 @@ import { RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NouisliderModule } from 'ng2-nouislider'; -import { NgbDatepickerModule, NgbModule, NgbTimepickerModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; +import { + NgbDatepickerModule, + NgbModule, + NgbTimepickerModule, + NgbTypeaheadModule +} from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; @@ -56,8 +61,6 @@ import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dyn import { DynamicFormsCoreModule } from '@ng-dynamic-forms/core'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { TextMaskModule } from 'angular2-text-mask'; -import { NotificationComponent } from './notifications/notification/notification.component'; -import { NotificationsBoardComponent } from './notifications/notifications-board/notifications-board.component'; import { DragClickDirective } from './utils/drag-click.directive'; import { TruncatePipe } from './utils/truncate.pipe'; import { TruncatableComponent } from './truncatable/truncatable.component'; @@ -78,8 +81,8 @@ import { ClickOutsideDirective } from './utils/click-outside.directive'; import { EmphasizePipe } from './utils/emphasize.pipe'; import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component'; import { CapitalizePipe } from './utils/capitalize.pipe'; -import { MomentModule } from 'angular2-moment'; import { ObjectKeysPipe } from './utils/object-keys-pipe'; +import { MomentModule } from 'ngx-moment'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here diff --git a/src/app/shared/testing/active-router-stub.ts b/src/app/shared/testing/active-router-stub.ts index e09ff2299a..89a417149a 100644 --- a/src/app/shared/testing/active-router-stub.ts +++ b/src/app/shared/testing/active-router-stub.ts @@ -1,3 +1,5 @@ + +import {map} from 'rxjs/operators'; import { convertToParamMap, ParamMap, Params } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; @@ -10,7 +12,7 @@ export class ActivatedRouteStub { params = this.subject.asObservable(); queryParams = this.subject.asObservable(); - queryParamMap = this.subject.asObservable().map((params: Params) => convertToParamMap(params)); + queryParamMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params))); constructor(params?: Params) { if (params) { diff --git a/src/app/shared/testing/auth-request-service-stub.ts b/src/app/shared/testing/auth-request-service-stub.ts index 8a38fff9e7..7ade392aa0 100644 --- a/src/app/shared/testing/auth-request-service-stub.ts +++ b/src/app/shared/testing/auth-request-service-stub.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; import { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service'; import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; @@ -31,7 +31,7 @@ export class AuthRequestServiceStub { authStatusStub.authenticated = false; } } - return Observable.of(authStatusStub); + return observableOf(authStatusStub); } public getRequest(method: string, options?: HttpOptions): Observable { @@ -51,7 +51,7 @@ export class AuthRequestServiceStub { } break; } - return Observable.of(authStatusStub); + return observableOf(authStatusStub); } private validateToken(token): boolean { diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index 2746eb392e..ea0993d8dd 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -1,5 +1,6 @@ + +import {of as observableOf, Observable } from 'rxjs'; import { AuthStatus } from '../../core/auth/models/auth-status.model'; -import { Observable } from 'rxjs'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { EpersonMock } from './eperson-mock'; import { Eperson } from '../../core/eperson/models/eperson.model'; @@ -20,7 +21,7 @@ export class AuthServiceStub { authStatus.authenticated = true; authStatus.token = this.token; authStatus.eperson = [EpersonMock]; - return Observable.of(authStatus); + return observableOf(authStatus); } else { console.log('error'); throw(new Error('Message Error test')); @@ -29,7 +30,7 @@ export class AuthServiceStub { public authenticatedUser(token: AuthTokenInfo): Observable { if (token.accessToken === 'token_test') { - return Observable.of(EpersonMock); + return observableOf(EpersonMock); } else { throw(new Error('Message Error test')); } @@ -44,11 +45,11 @@ export class AuthServiceStub { } public hasValidAuthenticationToken(): Observable { - return Observable.of(this.token); + return observableOf(this.token); } public logout(): Observable { - return Observable.of(true); + return observableOf(true); } public isTokenExpired(token?: AuthTokenInfo): boolean { @@ -70,11 +71,11 @@ export class AuthServiceStub { } public isTokenExpiring(): Observable { - return Observable.of(false); + return observableOf(false); } public refreshAuthenticationToken(token: AuthTokenInfo): Observable { - return Observable.of(this.token); + return observableOf(this.token); } public redirectToPreviousUrl() { diff --git a/src/app/shared/testing/authority-service-stub.ts b/src/app/shared/testing/authority-service-stub.ts index cc74708172..1122901f7f 100644 --- a/src/app/shared/testing/authority-service-stub.ts +++ b/src/app/shared/testing/authority-service-stub.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; import { IntegrationSearchOptions } from '../../core/integration/models/integration-options.model'; import { IntegrationData } from '../../core/integration/integration-data'; import { PageInfo } from '../../core/shared/page-info.model'; @@ -16,6 +16,6 @@ export class AuthorityServiceStub { } getEntriesByName(options: IntegrationSearchOptions) { - return Observable.of(new IntegrationData(new PageInfo(), this._payload)); + return observableOf(new IntegrationData(new PageInfo(), this._payload)); } } diff --git a/src/app/shared/testing/hal-endpoint-service-stub.ts b/src/app/shared/testing/hal-endpoint-service-stub.ts index deefef7f2a..46d6a452a3 100644 --- a/src/app/shared/testing/hal-endpoint-service-stub.ts +++ b/src/app/shared/testing/hal-endpoint-service-stub.ts @@ -1,9 +1,9 @@ -import { Observable } from 'rxjs'; +import { of as observableOf } from 'rxjs'; export class HALEndpointServiceStub { constructor(private url: string) {}; getEndpoint(path: string) { - return Observable.of(this.url + '/' + path); + return observableOf(this.url + '/' + path); } } diff --git a/src/app/shared/testing/host-window-service-stub.ts b/src/app/shared/testing/host-window-service-stub.ts index df1bb567fc..ecb8c26acb 100644 --- a/src/app/shared/testing/host-window-service-stub.ts +++ b/src/app/shared/testing/host-window-service-stub.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; // declare a stub service export class HostWindowServiceStub { @@ -14,7 +14,7 @@ export class HostWindowServiceStub { } isXs(): Observable { - return Observable.of(this.width < 576); + return observableOf(this.width < 576); } isXsOrSm(): Observable { diff --git a/src/app/shared/testing/mock-store.ts b/src/app/shared/testing/mock-store.ts index f366ad45ab..fd47c71ae0 100644 --- a/src/app/shared/testing/mock-store.ts +++ b/src/app/shared/testing/mock-store.ts @@ -1,3 +1,5 @@ + +import {map} from 'rxjs/operators'; import { Action } from '@ngrx/store'; import { Observable , BehaviorSubject } from 'rxjs'; @@ -12,8 +14,8 @@ export class MockStore extends BehaviorSubject { }; select = (pathOrMapFn: any): Observable => { - return this.asObservable() - .map((value) => pathOrMapFn.projector(value)) + return this.asObservable().pipe( + map((value) => pathOrMapFn.projector(value))) }; nextState(_newState: T) { diff --git a/src/app/shared/testing/mock-translate-loader.ts b/src/app/shared/testing/mock-translate-loader.ts index 06a13ae70a..529a257ec0 100644 --- a/src/app/shared/testing/mock-translate-loader.ts +++ b/src/app/shared/testing/mock-translate-loader.ts @@ -1,8 +1,8 @@ +import {of as observableOf, Observable } from 'rxjs'; import { TranslateLoader } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; export class MockTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable { - return Observable.of({}); + return observableOf({}); } } diff --git a/src/app/shared/testing/notifications-service-stub.ts b/src/app/shared/testing/notifications-service-stub.ts index d8ec867fea..16588c2017 100644 --- a/src/app/shared/testing/notifications-service-stub.ts +++ b/src/app/shared/testing/notifications-service-stub.ts @@ -1,32 +1,32 @@ -import { Observable } from 'rxjs'; +import {of as observableOf, Observable } from 'rxjs'; import { INotification } from '../notifications/models/notification.model'; import { NotificationOptions } from '../notifications/models/notification-options.model'; export class NotificationsServiceStub { - success(title: any = Observable.of(''), - content: any = Observable.of(''), + success(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html?: any): INotification { return } - error(title: any = Observable.of(''), - content: any = Observable.of(''), + error(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html?: any): INotification { return } - info(title: any = Observable.of(''), - content: any = Observable.of(''), + info(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html?: any): INotification { return } - warning(title: any = Observable.of(''), - content: any = Observable.of(''), + warning(title: any = observableOf(''), + content: any = observableOf(''), options: NotificationOptions = this.getDefaultOptions(), html?: any): INotification { return diff --git a/src/app/shared/testing/search-service-stub.ts b/src/app/shared/testing/search-service-stub.ts index f58e6df954..cbc0611a47 100644 --- a/src/app/shared/testing/search-service-stub.ts +++ b/src/app/shared/testing/search-service-stub.ts @@ -1,4 +1,4 @@ -import { Observable, BehaviorSubject } from 'rxjs'; +import {of as observableOf, Observable , BehaviorSubject } from 'rxjs'; import { ViewMode } from '../../core/shared/view-mode.model'; export class SearchServiceStub { @@ -38,6 +38,6 @@ export class SearchServiceStub { } getFilterLabels() { - return Observable.of([]); + return observableOf([]); } } diff --git a/src/app/shared/truncatable/truncatable.service.ts b/src/app/shared/truncatable/truncatable.service.ts index c25bf30c27..60e8a7df41 100644 --- a/src/app/shared/truncatable/truncatable.service.ts +++ b/src/app/shared/truncatable/truncatable.service.ts @@ -1,8 +1,13 @@ +import { map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { TruncatablesState, TruncatableState } from './truncatable.reducer'; -import { TruncatableExpandAction, TruncatableToggleAction, TruncatableCollapseAction } from './truncatable.actions'; +import { + TruncatableExpandAction, + TruncatableToggleAction, + TruncatableCollapseAction +} from './truncatable.actions'; import { hasValue } from '../empty.util'; const truncatableStateSelector = (state: TruncatablesState) => state.truncatable; @@ -22,14 +27,16 @@ export class TruncatableService { * @returns {Observable} Emits true if the state in the store is currently collapsed for the given truncatable component */ isCollapsed(id: string): Observable { - return this.store.select(truncatableByIdSelector(id)) - .map((object: TruncatableState) => { + return this.store.pipe( + select(truncatableByIdSelector(id)), + map((object: TruncatableState) => { if (object) { return object.collapsed; } else { return false; } - }); + }) + ); } /** diff --git a/src/app/shared/uploader/uploader.component.ts b/src/app/shared/uploader/uploader.component.ts index ac3e701ca2..c43ac91082 100644 --- a/src/app/shared/uploader/uploader.component.ts +++ b/src/app/shared/uploader/uploader.component.ts @@ -1,3 +1,5 @@ + +import {of as observableOf, Observable } from 'rxjs'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -10,7 +12,6 @@ import { } from '@angular/core' import { FileUploader } from 'ng2-file-upload'; -import { Observable } from 'rxjs'; import { uniqueId } from 'lodash'; import { ScrollToConfigOptions, ScrollToService } from '@nicky-lenaers/ngx-scroll-to'; @@ -60,8 +61,8 @@ export class UploaderComponent { public uploader: FileUploader; public uploaderId: string; - public isOverBaseDropZone = Observable.of(false); - public isOverDocumentDropZone = Observable.of(false); + public isOverBaseDropZone = observableOf(false); + public isOverDocumentDropZone = observableOf(false); @HostListener('window:dragover', ['$event']) onDragOver(event: any) { @@ -70,7 +71,7 @@ export class UploaderComponent { // Show drop area on the page event.preventDefault(); if ((event.target as any).tagName !== 'HTML') { - this.isOverDocumentDropZone = Observable.of(true); + this.isOverDocumentDropZone = observableOf(true); } } } @@ -111,7 +112,7 @@ export class UploaderComponent { }); this.uploader.onBeforeUploadItem = () => { this.onBeforeUpload(); - this.isOverDocumentDropZone = Observable.of(false); + this.isOverDocumentDropZone = observableOf(false); // Move page target to the uploader const config: ScrollToConfigOptions = { @@ -133,7 +134,7 @@ export class UploaderComponent { * Called when files are dragged on the base drop area. */ public fileOverBase(isOver: boolean): void { - this.isOverBaseDropZone = Observable.of(isOver); + this.isOverBaseDropZone = observableOf(isOver); } /** @@ -141,7 +142,7 @@ export class UploaderComponent { */ public fileOverDocument(isOver: boolean) { if (!isOver) { - this.isOverDocumentDropZone = Observable.of(isOver); + this.isOverDocumentDropZone = observableOf(isOver); } } diff --git a/src/app/shared/utils/debounce.directive.ts b/src/app/shared/utils/debounce.directive.ts index 3e6b0e6d22..48cabe006f 100644 --- a/src/app/shared/utils/debounce.directive.ts +++ b/src/app/shared/utils/debounce.directive.ts @@ -1,3 +1,5 @@ + +import {distinctUntilChanged, debounceTime, takeUntil} from 'rxjs/operators'; import { Directive, Input, Output, EventEmitter, OnDestroy, OnInit } from '@angular/core'; import { NgControl } from '@angular/forms'; @@ -44,10 +46,10 @@ export class DebounceDirective implements OnInit, OnDestroy { * Emit it when the debounceTime is over without new changes */ ngOnInit() { - this.model.valueChanges - .takeUntil(this.subject) - .debounceTime(this.dsDebounce) - .distinctUntilChanged() + this.model.valueChanges.pipe( + takeUntil(this.subject), + debounceTime(this.dsDebounce), + distinctUntilChanged(),) .subscribe((modelValue) => { if (this.isFirstChange) { this.isFirstChange = false; diff --git a/src/app/store.effects.ts b/src/app/store.effects.ts index f264c0f9bd..b13e816652 100644 --- a/src/app/store.effects.ts +++ b/src/app/store.effects.ts @@ -1,27 +1,33 @@ +import { of as observableOf } from 'rxjs'; + +import { map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Action, Store } from '@ngrx/store'; -import { Effect, Actions } from '@ngrx/effects'; - -import { Observable } from 'rxjs'; +import { Actions, Effect, ofType } from '@ngrx/effects'; import { AppState } from './app.reducer'; -import { StoreAction, StoreActionTypes } from './store.actions'; -import { HostWindowResizeAction, HostWindowActionTypes } from './shared/host-window.actions'; +import { StoreActionTypes } from './store.actions'; +import { HostWindowResizeAction } from './shared/host-window.actions'; @Injectable() export class StoreEffects { - @Effect({ dispatch: false }) replay = this.actions.ofType(StoreActionTypes.REPLAY).map((replayAction: Action) => { - // TODO: should be able to replay all actions before the browser attempts to - // replayAction.payload.forEach((action: Action) => { - // this.store.dispatch(action); - // }); - return Observable.of({}); - }); + @Effect({ dispatch: false }) replay = this.actions.pipe( + ofType(StoreActionTypes.REPLAY), + map((replayAction: Action) => { + // TODO: should be able to replay all actions before the browser attempts to + // replayAction.payload.forEach((action: Action) => { + // this.store.dispatch(action); + // }); + return observableOf({}); + })); - @Effect() resize = this.actions.ofType(StoreActionTypes.REPLAY, StoreActionTypes.REHYDRATE).map(() => new HostWindowResizeAction(window.innerWidth, window.innerHeight)); + @Effect() resize = this.actions.pipe( + ofType(StoreActionTypes.REPLAY, StoreActionTypes.REHYDRATE), + map(() => new HostWindowResizeAction(window.innerWidth, window.innerHeight)) + ); constructor(private actions: Actions, private store: Store) { diff --git a/src/modules/transfer-state/dspace-server-transfer-state.service.ts b/src/modules/transfer-state/dspace-server-transfer-state.service.ts index 380bb3f9f3..ac8c817d84 100644 --- a/src/modules/transfer-state/dspace-server-transfer-state.service.ts +++ b/src/modules/transfer-state/dspace-server-transfer-state.service.ts @@ -1,3 +1,5 @@ + +import {take} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { DSpaceTransferState } from './dspace-transfer-state.service'; @@ -6,7 +8,7 @@ export class DSpaceServerTransferState extends DSpaceTransferState { transfer() { this.transferState.onSerialize(DSpaceTransferState.NGRX_STATE, () => { let state; - this.store.take(1).subscribe((saveState: any) => { + this.store.pipe(take(1)).subscribe((saveState: any) => { state = saveState; }); diff --git a/yarn.lock b/yarn.lock index 561cdcf27d..7e479f9404 100644 --- a/yarn.lock +++ b/yarn.lock @@ -267,16 +267,16 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" "@types/node@*": - version "10.9.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.1.tgz#06f002136fbcf51e730995149050bb3c45ee54e6" + version "10.9.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.2.tgz#f0ab8dced5cd6c56b26765e1c0d9e4fdcc9f2a00" "@types/node@^6.0.46": version "6.0.116" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.116.tgz#2f9cd62b4ecc4927e3942e2655c182eecf5b45f1" "@types/node@^9.4.6": - version "9.6.29" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.29.tgz#9b3c5b288c77fbd2ab5684d36e3528cb9ee5429f" + version "9.6.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.30.tgz#1ecf83eaf7ac2d0dada7a9d61a1e4e7a6183ac06" "@types/q@^0.0.32": version "0.0.32" @@ -318,6 +318,10 @@ version "0.0.30" resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" +"@types/tapable@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.2.tgz#e13182e1b69871a422d7863e11a4a6f5b814a4bd" + "@types/uuid@^3.4.3": version "3.4.3" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" @@ -816,13 +820,6 @@ array-flatten@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" -array-includes@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.7.0" - array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -2127,7 +2124,7 @@ convert-source-map@^0.3.3: version "0.3.5" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" -convert-source-map@^1.1.1, convert-source-map@^1.5.0: +convert-source-map@^1.5.0, convert-source-map@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -2960,7 +2957,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: +es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1: version "1.12.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" dependencies: @@ -3479,6 +3476,12 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + dependencies: + locate-path "^3.0.0" + flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" @@ -3679,8 +3682,8 @@ gaze@^1.0.0: globule "^1.0.0" generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + version "2.2.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.2.0.tgz#1aeac896147293d27bce65eb295ce5f3f094a292" generate-object-property@^1.1.0: version "1.2.0" @@ -3763,19 +3766,9 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -4135,16 +4128,16 @@ html-minifier@^3.2.3: relateurl "0.2.x" uglify-js "3.4.x" -html-webpack-plugin@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" +html-webpack-plugin@^4.0.0-alpha: + version "4.0.0-alpha" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-alpha.tgz#b2c7b6d4885a209c999dfce3ffb9866e2c8c0eaa" dependencies: + "@types/tapable" "1.0.2" html-minifier "^3.2.3" - loader-utils "^0.2.16" - lodash "^4.17.3" + loader-utils "^1.1.0" + lodash "^4.17.10" pretty-error "^2.0.2" tapable "^1.0.0" - toposort "^1.0.0" util.promisify "1.0.0" htmlescape@^1.1.0: @@ -5298,6 +5291,13 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -5468,7 +5468,7 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@4.17.10, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.8.0, lodash@~4.17.10: +lodash@4.17.10, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.8.0, lodash@~4.17.10: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -5779,19 +5779,15 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.34.0 < 2": +"mime-db@>= 1.34.0 < 2", mime-db@~1.36.0: version "1.36.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" -mime-db@~1.35.0: - version "1.35.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" - mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19, mime-types@~2.1.7: - version "2.1.19" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" + version "2.1.20" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" dependencies: - mime-db "~1.35.0" + mime-db "~1.36.0" mime@1.4.1: version "1.4.1" @@ -5965,8 +5961,8 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" nan@^2.10.0, nan@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" + version "2.11.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" nanomatch@^1.2.9: version "1.2.13" @@ -6032,6 +6028,12 @@ ngx-infinite-scroll@6.0.1: dependencies: opencollective "^1.0.3" +ngx-moment@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ngx-moment/-/ngx-moment-3.1.0.tgz#41380b4dd8b68e7bd6d17cc6fe7f703ae506dc3a" + dependencies: + tslib "^1.9.0" + ngx-pagination@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-3.0.3.tgz#314145263613738d8c544da36cd8dacc5aa89a6f" @@ -6534,12 +6536,24 @@ p-limit@^1.0.0, p-limit@^1.1.0: dependencies: p-try "^1.0.0" +p-limit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + dependencies: + p-limit "^2.0.0" + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -6548,6 +6562,10 @@ p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + pac-proxy-agent@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz#90d9f6730ab0f4d2607dcdcd4d3d641aa26c3896" @@ -8129,18 +8147,18 @@ resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" -resolve-url-loader@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.2.1.tgz#13a1396fb773edf959550e400e688f5ed32548bf" +resolve-url-loader@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.3.0.tgz#e1b37034d48f22f8cfb9f04c026faaa070fdaf26" dependencies: adjust-sourcemap-loader "^1.1.0" - camelcase "^4.0.0" - convert-source-map "^1.1.1" - loader-utils "^1.0.0" + camelcase "^4.1.0" + convert-source-map "^1.5.1" + loader-utils "^1.1.0" lodash.defaults "^4.0.0" rework "^1.0.1" rework-visit "^1.0.0" - source-map "^0.5.6" + source-map "^0.5.7" urix "^0.1.0" resolve-url@^0.2.1: @@ -8912,8 +8930,8 @@ statuses@~1.4.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" stdout-stream@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" + version "1.4.1" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" dependencies: readable-stream "^2.0.1" @@ -9286,10 +9304,6 @@ to-string-loader@1.1.5: dependencies: loader-utils "^0.2.16" -toposort@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" - touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -9326,10 +9340,10 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" "true-case-path@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62" + version "1.0.3" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" dependencies: - glob "^6.0.4" + glob "^7.1.2" tryer@^1.0.0: version "1.0.1" @@ -9477,14 +9491,10 @@ typescript@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" -typescript@^2.5.0: +typescript@^2.5.0, typescript@^2.9.1: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" -typescript@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961" - uglify-es@^3.3.4, uglify-es@^3.3.7: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" @@ -9921,18 +9931,6 @@ webpack-command@^0.4.1: webpack-log "^1.1.2" wordwrap "^1.0.0" -webpack-dev-middleware@3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz#8b32aa43da9ae79368c1bf1183f2b6cf5e1f39ed" - dependencies: - loud-rejection "^1.6.0" - memory-fs "~0.4.1" - mime "^2.1.0" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - url-join "^4.0.0" - webpack-log "^1.0.1" - webpack-dev-middleware@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz#a20ceef194873710052da678f3c6ee0aeed92552" @@ -9958,11 +9956,10 @@ webpack-dev-middleware@^2.0.6: webpack-log "^1.0.1" webpack-dev-server@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.5.tgz#87477252e1ac6789303fb8cd3e585fa5d508a401" + version "3.1.6" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.6.tgz#8617503768b1131fd539cf43c3e2e63bd34c1521" dependencies: ansi-html "0.0.7" - array-includes "^3.0.3" bonjour "^3.5.0" chokidar "^2.0.0" compression "^1.5.2" @@ -9986,9 +9983,9 @@ webpack-dev-server@^3.1.5: spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^5.1.0" - webpack-dev-middleware "3.1.3" - webpack-log "^1.1.2" - yargs "11.0.0" + webpack-dev-middleware "3.2.0" + webpack-log "^2.0.0" + yargs "12.0.1" webpack-log@^1.0.1, webpack-log@^1.1.2: version "1.2.0" @@ -10197,7 +10194,7 @@ y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" -y18n@^4.0.0: +"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" @@ -10209,7 +10206,7 @@ yallist@^3.0.0, yallist@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" -yargs-parser@^10.0.0: +yargs-parser@^10.0.0, yargs-parser@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" dependencies: @@ -10227,13 +10224,13 @@ yargs-parser@^9.0.2: dependencies: camelcase "^4.1.0" -yargs@11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" +yargs@12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.1.tgz#6432e56123bb4e7c3562115401e98374060261c2" dependencies: cliui "^4.0.0" - decamelize "^1.1.1" - find-up "^2.1.0" + decamelize "^2.0.0" + find-up "^3.0.0" get-caller-file "^1.0.1" os-locale "^2.0.0" require-directory "^2.1.1" @@ -10241,8 +10238,8 @@ yargs@11.0.0: set-blocking "^2.0.0" string-width "^2.0.0" which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^9.0.2" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^10.1.0" yargs@^11.0.0: version "11.1.0"