@ssv/ngx.command
Version:
Command pattern implementation for angular. Command used to encapsulate information which is needed to perform an action.
229 lines (219 loc) • 11.5 kB
TypeScript
import * as _angular_core from '@angular/core';
import { Provider, InjectionToken, Signal, Injector, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { AbstractControl } from '@angular/forms';
interface CommandOptions {
/**
* Css Class which gets added/removed on the Command element's host while Command `isExecuting$`.
*/
executingCssClass: string;
/** Determines whether the disabled will be handled by the directive or not.
* Disable handled by directive's doesn't always play nice when used with other component/pipe/directive and they also handle disabled.
* This disables the handling manually and need to pass explicitly `[disabled]="!saveCmd.canExecute"`.
*/
handleDisabled: boolean;
}
declare const COMMAND_OPTIONS: InjectionToken<CommandOptions>;
declare function provideSsvCommandOptions(options: Partial<CommandOptions> | ((defaults: Readonly<CommandOptions>) => Partial<CommandOptions>)): Provider[];
/** Type that represents a sync or async value. */
type MaybeAsync<T> = T | Observable<T> | Promise<T>;
type SignalLike<T> = (() => T);
/** Converts Observable<T> to Promise<T>, leaves other types unchanged */
type ConvertObservableToPromise<T> = T extends Observable<infer U> ? Promise<U> : T;
/** Return type of Execute function, converting `Observable` to `Promise` if needed */
type ExecuteReturnType<TExecute extends ExecuteFn> = ConvertObservableToPromise<ReturnType<TExecute>>;
type ExecuteFn<TArgs extends any[] = any[], TReturn = unknown> = (...args: TArgs) => MaybeAsync<TReturn>;
type CanExecute = SignalLike<boolean> | Signal<boolean> | Observable<boolean> | boolean;
/**
* Type for command parameters that allows:
* - Single parameter passed directly (not in array)
* - Multiple parameters as tuple array
* - Single array parameter wrapped in array to prevent spreading
*/
type CommandParams<TExec extends ExecuteFn> = Parameters<TExec> extends [infer Single] ? Single | [Single] : Parameters<TExec> extends [] ? never : Parameters<TExec>;
/** `command` input type convenience.
* @example
* For a command with a single parameter:
* ```ts
* readonly myCmd = input.required<CommandInput<MyType>>();
* ```
* For a command with multiple parameters:
* ```ts
* readonly myCmd = input.required<CommandInput<[param1: string, param2: number]>>();
* ```
*/
type CommandInput<TArgs = unknown, R = unknown> = TArgs extends readonly [unknown, ...unknown[]] ? Command<(...args: TArgs) => R> : TArgs extends readonly [infer Single] ? Command<(arg: Single) => R> : Command<(arg: TArgs) => R>;
interface ICommand<TExecute extends ExecuteFn = ExecuteFn> {
/** Determines whether the command is currently executing, as a snapshot value.
* @deprecated Use {@link $isExecuting} signal instead.
*/
readonly isExecuting: boolean;
/** Determines whether the command is currently executing, as a signal. */
readonly $isExecuting: Signal<boolean>;
/** Determines whether the command can execute or not, as a snapshot value.
* @deprecated Use {@link $canExecute} signal instead.
*/
readonly canExecute: boolean;
/** Determines whether the command can execute or not, as a signal. */
readonly $canExecute: Signal<boolean>;
/** Execute function to invoke. Returns Promise if the execute function returns Observable, otherwise returns the original type. */
execute(...args: Parameters<TExecute>): ExecuteReturnType<TExecute>;
}
interface CommandCreator<TExecute extends ExecuteFn = ExecuteFn> {
/** Execute function to invoke. */
execute: TExecute;
/** Determines whether the command can execute or not. Can be a signal, observable, or function. */
canExecute?: CanExecute | ((...args: Parameters<TExecute>) => CanExecute);
/** Parameters to pass to the execute function. */
params?: CommandParams<TExecute>;
/** Host context for binding the execute function. */
host: unknown;
}
interface CommandCreateOptions {
injector?: Injector;
}
/** Creates an async {@link Command}. Must be used within an injection context.
* @deprecated Use {@link command} instead, as it handles both sync and async execute functions.
*/
declare const commandAsync: typeof command;
/** Creates a {@link Command}. Must be used within an injection context (or the injector must be provided in the options). */
declare function command<TExecute extends ExecuteFn>(execute: TExecute, canExecute?: CanExecute, opts?: CommandCreateOptions): Command<TExecute>;
/**
* Command object used to encapsulate information which is needed to perform an action.
*/
declare class Command<TExecute extends ExecuteFn = ExecuteFn> implements ICommand<TExecute> {
#private;
private readonly _execute;
get isExecuting(): boolean;
get canExecute(): boolean;
readonly $isExecuting: _angular_core.WritableSignal<boolean>;
readonly $canExecute: Signal<boolean>;
/**
* Creates an instance of Command.
*
* @param execute Execute function to invoke.
* @param canExecute Observable which determines whether it can execute or not.
* @deprecated Use {@link command} or {@link commandAsync} instead for creating instances.
*/
constructor(_execute: TExecute, canExecute?: CanExecute, injector?: Injector);
/** Execute function to invoke. Returns Promise if the execute function returns Observable, otherwise returns the original type. */
execute(...args: Parameters<TExecute>): ExecuteReturnType<TExecute>;
}
/** Helper type to extract ExecuteFn from ICommand/Command or use ExecuteFn directly */
type ExtractExecuteFn<T> = T extends Command<infer TExec> ? TExec : T extends ICommand<infer TExec> ? TExec : T extends ExecuteFn ? T : never;
/**
* Controls the state of a component in sync with `Command`.
*
* @example
* ### Most common usage
* ```html
* <button [ssvCommand]="saveCmd">Save</button>
* ```
*
*
* ### Usage with options
* ```html
* <button [ssvCommand]="saveCmd" [ssvCommandOptions]="{executingCssClass: 'in-progress'}">Save</button>
* ```
*
*
* ### Usage with params
* This is useful for collections (loops) or using multiple actions with different args.
* *NOTE: This will share the `isExecuting` when used with multiple controls.*
*
* #### With single param (direct)
*
* ```html
* <button [ssvCommand]="saveCmd" [ssvCommandParams]="hero">Save</button>
* ```
*
* #### With single param (array)
*
* ```html
* <button [ssvCommand]="saveCmd" [ssvCommandParams]="[hero]">Save</button>
* ```
*
* *NOTE: if you have only 1 argument as an array, it should be enclosed within an array e.g. `[['apple', 'banana']]`,
* else it will spread and you will get `arg1: "apple", arg2: "banana"`*
*
* #### With multi params
* ```html
* <button [ssvCommand]="saveCmd" [ssvCommandParams]="[{id: 1}, 'hello', hero]">Save</button>
* ```
*
* ### Usage with Command Creator
* This is useful for collections (loops) or using multiple actions with different args, whilst not sharing `isExecuting`.
*
*
* ```html
* <button [ssvCommand]="{host: this, execute: removeHero$, canExecute: isValid$, params: [hero, 1337, 'xx']}">Save</button>
* ```
*
*/
declare class SsvCommand<T extends ICommand | ExecuteFn = ExecuteFn> implements OnInit {
#private;
readonly commandOrCreator: _angular_core.InputSignal<T extends ICommand<ExecuteFn<any[], unknown>> ? T : ICommand<ExtractExecuteFn<T>> | CommandCreator<ExtractExecuteFn<T>>>;
readonly ssvCommandOptions: _angular_core.InputSignal<Partial<CommandOptions>>;
readonly commandOptions: _angular_core.Signal<CommandOptions>;
readonly ssvCommandParams: _angular_core.InputSignal<(CommandParams<ExtractExecuteFn<T>> extends infer T_1 ? { [KeyType in keyof T_1]: T_1[KeyType]; } : never) | undefined>;
readonly commandParams: _angular_core.Signal<Parameters<ExtractExecuteFn<T>> | undefined>;
readonly _hostClasses: _angular_core.Signal<string[]>;
readonly command: _angular_core.Signal<ICommand<ExtractExecuteFn<T>>>;
constructor();
ngOnInit(): void;
_handleClick(): void;
private trySetDisabled;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SsvCommand<any>, never>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SsvCommand<any>, "[ssvCommand]", ["ssvCommand"], { "commandOrCreator": { "alias": "ssvCommand"; "required": true; "isSignal": true; }; "ssvCommandOptions": { "alias": "ssvCommandOptions"; "required": false; "isSignal": true; }; "ssvCommandParams": { "alias": "ssvCommandParams"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
}
/**
* Command creator ref, directive which allows creating Command in the template
* and associate it to a command (in order to share executions).
* @example
* ### Most common usage
* ```html
* <div #actionCmd="ssvCommandRef" [ssvCommandRef]="{host: this, execute: removeHero$, canExecute: isValid$}">
* <button [ssvCommand]="actionCmd.command()" [ssvCommandParams]="[hero]">
* Remove
* </button>
* <button [ssvCommand]="actionCmd.command()" [ssvCommandParams]="[hero]">
* Remove
* </button>
* </div>
* ```
*
*/
declare class SsvCommandRef<TExecute extends ExecuteFn = ExecuteFn> implements OnInit {
#private;
readonly commandCreator: _angular_core.InputSignal<CommandCreator<TExecute>>;
readonly command: _angular_core.Signal<ICommand<TExecute>>;
ngOnInit(): void;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SsvCommandRef<any>, never>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SsvCommandRef<any>, "[ssvCommandRef]", ["ssvCommandRef"], { "commandCreator": { "alias": "ssvCommandRef"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
}
/** @deprecated Use standalone instead. */
declare class SsvCommandModule {
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SsvCommandModule, never>;
static ɵmod: _angular_core.ɵɵNgModuleDeclaration<SsvCommandModule, never, [typeof SsvCommand, typeof SsvCommandRef], [typeof SsvCommand, typeof SsvCommandRef]>;
static ɵinj: _angular_core.ɵɵInjectorDeclaration<SsvCommandModule>;
}
/** Determines whether the arg object is of type `Command`. */
declare function isCommand<T extends ICommand>(arg: unknown | T): arg is T;
/** Determines whether the arg object is of type `CommandCreator`. */
declare function isCommandCreator<T extends CommandCreator>(arg: unknown | T): arg is T;
interface CanExecuteFormOptions {
/** Determines whether to check for validity. (defaults: true) */
validity?: boolean;
/** Determines whether to check whether UI has been touched. (defaults: true) */
dirty?: boolean;
}
/** Get can execute from form validity/pristine as an observable. */
declare function canExecuteFromNgForm(form: AbstractControl, options?: CanExecuteFormOptions): Observable<boolean>;
/** Can executed based on valid/dirty signal inputs. */
declare function canExecuteFromSignals(signals: {
valid: Signal<boolean>;
dirty: Signal<boolean>;
}, options?: CanExecuteFormOptions): Signal<boolean>;
declare const VERSION = "5.1.0";
export { COMMAND_OPTIONS, Command, SsvCommand, SsvCommandModule, SsvCommandRef, VERSION, canExecuteFromNgForm, canExecuteFromSignals, command, commandAsync, isCommand, isCommandCreator, provideSsvCommandOptions };
export type { CanExecute, CanExecuteFormOptions, CommandCreateOptions, CommandCreator, CommandInput, CommandOptions, ExecuteFn, ICommand };