From a8a47ae7ad2d913f966849251d45cd0aafc5070a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 31 Mar 2019 16:43:51 +0200 Subject: [PATCH 1/6] fix(core): Improve typings involving null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ingo Bürk --- projects/ngqp/core/src/lib/accessors/util.ts | 4 ++-- .../core/src/lib/directives/query-param-name.directive.ts | 2 +- .../ngqp/core/src/lib/directives/query-param.directive.ts | 2 +- .../src/lib/router-adapter/router-adapter.interface.ts | 2 +- projects/ngqp/core/src/lib/types.ts | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/projects/ngqp/core/src/lib/accessors/util.ts b/projects/ngqp/core/src/lib/accessors/util.ts index 22c69d0..197cde7 100644 --- a/projects/ngqp/core/src/lib/accessors/util.ts +++ b/projects/ngqp/core/src/lib/accessors/util.ts @@ -10,9 +10,9 @@ import { NGQP_BUILT_IN_ACCESSORS } from './ngqp-accessors'; * * @internal */ -export function selectValueAccessor(valueAccessors: ControlValueAccessor[]): ControlValueAccessor | null { +export function selectValueAccessor(valueAccessors: ControlValueAccessor[]): ControlValueAccessor { if (!valueAccessors || !Array.isArray(valueAccessors)) { - return null; + throw new Error(`No matching ControlValueAccessor has been found for this form control`); } let defaultAccessor: ControlValueAccessor | null = null; diff --git a/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts b/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts index c06766b..0bdbd8d 100644 --- a/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts +++ b/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts @@ -24,7 +24,7 @@ export class QueryParamNameDirective implements QueryParamAccessor, OnChanges, O public name: string; /** @internal */ - public valueAccessor: ControlValueAccessor | null = null; + public valueAccessor: ControlValueAccessor; /** @internal */ constructor( diff --git a/projects/ngqp/core/src/lib/directives/query-param.directive.ts b/projects/ngqp/core/src/lib/directives/query-param.directive.ts index 1e0111b..cd4965f 100644 --- a/projects/ngqp/core/src/lib/directives/query-param.directive.ts +++ b/projects/ngqp/core/src/lib/directives/query-param.directive.ts @@ -29,7 +29,7 @@ export class QueryParamDirective implements QueryParamAccessor, OnChanges, OnDes public readonly name = 'param'; /** @internal */ - public valueAccessor: ControlValueAccessor | null = null; + public valueAccessor: ControlValueAccessor; /** @internal */ private group = new QueryParamGroup({}); diff --git a/projects/ngqp/core/src/lib/router-adapter/router-adapter.interface.ts b/projects/ngqp/core/src/lib/router-adapter/router-adapter.interface.ts index 337c24b..3677c34 100644 --- a/projects/ngqp/core/src/lib/router-adapter/router-adapter.interface.ts +++ b/projects/ngqp/core/src/lib/router-adapter/router-adapter.interface.ts @@ -23,7 +23,7 @@ export interface RouterAdapter { navigate(queryParams: Params, extras?: RouterOptions & { state?: any }): Promise; /** @internal */ - getCurrentNavigation(): Pick; + getCurrentNavigation(): Pick | null; } diff --git a/projects/ngqp/core/src/lib/types.ts b/projects/ngqp/core/src/lib/types.ts index c0efba7..1a168b9 100644 --- a/projects/ngqp/core/src/lib/types.ts +++ b/projects/ngqp/core/src/lib/types.ts @@ -4,13 +4,13 @@ import { Params } from '@angular/router'; * A serializer defines how the represented form control's * value is converted into a string to be used in the URL. */ -export type ParamSerializer = (model: T | undefined | null) => string | null; +export type ParamSerializer = (model: T | null) => string | null; /** * A deserializer defines how a URL parameter is converted * into the represented form control's value. */ -export type ParamDeserializer = (value: string | null) => T | undefined | null; +export type ParamDeserializer = (value: string | null) => T | null; /** * A partitioner can split a value into an array of parts. @@ -28,7 +28,7 @@ export type Reducer = (values: T) => R; * * See {@link QueryParamOpts#combineWith}. */ -export type ParamCombinator = (newValue: T | undefined | null) => Params | null; +export type ParamCombinator = (newValue: T | null) => Params | null; /** @internal */ export type OnChangeFunction = (value: T | null) => void; @@ -42,4 +42,4 @@ export type OnChangeFunction = (value: T | null) => void; * @param b Second value to compare. * @returns `true` or `0` if and only if `a` and `b` should be considered equal. */ -export type Comparator = (a: T | undefined | null, b: T | undefined | null) => boolean | number; \ No newline at end of file +export type Comparator = (a: T | null, b: T | null) => boolean | number; \ No newline at end of file From 69ec667bf8520241aa715597f3494728810dc8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 31 Mar 2019 16:48:59 +0200 Subject: [PATCH 2/6] fix(core): Enforce strictNullChecks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ingo Bürk --- ...select-control-value-accessor.directive.ts | 5 ++- .../multi-select-option.directive.ts | 4 +- ...number-control-value-accessor.directive.ts | 2 +- .../range-control-value-accessor.directive.ts | 2 +- ...select-control-value-accessor.directive.ts | 9 ++-- .../lib/accessors/select-option.directive.ts | 4 +- .../directives/query-param-group.service.ts | 8 +++- .../ngqp/core/src/lib/model/query-param.ts | 44 +++++++++---------- projects/ngqp/core/tsconfig.lib.json | 1 + tslint.json | 2 +- 10 files changed, 44 insertions(+), 37 deletions(-) diff --git a/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts b/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts index 44c1f75..fb2794d 100644 --- a/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts @@ -29,7 +29,7 @@ export class MultiSelectControlValueAccessorDirective implements ControlValue this.selectedIds = Array.from(this.options.entries()) .filter(([id, option]) => option.selected) .map(([id]) => id); - const values = this.selectedIds.map(id => this.optionMap.get(id)); + const values = this.selectedIds.map(id => this.optionMap.get(id)!); this.fnChange(values); } @@ -49,7 +49,8 @@ export class MultiSelectControlValueAccessorDirective implements ControlValue this.selectedIds = values .map(value => this.getOptionId(value)) - .filter(id => id !== null); + .filter(id => id !== null) + .map(id => id as string); this.options.forEach((option, id) => option.selected = this.selectedIds.includes(id)); } diff --git a/projects/ngqp/core/src/lib/accessors/multi-select-option.directive.ts b/projects/ngqp/core/src/lib/accessors/multi-select-option.directive.ts index 7ac7430..c330157 100644 --- a/projects/ngqp/core/src/lib/accessors/multi-select-option.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/multi-select-option.directive.ts @@ -27,14 +27,14 @@ export class MultiSelectOptionDirective implements OnInit, OnDestroy { public ngOnDestroy() { if (this.parent) { - this.parent.deregisterOption(this.id); + this.parent.deregisterOption(this.id!); } } @Input('value') public set value(value: T) { if (this.parent) { - this.parent.updateOptionValue(this.id, value); + this.parent.updateOptionValue(this.id!, value); } } diff --git a/projects/ngqp/core/src/lib/accessors/number-control-value-accessor.directive.ts b/projects/ngqp/core/src/lib/accessors/number-control-value-accessor.directive.ts index 7140040..ed42db3 100644 --- a/projects/ngqp/core/src/lib/accessors/number-control-value-accessor.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/number-control-value-accessor.directive.ts @@ -15,7 +15,7 @@ export const NGQP_NUMBER_VALUE_ACCESSOR: any = { }) export class NumberControlValueAccessorDirective implements ControlValueAccessor { - private fnChange = (_: number) => {}; + private fnChange = (_: number | null) => {}; private fnTouched = () => {}; @HostListener('input', ['$event']) diff --git a/projects/ngqp/core/src/lib/accessors/range-control-value-accessor.directive.ts b/projects/ngqp/core/src/lib/accessors/range-control-value-accessor.directive.ts index a96a9b3..020cb8d 100644 --- a/projects/ngqp/core/src/lib/accessors/range-control-value-accessor.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/range-control-value-accessor.directive.ts @@ -15,7 +15,7 @@ export const NGQP_RANGE_VALUE_ACCESSOR: any = { }) export class RangeControlValueAccessorDirective implements ControlValueAccessor { - private fnChange = (_: number) => {}; + private fnChange = (_: number | null) => {}; private fnTouched = () => {}; @HostListener('input', ['$event']) diff --git a/projects/ngqp/core/src/lib/accessors/select-control-value-accessor.directive.ts b/projects/ngqp/core/src/lib/accessors/select-control-value-accessor.directive.ts index 65bc5b8..6c0121e 100644 --- a/projects/ngqp/core/src/lib/accessors/select-control-value-accessor.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/select-control-value-accessor.directive.ts @@ -1,5 +1,6 @@ import { Directive, ElementRef, forwardRef, HostListener, Renderer2, StaticProvider } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { undefinedToNull } from '../util'; /** @ignore */ export const NGQP_SELECT_VALUE_ACCESSOR: StaticProvider = { @@ -20,13 +21,13 @@ export class SelectControlValueAccessorDirective implements ControlValueAcces private optionMap = new Map(); private idCounter = 0; - private fnChange = (_: T) => {}; + private fnChange = (_: T | null) => {}; private fnTouched = () => {}; @HostListener('change', ['$event']) public onChange(event: UIEvent) { this.selectedId = (event.target as HTMLOptionElement).value; - this.value = this.optionMap.get(this.selectedId); + this.value = undefinedToNull(this.optionMap.get(this.selectedId)); this.fnChange(this.value); } @@ -38,10 +39,10 @@ export class SelectControlValueAccessorDirective implements ControlValueAcces constructor(private renderer: Renderer2, private elementRef: ElementRef) { } - public writeValue(value: T) { + public writeValue(value: T | null) { this.value = value; - this.selectedId = this.getOptionId(value); + this.selectedId = value === null ? null : this.getOptionId(value); if (this.selectedId === null) { this.renderer.setProperty(this.elementRef.nativeElement, 'selectedIndex', -1); } diff --git a/projects/ngqp/core/src/lib/accessors/select-option.directive.ts b/projects/ngqp/core/src/lib/accessors/select-option.directive.ts index 0f1fbf2..b416261 100644 --- a/projects/ngqp/core/src/lib/accessors/select-option.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/select-option.directive.ts @@ -27,7 +27,7 @@ export class SelectOptionDirective implements OnInit, OnDestroy { public ngOnDestroy() { if (this.parent) { - this.parent.deregisterOption(this.id); + this.parent.deregisterOption(this.id!); this.parent.writeValue(this.parent.value); } } @@ -35,7 +35,7 @@ export class SelectOptionDirective implements OnInit, OnDestroy { @Input('value') public set value(value: T) { if (this.parent) { - this.parent.updateOptionValue(this.id, value); + this.parent.updateOptionValue(this.id!, value); this.parent.writeValue(this.parent.value); } } diff --git a/projects/ngqp/core/src/lib/directives/query-param-group.service.ts b/projects/ngqp/core/src/lib/directives/query-param-group.service.ts index 80540d3..bf725ad 100644 --- a/projects/ngqp/core/src/lib/directives/query-param-group.service.ts +++ b/projects/ngqp/core/src/lib/directives/query-param-group.service.ts @@ -170,7 +170,11 @@ export class QueryParamGroupService implements OnDestroy { /** Listens for programmatic changes on group level and synchronizes to the router. */ private setupGroupChangeListener(): void { - this.queryParamGroup._registerOnChange((newValue: Record) => { + this.queryParamGroup._registerOnChange((newValue: Record | null) => { + if (newValue === null) { + throw new Error(`Received null value from QueryParamGroup.`); + } + let params: Params = {}; Object.keys(newValue).forEach(queryParamName => { const queryParam = this.queryParamGroup.get(queryParamName); @@ -229,7 +233,7 @@ export class QueryParamGroupService implements OnDestroy { Object.keys(this.queryParamGroup.queryParams).forEach(queryParamName => { const partitionedQueryParam = this.getQueryParamAsPartition(queryParamName); - const newValues = partitionedQueryParam.queryParams.map(queryParam => isMultiQueryParam(queryParam) + const newValues = partitionedQueryParam.queryParams.map(queryParam => isMultiQueryParam(queryParam) ? queryParam.deserializeValue(queryParamMap.getAll(queryParam.urlParam)) : queryParam.deserializeValue(queryParamMap.get(queryParam.urlParam)) ); diff --git a/projects/ngqp/core/src/lib/model/query-param.ts b/projects/ngqp/core/src/lib/model/query-param.ts index a32111c..1bffffd 100644 --- a/projects/ngqp/core/src/lib/model/query-param.ts +++ b/projects/ngqp/core/src/lib/model/query-param.ts @@ -1,5 +1,5 @@ import { Observable, Subject } from 'rxjs'; -import { areEqualUsing, isFunction, isMissing, isPresent, wrapTryCatch } from '../util'; +import { areEqualUsing, isFunction, isMissing, isPresent, undefinedToNull, wrapTryCatch } from '../util'; import { Comparator, OnChangeFunction, ParamCombinator, ParamDeserializer, ParamSerializer, Partitioner, Reducer } from '../types'; import { QueryParamGroup } from './query-param-group'; import { MultiQueryParamOpts, PartitionedQueryParamOpts, QueryParamOpts, QueryParamOptsBase } from './query-param-opts'; @@ -7,10 +7,10 @@ import { MultiQueryParamOpts, PartitionedQueryParamOpts, QueryParamOpts, QueryPa /** @internal */ abstract class AbstractQueryParamBase { - public abstract value: T; + public abstract value: T | null; protected parent: QueryParamGroup | null = null; - protected _valueChanges = new Subject(); + protected readonly _valueChanges = new Subject(); protected changeFunctions: OnChangeFunction[] = []; /** @@ -18,7 +18,7 @@ abstract class AbstractQueryParamBase { * * NOTE: This observable does not complete on its own, so ensure to unsubscribe from it. */ - public readonly valueChanges: Observable = this._valueChanges.asObservable(); + public readonly valueChanges: Observable = this._valueChanges.asObservable(); public _registerOnChange(fn: OnChangeFunction): void { this.changeFunctions.push(fn); @@ -34,7 +34,7 @@ abstract class AbstractQueryParamBase { emitModelToViewChange?: boolean, }): void; - public _setParent(parent: QueryParamGroup): void { + public _setParent(parent: QueryParamGroup | null): void { if (this.parent && parent) { throw new Error(`Parameter already belongs to a QueryParamGroup.`); } @@ -55,7 +55,7 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase /** * The current value of this parameter. */ - public value: T = null; + public value: T | null = null; /** * The name of the parameter to be used in the URL. @@ -76,13 +76,13 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase public readonly debounceTime: number | null; /** See {@link QueryParamOpts}. */ - public readonly emptyOn: T; + public readonly emptyOn?: T; /** See {@link QueryParamOpts}. */ - public readonly compareWith: Comparator; + public readonly compareWith?: Comparator; /** See {@link QueryParamOpts}. */ - public readonly combineWith: ParamCombinator; + public readonly combineWith?: ParamCombinator; constructor(urlParam: string, opts: QueryParamOptsBase = {}) { super(); @@ -111,17 +111,17 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase this.urlParam = urlParam; this.serialize = wrapTryCatch(serialize, `Error while serializing value for ${this.urlParam}`); this.deserialize = wrapTryCatch(deserialize, `Error while deserializing value for ${this.urlParam}`); - this.debounceTime = debounceTime; + this.debounceTime = undefinedToNull(debounceTime); this.emptyOn = emptyOn; this.compareWith = compareWith; this.combineWith = combineWith; } /** @internal */ - public abstract serializeValue(value: T): string | string[]; + public abstract serializeValue(value: T | null): string | string[] | null; /** @internal */ - public abstract deserializeValue(value: string | string[]): T; + public abstract deserializeValue(value: string | string[] | null): T | null; /** * Updates the value of this parameter. @@ -161,7 +161,7 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase * as the glue between its representation in the URL and its connection * to a form control. */ -export class QueryParam extends AbstractQueryParam implements Required>> { +export class QueryParam extends AbstractQueryParam implements Readonly> { /** See {@link QueryParamOpts}. */ public readonly multi = false; @@ -171,8 +171,8 @@ export class QueryParam extends AbstractQueryParam implements Required< } /** @internal */ - public serializeValue(value: T): string { - if (this.emptyOn !== undefined && areEqualUsing(value, this.emptyOn, this.compareWith)) { + public serializeValue(value: T | null): string | null { + if (this.emptyOn !== undefined && areEqualUsing(value, this.emptyOn, this.compareWith!)) { return null; } @@ -180,7 +180,7 @@ export class QueryParam extends AbstractQueryParam implements Required< } /** @internal */ - public deserializeValue(value: string): T { + public deserializeValue(value: string | null): T | null { if (this.emptyOn !== undefined && value === null) { return this.emptyOn; } @@ -193,7 +193,7 @@ export class QueryParam extends AbstractQueryParam implements Required< /** * Like {@link QueryParam}, but for array-typed parameters */ -export class MultiQueryParam extends AbstractQueryParam implements Required>> { +export class MultiQueryParam extends AbstractQueryParam implements Readonly> { /** See {@link QueryParamOpts}. */ public readonly multi = true; @@ -203,8 +203,8 @@ export class MultiQueryParam extends AbstractQueryParam implements Re } /** @internal */ - public serializeValue(value: T[]): string[] { - if (this.emptyOn !== undefined && areEqualUsing(value, this.emptyOn, this.compareWith)) { + public serializeValue(value: T[] | null): string[] | null { + if (this.emptyOn !== undefined && areEqualUsing(value, this.emptyOn, this.compareWith!)) { return null; } @@ -212,12 +212,12 @@ export class MultiQueryParam extends AbstractQueryParam implements Re } /** @internal */ - public deserializeValue(value: string[]): T[] { - if (this.emptyOn !== undefined && value.length === 0) { + public deserializeValue(value: string[] | null): T[] | null { + if (this.emptyOn !== undefined && (value || []).length === 0) { return this.emptyOn; } - return value.map(this.deserialize.bind(this)); + return (value || []).map(this.deserialize.bind(this)); } } diff --git a/projects/ngqp/core/tsconfig.lib.json b/projects/ngqp/core/tsconfig.lib.json index 9eb087c..f6be547 100644 --- a/projects/ngqp/core/tsconfig.lib.json +++ b/projects/ngqp/core/tsconfig.lib.json @@ -15,6 +15,7 @@ "noImplicitThis": true, "noImplicitReturns": true, "strictFunctionTypes": true, + "strictNullChecks": true, "types": [], "lib": [ "dom", diff --git a/tslint.json b/tslint.json index 4819581..8aa6c76 100644 --- a/tslint.json +++ b/tslint.json @@ -61,7 +61,7 @@ "ignore-params" ], "no-misused-new": true, - "no-non-null-assertion": true, + "no-non-null-assertion": false, "no-redundant-jsdoc": true, "no-shadowed-variable": true, "no-string-literal": false, From f5c72627016d975c267a6402ef3901d8c2fe978a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 31 Mar 2019 16:57:33 +0200 Subject: [PATCH 3/6] fix(core): Enable strictPropertyInitialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ingo Bürk --- .../directives/query-param-group.directive.ts | 2 +- .../directives/query-param-group.service.ts | 34 ++++++++++++------- .../directives/query-param-name.directive.ts | 14 +++++++- .../lib/directives/query-param.directive.ts | 2 +- projects/ngqp/core/tsconfig.lib.json | 1 + 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/projects/ngqp/core/src/lib/directives/query-param-group.directive.ts b/projects/ngqp/core/src/lib/directives/query-param-group.directive.ts index 23b678f..e526129 100644 --- a/projects/ngqp/core/src/lib/directives/query-param-group.directive.ts +++ b/projects/ngqp/core/src/lib/directives/query-param-group.directive.ts @@ -20,7 +20,7 @@ export class QueryParamGroupDirective implements OnChanges { * The {@link QueryParamGroup} to bind. */ @Input('queryParamGroup') - public queryParamGroup: QueryParamGroup; + public queryParamGroup: QueryParamGroup | null = null; /** @internal */ constructor(private groupService: QueryParamGroupService) { diff --git a/projects/ngqp/core/src/lib/directives/query-param-group.service.ts b/projects/ngqp/core/src/lib/directives/query-param-group.service.ts index bf725ad..c512343 100644 --- a/projects/ngqp/core/src/lib/directives/query-param-group.service.ts +++ b/projects/ngqp/core/src/lib/directives/query-param-group.service.ts @@ -42,7 +42,7 @@ class NavigationData { export class QueryParamGroupService implements OnDestroy { /** The {@link QueryParamGroup} to bind. */ - private queryParamGroup: QueryParamGroup; + private queryParamGroup: QueryParamGroup | null = null; /** List of {@link QueryParamAccessor} registered to this service. */ private directives = new Map(); @@ -122,7 +122,7 @@ export class QueryParamGroupService implements OnDestroy { }) ).pipe( // Do not synchronize while the param is detached from the group - filter(() => !!this.queryParamGroup.get(queryParamName)), + filter(() => !!this.getQueryParamGroup().get(queryParamName)), map((newValue: unknown[]) => this.getParamsForValue(partitionedQueryParam, partitionedQueryParam.reduce(newValue))), takeUntil(this.destroy$), ).subscribe(params => this.enqueueNavigation(new NavigationData(params))); @@ -154,7 +154,7 @@ export class QueryParamGroupService implements OnDestroy { }); this.directives.delete(queryParamName); - const queryParam = this.queryParamGroup.get(queryParamName); + const queryParam = this.getQueryParamGroup().get(queryParamName); if (queryParam) { queryParam._clearChangeFunctions(); } @@ -170,14 +170,14 @@ export class QueryParamGroupService implements OnDestroy { /** Listens for programmatic changes on group level and synchronizes to the router. */ private setupGroupChangeListener(): void { - this.queryParamGroup._registerOnChange((newValue: Record | null) => { + this.getQueryParamGroup()._registerOnChange((newValue: Record | null) => { if (newValue === null) { throw new Error(`Received null value from QueryParamGroup.`); } let params: Params = {}; Object.keys(newValue).forEach(queryParamName => { - const queryParam = this.queryParamGroup.get(queryParamName); + const queryParam = this.getQueryParamGroup().get(queryParamName); if (isMissing(queryParam)) { return; } @@ -191,12 +191,12 @@ export class QueryParamGroupService implements OnDestroy { /** Listens for programmatic changes on parameter level and synchronizes to the router. */ private setupParamChangeListeners(): void { - Object.keys(this.queryParamGroup.queryParams) + Object.keys(this.getQueryParamGroup().queryParams) .forEach(queryParamName => this.setupParamChangeListener(queryParamName)); } private setupParamChangeListener(queryParamName: string): void { - const queryParam = this.queryParamGroup.get(queryParamName); + const queryParam = this.getQueryParamGroup().get(queryParamName); if (!queryParam) { throw new Error(`No param in group found for name ${queryParamName}`); } @@ -215,7 +215,7 @@ export class QueryParamGroupService implements OnDestroy { // particular group; however, we do need to react if one of our parameters has // vanished when it was set before. distinctUntilChanged((previousMap, currentMap) => { - const keys = Object.values(this.queryParamGroup.queryParams) + const keys = Object.values(this.getQueryParamGroup().queryParams) .map(queryParam => this.wrapIntoPartition(queryParam)) .map(partitionedQueryParam => partitionedQueryParam.queryParams.map(queryParam => queryParam.urlParam)) .reduce((a, b) => [...a, ...b], []); @@ -231,7 +231,7 @@ export class QueryParamGroupService implements OnDestroy { const synthetic = this.isSyntheticNavigation(); const groupValue: Record = {}; - Object.keys(this.queryParamGroup.queryParams).forEach(queryParamName => { + Object.keys(this.getQueryParamGroup().queryParams).forEach(queryParamName => { const partitionedQueryParam = this.getQueryParamAsPartition(queryParamName); const newValues = partitionedQueryParam.queryParams.map(queryParam => isMultiQueryParam(queryParam) ? queryParam.deserializeValue(queryParamMap.getAll(queryParam.urlParam)) @@ -247,7 +247,7 @@ export class QueryParamGroupService implements OnDestroy { groupValue[ queryParamName ] = newValue; }); - this.queryParamGroup.setValue(groupValue, { + this.getQueryParamGroup().setValue(groupValue, { emitEvent: !synthetic, emitModelToViewChange: false, }); @@ -256,7 +256,7 @@ export class QueryParamGroupService implements OnDestroy { /** Listens for newly added parameters and starts synchronization for them. */ private watchNewParams(): void { - this.queryParamGroup.queryParamAdded$.pipe( + this.getQueryParamGroup().queryParamAdded$.pipe( takeUntil(this.destroy$) ).subscribe(queryParamName => { this.setupParamChangeListener(queryParamName); @@ -346,7 +346,7 @@ export class QueryParamGroupService implements OnDestroy { * This merges the global configuration with the group specific configuration. */ private get routerOptions(): RouterOptions { - const groupOptions = this.queryParamGroup ? this.queryParamGroup.routerOptions : {}; + const groupOptions = this.getQueryParamGroup().routerOptions; return { ...(this.globalRouterOptions || {}), @@ -362,7 +362,7 @@ export class QueryParamGroupService implements OnDestroy { * query parameters independent of whether they are partitioned. */ private getQueryParamAsPartition(queryParamName: string): PartitionedQueryParam { - const queryParam = this.queryParamGroup.get(queryParamName); + const queryParam = this.getQueryParamGroup().get(queryParamName); if (!queryParam) { throw new Error(`Could not find query param with name ${queryParamName}. Did you forget to add it to your QueryParamGroup?`); } @@ -386,4 +386,12 @@ export class QueryParamGroupService implements OnDestroy { }); } + private getQueryParamGroup(): QueryParamGroup { + if (!this.queryParamGroup) { + throw new Error(`No QueryParamGroup has been registered yet.`); + } + + return this.queryParamGroup; + } + } \ No newline at end of file diff --git a/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts b/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts index 0bdbd8d..22c26dd 100644 --- a/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts +++ b/projects/ngqp/core/src/lib/directives/query-param-name.directive.ts @@ -21,11 +21,23 @@ export class QueryParamNameDirective implements QueryParamAccessor, OnChanges, O * Note that this does not refer to the [parameter name]{@link QueryParam#urlParam}. */ @Input('queryParamName') - public name: string; + public set name(name: string) { + this._name = name; + } + + public get name(): string { + if (!this._name) { + throw new Error(`No queryParamName has been specified.`); + } + + return this._name; + } /** @internal */ public valueAccessor: ControlValueAccessor; + private _name: string | null = null; + /** @internal */ constructor( @Optional() private groupService: QueryParamGroupService, diff --git a/projects/ngqp/core/src/lib/directives/query-param.directive.ts b/projects/ngqp/core/src/lib/directives/query-param.directive.ts index cd4965f..dc53bd8 100644 --- a/projects/ngqp/core/src/lib/directives/query-param.directive.ts +++ b/projects/ngqp/core/src/lib/directives/query-param.directive.ts @@ -23,7 +23,7 @@ export class QueryParamDirective implements QueryParamAccessor, OnChanges, OnDes * The {@link QueryParam} to bind to the host component. */ @Input('queryParam') - public queryParam: QueryParam; + public queryParam: QueryParam | null = null; /** @internal */ public readonly name = 'param'; diff --git a/projects/ngqp/core/tsconfig.lib.json b/projects/ngqp/core/tsconfig.lib.json index f6be547..0b662bb 100644 --- a/projects/ngqp/core/tsconfig.lib.json +++ b/projects/ngqp/core/tsconfig.lib.json @@ -16,6 +16,7 @@ "noImplicitReturns": true, "strictFunctionTypes": true, "strictNullChecks": true, + "strictPropertyInitialization": true, "types": [], "lib": [ "dom", From 5808d4a7de21ae0f1e189d0ab27beff5c1898a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 31 Mar 2019 17:14:19 +0200 Subject: [PATCH 4/6] fix(core): Replace map() with proper type guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ingo Bürk --- .../accessors/multi-select-control-value-accessor.directive.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts b/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts index fb2794d..8e3f3a1 100644 --- a/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts +++ b/projects/ngqp/core/src/lib/accessors/multi-select-control-value-accessor.directive.ts @@ -49,8 +49,7 @@ export class MultiSelectControlValueAccessorDirective implements ControlValue this.selectedIds = values .map(value => this.getOptionId(value)) - .filter(id => id !== null) - .map(id => id as string); + .filter((id: string | null): id is string => id !== null); this.options.forEach((option, id) => option.selected = this.selectedIds.includes(id)); } From e98c60115ac16ad2da71fdfb4e1599c2802e46bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 31 Mar 2019 17:14:38 +0200 Subject: [PATCH 5/6] fix(core): Make abstract constructor protected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ingo Bürk --- projects/ngqp/core/src/lib/model/query-param.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/ngqp/core/src/lib/model/query-param.ts b/projects/ngqp/core/src/lib/model/query-param.ts index 1bffffd..965bfa3 100644 --- a/projects/ngqp/core/src/lib/model/query-param.ts +++ b/projects/ngqp/core/src/lib/model/query-param.ts @@ -84,7 +84,7 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase /** See {@link QueryParamOpts}. */ public readonly combineWith?: ParamCombinator; - constructor(urlParam: string, opts: QueryParamOptsBase = {}) { + protected constructor(urlParam: string, opts: QueryParamOptsBase = {}) { super(); const { serialize, deserialize, debounceTime, compareWith, emptyOn, combineWith } = opts; From c3c0c54430576fb08910816f3dbd3389732422bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 31 Mar 2019 17:23:29 +0200 Subject: [PATCH 6/6] fix(core): Enable complete strict mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ingo Bürk --- .../ngqp/core/src/lib/model/query-param-opts.ts | 6 +++--- projects/ngqp/core/src/lib/model/query-param.ts | 14 +++++++------- projects/ngqp/core/tsconfig.lib.json | 7 +------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/projects/ngqp/core/src/lib/model/query-param-opts.ts b/projects/ngqp/core/src/lib/model/query-param-opts.ts index d93c341..d5ad85b 100644 --- a/projects/ngqp/core/src/lib/model/query-param-opts.ts +++ b/projects/ngqp/core/src/lib/model/query-param-opts.ts @@ -38,7 +38,7 @@ export interface QueryParamOptsBase { * to the form control. Vice versa, if the form control takes on this value, * the URL parameter will be removed. */ - emptyOn?: T; + emptyOn?: T | null; /** * The comparator to be used with {@link QueryParamOpts#emptyOn}. @@ -64,13 +64,13 @@ export interface QueryParamOptsBase { } /** See {@link QueryParamOpts}. */ -export interface QueryParamOpts extends QueryParamOptsBase { +export interface QueryParamOpts extends QueryParamOptsBase { /** See {@link MultiQueryParamOpts}. */ multi?: false; } /** See {@link QueryParamOpts}. */ -export interface MultiQueryParamOpts extends QueryParamOptsBase { +export interface MultiQueryParamOpts extends QueryParamOptsBase { /** * Whether this parameter can take on multiple values at once. * diff --git a/projects/ngqp/core/src/lib/model/query-param.ts b/projects/ngqp/core/src/lib/model/query-param.ts index 965bfa3..37b33b7 100644 --- a/projects/ngqp/core/src/lib/model/query-param.ts +++ b/projects/ngqp/core/src/lib/model/query-param.ts @@ -76,7 +76,7 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase public readonly debounceTime: number | null; /** See {@link QueryParamOpts}. */ - public readonly emptyOn?: T; + public readonly emptyOn?: T | null; /** See {@link QueryParamOpts}. */ public readonly compareWith?: Comparator; @@ -118,10 +118,10 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase } /** @internal */ - public abstract serializeValue(value: T | null): string | string[] | null; + public abstract serializeValue(value: T | null): (string | null) | (string | null)[]; /** @internal */ - public abstract deserializeValue(value: string | string[] | null): T | null; + public abstract deserializeValue(value: (string | null) | (string | null)[]): T | null; /** * Updates the value of this parameter. @@ -161,7 +161,7 @@ export abstract class AbstractQueryParam extends AbstractQueryParamBase * as the glue between its representation in the URL and its connection * to a form control. */ -export class QueryParam extends AbstractQueryParam implements Readonly> { +export class QueryParam extends AbstractQueryParam implements Readonly> { /** See {@link QueryParamOpts}. */ public readonly multi = false; @@ -193,7 +193,7 @@ export class QueryParam extends AbstractQueryParam implements Readonly< /** * Like {@link QueryParam}, but for array-typed parameters */ -export class MultiQueryParam extends AbstractQueryParam implements Readonly> { +export class MultiQueryParam extends AbstractQueryParam implements Readonly> { /** See {@link QueryParamOpts}. */ public readonly multi = true; @@ -203,7 +203,7 @@ export class MultiQueryParam extends AbstractQueryParam implements Re } /** @internal */ - public serializeValue(value: T[] | null): string[] | null { + public serializeValue(value: (T | null)[] | null): (string | null)[] | null { if (this.emptyOn !== undefined && areEqualUsing(value, this.emptyOn, this.compareWith!)) { return null; } @@ -212,7 +212,7 @@ export class MultiQueryParam extends AbstractQueryParam implements Re } /** @internal */ - public deserializeValue(value: string[] | null): T[] | null { + public deserializeValue(value: (string | null)[] | null): (T | null)[] | null { if (this.emptyOn !== undefined && (value || []).length === 0) { return this.emptyOn; } diff --git a/projects/ngqp/core/tsconfig.lib.json b/projects/ngqp/core/tsconfig.lib.json index 0b662bb..b00be71 100644 --- a/projects/ngqp/core/tsconfig.lib.json +++ b/projects/ngqp/core/tsconfig.lib.json @@ -11,12 +11,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, - "noImplicitAny": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, + "strict": true, "types": [], "lib": [ "dom",