di-controls
Version:
<!-- PROJECT LOGO -->
200 lines (199 loc) • 6.81 kB
TypeScript
import { InputSignal, OnChanges, Signal, SimpleChanges } from '@angular/core';
import { DICompareHost } from 'di-controls/classes';
import { DIControl, DIControlConfig } from './control';
import { DICompareFunction } from 'di-controls/types';
import * as i0 from "@angular/core";
/**
* Configuration for the `DIStateControl`.
*/
export interface DIStateControlConfig<TModel, TValue extends TModel = TModel> extends DIControlConfig<TModel, TModel> {
/**
* Value that will be used for the unchecked state.
*/
uncheckValue?: TModel;
/**
* Function that will be used to compare model value with the `value` property.
*/
compare?: DICompareHost<TModel | null, TValue> | DICompareFunction<TModel | null, TValue> | null;
/**
* Indicates whether the current control can have intermediate state.
*/
hasIntermediate?: boolean;
}
/**
* `DIStateControl` can be used to implement state controls (checkbox, radio, chip, switch, etc.).
* It extends `DIControl` and adds `checked` signal that can be used to get checked state.
* By default it works with `boolean` type, it adds `value` input that can be used to set custom
* "true" value.
*
* ## Creating a control
* To create a control you need to extend your `@Component` or `@Directive` from `DIStateControl` class.
* After that your control will be able to work with `NgModel`, `FormControl`.
*
* ```ts fileName="custom-control.component.ts"
* @Component({})
* export class CustomControlComponent<T = boolean> extends DIStateControl<T> {
* @Input({required: true})
* value!: T;
*
* constructor() {
* super();
* }
* }
* ```
*
* ## Injecting host control
* By default your control doesn't communicate with host controls. But you can inject host control and put it
* into `super` call. This will register your control in the host control and start communication between them.
*
* > **Note**
* > If you register your control as a host for another controls, then you can inject it
* > only with `skipSelf` option.
*
* ```ts {5} fileName="custom-control.component.ts"
* @Component({})
* export class CustomControlComponent<T = boolean> extends DIStateControl<T> {
* constructor() {
* // we add `optional` option to make it possible to use this control without host
* super({host: injectHostControl({optional: true})});
* }
* }
* ```
*
* ## Getting checked state
* To get checked state you need to use `checked` signal. It will return `true` if the current control is checked,
* `false` if it is unchecked and `null` if it is in intermediate state.
*
* ```ts {9} fileName="custom-control.component.ts"
* @Component({})
* export class CustomControlComponent<T> extends DIControl<T> {
* @Input({required: true})
* value!: T;
*
* constructor() {
* super();
*
* console.log(this.checked());
* }
* }
* ```
*
* ## Getting model
* To get model you need to use `model` property. It will return model for the current control.
*
* ```ts {9} fileName="custom-control.component.ts"
* @Component({})
* export class CustomControlComponent<T = boolean> extends DIStateControl<T> {
* constructor() {
* super();
* }
*
* @HostListener('click')
* onClick() {
* console.log(this.checked());
* }
* }
* ```
*
* ## Updating model
* To update model you should use `check`, `uncheck`, `intermediate` or `toggle` methods.
* They will update model based on the current state and configuration.
*
* ```ts {9} fileName="custom-control.component.ts"
* @Component({})
* export class CustomControlComponent<T = boolean> extends DIStateControl<T> {
* constructor() {
* super();
* }
*
* @HostListener('click')
* onClick() {
* this.toggle();
* }
* }
* ```
*
* ## Catching updates
* Sometimes you may need to catch updates from different sources. For example, to update the value of the native
* input element. To do this, you can provide the `onIncomingUpdate` hook.
*
* ```ts {6} fileName="custom-control.component.ts"
* @Component({})
* export class CustomControlComponent<T = boolean> extends DIStateControl<T> {
* constructor() {
* super({
* onIncomingUpdate: (value: string | null) => {
* this.elementRef.nativeElement.value = value;
* },
* });
* }
* }
* ```
*
*
* ## Using with DICollectionControl
* Using `DIStateControl` together with `DICollectionControl` will result in
* `DICollectionControl` containing a list of values from `DIStateControl` that have
* a checked state. If your `DIStateControl` has objects as values, you may
* likely need a comparison function because they can sometimes be immutable.
*
* To achieve this, provide your `DICollectionControl` as a `DICompareHost` and
* inject it into your `DIStateControl` to give `DIStateControl` access to
* the `compareFn` function.
*
* > **Warning**
* > `DICollectionControl` requires an explicit specification of the `uncheckedValue` in the `DIStateControl`
*
* ```ts {3-4} fileName="checkbox-group.component.ts"
* @Component({
* providers: [
* provideHostControl(CheckboxGroupComponent),
* provideCompareHost(CheckboxGroupComponent),
* ],
* })
* export class CheckboxGroupComponent<T> extends DICollectionControl<T> {
* constructor() {
* super();
* }
* }
* ```
*
* ```ts {5-7} fileName="checkbox.component.ts"
* @Component()
* export class CheckboxComponent<T> extends DIStateControl<T> {
* constructor() {
* super({
* host: injectHostControl({ optional: true }),
* compare: inject(DICompareHost, { optional: true }),
* hasIntermediate: true,
* });
*
* @HostListener('click')
* onClick() {
* this.toggle();
* }
* }
* ```
*/
export declare abstract class DIStateControl<TModel, TValue extends TModel = TModel> extends DIControl<TModel> implements OnChanges {
protected readonly config?: DIStateControlConfig<TModel, TValue> | undefined;
/**
* Value that will be used for the checked state.
* You can override it to transform it to `@Input` or to set value by default.
*/
abstract value: TValue | InputSignal<TValue>;
checked: Signal<boolean | null>;
protected constructor(config?: DIStateControlConfig<TModel, TValue> | undefined);
ngOnChanges({ value }: SimpleChanges): void;
/** Sets checked state */
check(): void;
/** Sets unchecked state */
uncheck(): void;
/** Sets intermediate state */
intermediate(): void;
/** Toggles checked state */
toggle(): void;
get isIntermediate(): boolean;
static ɵfac: i0.ɵɵFactoryDeclaration<DIStateControl<any, any>, never>;
static ɵdir: i0.ɵɵDirectiveDeclaration<DIStateControl<any, any>, never, never, {}, {}, never, never, false, never>;
}