Skip to content

Commit 702ab28

Browse files
crisbetothePunderWoman
authored andcommitted
feat(core): add support for model inputs (angular#54252)
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 angular#54252
1 parent d006aa3 commit 702ab28

File tree

6 files changed

+50
-5
lines changed

6 files changed

+50
-5
lines changed

goldens/public-api/core/errors.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ export const enum RuntimeErrorCode {
125125
// (undocumented)
126126
REQUIRED_QUERY_NO_VALUE = -951,
127127
// (undocumented)
128+
REQUIRED_MODEL_NO_VALUE = -952,
129+
// (undocumented)
128130
RUNTIME_DEPS_INVALID_IMPORTED_TYPE = 1000,
129131
// (undocumented)
130132
RUNTIME_DEPS_ORPHAN_COMPONENT = 1001,

goldens/public-api/core/index.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,32 @@ export enum MissingTranslationStrategy {
10751075
Warning = 1
10761076
}
10771077

1078+
// @public
1079+
export const model: ModelFunction;
1080+
1081+
// @public
1082+
export interface ModelFunction {
1083+
<T>(): ModelSignal<T | undefined>;
1084+
// (undocumented)
1085+
<T>(initialValue: T, opts?: ModelOptions): ModelSignal<T>;
1086+
required<T>(opts?: ModelOptions): ModelSignal<T>;
1087+
}
1088+
1089+
// @public
1090+
export interface ModelOptions {
1091+
alias?: string;
1092+
}
1093+
1094+
// @public
1095+
export interface ModelSignal<T> extends WritableSignal<T> {
1096+
// (undocumented)
1097+
INPUT_SIGNAL_BRAND_READ_TYPE]: T;
1098+
// (undocumented)
1099+
INPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
1100+
// (undocumented)
1101+
[SIGNAL]: ModelSignalNode<T>;
1102+
}
1103+
10781104
// @public @deprecated
10791105
export class ModuleWithComponentFactories<T> {
10801106
constructor(ngModuleFactory: NgModuleFactory<T>, componentFactories: ComponentFactory<any>[]);

packages/core/src/authoring.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

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

1212
export {InputFunction} from './authoring/input/input';
1313
export {InputOptions, InputOptionsWithoutTransform, InputOptionsWithTransform, InputSignal, InputSignalWithTransform, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE} from './authoring/input/input_signal';
1414
export {ɵUnwrapDirectiveSignalInputs} from './authoring/input/input_type_checking';
1515
export {ModelFunction} from './authoring/model/model';
16+
export {ModelOptions, ModelSignal} from './authoring/model/model_signal';
1617
export {output as ɵoutput, OutputEmitter as ɵOutputEmitter, OutputOptions as ɵOutputOptions} from './authoring/output';
1718
export {ContentChildFunction, ViewChildFunction} from './authoring/queries';

packages/core/src/core.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@
1313
*/
1414

1515
export * from './authoring';
16-
// Input and query authoring functions are exported separately as this file is exempted from
16+
// Authoring functions are exported separately as this file is exempted from
1717
// JSCompiler's conformance requirement for inferred const exports. See:
1818
// https://docs.google.com/document/d/1RXb1wYwsbJotO1KBgSDsAtKpduGmIHod9ADxuXcAvV4/edit?tab=t.0
1919
export {input} from './authoring/input/input';
2020
export {contentChild, contentChildren, viewChild, viewChildren} from './authoring/queries';
21-
22-
// TODO(crisbeto): export as a public API
23-
// export {model} from './authoring/model/model';
21+
export {model} from './authoring/model/model';
2422

2523
export * from './metadata';
2624
export * from './version';

packages/core/test/bundling/defer/bundle.golden_symbols.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,6 +1556,15 @@
15561556
{
15571557
"name": "init_misc_utils"
15581558
},
1559+
{
1560+
"name": "init_model"
1561+
},
1562+
{
1563+
"name": "init_model_signal"
1564+
},
1565+
{
1566+
"name": "init_model_signal_node"
1567+
},
15591568
{
15601569
"name": "init_module"
15611570
},

packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,9 @@
881881
{
882882
"name": "diPublicInInjector"
883883
},
884+
{
885+
"name": "elementPropertyInternal"
886+
},
884887
{
885888
"name": "enterDI"
886889
},
@@ -1289,6 +1292,9 @@
12891292
{
12901293
"name": "isReadableStreamLike"
12911294
},
1295+
{
1296+
"name": "isSignal"
1297+
},
12921298
{
12931299
"name": "isStylingMatch"
12941300
},
@@ -1331,6 +1337,9 @@
13311337
{
13321338
"name": "leaveViewLight"
13331339
},
1340+
{
1341+
"name": "listenerInternal"
1342+
},
13341343
{
13351344
"name": "lookupTokenUsingModuleInjector"
13361345
},

0 commit comments

Comments
 (0)