From 60ed00a601166d4a0cc8f624e7ba6d814865f9f3 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 18 Mar 2024 14:10:48 +0000 Subject: [PATCH] refactor(core): improve API documentation for `input` after angular.dev support (#55053) This commit improves the API documentation for `input` after we added support for initializer APIs in angular.dev docs generation. Changes: - Rename `ReadT` to `T`. This conceptually makes it easy to talk about inputs of type `T` if there is no transform involved. The common case. - Rename `WriteT` to `TransformT`. This makes it clear that this is the type that the "transform" needs to handle. - Improves the "overall" description of the input function so that it can be shown as a general overview for the API site. - Improves usage notes to be a little more helpful, yielding more useful content in the API docs usage notes section. - Add short JSDoc description for each individual overload. PR Close #55053 --- goldens/public-api/core/index.md | 36 +++--- .../ngtsc/typecheck/src/type_check_block.ts | 2 +- packages/core/src/authoring/input/input.ts | 106 ++++++++++-------- .../core/src/authoring/input/input_signal.ts | 32 +++--- .../src/authoring/input/input_signal_node.ts | 10 +- .../authoring/input/input_type_checking.ts | 2 +- 6 files changed, 103 insertions(+), 85 deletions(-) diff --git a/goldens/public-api/core/index.md b/goldens/public-api/core/index.md index 93d1423a907..13eac06c0c5 100644 --- a/goldens/public-api/core/index.md +++ b/goldens/public-api/core/index.md @@ -937,43 +937,41 @@ export interface InputDecorator { // @public export interface InputFunction { - (): InputSignal; - // (undocumented) - (initialValue: ReadT, opts?: InputOptionsWithoutTransform): InputSignal; - // (undocumented) - (initialValue: ReadT, opts: InputOptionsWithTransform): InputSignalWithTransform; + (): InputSignal; + (initialValue: T, opts?: InputOptionsWithoutTransform): InputSignal; + (initialValue: T, opts: InputOptionsWithTransform): InputSignalWithTransform; required: { - (opts?: InputOptionsWithoutTransform): InputSignal; - (opts: InputOptionsWithTransform): InputSignalWithTransform; + (opts?: InputOptionsWithoutTransform): InputSignal; + (opts: InputOptionsWithTransform): InputSignalWithTransform; }; } // @public -export interface InputOptions { +export interface InputOptions { alias?: string; - transform?: (v: WriteT) => ReadT; + transform?: (v: TransformT) => T; } // @public -export type InputOptionsWithoutTransform = Omit, 'transform'> & { +export type InputOptionsWithoutTransform = Omit, 'transform'> & { transform?: undefined; }; // @public -export type InputOptionsWithTransform = Required, 'transform'>> & InputOptions; +export type InputOptionsWithTransform = Required, 'transform'>> & InputOptions; // @public -export interface InputSignal extends InputSignalWithTransform { +export interface InputSignal extends InputSignalWithTransform { } // @public -export interface InputSignalWithTransform extends Signal { +export interface InputSignalWithTransform extends Signal { // (undocumented) - [ɵINPUT_SIGNAL_BRAND_READ_TYPE]: ReadT; + [ɵINPUT_SIGNAL_BRAND_READ_TYPE]: T; // (undocumented) - [ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: WriteT; + [ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: TransformT; // (undocumented) - [SIGNAL]: InputSignalNode; + [SIGNAL]: InputSignalNode; } // @public @@ -1101,9 +1099,11 @@ export const model: ModelFunction; // @public export interface ModelFunction { (): ModelSignal; - // (undocumented) (initialValue: T, opts?: ModelOptions): ModelSignal; - required(opts?: ModelOptions): ModelSignal; + // (undocumented) + required: { + (opts?: ModelOptions): ModelSignal; + }; } // @public diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index 6c2f236a8c9..e324cf2eea0 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -722,7 +722,7 @@ class TcbDirectiveInputsOp extends TcbOp { // For signal inputs, a `transformType` will never be set as we do not capture // the transform in the compiler metadata. Signal inputs incorporate their // transform write type into their member type, and we extract it below when - // unwrapping the `InputSignal`. + // setting the `WriteT` of such `InputSignalWithTransform<_, WriteT>`. if (this.dir.coercedInputFields.has(fieldName)) { let type: ts.TypeNode; diff --git a/packages/core/src/authoring/input/input.ts b/packages/core/src/authoring/input/input.ts index 549bbc225f9..b177d8aee4b 100644 --- a/packages/core/src/authoring/input/input.ts +++ b/packages/core/src/authoring/input/input.ts @@ -18,7 +18,7 @@ export function inputFunction( return createInputSignal(initialValue, opts); } -export function inputRequiredFunction(opts?: InputOptions): +export function inputRequiredFunction(opts?: InputOptions): InputSignalWithTransform { ngDevMode && assertInInjectionContext(input); return createInputSignal(REQUIRED_UNSET_VALUE as never, opts); @@ -31,77 +31,95 @@ export function inputRequiredFunction(opts?: InputOptions(); // string|undefined - * lastName = input.required(); // string - * age = input(0); // number - * } - * ``` - * * @developerPreview */ export interface InputFunction { /** - * Initializes an input with an initial value. If no explicit value - * is specified, Angular will use `undefined`. - * - * Consider using `input.required` for inputs that don't need an - * initial value. - * - * @developerPreview + * Initializes an input of type `T` with an initial value of `undefined`. + * Angular will implicitly use `undefined` as initial value. */ - (): InputSignal; - (initialValue: ReadT, opts?: InputOptionsWithoutTransform): InputSignal; - (initialValue: ReadT, opts: InputOptionsWithTransform): - InputSignalWithTransform; + (): InputSignal; + /** Declares an input of type `T` with an explicit initial value. */ + (initialValue: T, opts?: InputOptionsWithoutTransform): InputSignal; + /** + * Declares an input of type `T` with an initial value and a transform + * function. + * + * The input accepts values of type `TransformT` and the given + * transform function will transform the value to type `T`. + */ + (initialValue: T, opts: InputOptionsWithTransform): + InputSignalWithTransform; /** * Initializes a required input. * - * Users of your directive/component need to bind to this + * Consumers of your directive/component need to bind to this * input. If unset, a compile time error will be reported. * * @developerPreview */ required: { - (opts?: InputOptionsWithoutTransform): InputSignal; - - (opts: InputOptionsWithTransform): - InputSignalWithTransform; + /** Declares a required input of type `T`. */ + (opts?: InputOptionsWithoutTransform): InputSignal; + /** + * Declares a required input of type `T` with a transform function. + * + * The input accepts values of type `TransformT` and the given + * transform function will transform the value to type `T`. + */ + (opts: InputOptionsWithTransform): + InputSignalWithTransform; }; } /** - * The `input` function allows declaration of inputs in directives and - * components. + * The `input` function allows declaration of Angular inputs in directives + * and components. * - * Initializes an input with an initial value. If no explicit value - * is specified, Angular will use `undefined`. + * There are two variants of inputs that can be declared: * - * Consider using `input.required` for inputs that don't need an - * initial value. + * 1. **Optional inputs** with an initial value. + * 2. **Required inputs** that consumers need to set. + * + * By default, the `input` function will declare optional inputs that + * always have an initial value. Required inputs can be declared + * using the `input.required()` function. + * + * Inputs are signals. The values of an input are exposed as a `Signal`. + * The signal always holds the latest value of the input that is bound + * from the parent. * * @usageNotes - * Initialize an input in your directive or component by declaring a - * class field and initializing it with the `input()` function. + * To use signal-based inputs, import `input` from `@angular/core`. + * + * ``` + * import {input} from '@angular/core`; + * ``` + * + * Inside your component, introduce a new class member and initialize + * it with a call to `input` or `input.required`. * * ```ts - * @Directive({..}) - * export class MyDir { - * firstName = input(); // string|undefined - * lastName = input.required(); // string - * age = input(0); // number + * @Component({ + * ... + * }) + * export class UserProfileComponent { + * firstName = input(); // Signal + * lastName = input.required(); // Signal + * age = input(0) // Signal * } * ``` * + * Inside your component template, you can display values of the inputs + * by calling the signal. + * + * ```html + * {{firstName()}} + * ``` + * * @developerPreview + * @initializerApiFunction */ export const input: InputFunction = (() => { // Note: This may be considered a side-effect, but nothing will depend on diff --git a/packages/core/src/authoring/input/input_signal.ts b/packages/core/src/authoring/input/input_signal.ts index f271049952b..1ceb9270abb 100644 --- a/packages/core/src/authoring/input/input_signal.ts +++ b/packages/core/src/authoring/input/input_signal.ts @@ -18,7 +18,7 @@ import {INPUT_SIGNAL_NODE, InputSignalNode, REQUIRED_UNSET_VALUE} from './input_ * * Options for signal inputs. */ -export interface InputOptions { +export interface InputOptions { /** Optional public name for the input. By default, the class field name is used. */ alias?: string; /** @@ -31,7 +31,7 @@ export interface InputOptions { * attribute form to bind to the input via ``. A transform can then * handle such string values and convert them to `boolean`. See: {@link booleanAttribute}. */ - transform?: (v: WriteT) => ReadT; + transform?: (v: TransformT) => T; } /** @@ -39,16 +39,16 @@ export interface InputOptions { * * @developerPreview */ -export type InputOptionsWithoutTransform = +export type InputOptionsWithoutTransform = // Note: We still keep a notion of `transform` for auto-completion. - Omit, 'transform'>&{transform?: undefined}; + Omit, 'transform'>&{transform?: undefined}; /** * Signal input options with the transform option required. * * @developerPreview */ -export type InputOptionsWithTransform = - Required, 'transform'>>&InputOptions; +export type InputOptionsWithTransform = + Required, 'transform'>>&InputOptions; export const ɵINPUT_SIGNAL_BRAND_READ_TYPE = /* @__PURE__ */ Symbol(); export const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE = /* @__PURE__ */ Symbol(); @@ -77,10 +77,10 @@ export const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE = /* @__PURE__ */ Symbol(); * * @developerPreview */ -export interface InputSignalWithTransform extends Signal { - [SIGNAL]: InputSignalNode; - [ɵINPUT_SIGNAL_BRAND_READ_TYPE]: ReadT; - [ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: WriteT; +export interface InputSignalWithTransform extends Signal { + [SIGNAL]: InputSignalNode; + [ɵINPUT_SIGNAL_BRAND_READ_TYPE]: T; + [ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: TransformT; } /** @@ -94,7 +94,7 @@ export interface InputSignalWithTransform extends Signal { * * @developerPreview */ -export interface InputSignal extends InputSignalWithTransform {} +export interface InputSignal extends InputSignalWithTransform {} /** * Creates an input signal. @@ -103,10 +103,10 @@ export interface InputSignal extends InputSignalWithTransform( - initialValue: ReadT, - options?: InputOptions): InputSignalWithTransform { - const node: InputSignalNode = Object.create(INPUT_SIGNAL_NODE); +export function createInputSignal( + initialValue: T, + options?: InputOptions): InputSignalWithTransform { + const node: InputSignalNode = Object.create(INPUT_SIGNAL_NODE); node.value = initialValue; @@ -133,5 +133,5 @@ export function createInputSignal( inputValueFn.toString = () => `[Input Signal: ${inputValueFn()}]`; } - return inputValueFn as InputSignalWithTransform; + return inputValueFn as InputSignalWithTransform; } diff --git a/packages/core/src/authoring/input/input_signal_node.ts b/packages/core/src/authoring/input/input_signal_node.ts index 6fb55aa5c32..ed1f4bfe71d 100644 --- a/packages/core/src/authoring/input/input_signal_node.ts +++ b/packages/core/src/authoring/input/input_signal_node.ts @@ -14,12 +14,12 @@ export const REQUIRED_UNSET_VALUE = /* @__PURE__ */ Symbol('InputSignalNode#UNSE * Reactive node type for an input signal. An input signal extends a signal. * There are special properties to enable transforms and required inputs. */ -export interface InputSignalNode extends SignalNode { +export interface InputSignalNode extends SignalNode { /** * User-configured transform that will run whenever a new value is applied * to the input signal node. */ - transformFn: ((value: WriteT) => ReadT)|undefined; + transformFn: ((value: TransformT) => T)|undefined; /** * Applies a new value to the input signal. Expects transforms to be run @@ -27,9 +27,9 @@ export interface InputSignalNode extends SignalNode { * * This function is called by the framework runtime code whenever a binding * changes. The value can in practice be anything at runtime, but for typing - * purposes we assume it's a valid `ReadT` value. Type-checking will enforce that. + * purposes we assume it's a valid `T` value. Type-checking will enforce that. */ - applyValueToInputSignal(node: InputSignalNode, value: ReadT): void; + applyValueToInputSignal(node: InputSignalNode, value: T): void; } // Note: Using an IIFE here to ensure that the spread assignment is not considered @@ -40,7 +40,7 @@ export const INPUT_SIGNAL_NODE: InputSignalNode = /* @__PURE__ ...SIGNAL_NODE, transformFn: undefined, - applyValueToInputSignal(node: InputSignalNode, value: ReadT) { + applyValueToInputSignal(node: InputSignalNode, value: T) { signalSetFn(node, value); } }; diff --git a/packages/core/src/authoring/input/input_type_checking.ts b/packages/core/src/authoring/input/input_type_checking.ts index 47c0c54cbf5..a49e211802d 100644 --- a/packages/core/src/authoring/input/input_type_checking.ts +++ b/packages/core/src/authoring/input/input_type_checking.ts @@ -8,7 +8,7 @@ import {InputSignalWithTransform} from './input_signal'; -/** Retrieves the `WriteT` of an `InputSignal` and `InputSignalWithTransform`. */ +/** Retrieves the write type of an `InputSignal` and `InputSignalWithTransform`. */ export type ɵUnwrapInputSignalWriteType = Field extends InputSignalWithTransform? WriteT : never;