intermediate commit

This commit is contained in:
lotte
2018-08-29 15:12:01 +02:00
parent 2cbe6a6d91
commit 777facf5cd
91 changed files with 1150 additions and 964 deletions

View File

@@ -109,6 +109,7 @@
"ng2-nouislider": "^1.7.11", "ng2-nouislider": "^1.7.11",
"ngx-bootstrap": "^3.0.1", "ngx-bootstrap": "^3.0.1",
"ngx-infinite-scroll": "6.0.1", "ngx-infinite-scroll": "6.0.1",
"ngx-moment": "^3.1.0",
"ngx-pagination": "3.0.3", "ngx-pagination": "3.0.3",
"nouislider": "^11.0.0", "nouislider": "^11.0.0",
"pem": "1.12.3", "pem": "1.12.3",
@@ -154,7 +155,7 @@
"css-loader": "0.28.9", "css-loader": "0.28.9",
"deep-freeze": "0.0.1", "deep-freeze": "0.0.1",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "^4.0.0-alpha",
"imports-loader": "0.7.1", "imports-loader": "0.7.1",
"istanbul-instrumenter-loader": "3.0.1", "istanbul-instrumenter-loader": "3.0.1",
"jasmine-core": "^3.2.1", "jasmine-core": "^3.2.1",
@@ -188,7 +189,7 @@
"protractor": "^5.3.0", "protractor": "^5.3.0",
"protractor-istanbul-plugin": "2.0.0", "protractor-istanbul-plugin": "2.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"resolve-url-loader": "2.2.1", "resolve-url-loader": "^2.3.0",
"rimraf": "2.6.2", "rimraf": "2.6.2",
"rollup": "^0.56.0", "rollup": "^0.56.0",
"rollup-plugin-commonjs": "^8.3.0", "rollup-plugin-commonjs": "^8.3.0",

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; 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 { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { CollectionDataService } from '../core/data/collection-data.service'; import { CollectionDataService } from '../core/data/collection-data.service';
import { PaginatedList } from '../core/data/paginated-list'; import { PaginatedList } from '../core/data/paginated-list';

View File

@@ -1,7 +1,8 @@
import {mergeMap, filter, map} from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Subscription , Observable } from 'rxjs'; import { Subscription, Observable } from 'rxjs';
import { CommunityDataService } from '../core/data/community-data.service'; import { CommunityDataService } from '../core/data/community-data.service';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { Bitstream } from '../core/shared/bitstream.model'; import { Bitstream } from '../core/shared/bitstream.model';
@@ -34,11 +35,11 @@ export class CommunityPageComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.communityRD$ = this.route.data.pipe((data) => data.community); this.communityRD$ = this.route.data.pipe(map((data) => data.community));
this.logoRD$ = this.communityRD$ this.logoRD$ = this.communityRD$.pipe(
.map((rd: RemoteData<Community>) => rd.payload) map((rd: RemoteData<Community>) => rd.payload),
.filter((community: Community) => hasValue(community)) filter((community: Community) => hasValue(community)),
.flatMap((community: Community) => community.logo); mergeMap((community: Community) => community.logo));
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@@ -1,3 +1,5 @@
import {map} from 'rxjs/operators';
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -35,11 +37,11 @@ export class CollectionsComponent implements OnInit {
// TODO: this should use parents, but the collections // TODO: this should use parents, but the collections
// for an Item aren't returned by the REST API yet, // for an Item aren't returned by the REST API yet,
// only the owning collection // only the owning collection
this.collections = this.item.owner.map((rd: RemoteData<Collection>) => [rd.payload]); this.collections = this.item.owner.pipe(map((rd: RemoteData<Collection>) => [rd.payload]));
} }
hasSucceeded() { hasSucceeded() {
return this.item.owner.map((rd: RemoteData<Collection>) => rd.hasSucceeded); return this.item.owner.pipe(map((rd: RemoteData<Collection>) => rd.hasSucceeded));
} }
} }

View File

@@ -1,5 +1,6 @@
import {combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
@@ -33,7 +34,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
initialize(): void { initialize(): void {
const originals = this.item.getFiles(); const originals = this.item.getFiles();
const licenses = this.item.getBitstreamsByBundleName('LICENSE'); 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( this.bitstreamsObs.subscribe(
(files) => (files) =>
files.forEach( files.forEach(

View File

@@ -1,3 +1,5 @@
import {filter, map} from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; 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 **/ /*** AoT inheritance fix, will hopefully be resolved in the near future **/
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.metadata$ = this.itemRD$ this.metadata$ = this.itemRD$.pipe(
.map((rd: RemoteData<Item>) => rd.payload) map((rd: RemoteData<Item>) => rd.payload),
.filter((item: Item) => hasValue(item)) filter((item: Item) => hasValue(item)),
.map((item: Item) => item.metadata); map((item: Item) => item.metadata),);
} }
} }

View File

@@ -1,3 +1,5 @@
import {mergeMap, filter, map} from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
@@ -44,11 +46,11 @@ export class ItemPageComponent implements OnInit {
} }
ngOnInit(): void { 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.metadataService.processRemoteData(this.itemRD$);
this.thumbnail$ = this.itemRD$ this.thumbnail$ = this.itemRD$.pipe(
.map((rd: RemoteData<Item>) => rd.payload) map((rd: RemoteData<Item>) => rd.payload),
.filter((item: Item) => hasValue(item)) filter((item: Item) => hasValue(item)),
.flatMap((item: Item) => item.getThumbnail()); mergeMap((item: Item) => item.getThumbnail()),);
} }
} }

View File

@@ -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 { animate, state, style, transition, trigger } from '@angular/animations';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { BehaviorSubject , Observable , Subject , Subscription } from 'rxjs';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util'; import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe'; import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
import { SearchOptions } from '../../../search-options.model';
import { FacetValue } from '../../../search-service/facet-value.model'; import { FacetValue } from '../../../search-service/facet-value.model';
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
import { SearchService } from '../../../search-service/search.service'; import { SearchService } from '../../../search-service/search.service';
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service'; import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
import { getSucceededRemoteData } from '../../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../../core/shared/operators';
import { map } from 'rxjs/operators';
@Component({ @Component({
selector: 'ds-search-facet-filter', 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 * Emits the result values for this filter found by the current filter query
*/ */
filterSearchResults: Observable<any[]> = Observable.of([]); filterSearchResults: Observable<any[]> = observableOf([]);
/** /**
* Emits the active values for this filter * Emits the active values for this filter
@@ -79,25 +78,25 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined)); 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); this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
const searchOptions = this.searchConfigService.searchOptions; const searchOptions = this.searchConfigService.searchOptions;
this.subs.push(this.searchConfigService.searchOptions.subscribe(() => this.updateFilterValueList())); 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 } return { options, page }
}).switchMap(({ options, page }) => { }).pipe(switchMap(({ options, page }) => {
return this.searchService.getFacetValuesFor(this.filterConfig, page, options) return this.searchService.getFacetValuesFor(this.filterConfig, page, options)
.pipe( .pipe(
getSucceededRemoteData(), getSucceededRemoteData(),
map((results) => { map((results) => {
return { return {
values: Observable.of(results), values: observableOf(results),
page: page page: page
}; };
} }
) )
) )
}); }));
let filterValues = []; let filterValues = [];
this.subs.push(facetValues.subscribe((facetOutcome) => { this.subs.push(facetValues.subscribe((facetOutcome) => {
const newValues$ = facetOutcome.values; const newValues$ = facetOutcome.values;
@@ -117,7 +116,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
this.animationState = 'ready'; this.animationState = 'ready';
this.filterValues$.next(rd); 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)) 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 * @param data The string from the input field
*/ */
onSubmit(data: any) { onSubmit(data: any) {
this.selectedValues.first().subscribe((selectedValues) => { this.selectedValues.pipe(first()).subscribe((selectedValues) => {
if (isNotEmpty(data)) { if (isNotEmpty(data)) {
this.router.navigate([this.getSearchLink()], { this.router.navigate([this.getSearchLink()], {
queryParams: queryParams:
@@ -189,7 +188,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
}); });
this.filter = ''; this.filter = '';
} }
this.filterSearchResults = Observable.of([]); this.filterSearchResults = observableOf([]);
} }
) )
} }
@@ -211,12 +210,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
* @returns {Observable<any>} The changed filter parameters * @returns {Observable<any>} The changed filter parameters
*/ */
getRemoveParams(value: string): Observable<any> { getRemoveParams(value: string): Observable<any> {
return this.selectedValues.map((selectedValues) => { return this.selectedValues.pipe(map((selectedValues) => {
return { return {
[this.filterConfig.paramName]: selectedValues.filter((v) => v !== value), [this.filterConfig.paramName]: selectedValues.filter((v) => v !== value),
page: 1 page: 1
}; };
}); }));
} }
/** /**
@@ -225,12 +224,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
* @returns {Observable<any>} The changed filter parameters * @returns {Observable<any>} The changed filter parameters
*/ */
getAddParams(value: string): Observable<any> { getAddParams(value: string): Observable<any> {
return this.selectedValues.map((selectedValues) => { return this.selectedValues.pipe(map((selectedValues) => {
return { return {
[this.filterConfig.paramName]: [...selectedValues, value], [this.filterConfig.paramName]: [...selectedValues, value],
page: 1 page: 1
}; };
}); }));
} }
/** /**
@@ -249,7 +248,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
*/ */
findSuggestions(data): void { findSuggestions(data): void {
if (isNotEmpty(data)) { if (isNotEmpty(data)) {
this.searchConfigService.searchOptions.first().subscribe( this.searchConfigService.searchOptions.pipe(first()).subscribe(
(options) => { (options) => {
this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase()) this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase())
.pipe( .pipe(
@@ -264,7 +263,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
} }
) )
} else { } else {
this.filterSearchResults = Observable.of([]); this.filterSearchResults = observableOf([]);
} }
} }

View File

@@ -1,3 +1,5 @@
import {first} from 'rxjs/operators';
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
import { SearchFilterService } from './search-filter.service'; import { SearchFilterService } from './search-filter.service';
@@ -35,7 +37,7 @@ export class SearchFilterComponent implements OnInit {
* Else, the filter should initially be collapsed * Else, the filter should initially be collapsed
*/ */
ngOnInit() { ngOnInit() {
this.getSelectedValues().first().subscribe((isActive) => { this.getSelectedValues().pipe(first()).subscribe((isActive) => {
if (this.filter.isOpenByDefault || isNotEmpty(isActive)) { if (this.filter.isOpenByDefault || isNotEmpty(isActive)) {
this.initialExpand(); this.initialExpand();
} else { } else {

View File

@@ -1,8 +1,8 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { Injectable, InjectionToken } from '@angular/core'; import { Injectable, InjectionToken } from '@angular/core';
import { distinctUntilChanged, map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer'; import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { import {
SearchFilterCollapseAction, SearchFilterCollapseAction,
SearchFilterDecrementPageAction, SearchFilterDecrementPageAction,
@@ -63,8 +63,8 @@ export class SearchFilterService {
*/ */
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> { getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName); const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName);
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').map((params: Params) => [].concat(...Object.values(params))); const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(map((params: Params) => [].concat(...Object.values(params))));
return Observable.combineLatest(values$, prefixValues$, (values, prefixValues) => { return observableCombineLatest(values$, prefixValues$, (values, prefixValues) => {
if (isNotEmpty(values)) { if (isNotEmpty(values)) {
return values; return values;
} }
@@ -78,14 +78,16 @@ export class SearchFilterService {
* @returns {Observable<boolean>} Emits the current collapsed state of the given filter, if it's unavailable, return false * @returns {Observable<boolean>} Emits the current collapsed state of the given filter, if it's unavailable, return false
*/ */
isCollapsed(filterName: string): Observable<boolean> { isCollapsed(filterName: string): Observable<boolean> {
return this.store.select(filterByNameSelector(filterName)) return this.store.pipe(
.map((object: SearchFilterState) => { select(filterByNameSelector(filterName)),
map((object: SearchFilterState) => {
if (object) { if (object) {
return object.filterCollapsed; return object.filterCollapsed;
} else { } else {
return false; return false;
} }
}); })
);
} }
/** /**
@@ -94,14 +96,15 @@ export class SearchFilterService {
* @returns {Observable<boolean>} Emits the current page state of the given filter, if it's unavailable, return 1 * @returns {Observable<boolean>} Emits the current page state of the given filter, if it's unavailable, return 1
*/ */
getPage(filterName: string): Observable<number> { getPage(filterName: string): Observable<number> {
return this.store.select(filterByNameSelector(filterName)) return this.store.pipe(
.map((object: SearchFilterState) => { select(filterByNameSelector(filterName)),
map((object: SearchFilterState) => {
if (object) { if (object) {
return object.page; return object.page;
} else { } else {
return 1; return 1;
} }
}); }));
} }
/** /**
@@ -159,6 +162,7 @@ export class SearchFilterService {
public incrementPage(filterName: string): void { public incrementPage(filterName: string): void {
this.store.dispatch(new SearchFilterIncrementPageAction(filterName)); this.store.dispatch(new SearchFilterIncrementPageAction(filterName));
} }
/** /**
* Dispatches a reset page action to the store for a given filter * Dispatches a reset page action to the store for a given filter
* @param {string} filterName The filter for which the action is dispatched * @param {string} filterName The filter for which the action is dispatched

View File

@@ -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 { isPlatformBrowser } from '@angular/common';
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; 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 { SearchService } from '../../../search-service/search.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import * as moment from 'moment'; import * as moment from 'moment';
import { Observable , Subscription } from 'rxjs';
import { RouteService } from '../../../../shared/services/route.service'; import { RouteService } from '../../../../shared/services/route.service';
import { hasValue } from '../../../../shared/empty.util'; import { hasValue } from '../../../../shared/empty.util';
import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
@@ -79,9 +80,9 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
super.ngOnInit(); super.ngOnInit();
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min; this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max; this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).startWith(undefined); const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).pipe(startWith(undefined));
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).startWith(undefined); const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).pipe(startWith(undefined));
this.sub = Observable.combineLatest(iniMin, iniMax, (min, max) => { this.sub = observableCombineLatest(iniMin, iniMax, (min, max) => {
const minimum = hasValue(min) ? min : this.min; const minimum = hasValue(min) ? min : this.min;
const maximum = hasValue(max) ? max : this.max; const maximum = hasValue(max) ? max : this.max;
return [minimum, maximum] return [minimum, maximum]
@@ -97,7 +98,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
const parts = value.split(rangeDelimiter); const parts = value.split(rangeDelimiter);
const min = parts.length > 1 ? parts[0].trim() : value; const min = parts.length > 1 ? parts[0].trim() : value;
const max = parts.length > 1 ? parts[1].trim() : value; const max = parts.length > 1 ? parts[1].trim() : value;
return Observable.of( return observableOf(
{ {
[this.filterConfig.paramName + minSuffix]: [min], [this.filterConfig.paramName + minSuffix]: [min],
[this.filterConfig.paramName + maxSuffix]: [max], [this.filterConfig.paramName + maxSuffix]: [max],

View File

@@ -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 { Component } from '@angular/core';
import { SearchService } from '../search-service/search.service'; import { SearchService } from '../search-service/search.service';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { SearchFilterConfig } from '../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../search-service/search-filter-config.model';
import { Observable } from 'rxjs';
import { SearchConfigurationService } from '../search-service/search-configuration.service'; import { SearchConfigurationService } from '../search-service/search-configuration.service';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { SearchFilterService } from './search-filter/search-filter.service'; 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) { constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService, private filterService: SearchFilterService) {
this.filters = searchService.getConfig().pipe(getSucceededRemoteData()); 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); Object.keys(filters).forEach((f) => filters[f] = null);
return filters; return filters;
}); }));
} }
/** /**
@@ -55,23 +57,23 @@ export class SearchFiltersComponent {
* @param {SearchFilterConfig} filter The filter to check for * @param {SearchFilterConfig} filter The filter to check for
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown * @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
*/ */
isActive(filter: SearchFilterConfig): Observable<boolean> { isActive(filterConfig: SearchFilterConfig): Observable<boolean> {
// console.log(filter.name); // console.log(filter.name);
return this.filterService.getSelectedValuesForFilter(filter) return this.filterService.getSelectedValuesForFilter(filterConfig).pipe(
.flatMap((isActive) => { mergeMap((isActive) => {
if (isNotEmpty(isActive)) { if (isNotEmpty(isActive)) {
return Observable.of(true); return observableOf(true);
} else { } else {
return this.searchConfigService.searchOptions return this.searchConfigService.searchOptions.pipe(
.switchMap((options) => { switchMap((options) => {
return this.searchService.getFacetValuesFor(filter, 1, options) return this.searchService.getFacetValuesFor(filterConfig, 1, options).pipe(
.filter((RD) => !RD.isLoading) filter((RD) => !RD.isLoading),
.map((valuesRD) => { map((valuesRD) => {
return valuesRD.payload.totalElements > 0 return valuesRD.payload.totalElements > 0
}) }),)
} }
) ))
} }
}).startWith(true); }),startWith(true),);
} }
} }

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable , Subscription , BehaviorSubject } from 'rxjs'; import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { flatMap, switchMap, } from 'rxjs/operators'; import { switchMap, } from 'rxjs/operators';
import { PaginatedList } from '../core/data/paginated-list'; import { PaginatedList } from '../core/data/paginated-list';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { DSpaceObject } from '../core/shared/dspace-object.model'; import { DSpaceObject } from '../core/shared/dspace-object.model';
@@ -76,8 +76,8 @@ export class SearchPageComponent implements OnInit {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
this.sub = this.searchOptions$ this.sub = this.searchOptions$.pipe(
.switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData())) switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData())))
.subscribe((results) => { .subscribe((results) => {
this.resultsRD$.next(results); this.resultsRD$.next(results);
}); });

View File

@@ -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 { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SearchOptions } from '../search-options.model'; import { SearchOptions } from '../search-options.model';
import { Observable , BehaviorSubject , Subscription } from 'rxjs';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { PaginatedSearchOptions } from '../paginated-search-options.model';
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
@@ -85,27 +86,27 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Observable<string>} Emits the current scope's identifier * @returns {Observable<string>} Emits the current scope's identifier
*/ */
getCurrentScope(defaultScope: string) { getCurrentScope(defaultScope: string) {
return this.routeService.getQueryParameterValue('scope').map((scope) => { return this.routeService.getQueryParameterValue('scope').pipe(map((scope) => {
return scope || defaultScope; return scope || defaultScope;
}); }));
} }
/** /**
* @returns {Observable<string>} Emits the current query string * @returns {Observable<string>} Emits the current query string
*/ */
getCurrentQuery(defaultQuery: string) { getCurrentQuery(defaultQuery: string) {
return this.routeService.getQueryParameterValue('query').map((query) => { return this.routeService.getQueryParameterValue('query').pipe(map((query) => {
return query || defaultQuery; return query || defaultQuery;
}); }));
} }
/** /**
* @returns {Observable<number>} Emits the current DSpaceObject type as a number * @returns {Observable<number>} Emits the current DSpaceObject type as a number
*/ */
getCurrentDSOType(): Observable<DSpaceObjectType> { getCurrentDSOType(): Observable<DSpaceObjectType> {
return this.routeService.getQueryParameterValue('dsoType') return this.routeService.getQueryParameterValue('dsoType').pipe(
.filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()])) filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()])),
.map((type) => DSpaceObjectType[type.toUpperCase()]); map((type) => DSpaceObjectType[type.toUpperCase()]),);
} }
/** /**
@@ -114,7 +115,7 @@ export class SearchConfigurationService implements OnDestroy {
getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable<PaginationComponentOptions> { getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable<PaginationComponentOptions> {
const page$ = this.routeService.getQueryParameterValue('page'); const page$ = this.routeService.getQueryParameterValue('page');
const size$ = this.routeService.getQueryParameterValue('pageSize'); 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, { return Object.assign(new PaginationComponentOptions(), defaultPagination, {
currentPage: page || defaultPagination.currentPage, currentPage: page || defaultPagination.currentPage,
pageSize: size || defaultPagination.pageSize pageSize: size || defaultPagination.pageSize
@@ -128,7 +129,7 @@ export class SearchConfigurationService implements OnDestroy {
getCurrentSort(defaultSort: SortOptions): Observable<SortOptions> { getCurrentSort(defaultSort: SortOptions): Observable<SortOptions> {
const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection');
const sortField$ = this.routeService.getQueryParameterValue('sortField'); 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 // Dirty fix because sometimes the observable value is null somehow
sortField = this.route.snapshot.queryParamMap.get('sortField'); sortField = this.route.snapshot.queryParamMap.get('sortField');
@@ -143,7 +144,7 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend * @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend
*/ */
getCurrentFilters(): Observable<SearchFilter[]> { getCurrentFilters(): Observable<SearchFilter[]> {
return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => { return this.routeService.getQueryParamsWithPrefix('f.').pipe(map((filterParams) => {
if (isNotEmpty(filterParams)) { if (isNotEmpty(filterParams)) {
const filters = []; const filters = [];
Object.keys(filterParams).forEach((key) => { Object.keys(filterParams).forEach((key) => {
@@ -161,7 +162,7 @@ export class SearchConfigurationService implements OnDestroy {
return filters; return filters;
} }
return []; return [];
}); }));
} }
/** /**
@@ -177,7 +178,7 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Subscription} The subscription to unsubscribe from * @returns {Subscription} The subscription to unsubscribe from
*/ */
subscribeToSearchOptions(defaults: SearchOptions): Subscription { subscribeToSearchOptions(defaults: SearchOptions): Subscription {
return Observable.merge( return observableMerge(
this.getScopePart(defaults.scope), this.getScopePart(defaults.scope),
this.getQueryPart(defaults.query), this.getQueryPart(defaults.query),
this.getDSOTypePart(), this.getDSOTypePart(),
@@ -195,7 +196,7 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Subscription} The subscription to unsubscribe from * @returns {Subscription} The subscription to unsubscribe from
*/ */
subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription { subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription {
return Observable.merge( return observableMerge(
this.getPaginationPart(defaults.pagination), this.getPaginationPart(defaults.pagination),
this.getSortPart(defaults.sort), this.getSortPart(defaults.sort),
this.getScopePart(defaults.scope), this.getScopePart(defaults.scope),
@@ -220,7 +221,7 @@ export class SearchConfigurationService implements OnDestroy {
scope: this.defaultScope, scope: this.defaultScope,
query: this.defaultQuery 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; return this._defaults;
} }
@@ -238,53 +239,53 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Observable<string>} Emits the current scope's identifier * @returns {Observable<string>} Emits the current scope's identifier
*/ */
private getScopePart(defaultScope: string): Observable<any> { private getScopePart(defaultScope: string): Observable<any> {
return this.getCurrentScope(defaultScope).map((scope) => { return this.getCurrentScope(defaultScope).pipe(map((scope) => {
return { scope } return { scope }
}); }));
} }
/** /**
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object * @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
*/ */
private getQueryPart(defaultQuery: string): Observable<any> { private getQueryPart(defaultQuery: string): Observable<any> {
return this.getCurrentQuery(defaultQuery).map((query) => { return this.getCurrentQuery(defaultQuery).pipe(map((query) => {
return { query } return { query }
}); }));
} }
/** /**
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object * @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
*/ */
private getDSOTypePart(): Observable<any> { private getDSOTypePart(): Observable<any> {
return this.getCurrentDSOType().map((dsoType) => { return this.getCurrentDSOType().pipe(map((dsoType) => {
return { dsoType } return { dsoType }
}); }));
} }
/** /**
* @returns {Observable<string>} Emits the current pagination settings as a partial SearchOptions object * @returns {Observable<string>} Emits the current pagination settings as a partial SearchOptions object
*/ */
private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable<any> { private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable<any> {
return this.getCurrentPagination(defaultPagination).map((pagination) => { return this.getCurrentPagination(defaultPagination).pipe(map((pagination) => {
return { pagination } return { pagination }
}); }));
} }
/** /**
* @returns {Observable<string>} Emits the current sorting settings as a partial SearchOptions object * @returns {Observable<string>} Emits the current sorting settings as a partial SearchOptions object
*/ */
private getSortPart(defaultSort: SortOptions): Observable<any> { private getSortPart(defaultSort: SortOptions): Observable<any> {
return this.getCurrentSort(defaultSort).map((sort) => { return this.getCurrentSort(defaultSort).pipe(map((sort) => {
return { sort } return { sort }
}); }));
} }
/** /**
* @returns {Observable<Params>} Emits the current active filters as a partial SearchOptions object * @returns {Observable<Params>} Emits the current active filters as a partial SearchOptions object
*/ */
private getFiltersPart(): Observable<any> { private getFiltersPart(): Observable<any> {
return this.getCurrentFilters().map((filters) => { return this.getCurrentFilters().pipe(map((filters) => {
return { filters } return { filters }
}); }));
} }
} }

View File

@@ -1,3 +1,4 @@
import {of as observableOf, combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { import {
ActivatedRoute, ActivatedRoute,
@@ -6,7 +7,6 @@ import {
Router, Router,
UrlSegmentGroup UrlSegmentGroup
} from '@angular/router'; } from '@angular/router';
import { Observable } from 'rxjs';
import { flatMap, map, switchMap } from 'rxjs/operators'; import { flatMap, map, switchMap } from 'rxjs/operators';
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
import { import {
@@ -122,7 +122,7 @@ export class SearchService implements OnDestroy {
); );
// Create search results again with the correct dso objects linked to each result // Create search results again with the correct dso objects linked to each result
const tDomainListObs = Observable.combineLatest(sqrObs, dsoObs, (sqr: SearchQueryResponse, dsos: RemoteData<DSpaceObject[]>) => { const tDomainListObs = observableCombineLatest(sqrObs, dsoObs, (sqr: SearchQueryResponse, dsos: RemoteData<DSpaceObject[]>) => {
return sqr.objects.map((object: NormalizedSearchResult, index: number) => { return sqr.objects.map((object: NormalizedSearchResult, index: number) => {
let co = DSpaceObject; let co = DSpaceObject;
@@ -143,7 +143,7 @@ export class SearchService implements OnDestroy {
map((response: FacetValueSuccessResponse) => response.pageInfo) 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); return new PaginatedList(pageInfo, tDomainList);
}); });
@@ -244,7 +244,7 @@ export class SearchService implements OnDestroy {
map((response: FacetValueSuccessResponse) => response.pageInfo) 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); return new PaginatedList(pageInfo, facetValue);
}); });
@@ -272,12 +272,12 @@ export class SearchService implements OnDestroy {
switchMap((dsoRD: RemoteData<DSpaceObject>) => { switchMap((dsoRD: RemoteData<DSpaceObject>) => {
if (dsoRD.payload.type === ResourceType.Community) { if (dsoRD.payload.type === ResourceType.Community) {
const community: Community = dsoRD.payload as 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*/ /*if this is a community, we also need to show the direct children*/
return [community, ...subCommunities.payload.page, ...collections.payload.page] return [community, ...subCommunities.payload.page, ...collections.payload.page]
}) })
} else { } else {
return Observable.of([dsoRD.payload]); return observableOf([dsoRD.payload]);
} }
} }
)); ));
@@ -291,13 +291,13 @@ export class SearchService implements OnDestroy {
* @returns {Observable<ViewMode>} The current view mode * @returns {Observable<ViewMode>} The current view mode
*/ */
getViewMode(): Observable<ViewMode> { getViewMode(): Observable<ViewMode> {
return this.route.queryParams.map((params) => { return this.route.queryParams.pipe(map((params) => {
if (isNotEmpty(params.view) && hasValue(params.view)) { if (isNotEmpty(params.view) && hasValue(params.view)) {
return params.view; return params.view;
} else { } else {
return ViewMode.List; return ViewMode.List;
} }
}); }));
} }
/** /**

View File

@@ -1,5 +1,6 @@
import { map, tap, filter } from 'rxjs/operators';
import { Injectable } from '@angular/core'; 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 * as fromRouter from '@ngrx/router-store';
import { SearchSidebarCollapseAction } from './search-sidebar.actions'; import { SearchSidebarCollapseAction } from './search-sidebar.actions';
@@ -12,10 +13,14 @@ import { URLBaser } from '../../core/url-baser/url-baser';
export class SearchSidebarEffects { export class SearchSidebarEffects {
private previousPath: string; private previousPath: string;
@Effect() routeChange$ = this.actions$ @Effect() routeChange$ = this.actions$
.ofType(fromRouter.ROUTER_NAVIGATION) .pipe(
.filter((action) => this.previousPath !== this.getBaseUrl(action)) ofType(fromRouter.ROUTER_NAVIGATION),
.do((action) => {this.previousPath = this.getBaseUrl(action)}) filter((action) => this.previousPath !== this.getBaseUrl(action)),
.map(() => new SearchSidebarCollapseAction()); tap((action) => {
this.previousPath = this.getBaseUrl(action)
}),
map(() => new SearchSidebarCollapseAction())
);
constructor(private actions$: Actions) { constructor(private actions$: Actions) {

View File

@@ -1,8 +1,8 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { SearchSidebarState } from './search-sidebar.reducer'; 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 { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
import { Observable } from 'rxjs';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowService } from '../../shared/host-window.service';
@@ -26,7 +26,7 @@ export class SearchSidebarService {
constructor(private store: Store<AppState>, private windowService: HostWindowService) { constructor(private store: Store<AppState>, private windowService: HostWindowService) {
this.isXsOrSm$ = this.windowService.isXsOrSm(); 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<boolean>} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed * @returns {Observable<boolean>} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed
*/ */
get isCollapsed(): Observable<boolean> { get isCollapsed(): Observable<boolean> {
return Observable.combineLatest( return observableCombineLatest(
this.isXsOrSm$, this.isXsOrSm$,
this.isCollapsedInStore, this.isCollapsedInStore,
(mobile, store) => mobile ? store : true); (mobile, store) => mobile ? store : true);

View File

@@ -1,3 +1,4 @@
import { filter, first, take } from 'rxjs/operators';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@@ -9,7 +10,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router'; 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'; 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); this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
// Whether is not authenticathed try to retrieve a possible stored auth token // Whether is not authenticathed try to retrieve a possible stored auth token
this.store.select(isAuthenticated) this.store.pipe(select(isAuthenticated),
.take(1) first(),
.filter((authenticated) => !authenticated) filter((authenticated) => !authenticated)
.subscribe((authenticated) => this.authService.checkAuthenticationToken()); ).subscribe((authenticated) => this.authService.checkAuthenticationToken());
} }

View File

@@ -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 { Inject, Injectable } from '@angular/core';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ResponseCacheService } from '../cache/response-cache.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 { isNotEmpty } from '../../shared/empty.util';
import { AuthGetRequest, AuthPostRequest, PostRequest, RestRequest } from '../data/request.models'; import { AuthGetRequest, AuthPostRequest, PostRequest, RestRequest } from '../data/request.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; 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'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
@Injectable() @Injectable()
@@ -23,18 +25,18 @@ export class AuthRequestService {
} }
protected fetchRequest(request: RestRequest): Observable<any> { protected fetchRequest(request: RestRequest): Observable<any> {
const [successResponse, errorResponse] = this.responseCache.get(request.href) return this.responseCache.get(request.href).pipe(
.map((entry: ResponseCacheEntry) => entry.response) map((entry: ResponseCacheEntry) => entry.response),
// TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed // TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
.do(() => this.responseCache.remove(request.href)) tap(() => this.responseCache.remove(request.href)),
.partition((response: RestResponse) => response.isSuccessful); mergeMap((response) => {
return Observable.merge( if (response.isSuccessful && isNotEmpty(response)) {
errorResponse.flatMap((response: ErrorResponse) => return observableOf((response as AuthStatusResponse).response);
observableThrowError(new Error(response.errorMessage))), } else if (!response.isSuccessful) {
successResponse return observableThrowError(new Error((response as ErrorResponse).errorMessage));
.filter((response: AuthStatusResponse) => isNotEmpty(response)) }
.map((response: AuthStatusResponse) => response.response) })
.distinctUntilChanged()); );
} }
protected getEndpointByMethod(endpoint: string, method: string): string { protected getEndpointByMethod(endpoint: string, method: string): string {
@@ -42,24 +44,24 @@ export class AuthRequestService {
} }
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> { public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> {
return this.halService.getEndpoint(this.linkName) return this.halService.getEndpoint(this.linkName).pipe(
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.map((endpointURL) => this.getEndpointByMethod(endpointURL, method)) map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
.distinctUntilChanged() distinctUntilChanged(),
.map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)) map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)),
.do((request: PostRequest) => this.requestService.configure(request, true)) tap((request: PostRequest) => this.requestService.configure(request, true)),
.flatMap((request: PostRequest) => this.fetchRequest(request)) mergeMap((request: PostRequest) => this.fetchRequest(request)),
.distinctUntilChanged(); distinctUntilChanged());
} }
public getRequest(method: string, options?: HttpOptions): Observable<any> { public getRequest(method: string, options?: HttpOptions): Observable<any> {
return this.halService.getEndpoint(this.linkName) return this.halService.getEndpoint(this.linkName).pipe(
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.map((endpointURL) => this.getEndpointByMethod(endpointURL, method)) map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
.distinctUntilChanged() distinctUntilChanged(),
.map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)) map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)),
.do((request: PostRequest) => this.requestService.configure(request, true)) tap((request: PostRequest) => this.requestService.configure(request, true)),
.flatMap((request: PostRequest) => this.fetchRequest(request)) mergeMap((request: PostRequest) => this.fetchRequest(request)),
.distinctUntilChanged(); distinctUntilChanged());
} }
} }

View File

@@ -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 { Injectable } from '@angular/core';
// import @ngrx // import @ngrx
import { Actions, Effect } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action, Store } from '@ngrx/store'; import { Action, select, Store } from '@ngrx/store';
// import rxjs
import { Observable } from 'rxjs';
// import services // import services
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
@@ -43,112 +43,131 @@ export class AuthEffects {
* @method authenticate * @method authenticate
*/ */
@Effect() @Effect()
public authenticate$: Observable<Action> = this.actions$ public authenticate$: Observable<Action> = this.actions$.pipe(
.ofType(AuthActionTypes.AUTHENTICATE) ofType(AuthActionTypes.AUTHENTICATE),
.switchMap((action: AuthenticateAction) => { switchMap((action: AuthenticateAction) => {
return this.authService.authenticate(action.payload.email, action.payload.password) return this.authService.authenticate(action.payload.email, action.payload.password).pipe(
.first() first(),
.map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)) map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)),
.catch((error) => Observable.of(new AuthenticationErrorAction(error))); catchError((error) => observableOf(new AuthenticationErrorAction(error)))
}); );
})
);
@Effect() @Effect()
public authenticateSuccess$: Observable<Action> = this.actions$ public authenticateSuccess$: Observable<Action> = this.actions$.pipe(
.ofType(AuthActionTypes.AUTHENTICATE_SUCCESS) ofType(AuthActionTypes.AUTHENTICATE_SUCCESS),
.do((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)) tap((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)),
.map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload)); map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload))
);
@Effect() @Effect()
public authenticated$: Observable<Action> = this.actions$ public authenticated$: Observable<Action> = this.actions$.pipe(
.ofType(AuthActionTypes.AUTHENTICATED) ofType(AuthActionTypes.AUTHENTICATED),
.switchMap((action: AuthenticatedAction) => { switchMap((action: AuthenticatedAction) => {
return this.authService.authenticatedUser(action.payload) return this.authService.authenticatedUser(action.payload).pipe(
.map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)) map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
.catch((error) => Observable.of(new AuthenticatedErrorAction(error))); catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
}); })
);
// It means "reacts to this action but don't send another" // It means "reacts to this action but don't send another"
@Effect({dispatch: false}) @Effect({ dispatch: false })
public authenticatedError$: Observable<Action> = this.actions$ public authenticatedError$: Observable<Action> = this.actions$.pipe(
.ofType(AuthActionTypes.AUTHENTICATED_ERROR) ofType(AuthActionTypes.AUTHENTICATED_ERROR),
.do((action: LogOutSuccessAction) => this.authService.removeToken()); tap((action: LogOutSuccessAction) => this.authService.removeToken())
);
@Effect() @Effect()
public checkToken$: Observable<Action> = this.actions$ public checkToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN),
.ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN) switchMap(() => {
.switchMap(() => { return this.authService.hasValidAuthenticationToken().pipe(
return this.authService.hasValidAuthenticationToken() map((token: AuthTokenInfo) => new AuthenticatedAction(token)),
.map((token: AuthTokenInfo) => new AuthenticatedAction(token)) catchError((error) => observableOf(new CheckAuthenticationTokenErrorAction()))
.catch((error) => Observable.of(new CheckAuthenticationTokenErrorAction())); );
}); })
);
@Effect() @Effect()
public createUser$: Observable<Action> = this.actions$ public createUser$: Observable<Action> = this.actions$.pipe(
.ofType(AuthActionTypes.REGISTRATION) ofType(AuthActionTypes.REGISTRATION),
.debounceTime(500) // to remove when functionality is implemented debounceTime(500), // to remove when functionality is implemented
.switchMap((action: RegistrationAction) => { switchMap((action: RegistrationAction) => {
return this.authService.create(action.payload) return this.authService.create(action.payload).pipe(
.map((user: Eperson) => new RegistrationSuccessAction(user)) map((user: Eperson) => new RegistrationSuccessAction(user)),
.catch((error) => Observable.of(new RegistrationErrorAction(error))); catchError((error) => observableOf(new RegistrationErrorAction(error)))
}); );
})
);
@Effect() @Effect()
public refreshToken$: Observable<Action> = this.actions$ public refreshToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.REFRESH_TOKEN),
.ofType(AuthActionTypes.REFRESH_TOKEN) switchMap((action: RefreshTokenAction) => {
.switchMap((action: RefreshTokenAction) => { return this.authService.refreshAuthenticationToken(action.payload).pipe(
return this.authService.refreshAuthenticationToken(action.payload) map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)),
.map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)) catchError((error) => observableOf(new RefreshTokenErrorAction()))
.catch((error) => Observable.of(new RefreshTokenErrorAction())); );
}); })
);
// It means "reacts to this action but don't send another" // It means "reacts to this action but don't send another"
@Effect({dispatch: false}) @Effect({ dispatch: false })
public refreshTokenSuccess$: Observable<Action> = this.actions$ public refreshTokenSuccess$: Observable<Action> = this.actions$.pipe(
.ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS) ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS),
.do((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload)); tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload))
);
/** /**
* When the store is rehydrated in the browser, * When the store is rehydrated in the browser,
* clear a possible invalid token or authentication errors * clear a possible invalid token or authentication errors
*/ */
@Effect({dispatch: false}) @Effect({ dispatch: false })
public clearInvalidTokenOnRehydrate$: Observable<any> = this.actions$ public clearInvalidTokenOnRehydrate$: Observable<any> = this.actions$.pipe(
.ofType(StoreActionTypes.REHYDRATE) ofType(StoreActionTypes.REHYDRATE),
.switchMap(() => { switchMap(() => {
return this.store.select(isAuthenticated) return this.store.pipe(
.take(1) select(isAuthenticated),
.filter((authenticated) => !authenticated) first(),
.do(() => this.authService.removeToken()) filter((authenticated) => !authenticated),
.do(() => this.authService.resetAuthenticationError()); tap(() => this.authService.removeToken()),
}); tap(() => this.authService.resetAuthenticationError())
);
}));
@Effect() @Effect()
public logOut$: Observable<Action> = this.actions$ public logOut$: Observable<Action> = this.actions$
.ofType(AuthActionTypes.LOG_OUT) .pipe(
.switchMap(() => { ofType(AuthActionTypes.LOG_OUT),
return this.authService.logout() switchMap(() => {
.map((value) => new LogOutSuccessAction()) return this.authService.logout().pipe(
.catch((error) => Observable.of(new LogOutErrorAction(error))); map((value) => new LogOutSuccessAction()),
}); catchError((error) => observableOf(new LogOutErrorAction(error)))
);
})
);
@Effect({dispatch: false}) @Effect({ dispatch: false })
public logOutSuccess$: Observable<Action> = this.actions$ public logOutSuccess$: Observable<Action> = this.actions$
.ofType(AuthActionTypes.LOG_OUT_SUCCESS) .pipe(ofType(AuthActionTypes.LOG_OUT_SUCCESS),
.do(() => this.authService.removeToken()) tap(() => this.authService.removeToken()),
.do(() => this.authService.clearRedirectUrl()) tap(() => this.authService.clearRedirectUrl()),
.do(() => this.authService.refreshAfterLogout()); tap(() => this.authService.refreshAfterLogout())
);
@Effect({dispatch: false}) @Effect({ dispatch: false })
public redirectToLogin$: Observable<Action> = this.actions$ public redirectToLogin$: Observable<Action> = this.actions$
.ofType(AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED) .pipe(ofType(AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED),
.do(() => this.authService.removeToken()) tap(() => this.authService.removeToken()),
.do(() => this.authService.redirectToLogin()); tap(() => this.authService.redirectToLogin())
);
@Effect({dispatch: false}) @Effect({ dispatch: false })
public redirectToLoginTokenExpired$: Observable<Action> = this.actions$ public redirectToLoginTokenExpired$: Observable<Action> = this.actions$
.ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED) .pipe(
.do(() => this.authService.removeToken()) ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED),
.do(() => this.authService.redirectToLoginWhenTokenExpired()); tap(() => this.authService.removeToken()),
tap(() => this.authService.redirectToLoginWhenTokenExpired())
);
/** /**
* @constructor * @constructor

View File

@@ -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 { Injectable, Injector } from '@angular/core';
import { import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, HttpErrorResponse,
HttpErrorResponse, HttpResponseBase HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
HttpResponseBase
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs';
import { find } from 'lodash'; import { find } from 'lodash';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
@@ -76,11 +86,11 @@ export class AuthInterceptor implements HttpInterceptor {
// The access token is expired // The access token is expired
// Redirect to the login route // Redirect to the login route
this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired')); this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired'));
return Observable.of(null); return observableOf(null);
} else if (!this.isAuthRequest(req) && isNotEmpty(token)) { } else if (!this.isAuthRequest(req) && isNotEmpty(token)) {
// Intercept a request that is not to the authentication endpoint // Intercept a request that is not to the authentication endpoint
authService.isTokenExpiring() authService.isTokenExpiring().pipe(
.filter((isExpiring) => isExpiring) filter((isExpiring) => isExpiring))
.subscribe(() => { .subscribe(() => {
// If the current request url is already in the refresh token request list, skip it // If the current request url is already in the refresh token request list, skip it
if (isUndefined(find(this.refreshTokenRequestUrls, req.url))) { 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. // Pass on the new request instead of the original request.
return next.handle(newReq) return next.handle(newReq).pipe(
.map((response) => { map((response) => {
// Intercept a Login/Logout response // Intercept a Login/Logout response
if (response instanceof HttpResponse && this.isSuccess(response) && (this.isLoginResponse(response) || this.isLogoutResponse(response))) { if (response instanceof HttpResponse && this.isSuccess(response) && (this.isLoginResponse(response) || this.isLogoutResponse(response))) {
// It's a success Login/Logout response // It's a success Login/Logout response
@@ -119,8 +129,8 @@ export class AuthInterceptor implements HttpInterceptor {
} else { } else {
return response; return response;
} }
}) }),
.catch((error, caught) => { catchError((error, caught) => {
// Intercept an error response // Intercept an error response
if (error instanceof HttpErrorResponse) { if (error instanceof HttpErrorResponse) {
// Checks if is a response from a request to an authentication endpoint // Checks if is a response from a request to an authentication endpoint
@@ -135,7 +145,7 @@ export class AuthInterceptor implements HttpInterceptor {
statusText: error.statusText, statusText: error.statusText,
url: error.url url: error.url
}); });
return Observable.of(authResponse); return observableOf(authResponse);
} else if (this.isUnauthorized(error)) { } else if (this.isUnauthorized(error)) {
// The access token provided is expired, revoked, malformed, or invalid for other reasons // The access token provided is expired, revoked, malformed, or invalid for other reasons
// Redirect to the login route // Redirect to the login route
@@ -144,7 +154,6 @@ export class AuthInterceptor implements HttpInterceptor {
} }
// Return error response as is. // Return error response as is.
return observableThrowError(error); return observableThrowError(error);
}) as any; })) as any;
} }
} }

View File

@@ -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 { Inject, Injectable } from '@angular/core';
import { PRIMARY_OUTLET, Router, UrlSegmentGroup, UrlTree } from '@angular/router'; import { PRIMARY_OUTLET, Router, UrlSegmentGroup, UrlTree } from '@angular/router';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';
import { REQUEST } from '@nguniversal/express-engine/tokens'; import { REQUEST } from '@nguniversal/express-engine/tokens';
import { RouterReducerState } from '@ngrx/router-store'; import { RouterReducerState } from '@ngrx/router-store';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { CookieAttributes } from 'js-cookie'; import { CookieAttributes } from 'js-cookie';
import { Observable } from 'rxjs';
import { map, withLatestFrom } from 'rxjs/operators';
import { Eperson } from '../eperson/models/eperson.model'; import { Eperson } from '../eperson/models/eperson.model';
import { AuthRequestService } from './auth-request.service'; 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 { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
import { CookieService } from '../../shared/services/cookie.service'; 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 { AppState, routerStateSelector } from '../../app.reducer';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
@@ -46,21 +59,24 @@ export class AuthService {
protected router: Router, protected router: Router,
protected storage: CookieService, protected storage: CookieService,
protected store: Store<AppState>) { protected store: Store<AppState>) {
this.store.select(isAuthenticated) this.store.pipe(
.startWith(false) select(isAuthenticated),
.subscribe((authenticated: boolean) => this._authenticated = authenticated); startWith(false)
).subscribe((authenticated: boolean) => this._authenticated = authenticated);
// If current route is different from the one setted in authentication guard // If current route is different from the one setted in authentication guard
// and is not the login route, clear redirect url and messages // and is not the login route, clear redirect url and messages
const routeUrl$ = this.store.select(routerStateSelector) const routeUrl$ = this.store.pipe(
.filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state)) select(routerStateSelector),
.filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)) filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state)),
.map((routerState: RouterReducerState) => routerState.state.url); filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)),
const redirectUrl$ = this.store.select(getRedirectUrl).distinctUntilChanged(); map((routerState: RouterReducerState) => routerState.state.url)
);
const redirectUrl$ = this.store.pipe(select(getRedirectUrl), distinctUntilChanged());
routeUrl$.pipe( routeUrl$.pipe(
withLatestFrom(redirectUrl$), withLatestFrom(redirectUrl$),
map(([routeUrl, redirectUrl]) => [routeUrl, redirectUrl]) map(([routeUrl, redirectUrl]) => [routeUrl, redirectUrl])
).filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl)) ).pipe(filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl)))
.subscribe(() => { .subscribe(() => {
this.clearRedirectUrl(); this.clearRedirectUrl();
}); });
@@ -93,14 +109,14 @@ export class AuthService {
let headers = new HttpHeaders(); let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
options.headers = headers; options.headers = headers;
return this.authRequestService.postToEndpoint('login', body, options) return this.authRequestService.postToEndpoint('login', body, options).pipe(
.map((status: AuthStatus) => { map((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
return status; return status;
} else { } else {
throw(new Error('Invalid email or password')); throw(new Error('Invalid email or password'));
} }
}) }))
} }
@@ -109,7 +125,7 @@ export class AuthService {
* @returns {Observable<boolean>} * @returns {Observable<boolean>}
*/ */
public isAuthenticated(): Observable<boolean> { public isAuthenticated(): Observable<boolean> {
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('Accept', 'application/json');
headers = headers.append('Authorization', `Bearer ${token.accessToken}`); headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
options.headers = headers; options.headers = headers;
return this.authRequestService.getRequest('status', options) return this.authRequestService.getRequest('status', options).pipe(
.map((status: AuthStatus) => { map((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
return status.eperson[0]; return status.eperson[0];
} else { } else {
throw(new Error('Not authenticated')); throw(new Error('Not authenticated'));
} }
}); }));
} }
/** /**
@@ -144,9 +160,10 @@ export class AuthService {
* Checks if token is present into storage and is not expired * Checks if token is present into storage and is not expired
*/ */
public hasValidAuthenticationToken(): Observable<AuthTokenInfo> { public hasValidAuthenticationToken(): Observable<AuthTokenInfo> {
return this.store.select(getAuthenticationToken) return this.store.pipe(
.take(1) select(getAuthenticationToken),
.map((authTokenInfo: AuthTokenInfo) => { take(1),
map((authTokenInfo: AuthTokenInfo) => {
let token: AuthTokenInfo; let token: AuthTokenInfo;
// Retrieve authentication token info and check if is valid // Retrieve authentication token info and check if is valid
token = isNotEmpty(authTokenInfo) ? authTokenInfo : this.storage.get(TOKENITEM); token = isNotEmpty(authTokenInfo) ? authTokenInfo : this.storage.get(TOKENITEM);
@@ -155,7 +172,8 @@ export class AuthService {
} else { } else {
throw false; throw false;
} }
}); })
);
} }
/** /**
@@ -167,14 +185,14 @@ export class AuthService {
headers = headers.append('Accept', 'application/json'); headers = headers.append('Accept', 'application/json');
headers = headers.append('Authorization', `Bearer ${token.accessToken}`); headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
options.headers = headers; options.headers = headers;
return this.authRequestService.postToEndpoint('login', {}, options) return this.authRequestService.postToEndpoint('login', {}, options).pipe(
.map((status: AuthStatus) => { map((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
return status.token; return status.token;
} else { } else {
throw(new Error('Not authenticated')); throw(new Error('Not authenticated'));
} }
}); }));
} }
/** /**
@@ -193,7 +211,7 @@ export class AuthService {
// details and then return the new user object // details and then return the new user object
// but, let's just return the new user for this example. // but, let's just return the new user for this example.
// this._authenticated = true; // 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 // Send a request that sign end the session
let headers = new HttpHeaders(); let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
const options: HttpOptions = Object.create({headers, responseType: 'text'}); const options: HttpOptions = Object.create({ headers, responseType: 'text' });
return this.authRequestService.getRequest('logout', options) return this.authRequestService.getRequest('logout', options).pipe(
.map((status: AuthStatus) => { map((status: AuthStatus) => {
if (!status.authenticated) { if (!status.authenticated) {
return true; return true;
} else { } else {
throw(new Error('auth.errors.invalid-user')); throw(new Error('auth.errors.invalid-user'));
} }
}) }))
} }
@@ -233,7 +251,7 @@ export class AuthService {
*/ */
public getToken(): AuthTokenInfo { public getToken(): AuthTokenInfo {
let token: AuthTokenInfo; let token: AuthTokenInfo;
this.store.select(getAuthenticationToken) this.store.pipe(select(getAuthenticationToken))
.subscribe((authTokenInfo: AuthTokenInfo) => { .subscribe((authTokenInfo: AuthTokenInfo) => {
// Retrieve authentication token info and check if is valid // Retrieve authentication token info and check if is valid
token = authTokenInfo || null; token = authTokenInfo || null;
@@ -246,9 +264,10 @@ export class AuthService {
* @returns {boolean} * @returns {boolean}
*/ */
public isTokenExpiring(): Observable<boolean> { public isTokenExpiring(): Observable<boolean> {
return this.store.select(isTokenRefreshing) return this.store.pipe(
.first() select(isTokenRefreshing),
.map((isRefreshing: boolean) => { first(),
map((isRefreshing: boolean) => {
if (this.isTokenExpired() || isRefreshing) { if (this.isTokenExpired() || isRefreshing) {
return false; return false;
} else { } else {
@@ -256,6 +275,7 @@ export class AuthService {
return token.expires - (60 * 5 * 1000) < Date.now(); return token.expires - (60 * 5 * 1000) < Date.now();
} }
}) })
)
} }
/** /**
@@ -279,7 +299,7 @@ export class AuthService {
// Set the cookie expire date // Set the cookie expire date
const expires = new Date(expireDate); const expires = new Date(expireDate);
const options: CookieAttributes = {expires: expires}; const options: CookieAttributes = { expires: expires };
// Save cookie with the token // Save cookie with the token
return this.storage.set(TOKENITEM, token, options); return this.storage.set(TOKENITEM, token, options);
@@ -324,8 +344,8 @@ export class AuthService {
* Redirect to the route navigated before the login * Redirect to the route navigated before the login
*/ */
public redirectToPreviousUrl() { public redirectToPreviousUrl() {
this.getRedirectUrl() this.getRedirectUrl().pipe(
.first() first())
.subscribe((redirectUrl) => { .subscribe((redirectUrl) => {
if (isNotEmpty(redirectUrl)) { if (isNotEmpty(redirectUrl)) {
this.clearRedirectUrl(); this.clearRedirectUrl();
@@ -359,9 +379,9 @@ export class AuthService {
getRedirectUrl(): Observable<string> { getRedirectUrl(): Observable<string> {
const redirectUrl = this.storage.get(REDIRECT_COOKIE); const redirectUrl = this.storage.get(REDIRECT_COOKIE);
if (isNotEmpty(redirectUrl)) { if (isNotEmpty(redirectUrl)) {
return Observable.of(redirectUrl); return observableOf(redirectUrl);
} else { } else {
return this.store.select(getRedirectUrl); return this.store.pipe(select(getRedirectUrl));
} }
} }
@@ -374,7 +394,7 @@ export class AuthService {
// Set the cookie expire date // Set the cookie expire date
const expires = new Date(expireDate); const expires = new Date(expireDate);
const options: CookieAttributes = {expires: expires}; const options: CookieAttributes = { expires: expires };
this.storage.set(REDIRECT_COOKIE, url, options); this.storage.set(REDIRECT_COOKIE, url, options);
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : '')); this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
} }

View File

@@ -1,8 +1,10 @@
import {take} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
// reducers // reducers
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
@@ -52,12 +54,12 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
private handleAuth(url: string): Observable<boolean> { private handleAuth(url: string): Observable<boolean> {
// get 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 // 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) // .filter(() => isEmpty(this.router.routerState.snapshot.url) || this.router.routerState.snapshot.url === url)
.take(1) take(1))
.subscribe((authenticated) => { .subscribe((authenticated) => {
if (!authenticated) { if (!authenticated) {
this.authService.setRedirectUrl(url); this.authService.setRedirectUrl(url);

View File

@@ -1,3 +1,5 @@
import {first, map} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -32,14 +34,14 @@ export class ServerAuthService extends AuthService {
headers = headers.append('X-Forwarded-For', clientIp); headers = headers.append('X-Forwarded-For', clientIp);
options.headers = headers; options.headers = headers;
return this.authRequestService.getRequest('status', options) return this.authRequestService.getRequest('status', options).pipe(
.map((status: AuthStatus) => { map((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
return status.eperson[0]; return status.eperson[0];
} else { } else {
throw(new Error('Not authenticated')); throw(new Error('Not authenticated'));
} }
}); }));
} }
/** /**
@@ -53,8 +55,8 @@ export class ServerAuthService extends AuthService {
* Redirect to the route navigated before the login * Redirect to the route navigated before the login
*/ */
public redirectToPreviousUrl() { public redirectToPreviousUrl() {
this.getRedirectUrl() this.getRedirectUrl().pipe(
.first() first())
.subscribe((redirectUrl) => { .subscribe((redirectUrl) => {
if (isNotEmpty(redirectUrl)) { if (isNotEmpty(redirectUrl)) {
// override the route reuse strategy // override the route reuse strategy

View File

@@ -1,5 +1,6 @@
import {combineLatest as observableCombineLatest, of as observableOf, Observable, race as observableRace } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, flatMap, map, startWith } from 'rxjs/operators'; import { distinctUntilChanged, flatMap, map, startWith } from 'rxjs/operators';
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util';
import { PaginatedList } from '../../data/paginated-list'; import { PaginatedList } from '../../data/paginated-list';
@@ -32,24 +33,24 @@ export class RemoteDataBuildService {
buildSingle<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> { buildSingle<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> {
if (typeof href$ === 'string') { if (typeof href$ === 'string') {
href$ = Observable.of(href$); href$ = observableOf(href$);
} }
const requestHref$ = href$.pipe(flatMap((href: string) => const requestHref$ = href$.pipe(flatMap((href: string) =>
this.objectCache.getRequestHrefBySelfLink(href))); this.objectCache.getRequestHrefBySelfLink(href)));
const requestEntry$ = Observable.race( const requestEntry$ = observableRace(
href$.pipe(getRequestFromSelflink(this.requestService)), href$.pipe(getRequestFromSelflink(this.requestService)),
requestHref$.pipe(getRequestFromSelflink(this.requestService)) requestHref$.pipe(getRequestFromSelflink(this.requestService))
); );
const responseCache$ = Observable.race( const responseCache$ = observableRace(
href$.pipe(getResponseFromSelflink(this.responseCache)), href$.pipe(getResponseFromSelflink(this.responseCache)),
requestHref$.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. // always use self link if that is cached, only if it isn't, get it via the response.
const payload$ = const payload$ =
Observable.combineLatest( observableCombineLatest(
href$.pipe( href$.pipe(
flatMap((href: string) => this.objectCache.getBySelfLink<TNormalized>(href)), flatMap((href: string) => this.objectCache.getBySelfLink<TNormalized>(href)),
startWith(undefined) startWith(undefined)
@@ -60,7 +61,7 @@ export class RemoteDataBuildService {
if (isNotEmpty(resourceSelfLinks)) { if (isNotEmpty(resourceSelfLinks)) {
return this.objectCache.getBySelfLink(resourceSelfLinks[0]); return this.objectCache.getBySelfLink(resourceSelfLinks[0]);
} else { } else {
return Observable.of(undefined); return observableOf(undefined);
} }
}), }),
distinctUntilChanged(), distinctUntilChanged(),
@@ -85,7 +86,7 @@ export class RemoteDataBuildService {
} }
toRemoteDataObservable<T>(requestEntry$: Observable<RequestEntry>, responseCache$: Observable<ResponseCacheEntry>, payload$: Observable<T>) { toRemoteDataObservable<T>(requestEntry$: Observable<RequestEntry>, responseCache$: Observable<ResponseCacheEntry>, payload$: Observable<T>) {
return Observable.combineLatest(requestEntry$, responseCache$.startWith(undefined), payload$, return observableCombineLatest(requestEntry$, responseCache$.pipe(startWith(undefined)), payload$,
(reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => { (reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => {
const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
@@ -110,7 +111,7 @@ export class RemoteDataBuildService {
buildList<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<PaginatedList<TDomain>>> { buildList<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<PaginatedList<TDomain>>> {
if (typeof href$ === 'string') { if (typeof href$ === 'string') {
href$ = Observable.of(href$); href$ = observableOf(href$);
} }
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
@@ -119,12 +120,12 @@ export class RemoteDataBuildService {
const tDomainList$ = responseCache$.pipe( const tDomainList$ = responseCache$.pipe(
getResourceLinksFromResponse(), getResourceLinksFromResponse(),
flatMap((resourceUUIDs: string[]) => { flatMap((resourceUUIDs: string[]) => {
return this.objectCache.getList(resourceUUIDs) return this.objectCache.getList(resourceUUIDs).pipe(
.map((normList: TNormalized[]) => { map((normList: TNormalized[]) => {
return normList.map((normalized: TNormalized) => { return normList.map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized); return this.build<TNormalized, TDomain>(normalized);
}); });
}); }));
}), }),
startWith([]), startWith([]),
distinctUntilChanged() 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); return new PaginatedList(pageInfo, tDomainList);
}); });
@@ -204,10 +205,10 @@ export class RemoteDataBuildService {
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> { aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
if (isEmpty(input)) { 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, ...input,
(...arr: Array<RemoteData<T>>) => { (...arr: Array<RemoteData<T>>) => {
const requestPending: boolean = arr const requestPending: boolean = arr
@@ -255,7 +256,7 @@ export class RemoteDataBuildService {
} }
aggregatePaginatedList<T>(input: Observable<RemoteData<T[]>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> { aggregatePaginatedList<T>(input: Observable<RemoteData<T[]>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> {
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)})));
} }
} }

View File

@@ -1,5 +1,6 @@
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { StoreActionTypes } from '../../store.actions'; import { StoreActionTypes } from '../../store.actions';
import { ResetObjectCacheTimestampsAction } from './object-cache.actions'; import { ResetObjectCacheTimestampsAction } from './object-cache.actions';
@@ -16,9 +17,11 @@ export class ObjectCacheEffects {
* time ago, and will likely need to be revisited later * time ago, and will likely need to be revisited later
*/ */
@Effect() fixTimestampsOnRehydrate = this.actions$ @Effect() fixTimestampsOnRehydrate = this.actions$
.ofType(StoreActionTypes.REHYDRATE) .pipe(ofType(StoreActionTypes.REHYDRATE),
.map(() => new ResetObjectCacheTimestampsAction(new Date().getTime())); map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()))
);
constructor(private actions$: Actions) { } constructor(private actions$: Actions) {
}
} }

View File

@@ -1,16 +1,16 @@
import { Injectable } from '@angular/core'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { MemoizedSelector, Store } from '@ngrx/store';
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 { 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 { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
import { hasNoValue } from '../../shared/empty.util'; import { hasNoValue } from '../../shared/empty.util';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { coreSelector, CoreState } from '../core.reducers'; import { coreSelector, CoreState } from '../core.reducers';
import { pathSelector } from '../shared/selectors'; import { pathSelector } from '../shared/selectors';
import { Item } from '../shared/item.model';
import { NormalizedObjectFactory } from './models/normalized-object-factory'; import { NormalizedObjectFactory } from './models/normalized-object-factory';
import { NormalizedObject } from './models/normalized-object.model'; import { NormalizedObject } from './models/normalized-object.model';
@@ -73,33 +73,40 @@ export class ObjectCacheService {
* An observable of the requested object * An observable of the requested object
*/ */
getByUUID<T extends NormalizedObject>(uuid: string): Observable<T> { getByUUID<T extends NormalizedObject>(uuid: string): Observable<T> {
return this.store.select(selfLinkFromUuidSelector(uuid)) return this.store.pipe(
.flatMap((selfLink: string) => this.getBySelfLink(selfLink)) select(selfLinkFromUuidSelector(uuid)),
mergeMap((selfLink: string) => this.getBySelfLink(selfLink)
)
)
} }
getBySelfLink<T extends NormalizedObject>(selfLink: string): Observable<T> { getBySelfLink<T extends NormalizedObject>(selfLink: string): Observable<T> {
return this.getEntry(selfLink) return this.getEntry(selfLink).pipe(
.map((entry: ObjectCacheEntry) => { map((entry: ObjectCacheEntry) => {
const type: GenericConstructor<NormalizedObject>= NormalizedObjectFactory.getConstructor(entry.data.type); const type: GenericConstructor<NormalizedObject> = NormalizedObjectFactory.getConstructor(entry.data.type);
return Object.assign(new type(), entry.data) as T return Object.assign(new type(), entry.data) as T
}); }));
} }
private getEntry(selfLink: string): Observable<ObjectCacheEntry> { private getEntry(selfLink: string): Observable<ObjectCacheEntry> {
return this.store.select(entryFromSelfLinkSelector(selfLink)) return this.store.pipe(
.filter((entry) => this.isValid(entry)) select(entryFromSelfLinkSelector(selfLink)),
.distinctUntilChanged(); filter((entry) => this.isValid(entry)),
distinctUntilChanged()
);
} }
getRequestHrefBySelfLink(selfLink: string): Observable<string> { getRequestHrefBySelfLink(selfLink: string): Observable<string> {
return this.getEntry(selfLink) return this.getEntry(selfLink).pipe(
.map((entry: ObjectCacheEntry) => entry.requestHref) map((entry: ObjectCacheEntry) => entry.requestHref),
.distinctUntilChanged(); distinctUntilChanged(),);
} }
getRequestHrefByUUID(uuid: string): Observable<string> { getRequestHrefByUUID(uuid: string): Observable<string> {
return this.store.select(selfLinkFromUuidSelector(uuid)) return this.store.pipe(
.flatMap((selfLink: string) => this.getRequestHrefBySelfLink(selfLink)); select(selfLinkFromUuidSelector(uuid)),
mergeMap((selfLink: string) => this.getRequestHrefBySelfLink(selfLink))
);
} }
/** /**
@@ -122,7 +129,7 @@ export class ObjectCacheService {
* @return Observable<Array<T>> * @return Observable<Array<T>>
*/ */
getList<T extends NormalizedObject>(selfLinks: string[]): Observable<T[]> { getList<T extends NormalizedObject>(selfLinks: string[]): Observable<T[]> {
return Observable.combineLatest( return observableCombineLatest(
selfLinks.map((selfLink: string) => this.getBySelfLink<T>(selfLink)) selfLinks.map((selfLink: string) => this.getBySelfLink<T>(selfLink))
); );
} }
@@ -139,9 +146,10 @@ export class ObjectCacheService {
hasByUUID(uuid: string): boolean { hasByUUID(uuid: string): boolean {
let result: boolean; let result: boolean;
this.store.select(selfLinkFromUuidSelector(uuid)) this.store.pipe(
.take(1) select(selfLinkFromUuidSelector(uuid)),
.subscribe((selfLink: string) => result = this.hasBySelfLink(selfLink)); first()
).subscribe((selfLink: string) => result = this.hasBySelfLink(selfLink));
return result; return result;
} }
@@ -158,9 +166,9 @@ export class ObjectCacheService {
hasBySelfLink(selfLink: string): boolean { hasBySelfLink(selfLink: string): boolean {
let result = false; let result = false;
this.store.select(entryFromSelfLinkSelector(selfLink)) this.store.pipe(select(entryFromSelfLinkSelector(selfLink)),
.take(1) first()
.subscribe((entry: ObjectCacheEntry) => result = this.isValid(entry)); ).subscribe((entry: ObjectCacheEntry) => result = this.isValid(entry));
return result; return result;
} }

View File

@@ -1,5 +1,6 @@
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; 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 { ResetResponseCacheTimestampsAction } from './response-cache.actions';
import { StoreActionTypes } from '../../store.actions'; import { StoreActionTypes } from '../../store.actions';
@@ -16,9 +17,11 @@ export class ResponseCacheEffects {
* time ago, and will likely need to be revisited later * time ago, and will likely need to be revisited later
*/ */
@Effect() fixTimestampsOnRehydrate = this.actions$ @Effect() fixTimestampsOnRehydrate = this.actions$
.ofType(StoreActionTypes.REHYDRATE) .pipe(ofType(StoreActionTypes.REHYDRATE),
.map(() => new ResetResponseCacheTimestampsAction(new Date().getTime())); map(() => new ResetResponseCacheTimestampsAction(new Date().getTime()))
);
constructor(private actions$: Actions, ) { } constructor(private actions$: Actions,) {
}
} }

View File

@@ -1,5 +1,6 @@
import { filter, take, distinctUntilChanged, first } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MemoizedSelector, Store } from '@ngrx/store'; import { MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -21,7 +22,8 @@ function entryFromKeySelector(key: string): MemoizedSelector<CoreState, Response
export class ResponseCacheService { export class ResponseCacheService {
constructor( constructor(
private store: Store<CoreState> private store: Store<CoreState>
) { } ) {
}
add(key: string, response: RestResponse, msToLive: number): Observable<ResponseCacheEntry> { add(key: string, response: RestResponse, msToLive: number): Observable<ResponseCacheEntry> {
if (!this.has(key)) { if (!this.has(key)) {
@@ -39,9 +41,11 @@ export class ResponseCacheService {
* an observable of the ResponseCacheEntry with the specified key * an observable of the ResponseCacheEntry with the specified key
*/ */
get(key: string): Observable<ResponseCacheEntry> { get(key: string): Observable<ResponseCacheEntry> {
return this.store.select(entryFromKeySelector(key)) return this.store.pipe(
.filter((entry: ResponseCacheEntry) => this.isValid(entry)) select(entryFromKeySelector(key)),
.distinctUntilChanged() filter((entry: ResponseCacheEntry) => this.isValid(entry)),
distinctUntilChanged()
)
} }
/** /**
@@ -56,11 +60,11 @@ export class ResponseCacheService {
has(key: string): boolean { has(key: string): boolean {
let result: boolean; let result: boolean;
this.store.select(entryFromKeySelector(key)) this.store.pipe(select(entryFromKeySelector(key)),
.take(1) first()
.subscribe((entry: ResponseCacheEntry) => { ).subscribe((entry: ResponseCacheEntry) => {
result = this.isValid(entry); result = this.isValid(entry);
}); });
return result; return result;
} }
@@ -70,6 +74,7 @@ export class ResponseCacheService {
this.store.dispatch(new ResponseCacheRemoveAction(key)); this.store.dispatch(new ResponseCacheRemoveAction(key));
} }
} }
/** /**
* Check whether a ResponseCacheEntry should still be cached * Check whether a ResponseCacheEntry should still be cached
* *

View File

@@ -1,9 +1,8 @@
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
import {throwError as observableThrowError, Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { ResponseCacheService } from '../cache/response-cache.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 { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../shared/empty.util';
@@ -19,16 +18,18 @@ export abstract class ConfigService {
protected abstract halService: HALEndpointService; protected abstract halService: HALEndpointService;
protected getConfig(request: RestRequest): Observable<ConfigData> { protected getConfig(request: RestRequest): Observable<ConfigData> {
const [successResponse, errorResponse] = this.responseCache.get(request.href) return this.responseCache.get(request.href).pipe(
.map((entry: ResponseCacheEntry) => entry.response) map((entry: ResponseCacheEntry) => entry.response),
.partition((response: RestResponse) => response.isSuccessful); mergeMap((response) => {
return Observable.merge( if (response.isSuccessful && isNotEmpty(response) && isNotEmpty((response as ConfigSuccessResponse).configDefinition)) {
errorResponse.flatMap((response: ErrorResponse) => const configResponse = response as ConfigSuccessResponse;
observableThrowError(new Error(`Couldn't retrieve the config`))), return observableOf(new ConfigData(configResponse.pageInfo, configResponse.configDefinition));
successResponse } else if (!response.isSuccessful) {
.filter((response: ConfigSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.configDefinition)) return observableThrowError(new Error(`Couldn't retrieve the config`));
.map((response: ConfigSuccessResponse) => new ConfigData(response.pageInfo, response.configDefinition)) }
.distinctUntilChanged()); }),
distinctUntilChanged()
);
} }
protected getConfigByNameHref(endpoint, resourceName): string { protected getConfigByNameHref(endpoint, resourceName): string {
@@ -66,13 +67,13 @@ export abstract class ConfigService {
} }
public getConfigAll(): Observable<ConfigData> { public getConfigAll(): Observable<ConfigData> {
return this.halService.getEndpoint(this.linkPath) return this.halService.getEndpoint(this.linkPath).pipe(
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.distinctUntilChanged() distinctUntilChanged(),
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)) map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)),
.do((request: RestRequest) => this.requestService.configure(request)) tap((request: RestRequest) => this.requestService.configure(request)),
.flatMap((request: RestRequest) => this.getConfig(request)) mergeMap((request: RestRequest) => this.getConfig(request)),
.distinctUntilChanged(); distinctUntilChanged(),);
} }
public getConfigByHref(href: string): Observable<ConfigData> { public getConfigByHref(href: string): Observable<ConfigData> {
@@ -83,25 +84,25 @@ export abstract class ConfigService {
} }
public getConfigByName(name: string): Observable<ConfigData> { public getConfigByName(name: string): Observable<ConfigData> {
return this.halService.getEndpoint(this.linkPath) return this.halService.getEndpoint(this.linkPath).pipe(
.map((endpoint: string) => this.getConfigByNameHref(endpoint, name)) map((endpoint: string) => this.getConfigByNameHref(endpoint, name)),
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.distinctUntilChanged() distinctUntilChanged(),
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)) map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)),
.do((request: RestRequest) => this.requestService.configure(request)) tap((request: RestRequest) => this.requestService.configure(request)),
.flatMap((request: RestRequest) => this.getConfig(request)) mergeMap((request: RestRequest) => this.getConfig(request)),
.distinctUntilChanged(); distinctUntilChanged(),);
} }
public getConfigBySearch(options: FindAllOptions = {}): Observable<ConfigData> { public getConfigBySearch(options: FindAllOptions = {}): Observable<ConfigData> {
return this.halService.getEndpoint(this.linkPath) return this.halService.getEndpoint(this.linkPath).pipe(
.map((endpoint: string) => this.getConfigSearchHref(endpoint, options)) map((endpoint: string) => this.getConfigSearchHref(endpoint, options)),
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.distinctUntilChanged() distinctUntilChanged(),
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)) map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)),
.do((request: RestRequest) => this.requestService.configure(request)) tap((request: RestRequest) => this.requestService.configure(request)),
.flatMap((request: RestRequest) => this.getConfig(request)) mergeMap((request: RestRequest) => this.getConfig(request)),
.distinctUntilChanged(); distinctUntilChanged(),);
} }
} }

View File

@@ -1,10 +1,8 @@
import { Observable, throwError as observableThrowError } from 'rxjs';
import {throwError as observableThrowError, Observable } from 'rxjs'; import { distinctUntilChanged, filter, first, map, mergeMap, tap } from 'rxjs/operators';
import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { CommunityDataService } from './community-data.service'; import { CommunityDataService } from './community-data.service';
@@ -13,7 +11,7 @@ import { FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> { export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> {
protected abstract cds: CommunityDataService; protected abstract cds: CommunityDataService;
protected abstract objectCache: ObjectCacheService; protected abstract objectCache: ObjectCacheService;
protected abstract halService: HALEndpointService; protected abstract halService: HALEndpointService;
@@ -32,29 +30,31 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
if (isEmpty(scopeID)) { if (isEmpty(scopeID)) {
return this.halService.getEndpoint(this.linkPath); return this.halService.getEndpoint(this.linkPath);
} else { } else {
const scopeCommunityHrefObs = this.cds.getEndpoint() const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
.flatMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)) mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)),
.filter((href: string) => isNotEmpty(href)) first((href: string) => isNotEmpty(href)),
.take(1) tap((href: string) => {
.do((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID); const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID);
this.requestService.configure(request); this.requestService.configure(request);
}); }),);
const [successResponse, errorResponse] = scopeCommunityHrefObs return scopeCommunityHrefObs.pipe(
.flatMap((href: string) => this.responseCache.get(href)) mergeMap((href: string) => this.responseCache.get(href)),
.map((entry: ResponseCacheEntry) => entry.response) map((entry: ResponseCacheEntry) => entry.response),
.share() mergeMap((response) => {
.partition((response: RestResponse) => response.isSuccessful); if (response.isSuccessful) {
const community$: Observable<NormalizedCommunity> = this.objectCache.getByUUID(scopeID);
return Observable.merge( return community$.pipe(
errorResponse.flatMap((response: ErrorResponse) => map((community) => community._links[this.linkPath]),
observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))), filter((href) => isNotEmpty(href)),
successResponse distinctUntilChanged()
.flatMap((response: DSOSuccessResponse) => this.objectCache.getByUUID(scopeID)) );
.map((nc: NormalizedCommunity) => nc._links[this.linkPath]) } else if (!response.isSuccessful) {
.filter((href) => isNotEmpty(href)) return observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))
).distinctUntilChanged(); }
}),
distinctUntilChanged()
);
} }
} }
} }

View File

@@ -1,3 +1,5 @@
import {mergeMap, filter, take} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@@ -38,12 +40,12 @@ export class CommunityDataService extends ComColDataService<NormalizedCommunity,
} }
findTop(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Community>>> { findTop(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
const hrefObs = this.halService.getEndpoint(this.topLinkPath).filter((href: string) => isNotEmpty(href)) const hrefObs = this.halService.getEndpoint(this.topLinkPath).pipe(filter((href: string) => isNotEmpty(href)),
.flatMap((endpoint: string) => this.getFindAllHref(endpoint, options)); mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
hrefObs hrefObs.pipe(
.filter((href: string) => hasValue(href)) filter((href: string) => hasValue(href)),
.take(1) take(1),)
.subscribe((href: string) => { .subscribe((href: string) => {
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request); this.requestService.configure(request);

View File

@@ -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 { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
@@ -27,9 +30,9 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
const args = []; const args = [];
if (hasValue(options.scopeID)) { if (hasValue(options.scopeID)) {
result = this.getScopedEndpoint(options.scopeID).distinctUntilChanged(); result = this.getScopedEndpoint(options.scopeID).pipe(distinctUntilChanged());
} else { } else {
result = Observable.of(endpoint); result = observableOf(endpoint);
} }
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
@@ -50,19 +53,19 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
} }
if (isNotEmpty(args)) { 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 { } else {
return result; return result;
} }
} }
findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> { findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).filter((href: string) => isNotEmpty(href)) const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(filter((href: string) => isNotEmpty(href)),
.flatMap((endpoint: string) => this.getFindAllHref(endpoint, options)); mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
hrefObs hrefObs.pipe(
.filter((href: string) => hasValue(href)) filter((href: string) => hasValue(href)),
.take(1) take(1),)
.subscribe((href: string) => { .subscribe((href: string) => {
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request); this.requestService.configure(request);
@@ -76,11 +79,11 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
} }
findById(id: string): Observable<RemoteData<TDomain>> { findById(id: string): Observable<RemoteData<TDomain>> {
const hrefObs = this.halService.getEndpoint(this.linkPath) const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
.map((endpoint: string) => this.getFindByIDHref(endpoint, id)); map((endpoint: string) => this.getFindByIDHref(endpoint, id)));
hrefObs hrefObs.pipe(
.first((href: string) => hasValue(href)) first((href: string) => hasValue(href)))
.subscribe((href: string) => { .subscribe((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id);
this.requestService.configure(request); this.requestService.configure(request);

View File

@@ -1,3 +1,5 @@
import {distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@@ -34,10 +36,10 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
if (isEmpty(scopeID)) { if (isEmpty(scopeID)) {
return this.halService.getEndpoint(this.linkPath); return this.halService.getEndpoint(this.linkPath);
} else { } else {
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath) return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath).pipe(
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()) map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()),
.distinctUntilChanged(); distinctUntilChanged(),);
} }
} }

View File

@@ -1,9 +1,9 @@
import {of as observableOf, Observable } from 'rxjs';
import { Inject, Injectable, Injector } from '@angular/core'; import { Inject, Injectable, Injector } from '@angular/core';
import { Request } from '@angular/http'; import { Request } from '@angular/http';
import { RequestArgs } from '@angular/http/src/interfaces'; import { RequestArgs } from '@angular/http/src/interfaces';
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
// tslint:disable-next-line:import-blacklist
import { Observable } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
@@ -30,7 +30,8 @@ export const addToResponseCacheAndCompleteAction = (request: RestRequest, respon
@Injectable() @Injectable()
export class RequestEffects { export class RequestEffects {
@Effect() execute = this.actions$.ofType(RequestActionTypes.EXECUTE).pipe( @Effect() execute = this.actions$.pipe(
ofType(RequestActionTypes.EXECUTE),
flatMap((action: RequestExecuteAction) => { flatMap((action: RequestExecuteAction) => {
return this.requestService.getByUUID(action.payload).pipe( return this.requestService.getByUUID(action.payload).pipe(
take(1) take(1)
@@ -46,7 +47,7 @@ export class RequestEffects {
return this.restApi.request(request.method, request.href, body, request.options).pipe( return this.restApi.request(request.method, request.href, body, request.options).pipe(
map((data: DSpaceRESTV2Response) => this.injector.get(request.getResponseParser()).parse(request, data)), map((data: DSpaceRESTV2Response) => this.injector.get(request.getResponseParser()).parse(request, data)),
addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig), 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) addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig)
)) ))
); );

View File

@@ -1,7 +1,6 @@
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { cold, hot } from 'jasmine-marbles'; import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs'; import { of as observableOf } from 'rxjs';
import 'rxjs/add/observable/of';
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
import { getMockStore } from '../../shared/mocks/mock-store'; import { getMockStore } from '../../shared/mocks/mock-store';
@@ -18,7 +17,8 @@ import {
OptionsRequest, OptionsRequest,
PatchRequest, PatchRequest,
PostRequest, PostRequest,
PutRequest, RestRequest PutRequest,
RestRequest
} from './request.models'; } from './request.models';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
@@ -46,12 +46,12 @@ describe('RequestService', () => {
responseCache = getMockResponseCacheService(); responseCache = getMockResponseCacheService();
(responseCache.has as any).and.returnValue(false); (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(); uuidService = getMockUUIDService();
store = getMockStore<CoreState>(); store = getMockStore<CoreState>();
(store.select as any).and.returnValue(Observable.of(undefined)); (store.pipe as any).and.returnValue(observableOf(undefined));
service = new RequestService( service = new RequestService(
objectCache, objectCache,
@@ -74,7 +74,7 @@ describe('RequestService', () => {
describe('isPending', () => { describe('isPending', () => {
describe('before the request is configured', () => { describe('before the request is configured', () => {
beforeEach(() => { beforeEach(() => {
spyOn(service, 'getByHref').and.returnValue(Observable.of(undefined)); spyOn(service, 'getByHref').and.returnValue(observableOf(undefined));
}); });
it('should return false', () => { it('should return false', () => {
@@ -87,7 +87,7 @@ describe('RequestService', () => {
describe('when the request has been configured but hasn\'t reached the store yet', () => { describe('when the request has been configured but hasn\'t reached the store yet', () => {
beforeEach(() => { beforeEach(() => {
spyOn(service, 'getByHref').and.returnValue(Observable.of(undefined)); spyOn(service, 'getByHref').and.returnValue(observableOf(undefined));
serviceAsAny.requestsOnTheirWayToTheStore = [testHref]; serviceAsAny.requestsOnTheirWayToTheStore = [testHref];
}); });
@@ -101,7 +101,7 @@ describe('RequestService', () => {
describe('when the request has reached the store, before the server responds', () => { describe('when the request has reached the store, before the server responds', () => {
beforeEach(() => { beforeEach(() => {
spyOn(service, 'getByHref').and.returnValue(Observable.of({ spyOn(service, 'getByHref').and.returnValue(observableOf({
completed: false completed: false
})) }))
}); });
@@ -116,7 +116,7 @@ describe('RequestService', () => {
describe('after the server responds', () => { describe('after the server responds', () => {
beforeEach(() => { beforeEach(() => {
spyOn(service, 'getByHref').and.returnValues(Observable.of({ spyOn(service, 'getByHref').and.returnValues(observableOf({
completed: true completed: true
})); }));
}); });
@@ -134,7 +134,7 @@ describe('RequestService', () => {
describe('getByUUID', () => { describe('getByUUID', () => {
describe('if the request with the specified UUID exists in the store', () => { describe('if the request with the specified UUID exists in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.select as any).and.returnValues(hot('a', { (store.pipe as any).and.returnValues(hot('a', {
a: { a: {
completed: true completed: true
} }
@@ -155,7 +155,7 @@ describe('RequestService', () => {
describe('if the request with the specified UUID doesn\'t exist in the store', () => { describe('if the request with the specified UUID doesn\'t exist in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.select as any).and.returnValues(hot('a', { (store.pipe as any).and.returnValues(hot('a', {
a: undefined a: undefined
})); }));
}); });
@@ -175,7 +175,7 @@ describe('RequestService', () => {
describe('getByHref', () => { describe('getByHref', () => {
describe('when the request with the specified href exists in the store', () => { describe('when the request with the specified href exists in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.select as any).and.returnValues(hot('a', { (store.pipe as any).and.returnValues(hot('a', {
a: testUUID a: testUUID
})); }));
spyOn(service, 'getByUUID').and.returnValue(cold('b', { 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', () => { describe('when the request with the specified href doesn\'t exist in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.select as any).and.returnValues(hot('a', { (store.pipe as any).and.returnValues(hot('a', {
a: undefined a: undefined
})); }));
spyOn(service, 'getByUUID').and.returnValue(cold('b', { spyOn(service, 'getByUUID').and.returnValue(cold('b', {
@@ -323,7 +323,7 @@ describe('RequestService', () => {
describe('and it\'s a DSOSuccessResponse', () => { describe('and it\'s a DSOSuccessResponse', () => {
beforeEach(() => { beforeEach(() => {
(responseCache.get as any).and.returnValues(Observable.of({ (responseCache.get as any).and.returnValues(observableOf({
response: { response: {
isSuccessful: true, isSuccessful: true,
resourceSelfLinks: [ resourceSelfLinks: [
@@ -356,7 +356,7 @@ describe('RequestService', () => {
beforeEach(() => { beforeEach(() => {
(objectCache.hasBySelfLink as any).and.returnValues(false); (objectCache.hasBySelfLink as any).and.returnValues(false);
(responseCache.has as any).and.returnValues(true); (responseCache.has as any).and.returnValues(true);
(responseCache.get as any).and.returnValues(Observable.of({ (responseCache.get as any).and.returnValues(observableOf({
response: { response: {
isSuccessful: true isSuccessful: true
} }
@@ -428,7 +428,7 @@ describe('RequestService', () => {
describe('when the request is added to the store', () => { describe('when the request is added to the store', () => {
it('should stop tracking the request', () => { 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); serviceAsAny.trackRequestsOnTheirWayToTheStore(request);
expect(serviceAsAny.requestsOnTheirWayToTheStore.includes(request.href)).toBeFalsy(); expect(serviceAsAny.requestsOnTheirWayToTheStore.includes(request.href)).toBeFalsy();
}); });

View File

@@ -1,12 +1,12 @@
import { Observable } from 'rxjs';
import { filter, first, map, mergeMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; import { MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service'; 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 { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { coreSelector, CoreState } from '../core.reducers'; import { coreSelector, CoreState } from '../core.reducers';
@@ -16,8 +16,7 @@ import { UUIDService } from '../shared/uuid.service';
import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
import { GetRequest, RestRequest, RestRequestMethod } from './request.models'; import { GetRequest, RestRequest, RestRequestMethod } from './request.models';
import { RequestEntry, RequestState } from './request.reducer'; import { RequestEntry } from './request.reducer';
import { ResponseCacheRemoveAction } from '../cache/response-cache.actions';
@Injectable() @Injectable()
export class RequestService { export class RequestService {
@@ -49,8 +48,8 @@ export class RequestService {
// then check the store // then check the store
let isPending = false; let isPending = false;
this.getByHref(request.href) this.getByHref(request.href).pipe(
.take(1) take(1))
.subscribe((re: RequestEntry) => { .subscribe((re: RequestEntry) => {
isPending = (hasValue(re) && !re.completed) isPending = (hasValue(re) && !re.completed)
}); });
@@ -59,12 +58,14 @@ export class RequestService {
} }
getByUUID(uuid: string): Observable<RequestEntry> { getByUUID(uuid: string): Observable<RequestEntry> {
return this.store.select(this.entryFromUUIDSelector(uuid)); return this.store.pipe(select(this.entryFromUUIDSelector(uuid)));
} }
getByHref(href: string): Observable<RequestEntry> { getByHref(href: string): Observable<RequestEntry> {
return this.store.select(this.uuidFromHrefSelector(href)) return this.store.pipe(
.flatMap((uuid: string) => this.getByUUID(uuid)); 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 // 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) { private isCachedOrPending(request: GetRequest) {
let isCached = this.objectCache.hasBySelfLink(request.href); let isCached = this.objectCache.hasBySelfLink(request.href);
if (!isCached && this.responseCache.has(request.href)) { if (!isCached && this.responseCache.has(request.href)) {
const [successResponse, errorResponse] = this.responseCache.get(request.href) this.responseCache.get(request.href).pipe(
.take(1) first(),
.map((entry: ResponseCacheEntry) => entry.response) map((entry: ResponseCacheEntry) => {
.share() const response = entry.response;
.partition((response: RestResponse) => response.isSuccessful); if (response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)) {
return (response as DSOSuccessResponse).resourceSelfLinks.every((selfLink) => this.objectCache.hasBySelfLink(selfLink))
const [dsoSuccessResponse, otherSuccessResponse] = successResponse } else {
.share() return true;
.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))
)
).subscribe((c) => isCached = c); ).subscribe((c) => isCached = c);
} }
const isPending = this.isPending(request); const isPending = this.isPending(request);
return isCached || isPending; return isCached || isPending;
} }
@@ -121,11 +112,11 @@ export class RequestService {
*/ */
private trackRequestsOnTheirWayToTheStore(request: GetRequest) { private trackRequestsOnTheirWayToTheStore(request: GetRequest) {
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href]; this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href];
this.store.select(this.entryFromUUIDSelector(request.href)) this.store.pipe(select(this.entryFromUUIDSelector(request.href)),
.filter((re: RequestEntry) => hasValue(re)) filter((re: RequestEntry) => hasValue(re)),
.take(1) take(1)
.subscribe((re: RequestEntry) => { ).subscribe((re: RequestEntry) => {
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href) this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href)
}); });
} }
} }

View File

@@ -1,5 +1,5 @@
import {throwError as observableThrowError, Observable } from 'rxjs'; import {throwError as observableThrowError, Observable } from 'rxjs';
import {catchError, map} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Request } from '@angular/http'; import { Request } from '@angular/http';
import { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http' import { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'
@@ -37,12 +37,12 @@ export class DSpaceRESTv2Service {
* An Observable<string> containing the response from the server * An Observable<string> containing the response from the server
*/ */
get(absoluteURL: string): Observable<DSpaceRESTV2Response> { get(absoluteURL: string): Observable<DSpaceRESTV2Response> {
return this.http.get(absoluteURL, { observe: 'response' }) return this.http.get(absoluteURL, { observe: 'response' }).pipe(
.map((res: HttpResponse<any>) => ({ payload: res.body, statusCode: res.statusText })) map((res: HttpResponse<any>) => ({ payload: res.body, statusCode: res.statusText })),
.catch((err) => { catchError((err) => {
console.log('Error: ', err); console.log('Error: ', err);
return observableThrowError(err); return observableThrowError(err);
}); }));
} }
/** /**
@@ -67,12 +67,12 @@ export class DSpaceRESTv2Service {
if (options && options.responseType) { if (options && options.responseType) {
requestOptions.responseType = options.responseType; requestOptions.responseType = options.responseType;
} }
return this.http.request(method, url, requestOptions) return this.http.request(method, url, requestOptions).pipe(
.map((res) => ({ payload: res.body, headers: res.headers, statusCode: res.statusText })) map((res) => ({ payload: res.body, headers: res.headers, statusCode: res.statusText })),
.catch((err) => { catchError((err) => {
console.log('Error: ', err); console.log('Error: ', err);
return observableThrowError(err); return observableThrowError(err);
}); }));
} }
} }

View File

@@ -1,5 +1,6 @@
import { filter, map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Effect, Actions } from '@ngrx/effects'; import { Effect, Actions, ofType } from '@ngrx/effects';
import { import {
ObjectCacheActionTypes, AddToObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction,
@@ -15,44 +16,52 @@ import { IndexName } from './index.reducer';
export class UUIDIndexEffects { export class UUIDIndexEffects {
@Effect() addObject$ = this.actions$ @Effect() addObject$ = this.actions$
.ofType(ObjectCacheActionTypes.ADD) .pipe(
.filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid)) ofType(ObjectCacheActionTypes.ADD),
.map((action: AddToObjectCacheAction) => { filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid)),
return new AddToIndexAction( map((action: AddToObjectCacheAction) => {
IndexName.OBJECT, return new AddToIndexAction(
action.payload.objectToCache.uuid, IndexName.OBJECT,
action.payload.objectToCache.self action.payload.objectToCache.uuid,
); action.payload.objectToCache.self
}); );
})
);
@Effect() removeObject$ = this.actions$ @Effect() removeObject$ = this.actions$
.ofType(ObjectCacheActionTypes.REMOVE) .pipe(
.map((action: RemoveFromObjectCacheAction) => { ofType(ObjectCacheActionTypes.REMOVE),
return new RemoveFromIndexByValueAction( map((action: RemoveFromObjectCacheAction) => {
IndexName.OBJECT, return new RemoveFromIndexByValueAction(
action.payload IndexName.OBJECT,
); action.payload
}); );
})
);
@Effect() addRequest$ = this.actions$ @Effect() addRequest$ = this.actions$
.ofType(RequestActionTypes.CONFIGURE) .pipe(
.filter((action: RequestConfigureAction) => action.payload.method === RestRequestMethod.Get) ofType(RequestActionTypes.CONFIGURE),
.map((action: RequestConfigureAction) => { filter((action: RequestConfigureAction) => action.payload.method === RestRequestMethod.Get),
return new AddToIndexAction( map((action: RequestConfigureAction) => {
IndexName.REQUEST, return new AddToIndexAction(
action.payload.href, IndexName.REQUEST,
action.payload.uuid action.payload.href,
); action.payload.uuid
}); );
})
);
// @Effect() removeRequest$ = this.actions$ // @Effect() removeRequest$ = this.actions$
// .ofType(ObjectCacheActionTypes.REMOVE) // .pipe(
// .map((action: RemoveFromObjectCacheAction) => { // ofType(ObjectCacheActionTypes.REMOVE),
// map((action: RemoveFromObjectCacheAction) => {
// return new RemoveFromIndexByValueAction( // return new RemoveFromIndexByValueAction(
// IndexName.OBJECT, // IndexName.OBJECT,
// action.payload // action.payload
// ); // );
// }); // })
// )
constructor(private actions$: Actions) { constructor(private actions$: Actions) {

View File

@@ -1,8 +1,8 @@
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
import {throwError as observableThrowError, Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { ResponseCacheService } from '../cache/response-cache.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 { GetRequest, IntegrationRequest } from '../data/request.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../shared/empty.util';
@@ -19,16 +19,18 @@ export abstract class IntegrationService {
protected abstract halService: HALEndpointService; protected abstract halService: HALEndpointService;
protected getData(request: GetRequest): Observable<IntegrationData> { protected getData(request: GetRequest): Observable<IntegrationData> {
const [successResponse, errorResponse] = this.responseCache.get(request.href) return this.responseCache.get(request.href).pipe(
.map((entry: ResponseCacheEntry) => entry.response) map((entry: ResponseCacheEntry) => entry.response),
.partition((response: RestResponse) => response.isSuccessful); mergeMap((response) => {
return Observable.merge( if (response.isSuccessful && isNotEmpty(response)) {
errorResponse.flatMap((response: ErrorResponse) => const dataResponse = response as IntegrationSuccessResponse;
observableThrowError(new Error(`Couldn't retrieve the integration data`))), return observableOf(new IntegrationData(dataResponse.pageInfo, dataResponse.dataDefinition));
successResponse } else if (!response.isSuccessful) {
.filter((response: IntegrationSuccessResponse) => isNotEmpty(response)) return observableThrowError(new Error(`Couldn't retrieve the integration data`));
.map((response: IntegrationSuccessResponse) => new IntegrationData(response.pageInfo, response.dataDefinition)) }
.distinctUntilChanged()); }),
distinctUntilChanged()
);
} }
protected getIntegrationHref(endpoint, options: IntegrationSearchOptions = new IntegrationSearchOptions()): string { protected getIntegrationHref(endpoint, options: IntegrationSearchOptions = new IntegrationSearchOptions()): string {
@@ -73,14 +75,14 @@ export abstract class IntegrationService {
} }
public getEntriesByName(options: IntegrationSearchOptions): Observable<IntegrationData> { public getEntriesByName(options: IntegrationSearchOptions): Observable<IntegrationData> {
return this.halService.getEndpoint(this.linkPath) return this.halService.getEndpoint(this.linkPath).pipe(
.map((endpoint: string) => this.getIntegrationHref(endpoint, options)) map((endpoint: string) => this.getIntegrationHref(endpoint, options)),
.filter((href: string) => isNotEmpty(href)) filter((href: string) => isNotEmpty(href)),
.distinctUntilChanged() distinctUntilChanged(),
.map((endpointURL: string) => new IntegrationRequest(this.requestService.generateRequestId(), endpointURL)) map((endpointURL: string) => new IntegrationRequest(this.requestService.generateRequestId(), endpointURL)),
.do((request: GetRequest) => this.requestService.configure(request)) tap((request: GetRequest) => this.requestService.configure(request)),
.flatMap((request: GetRequest) => this.getData(request)) mergeMap((request: GetRequest) => this.getData(request)),
.distinctUntilChanged(); distinctUntilChanged());
} }
} }

View File

@@ -1,4 +1,6 @@
import {distinctUntilKeyChanged, map, filter, first, take} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
@@ -54,21 +56,21 @@ export class MetadataService {
} }
public listenForRouteChange(): void { public listenForRouteChange(): void {
this.router.events this.router.events.pipe(
.filter((event) => event instanceof NavigationEnd) filter((event) => event instanceof NavigationEnd),
.map(() => this.router.routerState.root) map(() => this.router.routerState.root),
.map((route: ActivatedRoute) => { map((route: ActivatedRoute) => {
route = this.getCurrentRoute(route); route = this.getCurrentRoute(route);
return { params: route.params, data: route.data }; return { params: route.params, data: route.data };
}).subscribe((routeInfo: any) => { }),).subscribe((routeInfo: any) => {
this.processRouteChange(routeInfo); this.processRouteChange(routeInfo);
}); });
} }
public processRemoteData(remoteData: Observable<RemoteData<CacheableObject>>): void { public processRemoteData(remoteData: Observable<RemoteData<CacheableObject>>): void {
remoteData.map((rd: RemoteData<CacheableObject>) => rd.payload) remoteData.pipe(map((rd: RemoteData<CacheableObject>) => rd.payload),
.filter((co: CacheableObject) => hasValue(co)) filter((co: CacheableObject) => hasValue(co)),
.take(1) take(1),)
.subscribe((dspaceObject: DSpaceObject) => { .subscribe((dspaceObject: DSpaceObject) => {
if (!this.initialized) { if (!this.initialized) {
this.initialize(dspaceObject); this.initialize(dspaceObject);
@@ -82,13 +84,13 @@ export class MetadataService {
this.clearMetaTags(); this.clearMetaTags();
} }
if (routeInfo.data.value.title) { 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.addMetaTag('title', translatedTitle);
this.title.setTitle(translatedTitle); this.title.setTitle(translatedTitle);
}); });
} }
if (routeInfo.data.value.description) { 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); this.addMetaTag('description', translatedDescription);
}); });
} }
@@ -96,7 +98,7 @@ export class MetadataService {
private initialize(dspaceObject: DSpaceObject): void { private initialize(dspaceObject: DSpaceObject): void {
this.currentObject = new BehaviorSubject<DSpaceObject>(dspaceObject); this.currentObject = new BehaviorSubject<DSpaceObject>(dspaceObject);
this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { this.currentObject.asObservable().pipe(distinctUntilKeyChanged('uuid')).subscribe(() => {
this.setMetaTags(); this.setMetaTags();
}); });
this.initialized = true; this.initialized = true;
@@ -268,11 +270,11 @@ export class MetadataService {
private setCitationPdfUrlTag(): void { private setCitationPdfUrlTag(): void {
if (this.currentObject.value instanceof Item) { if (this.currentObject.value instanceof Item) {
const item = this.currentObject.value as 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) { for (const bitstream of bitstreams) {
bitstream.format.first() bitstream.format.pipe(first(),
.map((rd: RemoteData<BitstreamFormat>) => rd.payload) map((rd: RemoteData<BitstreamFormat>) => rd.payload),
.filter((format: BitstreamFormat) => hasValue(format)) filter((format: BitstreamFormat) => hasValue(format)),)
.subscribe((format: BitstreamFormat) => { .subscribe((format: BitstreamFormat) => {
if (format.mimetype === 'application/pdf') { if (format.mimetype === 'application/pdf') {
this.addMetaTag('citation_pdf_url', bitstream.content); this.addMetaTag('citation_pdf_url', bitstream.content);

View File

@@ -1,5 +1,6 @@
import {combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { PageInfo } from '../shared/page-info.model'; import { PageInfo } from '../shared/page-info.model';
@@ -70,7 +71,7 @@ export class RegistryService {
map((response: RegistryMetadataschemasSuccessResponse) => response.pageInfo) 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); return new PaginatedList(pageInfo, metadataschemas);
}); });
@@ -132,7 +133,7 @@ export class RegistryService {
map((response: RegistryMetadatafieldsSuccessResponse) => response.pageInfo) 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); return new PaginatedList(pageInfo, metadatafields);
}); });
@@ -164,7 +165,7 @@ export class RegistryService {
map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo) 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); return new PaginatedList(pageInfo, bitstreamformats);
}); });

View File

@@ -1,5 +1,5 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import { distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators'; import {filter, distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface'; import { GlobalConfig } from '../../../config/global-config.interface';
@@ -30,11 +30,11 @@ export class HALEndpointService {
private getEndpointMapAt(href): Observable<EndpointMap> { private getEndpointMapAt(href): Observable<EndpointMap> {
const request = new EndpointMapRequest(this.requestService.generateRequestId(), href); const request = new EndpointMapRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request); this.requestService.configure(request);
return this.responseCache.get(request.href) return this.responseCache.get(request.href).pipe(
.map((entry: ResponseCacheEntry) => entry.response) map((entry: ResponseCacheEntry) => entry.response),
.filter((response: EndpointMapSuccessResponse) => isNotEmpty(response)) filter((response: EndpointMapSuccessResponse) => isNotEmpty(response)),
.map((response: EndpointMapSuccessResponse) => response.endpointMap) map((response: EndpointMapSuccessResponse) => response.endpointMap),
.distinctUntilChanged(); distinctUntilChanged(),);
} }
public getEndpoint(linkPath: string): Observable<string> { public getEndpoint(linkPath: string): Observable<string> {
@@ -61,7 +61,7 @@ export class HALEndpointService {
}), }),
]) ])
.reduce((combined, thisElement) => [...combined, ...thisElement], []); .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<boolean> { public isEnabledOnRestApi(linkPath: string): Observable<boolean> {

View File

@@ -1,3 +1,4 @@
import {map, startWith, filter} from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
@@ -58,9 +59,9 @@ export class Item extends DSpaceObject {
// TODO: currently this just picks the first thumbnail // TODO: currently this just picks the first thumbnail
// should be adjusted when we have a way to determine // should be adjusted when we have a way to determine
// the primary thumbnail from rest // the primary thumbnail from rest
return this.getBitstreamsByBundleName('THUMBNAIL') return this.getBitstreamsByBundleName('THUMBNAIL').pipe(
.filter((thumbnails) => isNotEmpty(thumbnails)) filter((thumbnails) => isNotEmpty(thumbnails)),
.map((thumbnails) => thumbnails[0]) map((thumbnails) => thumbnails[0]),)
} }
/** /**
@@ -68,10 +69,10 @@ export class Item extends DSpaceObject {
* @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle * @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
*/ */
getThumbnailForOriginal(original: Bitstream): Observable<Bitstream> { getThumbnailForOriginal(original: Bitstream): Observable<Bitstream> {
return this.getBitstreamsByBundleName('THUMBNAIL') return this.getBitstreamsByBundleName('THUMBNAIL').pipe(
.map((files) => { map((files) => {
return files.find((thumbnail) => thumbnail.name.startsWith(original.name)) return files.find((thumbnail) => thumbnail.name.startsWith(original.name))
}).startWith(undefined); }),startWith(undefined),);
} }
/** /**
@@ -88,15 +89,15 @@ export class Item extends DSpaceObject {
* @returns {Observable<Bitstream[]>} the bitstreams with the given bundleName * @returns {Observable<Bitstream[]>} the bitstreams with the given bundleName
*/ */
getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> { getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
return this.bitstreams return this.bitstreams.pipe(
.map((rd: RemoteData<PaginatedList<Bitstream>>) => rd.payload.page) map((rd: RemoteData<PaginatedList<Bitstream>>) => rd.payload.page),
.filter((bitstreams: Bitstream[]) => hasValue(bitstreams)) filter((bitstreams: Bitstream[]) => hasValue(bitstreams)),
.startWith([]) startWith([]),
.map((bitstreams) => { map((bitstreams) => {
return bitstreams return bitstreams
.filter((bitstream) => hasValue(bitstream)) .filter((bitstream) => hasValue(bitstream))
.filter((bitstream) => bitstream.bundleName === bundleName) .filter((bitstream) => bitstream.bundleName === bundleName)
}); }),);
} }
} }

View File

@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { createSelector, Store } from '@ngrx/store'; import { createSelector, select, Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { RouterReducerState } from '@ngrx/router-store'; import { RouterReducerState } from '@ngrx/router-store';
@@ -33,7 +33,7 @@ export class HeaderComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
// set loading // set loading
this.isNavBarCollapsed = this.store.select(navCollapsedSelector); this.isNavBarCollapsed = this.store.pipe(select(navCollapsedSelector));
} }
public toggle(): void { public toggle(): void {

View File

@@ -1,5 +1,6 @@
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; 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 * as fromRouter from '@ngrx/router-store';
import { HostWindowActionTypes } from '../shared/host-window.actions'; import { HostWindowActionTypes } from '../shared/host-window.actions';
@@ -9,12 +10,16 @@ import { HeaderCollapseAction } from './header.actions';
export class HeaderEffects { export class HeaderEffects {
@Effect() resize$ = this.actions$ @Effect() resize$ = this.actions$
.ofType(HostWindowActionTypes.RESIZE) .pipe(
.map(() => new HeaderCollapseAction()); ofType(HostWindowActionTypes.RESIZE),
map(() => new HeaderCollapseAction())
);
@Effect() routeChange$ = this.actions$ @Effect() routeChange$ = this.actions$
.ofType(fromRouter.ROUTER_NAVIGATION) .pipe(
.map(() => new HeaderCollapseAction()); ofType(fromRouter.ROUTER_NAVIGATION),
map(() => new HeaderCollapseAction())
);
constructor(private actions$: Actions) { constructor(private actions$: Actions) {

View File

@@ -1,13 +1,19 @@
import { of as observableOf, Observable } from 'rxjs';
import { map, filter } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { RouterReducerState } from '@ngrx/router-store'; import { RouterReducerState } from '@ngrx/router-store';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { fadeInOut, fadeOut } from '../animations/fade'; import { fadeInOut, fadeOut } from '../animations/fade';
import { HostWindowService } from '../host-window.service'; import { HostWindowService } from '../host-window.service';
import { AppState, routerStateSelector } from '../../app.reducer'; import { AppState, routerStateSelector } from '../../app.reducer';
import { isNotUndefined } from '../empty.util'; 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 { Eperson } from '../../core/eperson/models/eperson.model';
import { LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service'; import { LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service';
@@ -32,7 +38,7 @@ export class AuthNavMenuComponent implements OnInit {
public isXsOrSm$: Observable<boolean>; public isXsOrSm$: Observable<boolean>;
public showAuth = Observable.of(false); public showAuth = observableOf(false);
public user: Observable<Eperson>; public user: Observable<Eperson>;
@@ -43,17 +49,19 @@ export class AuthNavMenuComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
// set isAuthenticated // set isAuthenticated
this.isAuthenticated = this.store.select(isAuthenticated); this.isAuthenticated = this.store.pipe(select(isAuthenticated));
// set loading // 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) this.showAuth = this.store.pipe(
.filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)) select(routerStateSelector),
.map((router: RouterReducerState) => { filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)),
map((router: RouterReducerState) => {
return !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE); return !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE);
}); })
);
} }
} }

View File

@@ -1,24 +1,16 @@
import { import {
ChangeDetectorRef,
Component, Component,
ComponentFactoryResolver,
ContentChildren, ContentChildren,
EventEmitter, EventEmitter,
Input, Input,
OnChanges, OnChanges,
Output, Output,
QueryList, QueryList,
SimpleChanges SimpleChanges, Type
} from '@angular/core'; } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import {
DynamicDatePickerModel,
DynamicFormControlComponent,
DynamicFormControlEvent,
DynamicFormControlModel,
DynamicFormLayout,
DynamicFormLayoutService,
DynamicFormValidationService,
DynamicTemplateDirective,
DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DYNAMIC_FORM_CONTROL_TYPE_ARRAY,
DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX, DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX,
DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX_GROUP, DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX_GROUP,
@@ -29,6 +21,14 @@ import {
DYNAMIC_FORM_CONTROL_TYPE_SELECT, DYNAMIC_FORM_CONTROL_TYPE_SELECT,
DYNAMIC_FORM_CONTROL_TYPE_TEXTAREA, DYNAMIC_FORM_CONTROL_TYPE_TEXTAREA,
DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER, DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER,
DynamicDatePickerModel, DynamicFormControl,
DynamicFormControlContainerComponent,
DynamicFormControlEvent,
DynamicFormControlModel,
DynamicFormLayout,
DynamicFormLayoutService,
DynamicFormValidationService,
DynamicTemplateDirective,
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD } from './models/typeahead/dynamic-typeahead.model'; 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'; 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 { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP } from './models/lookup/dynamic-lookup.model';
import { DynamicListCheckboxGroupModel } from './models/list/dynamic-list-checkbox-group.model'; import { DynamicListCheckboxGroupModel } from './models/list/dynamic-list-checkbox-group.model';
import { DynamicListRadioGroupModel } from './models/list/dynamic-list-radio-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'; import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME } from './models/lookup/dynamic-lookup-name.model';
export const enum NGBootstrapFormControlType { export const enum NGBootstrapFormControlType {
@@ -69,11 +68,11 @@ export const enum NGBootstrapFormControlType {
styleUrls: ['../../form.component.scss', './ds-dynamic-form.component.scss'], styleUrls: ['../../form.component.scss', './ds-dynamic-form.component.scss'],
templateUrl: './ds-dynamic-form-control.component.html' 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<DynamicTemplateDirective>; @ContentChildren(DynamicTemplateDirective) contentTemplateList: QueryList<DynamicTemplateDirective>;
// tslint:disable-next-line:no-input-rename // tslint:disable-next-line:no-input-rename
@Input('templates') inputTemplateList: QueryList<DynamicTemplateDirective>; @Input('templates') inputTemplateList: QueryList<DynamicTemplateDirective>;
@Input() formId: string; @Input() formId: string;
@Input() asBootstrapFormGroup = true; @Input() asBootstrapFormGroup = true;
@@ -90,6 +89,7 @@ export class DsDynamicFormControlComponent extends DynamicFormControlComponent i
@Output('dfFocus') focus: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>(); @Output('dfFocus') focus: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
/* tslint:enable:no-output-rename */ /* tslint:enable:no-output-rename */
componentType: Type<DynamicFormControl> | null;
type: NGBootstrapFormControlType | null; type: NGBootstrapFormControlType | null;
static getFormControlType(model: DynamicFormControlModel): 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) { protected validationService: DynamicFormValidationService) {
super(changeDetectorRef, layoutService, validationService); super(componentFactoryResolver, layoutService, validationService);
} }
ngOnChanges(changes: SimpleChanges) { 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);
}
}
} }

View File

@@ -9,13 +9,13 @@ import {
} from '@angular/core'; } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import {
DynamicFormComponent, DynamicFormComponent, DynamicFormControlContainerComponent,
DynamicFormControlEvent, DynamicFormControlEvent,
DynamicFormControlModel, DynamicFormControlModel,
DynamicFormLayout, DynamicFormLayout,
DynamicFormLayoutService, DynamicFormLayoutService,
DynamicFormService, DynamicFormService,
DynamicTemplateDirective, DynamicTemplateDirective,
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { DsDynamicFormControlComponent } from './ds-dynamic-form-control.component'; import { DsDynamicFormControlComponent } from './ds-dynamic-form-control.component';
import { FormBuilderService } from '../form-builder.service'; import { FormBuilderService } from '../form-builder.service';
@@ -39,9 +39,10 @@ export class DsDynamicFormComponent extends DynamicFormComponent {
@ContentChildren(DynamicTemplateDirective) templates: QueryList<DynamicTemplateDirective>; @ContentChildren(DynamicTemplateDirective) templates: QueryList<DynamicTemplateDirective>;
@ViewChildren(DsDynamicFormControlComponent) components: QueryList<DsDynamicFormControlComponent>; @ViewChildren(DsDynamicFormControlComponent) components: QueryList<DynamicFormControlContainerComponent>;
constructor(protected formService: FormBuilderService, protected layoutService: DynamicFormLayoutService) {
super(formService, layoutService);
}
constructor(protected formService: FormBuilderService, protected layoutService: DynamicFormLayoutService) {
super(formService, layoutService);
}
} }

View File

@@ -1,3 +1,4 @@
import {of as observableOf, Observable , Subscription } from 'rxjs';
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
@@ -9,8 +10,6 @@ import {
Output, Output,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { Observable , Subscription } from 'rxjs';
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicInputModel } from '@ng-dynamic-forms/core'; import { DynamicFormControlModel, DynamicFormGroupModel, DynamicInputModel } from '@ng-dynamic-forms/core';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
@@ -48,7 +47,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
@Output() focus: EventEmitter<any> = new EventEmitter<any>(); @Output() focus: EventEmitter<any> = new EventEmitter<any>();
public chips: Chips; public chips: Chips;
public formCollapsed = Observable.of(false); public formCollapsed = observableOf(false);
public formModel: DynamicFormControlModel[]; public formModel: DynamicFormControlModel[];
public editMode = false; public editMode = false;
@@ -66,7 +65,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
ngOnInit() { ngOnInit() {
const config = {rows: this.model.formConfiguration} as SubmissionFormsModel; const config = {rows: this.model.formConfiguration} as SubmissionFormsModel;
if (!this.model.isEmpty()) { if (!this.model.isEmpty()) {
this.formCollapsed = Observable.of(true); this.formCollapsed = observableOf(true);
} }
this.model.valueUpdates.subscribe((value: any[]) => { this.model.valueUpdates.subscribe((value: any[]) => {
if ((isNotEmpty(value) && !(value.length === 1 && hasOnlyEmptyProperties(value[0])))) { if ((isNotEmpty(value) && !(value.length === 1 && hasOnlyEmptyProperties(value[0])))) {
@@ -151,12 +150,12 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
} }
collapseForm() { collapseForm() {
this.formCollapsed = Observable.of(true); this.formCollapsed = observableOf(true);
this.clear(); this.clear();
} }
expandForm() { expandForm() {
this.formCollapsed = Observable.of(false); this.formCollapsed = observableOf(false);
} }
clear() { clear() {
@@ -167,7 +166,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
} }
this.resetForm(); this.resetForm();
if (!this.model.isEmpty()) { if (!this.model.isEmpty()) {
this.formCollapsed = Observable.of(true); this.formCollapsed = observableOf(true);
} }
} }

View File

@@ -99,7 +99,7 @@ export class DsDynamicListComponent implements OnInit {
const value = option.id || option.value; const value = option.id || option.value;
const checked: boolean = isNotEmpty(findKey( const checked: boolean = isNotEmpty(findKey(
this.model.value, this.model.value,
{value: option.value})); (v) => v.value === option.value));
const item: ListItem = { const item: ListItem = {
id: value, id: value,

View File

@@ -1,3 +1,5 @@
import {distinctUntilChanged} from 'rxjs/operators';
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
@@ -137,8 +139,8 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
this.searchOptions.query = this.getCurrentValue(); this.searchOptions.query = this.getCurrentValue();
this.loading = true; this.loading = true;
this.authorityService.getEntriesByName(this.searchOptions) this.authorityService.getEntriesByName(this.searchOptions).pipe(
.distinctUntilChanged() distinctUntilChanged())
.subscribe((object: IntegrationData) => { .subscribe((object: IntegrationData) => {
this.optionsList = object.payload; this.optionsList = object.payload;
this.pageInfo = object.pageInfo; this.pageInfo = object.pageInfo;

View File

@@ -1,3 +1,5 @@
import {tap} from 'rxjs/operators';
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
@@ -66,8 +68,8 @@ export class DsDynamicScrollableDropdownComponent implements OnInit {
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) { if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
this.loading = true; this.loading = true;
this.searchOptions.currentPage++; this.searchOptions.currentPage++;
this.authorityService.getEntriesByName(this.searchOptions) this.authorityService.getEntriesByName(this.searchOptions).pipe(
.do(() => this.loading = false) tap(() => this.loading = false))
.subscribe((object: IntegrationData) => { .subscribe((object: IntegrationData) => {
this.optionsList = this.optionsList.concat(object.payload); this.optionsList = this.optionsList.concat(object.payload);
this.pageInfo = object.pageInfo; this.pageInfo = object.pageInfo;

View File

@@ -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 { ChangeDetectorRef, Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { AuthorityService } from '../../../../../../core/integration/authority.service';
@@ -40,33 +42,33 @@ export class DsDynamicTagComponent implements OnInit {
formatter = (x: { display: string }) => x.display; formatter = (x: { display: string }) => x.display;
search = (text$: Observable<string>) => search = (text$: Observable<string>) =>
text$ text$.pipe(
.debounceTime(300) debounceTime(300),
.distinctUntilChanged() distinctUntilChanged(),
.do(() => this.changeSearchingStatus(true)) tap(() => this.changeSearchingStatus(true)),
.switchMap((term) => { switchMap((term) => {
if (term === '' || term.length < this.model.minChars) { if (term === '' || term.length < this.model.minChars) {
return Observable.of({list: []}); return observableOf({list: []});
} else { } else {
this.searchOptions.query = term; this.searchOptions.query = term;
return this.authorityService.getEntriesByName(this.searchOptions) return this.authorityService.getEntriesByName(this.searchOptions).pipe(
.map((authorities) => { map((authorities) => {
// @TODO Pagination for authority is not working, to refactor when it will be fixed // @TODO Pagination for authority is not working, to refactor when it will be fixed
return { return {
list: authorities.payload, list: authorities.payload,
pageInfo: authorities.pageInfo pageInfo: authorities.pageInfo
}; };
}) }),
.do(() => this.searchFailed = false) tap(() => this.searchFailed = false),
.catch(() => { catchError(() => {
this.searchFailed = true; this.searchFailed = true;
return Observable.of({list: []}); return observableOf({list: []});
}); }),);
} }
}) }),
.map((results) => results.list) map((results) => results.list),
.do(() => this.changeSearchingStatus(false)) tap(() => this.changeSearchingStatus(false)),
.merge(this.hideSearchingWhenUnsubscribed); merge(this.hideSearchingWhenUnsubscribed),);
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
private authorityService: AuthorityService, private authorityService: AuthorityService,

View File

@@ -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 { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { AuthorityService } from '../../../../../../core/integration/authority.service';
@@ -37,33 +39,33 @@ export class DsDynamicTypeaheadComponent implements OnInit {
}; };
search = (text$: Observable<string>) => search = (text$: Observable<string>) =>
text$ text$.pipe(
.debounceTime(300) debounceTime(300),
.distinctUntilChanged() distinctUntilChanged(),
.do(() => this.changeSearchingStatus(true)) tap(() => this.changeSearchingStatus(true)),
.switchMap((term) => { switchMap((term) => {
if (term === '' || term.length < this.model.minChars) { if (term === '' || term.length < this.model.minChars) {
return Observable.of({list: []}); return observableOf({list: []});
} else { } else {
this.searchOptions.query = term; this.searchOptions.query = term;
return this.authorityService.getEntriesByName(this.searchOptions) return this.authorityService.getEntriesByName(this.searchOptions).pipe(
.map((authorities) => { map((authorities) => {
// @TODO Pagination for authority is not working, to refactor when it will be fixed // @TODO Pagination for authority is not working, to refactor when it will be fixed
return { return {
list: authorities.payload, list: authorities.payload,
pageInfo: authorities.pageInfo pageInfo: authorities.pageInfo
}; };
}) }),
.do(() => this.searchFailed = false) tap(() => this.searchFailed = false),
.catch(() => { catchError(() => {
this.searchFailed = true; this.searchFailed = true;
return Observable.of({list: []}); return observableOf({list: []});
}); }),);
} }
}) }),
.map((results) => results.list) map((results) => results.list),
.do(() => this.changeSearchingStatus(false)) tap(() => this.changeSearchingStatus(false)),
.merge(this.hideSearchingWhenUnsubscribed); merge(this.hideSearchingWhenUnsubscribed),);
constructor(private authorityService: AuthorityService, private cdr: ChangeDetectorRef) { constructor(private authorityService: AuthorityService, private cdr: ChangeDetectorRef) {
} }
@@ -74,8 +76,8 @@ export class DsDynamicTypeaheadComponent implements OnInit {
this.model.authorityOptions.scope, this.model.authorityOptions.scope,
this.model.authorityOptions.name, this.model.authorityOptions.name,
this.model.authorityOptions.metadata); this.model.authorityOptions.metadata);
this.group.get(this.model.id).valueChanges this.group.get(this.model.id).valueChanges.pipe(
.filter((value) => this.currentValue !== value) filter((value) => this.currentValue !== value))
.subscribe((value) => { .subscribe((value) => {
this.currentValue = value; this.currentValue = value;
}); });

View File

@@ -1,3 +1,5 @@
import {distinctUntilChanged, map, filter} from 'rxjs/operators';
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms'; import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
@@ -8,7 +10,7 @@ import {
DynamicFormGroupModel, DynamicFormGroupModel,
DynamicFormLayout, DynamicFormLayout,
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { findIndex } from 'lodash'; import { findIndex } from 'lodash';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
@@ -143,8 +145,8 @@ export class FormComponent implements OnDestroy, OnInit {
this.formValid = this.getFormGroupValidStatus(); this.formValid = this.getFormGroupValidStatus();
this.subs.push(this.formGroup.statusChanges this.subs.push(this.formGroup.statusChanges.pipe(
.filter((currentStatus) => this.formValid !== this.getFormGroupValidStatus()) filter((currentStatus) => this.formValid !== this.getFormGroupValidStatus()))
.subscribe((currentStatus) => { .subscribe((currentStatus) => {
// Dispatch a FormStatusChangeAction if the form status has changed // Dispatch a FormStatusChangeAction if the form status has changed
this.store.dispatch(new FormStatusChangeAction(this.formId, this.getFormGroupValidStatus())); this.store.dispatch(new FormStatusChangeAction(this.formId, this.getFormGroupValidStatus()));
@@ -152,10 +154,11 @@ export class FormComponent implements OnDestroy, OnInit {
})); }));
this.subs.push( this.subs.push(
this.store.select(formObjectFromIdSelector(this.formId)) this.store.pipe(
.filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))) select(formObjectFromIdSelector(this.formId)),
.map((formState) => formState.errors) filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))),
.distinctUntilChanged() map((formState) => formState.errors),
distinctUntilChanged(),)
// .delay(100) // this terrible delay is here to prevent the detection change error // .delay(100) // this terrible delay is here to prevent the detection change error
.subscribe((errors: FormError[]) => { .subscribe((errors: FormError[]) => {
const {formGroup, formModel} = this; const {formGroup, formModel} = this;

View File

@@ -1,7 +1,8 @@
import { map, distinctUntilChanged, filter } from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { formObjectFromIdSelector } from './selectors'; import { formObjectFromIdSelector } from './selectors';
@@ -25,39 +26,47 @@ export class FormService {
* Method to retrieve form's status from state * Method to retrieve form's status from state
*/ */
public isValid(formId: string): Observable<boolean> { public isValid(formId: string): Observable<boolean> {
return this.store.select(formObjectFromIdSelector(formId)) return this.store.pipe(
.filter((state) => isNotUndefined(state)) select(formObjectFromIdSelector(formId)),
.map((state) => state.valid) filter((state) => isNotUndefined(state)),
.distinctUntilChanged(); map((state) => state.valid),
distinctUntilChanged()
);
} }
/** /**
* Method to retrieve form's data from state * Method to retrieve form's data from state
*/ */
public getFormData(formId: string): Observable<any> { public getFormData(formId: string): Observable<any> {
return this.store.select(formObjectFromIdSelector(formId)) return this.store.pipe(
.filter((state) => isNotUndefined(state)) select(formObjectFromIdSelector(formId)),
.map((state) => state.data) filter((state) => isNotUndefined(state)),
.distinctUntilChanged(); map((state) => state.data),
distinctUntilChanged()
);
} }
/** /**
* Method to retrieve form's errors from state * Method to retrieve form's errors from state
*/ */
public getFormErrors(formId: string): Observable<any> { public getFormErrors(formId: string): Observable<any> {
return this.store.select(formObjectFromIdSelector(formId)) return this.store.pipe(
.filter((state) => isNotUndefined(state)) select(formObjectFromIdSelector(formId)),
.map((state) => state.errors) filter((state) => isNotUndefined(state)),
.distinctUntilChanged(); map((state) => state.errors),
distinctUntilChanged()
);
} }
/** /**
* Method to retrieve form's data from state * Method to retrieve form's data from state
*/ */
public isFormInitialized(formId: string): Observable<boolean> { public isFormInitialized(formId: string): Observable<boolean> {
return this.store.select(formObjectFromIdSelector(formId)) return this.store.pipe(
.distinctUntilChanged() select(formObjectFromIdSelector(formId)),
.map((state) => isNotUndefined(state)); distinctUntilChanged(),
map((state) => isNotUndefined(state))
);
} }
public getUniqueId(formId): string { public getUniqueId(formId): string {
@@ -71,7 +80,7 @@ export class FormService {
Object.keys(formGroup.controls).forEach((field) => { Object.keys(formGroup.controls).forEach((field) => {
const control = formGroup.get(field); const control = formGroup.get(field);
if (control instanceof FormControl) { if (control instanceof FormControl) {
control.markAsTouched({onlySelf: true}); control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) { } else if (control instanceof FormGroup) {
this.validateAllFormFields(control); this.validateAllFormFields(control);
} }

View File

@@ -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 { HostWindowState } from './host-window.reducer';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { createSelector, Store } from '@ngrx/store'; import { createSelector, select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { hasValue } from './empty.util'; import { hasValue } from './empty.util';
import { AppState } from '../app.reducer'; import { AppState } from '../app.reducer';
@@ -35,8 +36,10 @@ export class HostWindowService {
} }
private getWidthObs(): Observable<number> { private getWidthObs(): Observable<number> {
return this.store.select(widthSelector) return this.store.pipe(
.filter((width) => hasValue(width)); select(widthSelector),
filter((width) => hasValue(width))
);
} }
get widthCategory(): Observable<WidthCategory> { get widthCategory(): Observable<WidthCategory> {
@@ -94,10 +97,10 @@ export class HostWindowService {
} }
isXsOrSm(): Observable<boolean> { isXsOrSm(): Observable<boolean> {
return Observable.combineLatest( return observableCombineLatest(
this.isXs(), this.isXs(),
this.isSm(), this.isSm(),
((isXs, isSm) => isXs || isSm) ((isXs, isSm) => isXs || isSm)
).distinctUntilChanged(); ).pipe(distinctUntilChanged());
} }
} }

View File

@@ -1,12 +1,15 @@
import { filter, takeWhile, map } from 'rxjs/operators';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import {
import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../core/auth/auth.actions'; AuthenticateAction,
ResetAuthenticationMessagesAction
} from '../../core/auth/auth.actions';
import { import {
getAuthenticationError, getAuthenticationError,
@@ -99,7 +102,7 @@ export class LogInComponent implements OnDestroy, OnInit {
*/ */
public ngOnInit() { public ngOnInit() {
// set isAuthenticated // set isAuthenticated
this.isAuthenticated = this.store.select(isAuthenticated); this.isAuthenticated = this.store.pipe(select(isAuthenticated));
// set formGroup // set formGroup
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
@@ -108,29 +111,35 @@ export class LogInComponent implements OnDestroy, OnInit {
}); });
// set error // set error
this.error = this.store.select(getAuthenticationError) this.error = this.store.pipe(select(
.map((error) => { getAuthenticationError),
map((error) => {
this.hasError = (isNotEmpty(error)); this.hasError = (isNotEmpty(error));
return error; return error;
}); })
);
// set error // set error
this.message = this.store.select(getAuthenticationInfo) this.message = this.store.pipe(
.map((message) => { select(getAuthenticationInfo),
map((message) => {
this.hasMessage = (isNotEmpty(message)); this.hasMessage = (isNotEmpty(message));
return message; return message;
}); })
);
// set loading // set loading
this.loading = this.store.select(isAuthenticationLoading); this.loading = this.store.pipe(select(isAuthenticationLoading));
// subscribe to success // subscribe to success
this.store.select(isAuthenticated) this.store.pipe(
.takeWhile(() => this.alive) select(isAuthenticated),
.filter((authenticated) => authenticated) takeWhile(() => this.alive),
filter((authenticated) => authenticated),)
.subscribe(() => { .subscribe(() => {
this.authService.redirectToPreviousUrl(); this.authService.redirectToPreviousUrl();
}); }
);
} }
/** /**

View File

@@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
// @ngrx // @ngrx
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
// actions // actions
import { LogOutAction } from '../../core/auth/auth.actions'; import { LogOutAction } from '../../core/auth/auth.actions';
@@ -48,7 +48,8 @@ export class LogOutComponent implements OnDestroy, OnInit {
* @param {Store<State>} store * @param {Store<State>} store
*/ */
constructor(private router: Router, constructor(private router: Router,
private store: Store<AppState>) { } private store: Store<AppState>) {
}
/** /**
* Lifecycle hook that is called when a directive, pipe or service is destroyed. * Lifecycle hook that is called when a directive, pipe or service is destroyed.
@@ -62,10 +63,10 @@ export class LogOutComponent implements OnDestroy, OnInit {
*/ */
ngOnInit() { ngOnInit() {
// set error // set error
this.error = this.store.select(getLogOutError); this.error = this.store.pipe(select(getLogOutError));
// set loading // set loading
this.loading = this.store.select(isAuthenticationLoading); this.loading = this.store.pipe(select(isAuthenticationLoading));
} }
/** /**

View File

@@ -1,4 +1,4 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
// declare a stub service // declare a stub service
export class MockHostWindowService { export class MockHostWindowService {
@@ -14,10 +14,10 @@ export class MockHostWindowService {
} }
isXs(): Observable<boolean> { isXs(): Observable<boolean> {
return Observable.of(this.width < 576); return observableOf(this.width < 576);
} }
isSm(): Observable<boolean> { isSm(): Observable<boolean> {
return Observable.of(this.width < 768); return observableOf(this.width < 768);
} }
} }

View File

@@ -1,4 +1,4 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
@@ -9,7 +9,7 @@ export const MockItem: Item = Object.assign(new Item(), {
isArchived: true, isArchived: true,
isDiscoverable: true, isDiscoverable: true,
isWithdrawn: false, isWithdrawn: false,
bitstreams: Observable.of({ bitstreams: observableOf({
self: 'dspace-angular://aggregated/object/1507836003548', self: 'dspace-angular://aggregated/object/1507836003548',
requestPending: false, requestPending: false,
responsePending: false, responsePending: false,
@@ -28,7 +28,7 @@ export const MockItem: Item = Object.assign(new Item(), {
{ {
sizeBytes: 10201, sizeBytes: 10201,
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', 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', self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10',
requestPending: false, requestPending: false,
responsePending: false, responsePending: false,
@@ -63,7 +63,7 @@ export const MockItem: Item = Object.assign(new Item(), {
{ {
sizeBytes: 31302, sizeBytes: 31302,
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content', 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', self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4',
requestPending: false, requestPending: false,
responsePending: false, responsePending: false,
@@ -195,7 +195,7 @@ export const MockItem: Item = Object.assign(new Item(), {
value: 'text' value: 'text'
} }
], ],
owningCollection: Observable.of({ owningCollection: observableOf({
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb', self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb',
requestPending: false, requestPending: false,
responsePending: false, responsePending: false,

View File

@@ -1,8 +1,8 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import { RequestService } from '../../core/data/request.service'; import { RequestService } from '../../core/data/request.service';
import { RequestEntry } from '../../core/data/request.reducer'; import { RequestEntry } from '../../core/data/request.reducer';
export function getMockRequestService(getByHref$: Observable<RequestEntry> = Observable.of(new RequestEntry())): RequestService { export function getMockRequestService(getByHref$: Observable<RequestEntry> = observableOf(new RequestEntry())): RequestService {
return jasmine.createSpyObj('requestService', { return jasmine.createSpyObj('requestService', {
configure: false, configure: false,
generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78',

View File

@@ -1,10 +1,10 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
import { ResponseCacheService } from '../../core/cache/response-cache.service'; import { ResponseCacheService } from '../../core/cache/response-cache.service';
export function getMockResponseCacheService( export function getMockResponseCacheService(
add$: Observable<ResponseCacheEntry> = Observable.of(new ResponseCacheEntry()), add$: Observable<ResponseCacheEntry> = observableOf(new ResponseCacheEntry()),
get$: Observable<ResponseCacheEntry> = Observable.of(new ResponseCacheEntry()), get$: Observable<ResponseCacheEntry> = observableOf(new ResponseCacheEntry()),
has: boolean = false has: boolean = false
): ResponseCacheService { ): ResponseCacheService {
return jasmine.createSpyObj('ResponseCacheService', { return jasmine.createSpyObj('ResponseCacheService', {

View File

@@ -1,8 +1,8 @@
import {of as observableOf, Observable } from 'rxjs';
import { TranslateLoader } from '@ngx-translate/core'; import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs';
export class MockTranslateLoader implements TranslateLoader { export class MockTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> { getTranslation(lang: string): Observable<any> {
return Observable.of({}); return observableOf({});
} }
} }

View File

@@ -1,3 +1,5 @@
import {of as observableOf, Observable } from 'rxjs';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
@@ -21,7 +23,6 @@ import { fromLeftEnter, fromLeftInState, fromLeftLeave, fromLeftOutState } from
import { fromTopEnter, fromTopInState, fromTopLeave, fromTopOutState } from '../../animations/fromTop'; import { fromTopEnter, fromTopInState, fromTopLeave, fromTopOutState } from '../../animations/fromTop';
import { fadeInEnter, fadeInState, fadeOutLeave, fadeOutState } from '../../animations/fade'; import { fadeInEnter, fadeInState, fadeOutLeave, fadeOutState } from '../../animations/fade';
import { NotificationAnimationsStatus } from '../models/notification-animations-type'; import { NotificationAnimationsStatus } from '../models/notification-animations-type';
import { Observable } from 'rxjs';
import { isNotEmpty } from '../../empty.util'; import { isNotEmpty } from '../../empty.util';
@Component({ @Component({
@@ -130,14 +131,14 @@ export class NotificationComponent implements OnInit, OnDestroy {
let value = null; let value = null;
if (isNotEmpty(item)) { if (isNotEmpty(item)) {
if (typeof item === 'string') { if (typeof item === 'string') {
value = Observable.of(item); value = observableOf(item);
} else if (item instanceof Observable) { } else if (item instanceof Observable) {
value = item; value = item;
} else if (typeof item === 'object' && isNotEmpty(item.value)) { } else if (typeof item === 'object' && isNotEmpty(item.value)) {
// when notifications state is transferred from SSR to CSR, // when notifications state is transferred from SSR to CSR,
// Observables Object loses the instance type and become simply object, // Observables Object loses the instance type and become simply object,
// so converts it again to Observable // so converts it again to Observable
value = Observable.of(item.value); value = observableOf(item.value);
} }
} }
this[key] = value this[key] = value

View File

@@ -8,7 +8,7 @@ import {
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { difference } from 'lodash'; import { difference } from 'lodash';
@@ -50,7 +50,7 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.sub = this.store.select(notificationsStateSelector) this.sub = this.store.pipe(select(notificationsStateSelector))
.subscribe((state: NotificationsState) => { .subscribe((state: NotificationsState) => {
if (state.length === 0) { if (state.length === 0) {
this.notifications = []; this.notifications = [];

View File

@@ -12,14 +12,14 @@ export class NotificationsEffects {
*/ */
/* @Effect() /* @Effect()
public timer: Observable<Action> = this.actions$ public timer: Observable<Action> = this.actions$
.ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER) .pipe(ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER),
// .debounceTime((action: any) => action.payload.options.timeOut) // .debounceTime((action: any) => action.payload.options.timeOut)
.debounceTime(3000) debounceTime(3000),
.map(() => new RemoveNotificationAction()); map(() => new RemoveNotificationAction());
.switchMap((action: NewNotificationWithTimerAction) => Observable .switchMap((action: NewNotificationWithTimerAction) => Observable
.timer(30000) .timer(30000)
.mapTo(() => new RemoveNotificationAction()) .mapTo(() => new RemoveNotificationAction())
);*/ ));*/
/** /**
* @constructor * @constructor

View File

@@ -1,3 +1,5 @@
import {of as observableOf, Observable } from 'rxjs';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { INotification, Notification } from './models/notification.model'; import { INotification, Notification } from './models/notification.model';
import { NotificationType } from './models/notification-type'; import { NotificationType } from './models/notification-type';
@@ -5,7 +7,6 @@ import { NotificationOptions } from './models/notification-options.model';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { NewNotificationAction, RemoveAllNotificationsAction, RemoveNotificationAction } from './notifications.actions'; import { NewNotificationAction, RemoveAllNotificationsAction, RemoveNotificationAction } from './notifications.actions';
import { Observable } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
@Injectable() @Injectable()
@@ -21,8 +22,8 @@ export class NotificationsService {
this.store.dispatch(notificationAction); this.store.dispatch(notificationAction);
} }
success(title: any = Observable.of(''), success(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html: boolean = false): INotification { html: boolean = false): INotification {
const notification = new Notification(uniqueId(), NotificationType.Success, title, content, options, html); const notification = new Notification(uniqueId(), NotificationType.Success, title, content, options, html);
@@ -30,8 +31,8 @@ export class NotificationsService {
return notification; return notification;
} }
error(title: any = Observable.of(''), error(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html: boolean = false): INotification { html: boolean = false): INotification {
const notification = new Notification(uniqueId(), NotificationType.Error, title, content, options, html); const notification = new Notification(uniqueId(), NotificationType.Error, title, content, options, html);
@@ -39,8 +40,8 @@ export class NotificationsService {
return notification; return notification;
} }
info(title: any = Observable.of(''), info(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html: boolean = false): INotification { html: boolean = false): INotification {
const notification = new Notification(uniqueId(), NotificationType.Info, title, content, options, html); const notification = new Notification(uniqueId(), NotificationType.Info, title, content, options, html);
@@ -48,8 +49,8 @@ export class NotificationsService {
return notification; return notification;
} }
warning(title: any = Observable.of(''), warning(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html: boolean = false): INotification { html: boolean = false): INotification {
const notification = new Notification(uniqueId(), NotificationType.Warning, title, content, options, html); const notification = new Notification(uniqueId(), NotificationType.Warning, title, content, options, html);

View File

@@ -1,3 +1,5 @@
import {map} from 'rxjs/operators';
import { Component, EventEmitter, import { Component, EventEmitter,
Input, Input,
OnInit, OnInit,
@@ -88,11 +90,11 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
} }
getViewMode(): ViewMode { getViewMode(): ViewMode {
this.route.queryParams.map((params) => { this.route.queryParams.pipe(map((params) => {
if (isNotEmpty(params.view) && hasValue(params.view)) { if (isNotEmpty(params.view) && hasValue(params.view)) {
this.currentMode = params.view; this.currentMode = params.view;
} }
}); }));
return this.currentMode; return this.currentMode;
} }

View File

@@ -1,3 +1,7 @@
import {combineLatest as observableCombineLatest, BehaviorSubject , Observable } from 'rxjs';
import {startWith, distinctUntilChanged, map } from 'rxjs/operators';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@@ -6,8 +10,6 @@ import {
Output, Output,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } 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 { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list';
@@ -105,9 +107,9 @@ export class ObjectGridComponent implements OnInit {
} }
}), }),
distinctUntilChanged() distinctUntilChanged()
).startWith(3); ).pipe(startWith(3));
this.columns$ = Observable.combineLatest( this.columns$ = observableCombineLatest(
nbColumns$, nbColumns$,
this._objects$, this._objects$,
(nbColumns, objects) => { (nbColumns, objects) => {

View File

@@ -11,15 +11,14 @@ import {
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription , Observable } from 'rxjs'; import { Subscription, Observable } from 'rxjs';
import { isNumeric } from 'rxjs/util';
import { HostWindowService } from '../host-window.service'; import { HostWindowService } from '../host-window.service';
import { HostWindowState } from '../host-window.reducer'; import { HostWindowState } from '../host-window.reducer';
import { PaginationComponentOptions } from './pagination-component-options.model'; import { PaginationComponentOptions } from './pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { hasValue, isNotEmpty } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
import { isNumeric } from 'tslint';
/** /**
* The default pagination controls component. * The default pagination controls component.

View File

@@ -1,4 +1,6 @@
import { throwError as observableThrowError } from 'rxjs'; import { throwError as observableThrowError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
@@ -12,11 +14,11 @@ export class ApiService {
* whatever domain/feature method name * whatever domain/feature method name
*/ */
get(url: string, options?: any) { get(url: string, options?: any) {
return this._http.get(url, options) return this._http.get(url, options).pipe(
.catch((err) => { catchError((err) => {
console.log('Error: ', err); console.log('Error: ', err);
return observableThrowError(err); return observableThrowError(err);
}); }));
} }
} }

View File

@@ -1,3 +1,5 @@
import {distinctUntilChanged, map} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { import {
@@ -13,24 +15,24 @@ export class RouteService {
} }
getQueryParameterValues(paramName: string): Observable<string[]> { getQueryParameterValues(paramName: string): Observable<string[]> {
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<string> { getQueryParameterValue(paramName: string): Observable<string> {
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<boolean> { hasQueryParam(paramName: string): Observable<boolean> {
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<boolean> { hasQueryParamWithValue(paramName: string, paramValue: string): Observable<boolean> {
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<Params> { getQueryParamsWithPrefix(prefix: string): Observable<Params> {
return this.route.queryParamMap return this.route.queryParamMap.pipe(
.map((map) => { map((map) => {
const params = {}; const params = {};
map.keys map.keys
.filter((key) => key.startsWith(prefix)) .filter((key) => key.startsWith(prefix))
@@ -38,7 +40,7 @@ export class RouteService {
params[key] = [...map.getAll(key)]; params[key] = [...map.getAll(key)];
}); });
return params; return params;
}) }),
.distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)); distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),);
} }
} }

View File

@@ -4,7 +4,12 @@ import { RouterModule } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NouisliderModule } from 'ng2-nouislider'; 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'; 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 { DynamicFormsCoreModule } from '@ng-dynamic-forms/core';
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { TextMaskModule } from 'angular2-text-mask'; 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 { DragClickDirective } from './utils/drag-click.directive';
import { TruncatePipe } from './utils/truncate.pipe'; import { TruncatePipe } from './utils/truncate.pipe';
import { TruncatableComponent } from './truncatable/truncatable.component'; import { TruncatableComponent } from './truncatable/truncatable.component';
@@ -78,8 +81,8 @@ import { ClickOutsideDirective } from './utils/click-outside.directive';
import { EmphasizePipe } from './utils/emphasize.pipe'; import { EmphasizePipe } from './utils/emphasize.pipe';
import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component'; import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component';
import { CapitalizePipe } from './utils/capitalize.pipe'; import { CapitalizePipe } from './utils/capitalize.pipe';
import { MomentModule } from 'angular2-moment';
import { ObjectKeysPipe } from './utils/object-keys-pipe'; import { ObjectKeysPipe } from './utils/object-keys-pipe';
import { MomentModule } from 'ngx-moment';
const MODULES = [ const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here // Do NOT include UniversalModule, HttpModule, or JsonpModule here

View File

@@ -1,3 +1,5 @@
import {map} from 'rxjs/operators';
import { convertToParamMap, ParamMap, Params } from '@angular/router'; import { convertToParamMap, ParamMap, Params } from '@angular/router';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
@@ -10,7 +12,7 @@ export class ActivatedRouteStub {
params = this.subject.asObservable(); params = this.subject.asObservable();
queryParams = 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) { constructor(params?: Params) {
if (params) { if (params) {

View File

@@ -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 { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
@@ -31,7 +31,7 @@ export class AuthRequestServiceStub {
authStatusStub.authenticated = false; authStatusStub.authenticated = false;
} }
} }
return Observable.of(authStatusStub); return observableOf(authStatusStub);
} }
public getRequest(method: string, options?: HttpOptions): Observable<any> { public getRequest(method: string, options?: HttpOptions): Observable<any> {
@@ -51,7 +51,7 @@ export class AuthRequestServiceStub {
} }
break; break;
} }
return Observable.of(authStatusStub); return observableOf(authStatusStub);
} }
private validateToken(token): boolean { private validateToken(token): boolean {

View File

@@ -1,5 +1,6 @@
import {of as observableOf, Observable } from 'rxjs';
import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { Observable } from 'rxjs';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { EpersonMock } from './eperson-mock'; import { EpersonMock } from './eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model'; import { Eperson } from '../../core/eperson/models/eperson.model';
@@ -20,7 +21,7 @@ export class AuthServiceStub {
authStatus.authenticated = true; authStatus.authenticated = true;
authStatus.token = this.token; authStatus.token = this.token;
authStatus.eperson = [EpersonMock]; authStatus.eperson = [EpersonMock];
return Observable.of(authStatus); return observableOf(authStatus);
} else { } else {
console.log('error'); console.log('error');
throw(new Error('Message Error test')); throw(new Error('Message Error test'));
@@ -29,7 +30,7 @@ export class AuthServiceStub {
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> { public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
if (token.accessToken === 'token_test') { if (token.accessToken === 'token_test') {
return Observable.of(EpersonMock); return observableOf(EpersonMock);
} else { } else {
throw(new Error('Message Error test')); throw(new Error('Message Error test'));
} }
@@ -44,11 +45,11 @@ export class AuthServiceStub {
} }
public hasValidAuthenticationToken(): Observable<AuthTokenInfo> { public hasValidAuthenticationToken(): Observable<AuthTokenInfo> {
return Observable.of(this.token); return observableOf(this.token);
} }
public logout(): Observable<boolean> { public logout(): Observable<boolean> {
return Observable.of(true); return observableOf(true);
} }
public isTokenExpired(token?: AuthTokenInfo): boolean { public isTokenExpired(token?: AuthTokenInfo): boolean {
@@ -70,11 +71,11 @@ export class AuthServiceStub {
} }
public isTokenExpiring(): Observable<boolean> { public isTokenExpiring(): Observable<boolean> {
return Observable.of(false); return observableOf(false);
} }
public refreshAuthenticationToken(token: AuthTokenInfo): Observable<AuthTokenInfo> { public refreshAuthenticationToken(token: AuthTokenInfo): Observable<AuthTokenInfo> {
return Observable.of(this.token); return observableOf(this.token);
} }
public redirectToPreviousUrl() { public redirectToPreviousUrl() {

View File

@@ -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 { IntegrationSearchOptions } from '../../core/integration/models/integration-options.model';
import { IntegrationData } from '../../core/integration/integration-data'; import { IntegrationData } from '../../core/integration/integration-data';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
@@ -16,6 +16,6 @@ export class AuthorityServiceStub {
} }
getEntriesByName(options: IntegrationSearchOptions) { getEntriesByName(options: IntegrationSearchOptions) {
return Observable.of(new IntegrationData(new PageInfo(), this._payload)); return observableOf(new IntegrationData(new PageInfo(), this._payload));
} }
} }

View File

@@ -1,9 +1,9 @@
import { Observable } from 'rxjs'; import { of as observableOf } from 'rxjs';
export class HALEndpointServiceStub { export class HALEndpointServiceStub {
constructor(private url: string) {}; constructor(private url: string) {};
getEndpoint(path: string) { getEndpoint(path: string) {
return Observable.of(this.url + '/' + path); return observableOf(this.url + '/' + path);
} }
} }

View File

@@ -1,4 +1,4 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
// declare a stub service // declare a stub service
export class HostWindowServiceStub { export class HostWindowServiceStub {
@@ -14,7 +14,7 @@ export class HostWindowServiceStub {
} }
isXs(): Observable<boolean> { isXs(): Observable<boolean> {
return Observable.of(this.width < 576); return observableOf(this.width < 576);
} }
isXsOrSm(): Observable<boolean> { isXsOrSm(): Observable<boolean> {

View File

@@ -1,3 +1,5 @@
import {map} from 'rxjs/operators';
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { Observable , BehaviorSubject } from 'rxjs'; import { Observable , BehaviorSubject } from 'rxjs';
@@ -12,8 +14,8 @@ export class MockStore<T> extends BehaviorSubject<T> {
}; };
select = <R>(pathOrMapFn: any): Observable<T> => { select = <R>(pathOrMapFn: any): Observable<T> => {
return this.asObservable() return this.asObservable().pipe(
.map((value) => pathOrMapFn.projector(value)) map((value) => pathOrMapFn.projector(value)))
}; };
nextState(_newState: T) { nextState(_newState: T) {

View File

@@ -1,8 +1,8 @@
import {of as observableOf, Observable } from 'rxjs';
import { TranslateLoader } from '@ngx-translate/core'; import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs';
export class MockTranslateLoader implements TranslateLoader { export class MockTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> { getTranslation(lang: string): Observable<any> {
return Observable.of({}); return observableOf({});
} }
} }

View File

@@ -1,32 +1,32 @@
import { Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import { INotification } from '../notifications/models/notification.model'; import { INotification } from '../notifications/models/notification.model';
import { NotificationOptions } from '../notifications/models/notification-options.model'; import { NotificationOptions } from '../notifications/models/notification-options.model';
export class NotificationsServiceStub { export class NotificationsServiceStub {
success(title: any = Observable.of(''), success(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html?: any): INotification { html?: any): INotification {
return return
} }
error(title: any = Observable.of(''), error(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html?: any): INotification { html?: any): INotification {
return return
} }
info(title: any = Observable.of(''), info(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html?: any): INotification { html?: any): INotification {
return return
} }
warning(title: any = Observable.of(''), warning(title: any = observableOf(''),
content: any = Observable.of(''), content: any = observableOf(''),
options: NotificationOptions = this.getDefaultOptions(), options: NotificationOptions = this.getDefaultOptions(),
html?: any): INotification { html?: any): INotification {
return return

View File

@@ -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'; import { ViewMode } from '../../core/shared/view-mode.model';
export class SearchServiceStub { export class SearchServiceStub {
@@ -38,6 +38,6 @@ export class SearchServiceStub {
} }
getFilterLabels() { getFilterLabels() {
return Observable.of([]); return observableOf([]);
} }
} }

View File

@@ -1,8 +1,13 @@
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; 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 { Observable } from 'rxjs';
import { TruncatablesState, TruncatableState } from './truncatable.reducer'; 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'; import { hasValue } from '../empty.util';
const truncatableStateSelector = (state: TruncatablesState) => state.truncatable; const truncatableStateSelector = (state: TruncatablesState) => state.truncatable;
@@ -22,14 +27,16 @@ export class TruncatableService {
* @returns {Observable<boolean>} Emits true if the state in the store is currently collapsed for the given truncatable component * @returns {Observable<boolean>} Emits true if the state in the store is currently collapsed for the given truncatable component
*/ */
isCollapsed(id: string): Observable<boolean> { isCollapsed(id: string): Observable<boolean> {
return this.store.select(truncatableByIdSelector(id)) return this.store.pipe(
.map((object: TruncatableState) => { select(truncatableByIdSelector(id)),
map((object: TruncatableState) => {
if (object) { if (object) {
return object.collapsed; return object.collapsed;
} else { } else {
return false; return false;
} }
}); })
);
} }
/** /**

View File

@@ -1,3 +1,5 @@
import {of as observableOf, Observable } from 'rxjs';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
@@ -10,7 +12,6 @@ import {
} from '@angular/core' } from '@angular/core'
import { FileUploader } from 'ng2-file-upload'; import { FileUploader } from 'ng2-file-upload';
import { Observable } from 'rxjs';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { ScrollToConfigOptions, ScrollToService } from '@nicky-lenaers/ngx-scroll-to'; import { ScrollToConfigOptions, ScrollToService } from '@nicky-lenaers/ngx-scroll-to';
@@ -60,8 +61,8 @@ export class UploaderComponent {
public uploader: FileUploader; public uploader: FileUploader;
public uploaderId: string; public uploaderId: string;
public isOverBaseDropZone = Observable.of(false); public isOverBaseDropZone = observableOf(false);
public isOverDocumentDropZone = Observable.of(false); public isOverDocumentDropZone = observableOf(false);
@HostListener('window:dragover', ['$event']) @HostListener('window:dragover', ['$event'])
onDragOver(event: any) { onDragOver(event: any) {
@@ -70,7 +71,7 @@ export class UploaderComponent {
// Show drop area on the page // Show drop area on the page
event.preventDefault(); event.preventDefault();
if ((event.target as any).tagName !== 'HTML') { 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.uploader.onBeforeUploadItem = () => {
this.onBeforeUpload(); this.onBeforeUpload();
this.isOverDocumentDropZone = Observable.of(false); this.isOverDocumentDropZone = observableOf(false);
// Move page target to the uploader // Move page target to the uploader
const config: ScrollToConfigOptions = { const config: ScrollToConfigOptions = {
@@ -133,7 +134,7 @@ export class UploaderComponent {
* Called when files are dragged on the base drop area. * Called when files are dragged on the base drop area.
*/ */
public fileOverBase(isOver: boolean): void { 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) { public fileOverDocument(isOver: boolean) {
if (!isOver) { if (!isOver) {
this.isOverDocumentDropZone = Observable.of(isOver); this.isOverDocumentDropZone = observableOf(isOver);
} }
} }

View File

@@ -1,3 +1,5 @@
import {distinctUntilChanged, debounceTime, takeUntil} from 'rxjs/operators';
import { Directive, Input, Output, EventEmitter, OnDestroy, OnInit } from '@angular/core'; import { Directive, Input, Output, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import { NgControl } from '@angular/forms'; 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 * Emit it when the debounceTime is over without new changes
*/ */
ngOnInit() { ngOnInit() {
this.model.valueChanges this.model.valueChanges.pipe(
.takeUntil(this.subject) takeUntil(this.subject),
.debounceTime(this.dsDebounce) debounceTime(this.dsDebounce),
.distinctUntilChanged() distinctUntilChanged(),)
.subscribe((modelValue) => { .subscribe((modelValue) => {
if (this.isFirstChange) { if (this.isFirstChange) {
this.isFirstChange = false; this.isFirstChange = false;

View File

@@ -1,27 +1,33 @@
import { of as observableOf } from 'rxjs';
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store'; import { Action, Store } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { Observable } from 'rxjs';
import { AppState } from './app.reducer'; import { AppState } from './app.reducer';
import { StoreAction, StoreActionTypes } from './store.actions'; import { StoreActionTypes } from './store.actions';
import { HostWindowResizeAction, HostWindowActionTypes } from './shared/host-window.actions'; import { HostWindowResizeAction } from './shared/host-window.actions';
@Injectable() @Injectable()
export class StoreEffects { export class StoreEffects {
@Effect({ dispatch: false }) replay = this.actions.ofType(StoreActionTypes.REPLAY).map((replayAction: Action) => { @Effect({ dispatch: false }) replay = this.actions.pipe(
// TODO: should be able to replay all actions before the browser attempts to ofType(StoreActionTypes.REPLAY),
// replayAction.payload.forEach((action: Action) => { map((replayAction: Action) => {
// this.store.dispatch(action); // TODO: should be able to replay all actions before the browser attempts to
// }); // replayAction.payload.forEach((action: Action) => {
return Observable.of({}); // 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<AppState>) { constructor(private actions: Actions, private store: Store<AppState>) {

View File

@@ -1,3 +1,5 @@
import {take} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { DSpaceTransferState } from './dspace-transfer-state.service'; import { DSpaceTransferState } from './dspace-transfer-state.service';
@@ -6,7 +8,7 @@ export class DSpaceServerTransferState extends DSpaceTransferState {
transfer() { transfer() {
this.transferState.onSerialize(DSpaceTransferState.NGRX_STATE, () => { this.transferState.onSerialize(DSpaceTransferState.NGRX_STATE, () => {
let state; let state;
this.store.take(1).subscribe((saveState: any) => { this.store.pipe(take(1)).subscribe((saveState: any) => {
state = saveState; state = saveState;
}); });

181
yarn.lock
View File

@@ -267,16 +267,16 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a"
"@types/node@*": "@types/node@*":
version "10.9.1" version "10.9.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.1.tgz#06f002136fbcf51e730995149050bb3c45ee54e6" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.2.tgz#f0ab8dced5cd6c56b26765e1c0d9e4fdcc9f2a00"
"@types/node@^6.0.46": "@types/node@^6.0.46":
version "6.0.116" version "6.0.116"
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.116.tgz#2f9cd62b4ecc4927e3942e2655c182eecf5b45f1" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.116.tgz#2f9cd62b4ecc4927e3942e2655c182eecf5b45f1"
"@types/node@^9.4.6": "@types/node@^9.4.6":
version "9.6.29" version "9.6.30"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.29.tgz#9b3c5b288c77fbd2ab5684d36e3528cb9ee5429f" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.30.tgz#1ecf83eaf7ac2d0dada7a9d61a1e4e7a6183ac06"
"@types/q@^0.0.32": "@types/q@^0.0.32":
version "0.0.32" version "0.0.32"
@@ -318,6 +318,10 @@
version "0.0.30" version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" 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": "@types/uuid@^3.4.3":
version "3.4.3" version "3.4.3"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" 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" version "2.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" 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: array-map@~0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" 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" version "0.3.5"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" 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" version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" 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: dependencies:
is-arrayish "^0.2.1" 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" version "1.12.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
dependencies: dependencies:
@@ -3479,6 +3476,12 @@ find-up@^2.0.0, find-up@^2.1.0:
dependencies: dependencies:
locate-path "^2.0.0" 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: flatten@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
@@ -3679,8 +3682,8 @@ gaze@^1.0.0:
globule "^1.0.0" globule "^1.0.0"
generate-function@^2.0.0: generate-function@^2.0.0:
version "2.0.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.2.0.tgz#1aeac896147293d27bce65eb295ce5f3f094a292"
generate-object-property@^1.1.0: generate-object-property@^1.1.0:
version "1.2.0" version "1.2.0"
@@ -3763,19 +3766,9 @@ glob@^5.0.15:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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: 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" version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
dependencies: dependencies:
fs.realpath "^1.0.0" fs.realpath "^1.0.0"
inflight "^1.0.4" inflight "^1.0.4"
@@ -4135,16 +4128,16 @@ html-minifier@^3.2.3:
relateurl "0.2.x" relateurl "0.2.x"
uglify-js "3.4.x" uglify-js "3.4.x"
html-webpack-plugin@3.2.0: html-webpack-plugin@^4.0.0-alpha:
version "3.2.0" version "4.0.0-alpha"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-alpha.tgz#b2c7b6d4885a209c999dfce3ffb9866e2c8c0eaa"
dependencies: dependencies:
"@types/tapable" "1.0.2"
html-minifier "^3.2.3" html-minifier "^3.2.3"
loader-utils "^0.2.16" loader-utils "^1.1.0"
lodash "^4.17.3" lodash "^4.17.10"
pretty-error "^2.0.2" pretty-error "^2.0.2"
tapable "^1.0.0" tapable "^1.0.0"
toposort "^1.0.0"
util.promisify "1.0.0" util.promisify "1.0.0"
htmlescape@^1.1.0: htmlescape@^1.1.0:
@@ -5298,6 +5291,13 @@ locate-path@^2.0.0:
p-locate "^2.0.0" p-locate "^2.0.0"
path-exists "^3.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: lodash._baseassign@^3.0.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" 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" version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" 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" version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" 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" bn.js "^4.0.0"
brorand "^1.0.1" 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" version "1.36.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" 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: 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" version "2.1.20"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
dependencies: dependencies:
mime-db "~1.35.0" mime-db "~1.36.0"
mime@1.4.1: mime@1.4.1:
version "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" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
nan@^2.10.0, nan@^2.9.2: nan@^2.10.0, nan@^2.9.2:
version "2.10.0" version "2.11.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099"
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
@@ -6032,6 +6028,12 @@ ngx-infinite-scroll@6.0.1:
dependencies: dependencies:
opencollective "^1.0.3" 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: ngx-pagination@3.0.3:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-3.0.3.tgz#314145263613738d8c544da36cd8dacc5aa89a6f" 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: dependencies:
p-try "^1.0.0" 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: p-locate@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
dependencies: dependencies:
p-limit "^1.1.0" 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: p-map@^1.1.1:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" 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" version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" 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: pac-proxy-agent@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz#90d9f6730ab0f4d2607dcdcd4d3d641aa26c3896" 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" version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
resolve-url-loader@2.2.1: resolve-url-loader@^2.3.0:
version "2.2.1" version "2.3.0"
resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.2.1.tgz#13a1396fb773edf959550e400e688f5ed32548bf" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.3.0.tgz#e1b37034d48f22f8cfb9f04c026faaa070fdaf26"
dependencies: dependencies:
adjust-sourcemap-loader "^1.1.0" adjust-sourcemap-loader "^1.1.0"
camelcase "^4.0.0" camelcase "^4.1.0"
convert-source-map "^1.1.1" convert-source-map "^1.5.1"
loader-utils "^1.0.0" loader-utils "^1.1.0"
lodash.defaults "^4.0.0" lodash.defaults "^4.0.0"
rework "^1.0.1" rework "^1.0.1"
rework-visit "^1.0.0" rework-visit "^1.0.0"
source-map "^0.5.6" source-map "^0.5.7"
urix "^0.1.0" urix "^0.1.0"
resolve-url@^0.2.1: 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" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
stdout-stream@^1.4.0: stdout-stream@^1.4.0:
version "1.4.0" version "1.4.1"
resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de"
dependencies: dependencies:
readable-stream "^2.0.1" readable-stream "^2.0.1"
@@ -9286,10 +9304,6 @@ to-string-loader@1.1.5:
dependencies: dependencies:
loader-utils "^0.2.16" 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: touch@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 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" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
"true-case-path@^1.0.2": "true-case-path@^1.0.2":
version "1.0.2" version "1.0.3"
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
dependencies: dependencies:
glob "^6.0.4" glob "^7.1.2"
tryer@^1.0.0: tryer@^1.0.0:
version "1.0.1" version "1.0.1"
@@ -9477,14 +9491,10 @@ typescript@2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" 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" version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" 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: uglify-es@^3.3.4, uglify-es@^3.3.7:
version "3.3.9" version "3.3.9"
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" 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" webpack-log "^1.1.2"
wordwrap "^1.0.0" 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: webpack-dev-middleware@3.2.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz#a20ceef194873710052da678f3c6ee0aeed92552" 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-log "^1.0.1"
webpack-dev-server@^3.1.5: webpack-dev-server@^3.1.5:
version "3.1.5" version "3.1.6"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.5.tgz#87477252e1ac6789303fb8cd3e585fa5d508a401" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.6.tgz#8617503768b1131fd539cf43c3e2e63bd34c1521"
dependencies: dependencies:
ansi-html "0.0.7" ansi-html "0.0.7"
array-includes "^3.0.3"
bonjour "^3.5.0" bonjour "^3.5.0"
chokidar "^2.0.0" chokidar "^2.0.0"
compression "^1.5.2" compression "^1.5.2"
@@ -9986,9 +9983,9 @@ webpack-dev-server@^3.1.5:
spdy "^3.4.1" spdy "^3.4.1"
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
supports-color "^5.1.0" supports-color "^5.1.0"
webpack-dev-middleware "3.1.3" webpack-dev-middleware "3.2.0"
webpack-log "^1.1.2" webpack-log "^2.0.0"
yargs "11.0.0" yargs "12.0.1"
webpack-log@^1.0.1, webpack-log@^1.1.2: webpack-log@^1.0.1, webpack-log@^1.1.2:
version "1.2.0" version "1.2.0"
@@ -10197,7 +10194,7 @@ y18n@^3.2.1:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" 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" version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 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" version "3.0.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" 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" version "10.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"
dependencies: dependencies:
@@ -10227,13 +10224,13 @@ yargs-parser@^9.0.2:
dependencies: dependencies:
camelcase "^4.1.0" camelcase "^4.1.0"
yargs@11.0.0: yargs@12.0.1:
version "11.0.0" version "12.0.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.1.tgz#6432e56123bb4e7c3562115401e98374060261c2"
dependencies: dependencies:
cliui "^4.0.0" cliui "^4.0.0"
decamelize "^1.1.1" decamelize "^2.0.0"
find-up "^2.1.0" find-up "^3.0.0"
get-caller-file "^1.0.1" get-caller-file "^1.0.1"
os-locale "^2.0.0" os-locale "^2.0.0"
require-directory "^2.1.1" require-directory "^2.1.1"
@@ -10241,8 +10238,8 @@ yargs@11.0.0:
set-blocking "^2.0.0" set-blocking "^2.0.0"
string-width "^2.0.0" string-width "^2.0.0"
which-module "^2.0.0" which-module "^2.0.0"
y18n "^3.2.1" y18n "^3.2.1 || ^4.0.0"
yargs-parser "^9.0.2" yargs-parser "^10.1.0"
yargs@^11.0.0: yargs@^11.0.0:
version "11.1.0" version "11.1.0"