Skip to content

Commit 5537950

Browse files
hybristthePunderWoman
authored andcommitted
refactor(core): expose Binding for easier use of inputBinding (angular#61351)
When passing bindings as arguments or storing them before use in `createComponent`, it's handy to be able to reference this type. The current workaround is to use `ReturnType<typeof inputBinding>`. PR Close angular#61351
1 parent 17fe731 commit 5537950

4 files changed

Lines changed: 36 additions & 23 deletions

File tree

goldens/public-api/core/index.api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ export interface BaseResourceOptions<T, R> {
179179
params?: () => R;
180180
}
181181

182+
// @public
183+
export interface Binding {
184+
// (undocumented)
185+
readonly [BINDING]: unknown;
186+
}
187+
182188
// @public
183189
export function booleanAttribute(value: unknown): boolean;
184190

packages/core/src/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export {
113113
afterNextRender,
114114
ɵFirstAvailable,
115115
} from './render3/after_render/hooks';
116-
export {inputBinding, outputBinding, twoWayBinding} from './render3/dynamic_bindings';
116+
export {Binding, inputBinding, outputBinding, twoWayBinding} from './render3/dynamic_bindings';
117117
export {ApplicationConfig, mergeApplicationConfig} from './application/application_config';
118118
export {makeStateKey, StateKey, TransferState} from './transfer_state';
119119
export {booleanAttribute, numberAttribute} from './util/coercion';

packages/core/src/render3/component_ref.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import {Injector} from '../di/injector';
1717
import {EnvironmentInjector} from '../di/r3_injector';
1818
import {RuntimeError, RuntimeErrorCode} from '../errors';
19-
import {Type, Writable} from '../interface/type';
19+
import {Type} from '../interface/type';
2020
import {
2121
ComponentFactory as AbstractComponentFactory,
2222
ComponentRef as AbstractComponentRef,
@@ -80,7 +80,7 @@ import {getComponentLViewByIndex, getTNode} from './util/view_utils';
8080
import {elementLikeEndFirstCreatePass, elementLikeStartFirstCreatePass} from './view/elements';
8181
import {ViewRef} from './view_ref';
8282
import {createLView, createTView, getInitialLViewFlagsFromDef} from './view/construction';
83-
import {BINDING, Binding, DirectiveWithBindings} from './dynamic_bindings';
83+
import {BINDING, Binding, BindingInternal, DirectiveWithBindings} from './dynamic_bindings';
8484
import {NG_REFLECT_ATTRS_FLAG, NG_REFLECT_ATTRS_FLAG_DEFAULT} from '../ng_reflect';
8585

8686
export class ComponentFactoryResolver extends AbstractComponentFactoryResolver {
@@ -375,16 +375,16 @@ function createRootTView(
375375
let varsToAllocate = 0;
376376

377377
if (componentBindings) {
378-
for (const binding of componentBindings) {
378+
for (const binding of componentBindings as BindingInternal[]) {
379379
varsToAllocate += binding[BINDING].requiredVars;
380380

381381
if (binding.create) {
382-
(binding as Writable<Binding>).targetIdx = 0;
382+
(binding as BindingInternal).targetIdx = 0;
383383
(creationBindings ??= []).push(binding);
384384
}
385385

386386
if (binding.update) {
387-
(binding as Writable<Binding>).targetIdx = 0;
387+
(binding as BindingInternal).targetIdx = 0;
388388
(updateBindings ??= []).push(binding);
389389
}
390390
}
@@ -394,16 +394,16 @@ function createRootTView(
394394
for (let i = 0; i < directives.length; i++) {
395395
const directive = directives[i];
396396
if (typeof directive !== 'function') {
397-
for (const binding of directive.bindings) {
397+
for (const binding of directive.bindings as BindingInternal[]) {
398398
varsToAllocate += binding[BINDING].requiredVars;
399399
const targetDirectiveIdx = i + 1;
400400
if (binding.create) {
401-
(binding as Writable<Binding>).targetIdx = targetDirectiveIdx;
401+
(binding as BindingInternal).targetIdx = targetDirectiveIdx;
402402
(creationBindings ??= []).push(binding);
403403
}
404404

405405
if (binding.update) {
406-
(binding as Writable<Binding>).targetIdx = targetDirectiveIdx;
406+
(binding as BindingInternal).targetIdx = targetDirectiveIdx;
407407
(updateBindings ??= []).push(binding);
408408
}
409409
}
@@ -458,21 +458,21 @@ function getRootTViewTemplate(
458458

459459
return (flags) => {
460460
if (flags & RenderFlags.Create && creationBindings) {
461-
for (const binding of creationBindings) {
461+
for (const binding of creationBindings as BindingInternal[]) {
462462
binding.create!();
463463
}
464464
}
465465

466466
if (flags & RenderFlags.Update && updateBindings) {
467-
for (const binding of updateBindings) {
467+
for (const binding of updateBindings as BindingInternal[]) {
468468
binding.update!();
469469
}
470470
}
471471
};
472472
}
473473

474474
function isInputBinding(binding: Binding): boolean {
475-
const kind = binding[BINDING].kind;
475+
const kind = (binding as BindingInternal)[BINDING].kind;
476476
return kind === 'input' || kind === 'twoWay';
477477
}
478478

packages/core/src/render3/dynamic_bindings.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ export const BINDING: unique symbol = /* @__PURE__ */ Symbol('BINDING');
2525
* For example, `inputBinding('value', () => 123)` creates an input binding.
2626
*/
2727
export interface Binding {
28+
readonly [BINDING]: unknown;
29+
}
30+
31+
export interface BindingInternal extends Binding {
2832
readonly [BINDING]: {
2933
readonly kind: string;
3034
readonly requiredVars: number;
3135
};
3236

3337
/** Target index (in a view's registry) to which to apply the binding. */
34-
readonly targetIdx?: number;
38+
targetIdx?: number;
3539

3640
/** Callback that will be invoked during creation. */
3741
create?(): void;
@@ -52,8 +56,8 @@ export interface DirectiveWithBindings<T> {
5256
}
5357

5458
// These are constant between all the bindings so we can reuse the objects.
55-
const INPUT_BINDING_METADATA: Binding[typeof BINDING] = {kind: 'input', requiredVars: 1};
56-
const OUTPUT_BINDING_METADATA: Binding[typeof BINDING] = {kind: 'output', requiredVars: 0};
59+
const INPUT_BINDING_METADATA: BindingInternal[typeof BINDING] = {kind: 'input', requiredVars: 1};
60+
const OUTPUT_BINDING_METADATA: BindingInternal[typeof BINDING] = {kind: 'output', requiredVars: 0};
5761

5862
// TODO(pk): this is a sketch of an input binding instruction that still needs some cleanups
5963
// - take an index of a directive on TNode (as matched), review all the index mappings that we need to do
@@ -111,9 +115,9 @@ function inputBindingUpdate(targetDirectiveIdx: number, publicName: string, valu
111115
export function inputBinding(publicName: string, value: () => unknown): Binding {
112116
// Note: ideally we would use a class here, but it seems like they
113117
// don't get tree shaken when constructed by a function like this.
114-
const binding: Binding = {
118+
const binding: BindingInternal = {
115119
[BINDING]: INPUT_BINDING_METADATA,
116-
update: () => inputBindingUpdate(binding.targetIdx!, publicName, value()),
120+
update: () => inputBindingUpdate((binding as BindingInternal).targetIdx!, publicName, value()),
117121
};
118122

119123
return binding;
@@ -143,7 +147,7 @@ export function inputBinding(publicName: string, value: () => unknown): Binding
143147
export function outputBinding<T>(eventName: string, listener: (event: T) => unknown): Binding {
144148
// Note: ideally we would use a class here, but it seems like they
145149
// don't get tree shaken when constructed by a function like this.
146-
const binding: Binding = {
150+
const binding: BindingInternal = {
147151
[BINDING]: OUTPUT_BINDING_METADATA,
148152
create: () => {
149153
const lView = getLView<{} | null>();
@@ -178,8 +182,10 @@ export function outputBinding<T>(eventName: string, listener: (event: T) => unkn
178182
* ```
179183
*/
180184
export function twoWayBinding(publicName: string, value: WritableSignal<unknown>): Binding {
181-
const input = inputBinding(publicName, value);
182-
const output = outputBinding(publicName + 'Change', (eventValue) => value.set(eventValue));
185+
const input = inputBinding(publicName, value) as BindingInternal;
186+
const output = outputBinding(publicName + 'Change', (eventValue) =>
187+
value.set(eventValue),
188+
) as BindingInternal;
183189

184190
// We take advantage of inputs only having a `create` block and outputs only having an `update`
185191
// block by passing them through directly instead of creating dedicated functions here. This
@@ -188,16 +194,17 @@ export function twoWayBinding(publicName: string, value: WritableSignal<unknown>
188194
ngDevMode && assertNotDefined(input.create, 'Unexpected `create` callback in inputBinding');
189195
ngDevMode && assertNotDefined(output.update, 'Unexpected `update` callback in outputBinding');
190196

191-
return {
197+
const binding: BindingInternal = {
192198
[BINDING]: {
193199
kind: 'twoWay',
194200
requiredVars: input[BINDING].requiredVars + output[BINDING].requiredVars,
195201
},
196202
set targetIdx(idx: number) {
197-
(input as Writable<Binding>).targetIdx = idx;
198-
(output as Writable<Binding>).targetIdx = idx;
203+
(input as Writable<BindingInternal>).targetIdx = idx;
204+
(output as Writable<BindingInternal>).targetIdx = idx;
199205
},
200206
create: output.create,
201207
update: input.update,
202208
};
209+
return binding;
203210
}

0 commit comments

Comments
 (0)