Skip to content

Commit 0633526

Browse files
committed
fix(core): Improve cleanup behavior more
1 parent 3ab996d commit 0633526

5 files changed

Lines changed: 52 additions & 16 deletions

File tree

projects/ngqp/core/src/lib/directives/query-param-group.service.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Inject, Injectable, isDevMode, OnDestroy, Optional } from '@angular/cor
22
import { Params } from '@angular/router';
33
import { EMPTY, from, Observable, Subject } from 'rxjs';
44
import { catchError, concatMap, debounceTime, map, takeUntil, tap } from 'rxjs/operators';
5-
import { isMissing } from '../util';
5+
import { isMissing, NOP } from '../util';
66
import { Unpack } from '../types';
77
import { QueryParamGroup } from '../model/query-param-group';
88
import { QueryParam } from '../model/query-param';
@@ -24,6 +24,12 @@ function hasArraySerialization(queryParam: QueryParam<any>, values: string | str
2424
return isMultiQueryParam(queryParam);
2525
}
2626

27+
/** @internal */
28+
class QueryParamDirectiveState {
29+
constructor(public directive: QueryParamNameDirective, public queue$: Subject<any>) {
30+
}
31+
}
32+
2733
/**
2834
* Service implementing the synchronization logic
2935
*
@@ -38,8 +44,8 @@ export class QueryParamGroupService implements OnDestroy {
3844
/** The {@link QueryParamGroup} to bind. */
3945
private queryParamGroup: QueryParamGroup;
4046

41-
/** List of {@link QueryParamNameDirective} directives registered to this service. */
42-
private directives: QueryParamNameDirective[] = [];
47+
/** List of {@link QueryParamNameDirective} directives registered to this service (by name). */
48+
private directives = new Map<string, QueryParamDirectiveState>();
4349

4450
/**
4551
* Queue of navigation parameters
@@ -63,6 +69,10 @@ export class QueryParamGroupService implements OnDestroy {
6369
public ngOnDestroy() {
6470
this.destroy$.next();
6571
this.destroy$.complete();
72+
73+
if (this.queryParamGroup) {
74+
this.queryParamGroup._clearChangeFunctions();
75+
}
6676
}
6777

6878
/**
@@ -82,14 +92,17 @@ export class QueryParamGroupService implements OnDestroy {
8292
/**
8393
* Registers a {@link QueryParamNameDirective} directive.
8494
*/
85-
public addQueryParam(directive: QueryParamNameDirective): void {
95+
public registerQueryParamDirective(directive: QueryParamNameDirective): void {
8696
const queryParam: QueryParam<any> = this.queryParamGroup.get(directive.name);
8797
if (!queryParam) {
8898
throw new Error(`Could not find query param with name ${directive.name}. Did you forget to add it to your QueryParamGroup?`);
8999
}
90100
if (!directive.valueAccessor) {
91101
throw new Error(`No value accessor found for the form control. Please make sure to implement ControlValueAccessor on this component.`);
92102
}
103+
if (this.directives.has(directive.name)) {
104+
throw new Error(`A directive with name ${directive.name} has already been registered.`);
105+
}
93106

94107
// Chances are that we read the initial route before a directive has been registered here.
95108
// The value in the model will be correct, but we need to sync it to the view once initially.
@@ -105,19 +118,26 @@ export class QueryParamGroupService implements OnDestroy {
105118

106119
directive.valueAccessor.registerOnChange((newValue: any) => debouncedQueue$.next(newValue));
107120

108-
this.directives.push(directive);
121+
this.directives.set(directive.name, new QueryParamDirectiveState(directive, debouncedQueue$));
109122
}
110123

111124
/**
112125
* Deregisters a {@link QueryParamNameDirective} directive.
113126
*/
114-
public removeQueryParam(directive: QueryParamNameDirective): void {
115-
const index = this.directives.indexOf(directive);
116-
if (index === -1) {
117-
return;
127+
public deregisterQueryParamDirective(directive: QueryParamNameDirective): void {
128+
const state = this.directives.get(directive.name);
129+
if (state && state.queue$) {
130+
state.queue$.complete();
118131
}
119132

120-
this.directives.splice(index, 1);
133+
this.directives.delete(directive.name);
134+
directive.valueAccessor.registerOnChange(NOP);
135+
directive.valueAccessor.registerOnTouched(NOP);
136+
137+
const queryParam: QueryParam<any> = this.queryParamGroup.get(directive.name);
138+
if (queryParam) {
139+
queryParam._clearChangeFunctions();
140+
}
121141
}
122142

123143
private startSynchronization() {
@@ -155,17 +175,19 @@ export class QueryParamGroupService implements OnDestroy {
155175

156176
/** Listens for changes in the router and synchronizes to the model. */
157177
private setupRouterListener() {
158-
this.routerAdapter.queryParamMap.subscribe(queryParamMap => {
178+
this.routerAdapter.queryParamMap.pipe(
179+
takeUntil(this.destroy$)
180+
).subscribe(queryParamMap => {
159181
Object.keys(this.queryParamGroup.queryParams).forEach(queryParamName => {
160182
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
161183
const newValue = queryParam.multi
162184
? this.deserialize(queryParam, queryParamMap.getAll(queryParam.param))
163185
: this.deserialize(queryParam, queryParamMap.get(queryParam.param));
164186

165187
// Get the directive, if it has been initialized yet.
166-
const directive = this.directives.find(dir => dir.name === queryParamName);
167-
if (!isMissing(directive)) {
168-
directive.valueAccessor.writeValue(newValue);
188+
const state = this.directives.get(queryParamName);
189+
if (state && state.directive) {
190+
state.directive.valueAccessor.writeValue(newValue);
169191
}
170192

171193
queryParam.value = newValue;

projects/ngqp/core/src/lib/directives/query-param-name.directive.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Directive, Inject, Input, OnChanges, OnDestroy, Optional, Self, SimpleChanges } from '@angular/core';
22
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3+
import { Subject } from 'rxjs';
34
import { DefaultControlValueAccessorDirective, NGQP_BUILT_IN_ACCESSORS } from '../accessors/accessors';
45
import { QueryParamGroupService } from './query-param-group.service';
56

@@ -49,14 +50,14 @@ export class QueryParamNameDirective implements OnChanges, OnDestroy {
4950
throw new Error(`queryParamName has been added, but without specifying the name.`);
5051
}
5152

52-
this.groupService.addQueryParam(this);
53+
this.groupService.registerQueryParamDirective(this);
5354
}
5455
}
5556

5657
/** @ignore */
5758
public ngOnDestroy() {
5859
if (this.groupService) {
59-
this.groupService.removeQueryParam(this);
60+
this.groupService.deregisterQueryParamDirective(this);
6061
}
6162
}
6263

projects/ngqp/core/src/lib/model/query-param-group.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ export class QueryParamGroup {
4949
this.changeFunctions.push(fn);
5050
}
5151

52+
/** @internal */
53+
public _clearChangeFunctions(): void {
54+
this.changeFunctions = [];
55+
}
56+
5257
/**
5358
* Retrieves a specific {@link QueryParam} from this group by name.
5459
*

projects/ngqp/core/src/lib/model/query-param.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ export class QueryParam<T> {
9797
this.changeFunctions.push(fn);
9898
}
9999

100+
/** @internal */
101+
public _clearChangeFunctions(): void {
102+
this.changeFunctions = [];
103+
}
104+
100105
/**
101106
* Updates the value of this parameter.
102107
*

projects/ngqp/core/src/lib/util.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { Comparator } from './types';
44
// tslint:disable-next-line:triple-equals
55
export const LOOSE_IDENTITY_COMPARATOR = <T>(a: T, b: T) => a == b;
66

7+
/** @internal */
8+
export const NOP: Function = () => {};
9+
710
/** @internal */
811
export function isMissing(obj: any): obj is null | undefined {
912
return obj === undefined || obj === null;

0 commit comments

Comments
 (0)