@deepkit/desktop-ui
Version:
Library for desktop UI widgets in Angular 10+
221 lines (188 loc) • 5.28 kB
text/typescript
/*
* Deepkit Framework
* Copyright (C) 2021 Deepkit UG, Marc J. Schmidt
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the MIT License.
*
* You should have received a copy of the MIT License along with this program.
*/
import {
ChangeDetectorRef,
Directive,
EventEmitter,
forwardRef,
HostBinding,
Inject,
Injector,
Input,
OnDestroy,
Output,
SkipSelf,
Type
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { detectChangesNextFrame } from '../components/app/utils';
import { FormComponent } from '../components/form/form.component';
export function ngValueAccessor<T>(clazz: Type<T>) {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => clazz),
multi: true
};
}
/**
* If you sub class this class and have own constructor or property initialization you need
* to provide the dependencies of this class manually.
*
*
constructor(
protected injector: Injector,
protected cd: ChangeDetectorRef,
@SkipSelf() protected cdParent: ChangeDetectorRef,
) {
super(injector, cd, cdParent);
}
*
*/
export class ValueAccessorBase<T> implements ControlValueAccessor, OnDestroy {
/**
* @hidden
*/
private _innerValue: T | undefined;
/**
* @hidden
*/
public readonly _changedCallback: ((value: T | undefined) => void)[] = [];
/**
* @hidden
*/
public readonly _touchedCallback: (() => void)[] = [];
private _ngControl?: NgControl;
private _ngControlFetched = false;
disabled?: boolean | '';
get isDisabled(): boolean {
if (this.formComponent && this.formComponent.disabled) return true;
if (undefined === this.disabled && this.ngControl) {
return !!this.ngControl.disabled;
}
return this.disabled !== false && this.disabled !== undefined;
}
valid?: boolean;
get isValid() {
return this.valid === true;
}
error?: boolean;
get isError() {
if (undefined === this.error && this.ngControl) {
return (this.ngControl.dirty || this.ngControl.touched) && this.ngControl.invalid;
}
return this.error;
}
required: boolean | '' = false;
public readonly change = new EventEmitter<T>();
protected formComponent?: FormComponent;
constructor(
protected readonly injector: Injector,
public readonly cd: ChangeDetectorRef,
public readonly cdParent: ChangeDetectorRef,
) {
try {
this.formComponent = injector.get(FormComponent, undefined);
} catch (e) {
}
}
get ngControl(): NgControl | undefined {
if (!this._ngControlFetched) {
try {
this._ngControl = this.injector.get(NgControl);
} catch (e) {
}
this._ngControlFetched = true;
}
return this._ngControl;
}
/**
* @hidden
*/
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
/**
* @hidden
*/
ngOnDestroy(): void {
}
/**
* @hidden
*/
get innerValue(): T | undefined {
return this._innerValue;
}
/**
* Sets the internal value and signals Angular's form and other users (that subscribed via registerOnChange())
* that a change happened.
*
* @hidden
*/
set innerValue(value: T | undefined) {
if (this._innerValue !== value) {
this._innerValue = value;
for (const callback of this._changedCallback) {
callback(value);
}
this.onInnerValueChange().then(() => {
detectChangesNextFrame(this.cd);
});
this.change.emit(value);
detectChangesNextFrame(this.cd);
}
}
/**
* Internal note: This method is called from outside. Either from Angular's form or other users.
*
* @hidden
*/
writeValue(value?: T) {
if (this._innerValue !== value) {
this._innerValue = value;
}
detectChangesNextFrame(this.cd);
}
/**
* This method can be overwritten to get easily notified when innerValue has been changed, either
* by outside or inside.
*
* @hidden
*/
async onInnerValueChange() {
}
/**
* Call this method to signal Angular's form or other users that this widget has been touched.
* @hidden
*/
touch() {
for (const callback of this._touchedCallback) {
callback();
}
}
/**
* @hidden
*/
registerOnChange(fn: (value: T | undefined) => void) {
this._changedCallback.push(fn);
}
/**
* @hidden
*/
registerOnTouched(fn: () => void) {
this._touchedCallback.push(fn);
}
}