Skip to content

Commit

Permalink
feat(core): add support for model inputs (#54252)
Browse files Browse the repository at this point in the history
Adds support for model inputs in the framework. `model()` returns a writable signal that implicitly defines a input/output pair that can be used either in two-way bindings to keep two values in sync or by binding individually to the input and output. When the value of the `model` changes, it will emit an event with the current value.

Furthermore, these changes expand two-way bindings to accept `WritableSignal`. This will make it easier to transition existing code to signals in a backwards-compatible way.

Example:

```ts
@directive({
  selector: 'counter',
  standalone: true,
  host: {
    '(click)': 'increment()',
  }
})
export class Counter {
  value = model(0);

  increment(): void {
    this.value.update(current => current + 1);
  }
}

@component({
  template: `<counter [(value)]="count"/> The current count is: {{count()}}`,
})
class App {
  count = signal(0);
}
```

PR Close #54252
  • Loading branch information
crisbeto authored and thePunderWoman committed Feb 7, 2024
1 parent d006aa3 commit 702ab28
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 5 deletions.
2 changes: 2 additions & 0 deletions goldens/public-api/core/errors.md
Expand Up @@ -125,6 +125,8 @@ export const enum RuntimeErrorCode {
// (undocumented)
REQUIRED_QUERY_NO_VALUE = -951,
// (undocumented)
REQUIRED_MODEL_NO_VALUE = -952,
// (undocumented)
RUNTIME_DEPS_INVALID_IMPORTED_TYPE = 1000,
// (undocumented)
RUNTIME_DEPS_ORPHAN_COMPONENT = 1001,
Expand Down
26 changes: 26 additions & 0 deletions goldens/public-api/core/index.md
Expand Up @@ -1075,6 +1075,32 @@ export enum MissingTranslationStrategy {
Warning = 1
}

// @public
export const model: ModelFunction;

// @public
export interface ModelFunction {
<T>(): ModelSignal<T | undefined>;
// (undocumented)
<T>(initialValue: T, opts?: ModelOptions): ModelSignal<T>;
required<T>(opts?: ModelOptions): ModelSignal<T>;
}

// @public
export interface ModelOptions {
alias?: string;
}

// @public
export interface ModelSignal<T> extends WritableSignal<T> {
// (undocumented)
INPUT_SIGNAL_BRAND_READ_TYPE]: T;
// (undocumented)
INPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
// (undocumented)
[SIGNAL]: ModelSignalNode<T>;
}

// @public @deprecated
export class ModuleWithComponentFactories<T> {
constructor(ngModuleFactory: NgModuleFactory<T>, componentFactories: ComponentFactory<any>[]);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/authoring.ts
Expand Up @@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

// Note: `input` is exported in `core.ts` due to:
// Note: `input` and `model` are exported in `core.ts` due to:
// https://docs.google.com/document/d/1RXb1wYwsbJotO1KBgSDsAtKpduGmIHod9ADxuXcAvV4/edit?tab=t.0.

export {InputFunction} from './authoring/input/input';
export {InputOptions, InputOptionsWithoutTransform, InputOptionsWithTransform, InputSignal, InputSignalWithTransform, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE} from './authoring/input/input_signal';
export {ɵUnwrapDirectiveSignalInputs} from './authoring/input/input_type_checking';
export {ModelFunction} from './authoring/model/model';
export {ModelOptions, ModelSignal} from './authoring/model/model_signal';
export {output as ɵoutput, OutputEmitter as ɵOutputEmitter, OutputOptions as ɵOutputOptions} from './authoring/output';
export {ContentChildFunction, ViewChildFunction} from './authoring/queries';
6 changes: 2 additions & 4 deletions packages/core/src/core.ts
Expand Up @@ -13,14 +13,12 @@
*/

export * from './authoring';
// Input and query authoring functions are exported separately as this file is exempted from
// Authoring functions are exported separately as this file is exempted from
// JSCompiler's conformance requirement for inferred const exports. See:
// https://docs.google.com/document/d/1RXb1wYwsbJotO1KBgSDsAtKpduGmIHod9ADxuXcAvV4/edit?tab=t.0
export {input} from './authoring/input/input';
export {contentChild, contentChildren, viewChild, viewChildren} from './authoring/queries';

// TODO(crisbeto): export as a public API
// export {model} from './authoring/model/model';
export {model} from './authoring/model/model';

export * from './metadata';
export * from './version';
Expand Down
9 changes: 9 additions & 0 deletions packages/core/test/bundling/defer/bundle.golden_symbols.json
Expand Up @@ -1556,6 +1556,15 @@
{
"name": "init_misc_utils"
},
{
"name": "init_model"
},
{
"name": "init_model_signal"
},
{
"name": "init_model_signal_node"
},
{
"name": "init_module"
},
Expand Down
Expand Up @@ -881,6 +881,9 @@
{
"name": "diPublicInInjector"
},
{
"name": "elementPropertyInternal"
},
{
"name": "enterDI"
},
Expand Down Expand Up @@ -1289,6 +1292,9 @@
{
"name": "isReadableStreamLike"
},
{
"name": "isSignal"
},
{
"name": "isStylingMatch"
},
Expand Down Expand Up @@ -1331,6 +1337,9 @@
{
"name": "leaveViewLight"
},
{
"name": "listenerInternal"
},
{
"name": "lookupTokenUsingModuleInjector"
},
Expand Down

0 comments on commit 702ab28

Please sign in to comment.