UNPKG

@deepkit/desktop-ui

Version:

Library for desktop UI widgets in Angular 10+

221 lines (188 loc) 5.28 kB
/* * 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); } * */ @Directive() 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; @Input() disabled?: boolean | ''; @HostBinding('class.disabled') 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; } @Input() valid?: boolean; @HostBinding('class.valid') get isValid() { return this.valid === true; } @Input() error?: boolean; @HostBinding('class.error') get isError() { if (undefined === this.error && this.ngControl) { return (this.ngControl.dirty || this.ngControl.touched) && this.ngControl.invalid; } return this.error; } @HostBinding('class.required') @Input() required: boolean | '' = false; @Output() public readonly change = new EventEmitter<T>(); protected formComponent?: FormComponent; constructor( @Inject(Injector) protected readonly injector: Injector, @Inject(ChangeDetectorRef) public readonly cd: ChangeDetectorRef, @Inject(ChangeDetectorRef) @SkipSelf() 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); } }