Skip to content

Commit

Permalink
CM-68: Refactor form utils, styles (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
A77AY committed May 26, 2023
1 parent 5ca87f9 commit d0276a1
Show file tree
Hide file tree
Showing 33 changed files with 160 additions and 141 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion projects/ng-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vality/ng-core",
"version": "0.6.0",
"version": "0.7.0",
"sideEffects": false,
"exports": {
".": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<input [formControl]="control" matInput />
</mat-form-field>
<v-dialog-actions>
<button mat-button (click)="cancel()">Cancel</button>
<button mat-button (click)="closeWithCancellation()">Cancel</button>
<button color="primary" mat-flat-button (click)="confirm()">
{{ dialogData?.['confirmLabel'] || 'Confirm' }}
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl } from '@angular/forms';

import { DialogResponseStatus, DialogSuperclass } from '../dialog';
import { DialogSuperclass } from '../dialog';

@Component({
selector: 'v-action-dialog',
Expand All @@ -15,17 +15,9 @@ export class ConfirmDialogComponent extends DialogSuperclass<
> {
control = new FormControl<string>('', { nonNullable: true });

cancel() {
this.dialogRef.close({ status: DialogResponseStatus.Cancelled });
}

confirm() {
this.dialogRef.close({
status: DialogResponseStatus.Success,
data:
this.dialogData && this.dialogData.hasReason
? { reason: this.control.value }
: undefined,
});
this.closeWithSuccess(
this.dialogData?.hasReason ? { reason: this.control.value } : undefined
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core';
import { NonNullableFormBuilder } from '@angular/forms';
import { coerceBoolean } from 'coerce-property';

import { ValidatedControlSuperclass, createControlProviders } from '../../utils';
import { FormGroupSuperclass, createControlProviders } from '../../utils';

export interface DateRange {
start: Date;
Expand All @@ -15,7 +15,7 @@ export interface DateRange {
styleUrls: ['./date-range-field.component.scss'],
providers: createControlProviders(() => DateRangeFieldComponent),
})
export class DateRangeFieldComponent extends ValidatedControlSuperclass<DateRange> {
export class DateRangeFieldComponent extends FormGroupSuperclass<DateRange> {
@Input() @coerceBoolean required: boolean | '' = false;

control = this.fb.group({
Expand Down
11 changes: 11 additions & 0 deletions projects/ng-core/src/lib/components/dialog/_dialog-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '@angular/material' as mat;

@mixin theme($theme) {
$typography: map-get($theme, typography);

.v-dialog {
.title {
@include mat.typography-level($typography, headline-5);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="dialog">
<div class="dialog-title">
<div>
<h2 class="mat-h2">{{ title }}</h2>
<h2 class="title">{{ title }}</h2>
</div>
<mat-icon
*ngIf="!noCloseButton"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
import { coerceBoolean } from 'coerce-property';

import { DialogResponseStatus } from './types/dialog-response-status';
Expand All @@ -10,6 +10,8 @@ import { Progressable } from '../../types/progressable';
styleUrls: ['dialog.component.scss'],
})
export class DialogComponent implements Progressable {
@HostBinding('class.v-dialog') hostClass: boolean = true;

@Input() title!: string;

@coerceBoolean @Input() disabled: boolean | string = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core';
import { NonNullableFormBuilder } from '@angular/forms';

import { ValidatedControlSuperclass, createControlProviders } from '../../utils';
import { FormGroupSuperclass, createControlProviders } from '../../utils';

export type NumberRange = {
start?: number;
Expand All @@ -14,7 +14,7 @@ export type NumberRange = {
styleUrls: ['./number-range-field.component.scss'],
providers: createControlProviders(() => NumberRangeFieldComponent),
})
export class NumberRangeFieldComponent extends ValidatedControlSuperclass<NumberRange> {
export class NumberRangeFieldComponent extends FormGroupSuperclass<NumberRange> {
@Input() label!: string;

control = this.fb.group<NumberRange>({
Expand Down
2 changes: 2 additions & 0 deletions projects/ng-core/src/lib/components/table/_table-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
@mixin theme($theme) {
$foreground: map-get($theme, foreground);
$warn: map-get($theme, warn);
$typography: map-get($theme, typography);

.v-table-description-cell-template {
.description.description {
color: mat.get-color-from-palette($foreground, secondary-text);
@include mat.typography-level($typography, 'caption');
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export function createCurrencyColumn<T>(
return {
type: 'currency',
...extColumn,
header: typeof column === 'string' ? '' : extColumn.header,
data: {
...(extColumn.data || {}),
currencyField,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type DescriptionColumn<T> = TemplateColumn<
<ng-template let-col="colDef" let-index="index" let-row>
<div class="v-table-description-cell-template">
{{ getValue(col, row) }}
<div class="mat-caption description">
<div class="description">
{{ getDescription(col, row) }}
</div>
</div>
Expand All @@ -45,7 +45,6 @@ export function createDescriptionColumn<T>(
return {
type: 'description',
...descColumn,
header: typeof column === 'string' ? '' : descColumn.header,
data: {
...(descColumn.data || {}),
description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export function createTagColumn<T, TTag extends PropertyKey>(
return {
type: 'tag',
...extCol,
header: typeof column === 'string' ? '' : extCol.header,
data: {
...(extCol.data || {}),
tags,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type TooltipColumn<T> = TemplateColumn<
T,
'tooltip',
{
tooltip: string | ((data: T) => string);
tooltip: string | ((data: T) => unknown);
}
>;

Expand Down
5 changes: 4 additions & 1 deletion projects/ng-core/src/lib/components/table/table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { coerceBoolean } from 'coerce-property';
import { get } from 'lodash-es';
import { BehaviorSubject } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';

import { TableActionsComponent } from './components/table-actions.component';
import { Column, ExtColumn } from './types/column';
Expand Down Expand Up @@ -71,7 +72,9 @@ export class TableComponent<T> implements OnInit, Progressable, OnChanges {
constructor(private cdr: ChangeDetectorRef) {}

ngOnInit() {
this.size$.pipe(untilDestroyed(this)).subscribe((v) => this.sizeChange.emit(v));
this.size$
.pipe(distinctUntilChanged(), untilDestroyed(this))
.subscribe((v) => this.sizeChange.emit(v));
}

ngOnChanges(changes: ComponentChanges<TableComponent<T>>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import {
shareReplay,
Observable,
defer,
mergeScan,
map,
BehaviorSubject,
ReplaySubject,
skipWhile,
} from 'rxjs';
import { Observable, defer, mergeScan, map, BehaviorSubject, Subject } from 'rxjs';
import { shareReplay } from 'rxjs/operators';

import { inProgressFrom, progressTo } from '../../utils';

Expand Down Expand Up @@ -42,9 +34,9 @@ export abstract class FetchSuperclass<TResultItem, TParams = void, TContinuation
() => this.state$
);

private fetch$ = new ReplaySubject<Action<TParams>>(1);
private fetch$ = new Subject<Action<TParams>>();
private progress$ = new BehaviorSubject(0);
private state$ = defer(() => this.fetch$.pipe(skipWhile((r) => r.type !== 'load'))).pipe(
private state$ = this.fetch$.pipe(
mergeScan<Action<TParams>, Accumulator<TResultItem, TParams, TContinuationToken>>(
(acc, action) => {
const params = (action.type === 'load' ? action.params : acc.params) as TParams;
Expand Down
2 changes: 2 additions & 0 deletions projects/ng-core/src/lib/styles/_all-theme.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@use '../components/table/table-theme';
@use '../components/dialog/dialog-theme';

@mixin all-component-themes($theme-or-color-config) {
@include table-theme.theme($theme-or-color-config);
@include dialog-theme.theme($theme-or-color-config);
}
16 changes: 8 additions & 8 deletions projects/ng-core/src/lib/utils/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ export function clean<
T extends ReadonlyArray<any> | ArrayLike<any> | Record<any, any>,
TAllowRootRemoval extends boolean = false
>(
value: T,
obj: T,
allowRootRemoval: TAllowRootRemoval = false as TAllowRootRemoval,
isNotDeep = false,
filterPredicate: (v: unknown, k?: PropertyKey) => boolean = (v) => !isEmpty(v)
): TAllowRootRemoval extends true ? T | null : T {
if (!isObject(value) || (value.constructor !== Object && !Array.isArray(value))) return value;
if (allowRootRemoval && !filterPredicate(value as never)) return null as never;
let result: unknown;
const cleanChild = (v: unknown) =>
isNotDeep ? v : clean(v as never, allowRootRemoval, isNotDeep, filterPredicate);
if (Array.isArray(value))
result = (value as ValuesType<T>[])
.slice()
if (Array.isArray(obj)) {
result = (obj as ValuesType<T>[])
.map((v) => cleanChild(v))
.filter((v, idx) => filterPredicate(v, idx));
else
} else if (isObject(obj)) {
result = Object.fromEntries(
(Object.entries(value) as [keyof T, ValuesType<T>][])
(Object.entries(obj) as [keyof T, ValuesType<T>][])
.map(([k, v]) => [k, cleanChild(v)] as const)
.filter(([k, v]) => filterPredicate(v, k))
);
} else {
result = obj;
}
return allowRootRemoval && !filterPredicate(result) ? (null as never) : (result as never);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ValidationErrors } from '@angular/forms';
import { WrappedControlSuperclass } from '@s-libs/ng-core';
import isEqual from 'lodash-es/isEqual';
import { EMPTY, Observable, switchMap } from 'rxjs';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';

import { getErrorsTree } from './utils/get-errors-tree';
import { hasControls } from '../has-controls';

export abstract class AbstractControlSuperclass<
OuterType,
InnerType = OuterType
> extends WrappedControlSuperclass<OuterType, InnerType> {
override setDisabledState(isDisabled: boolean): void {
// If the form group control is disabled, then it turns it on during initialization
if (hasControls(this.control)) {
return;
}
super.setDisabledState(isDisabled);
}

protected override setUpInnerToOuterErrors$(
inner$: Observable<ValidationErrors>
): Observable<ValidationErrors> {
return inner$.pipe(
switchMap(() => this.control.statusChanges.pipe(startWith(this.control.status))),
map(() => getErrorsTree(this.control) || {}),
distinctUntilChanged(isEqual)
);
}

protected override setUpOuterToInnerErrors$(
_outer$: Observable<ValidationErrors>
): Observable<ValidationErrors> {
return EMPTY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Directive } from '@angular/core';

import { FormGroupSuperclass } from './form-group-superclass.directive';

@Directive()
export abstract class FormArraySuperclass<
OuterType,
InnerType = OuterType
> extends FormGroupSuperclass<OuterType, InnerType> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Directive } from '@angular/core';
import { FormControl } from '@angular/forms';

import { AbstractControlSuperclass } from './abstract-control-superclass';

@Directive()
export class FormControlSuperclass<
OuterType,
InnerType = OuterType
> extends AbstractControlSuperclass<OuterType, InnerType> {
control = new FormControl() as FormControl<InnerType>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Directive, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { AbstractControlSuperclass } from './abstract-control-superclass';
import { getValue } from '../get-value';
import { hasControls } from '../has-controls';

@Directive()
export abstract class FormGroupSuperclass<OuterType, InnerType = OuterType>
extends AbstractControlSuperclass<OuterType, InnerType>
implements OnInit
{
protected emptyValue!: InnerType;

override ngOnInit() {
this.emptyValue = getValue(this.control) as InnerType;
super.ngOnInit();
}

protected override outerToInnerValue(outer: OuterType): InnerType {
if (hasControls(this.control)) {
if (!outer) return this.emptyValue;
if (
Object.keys(outer).length < Object.keys((this.control as FormGroup).controls).length
)
return Object.assign({}, this.emptyValue, outer);
}
return outer as never;
}
}
5 changes: 5 additions & 0 deletions projects/ng-core/src/lib/utils/form/form-wrappers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './form-group-superclass.directive';
export * from './utils/provide-value-accessor';
export * from './utils/create-control-providers';
export * from './utils/get-errors-tree';
export * from './form-control-superclass.directive';
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Provider, Type } from '@angular/core';

import { provideValidators } from './provide-validators';
import { provideValueAccessor } from './provide-value-accessor';

export const createControlProviders = (component: () => Type<unknown>): Provider[] => [
provideValueAccessor(component),
provideValidators(component),
];
3 changes: 2 additions & 1 deletion projects/ng-core/src/lib/utils/form/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './get-form-value-changes';
export * from './validated-control-superclass';
export * from './form-wrappers';
export * from './get-valid-value-changes';
export * from './set-disabled';
11 changes: 11 additions & 0 deletions projects/ng-core/src/lib/utils/form/set-disabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AbstractControl } from '@angular/forms';

export function setDisabled(
control?: AbstractControl,
disabled: boolean = true,
options: { onlySelf?: boolean; emitEvent?: boolean } = {}
) {
if (!control) return;
if (disabled) control.disable(options);
else control.enable(options);
}
Loading

0 comments on commit d0276a1

Please sign in to comment.