or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attributes.mdcontext-system.mdcss-styling.mddata-binding.mddependency-injection.mdhtml-templates.mdindex.mdobservable-system.mdssr-hydration.mdstate-management.mdtemplate-directives.mdtesting-utilities.mdutilities.mdweb-components.md

data-binding.mddocs/

0

# Data Binding

1

2

Reactive data binding system supporting one-way, two-way, and event bindings with automatic dependency tracking and efficient updates for creating dynamic user interfaces.

3

4

## Capabilities

5

6

### One-Way Binding

7

8

Creates standard reactive bindings that automatically update when source data changes, supporting both function expressions and binding configurations.

9

10

```typescript { .api }

11

/**

12

* Creates a standard one-way binding

13

* @param expression - The binding expression to evaluate

14

* @param policy - The security policy to associate with the binding

15

* @param isVolatile - Indicates whether the binding is volatile or not

16

* @returns A binding configuration

17

*/

18

function oneWay<T = any>(

19

expression: Expression<T>,

20

policy?: DOMPolicy,

21

isVolatile?: boolean

22

): Binding<T>;

23

24

/**

25

* Creates an event listener binding

26

* @param expression - The binding to invoke when the event is raised

27

* @param options - Event listener options

28

* @returns A binding configuration

29

*/

30

function listener<T = any>(

31

expression: Expression<T>,

32

options?: AddEventListenerOptions

33

): Binding<T>;

34

```

35

36

**Usage Examples:**

37

38

```typescript

39

import { FASTElement, customElement, html, oneWay, listener, attr } from "@microsoft/fast-element";

40

41

const template = html<MyComponent>`

42

<!-- Simple property binding -->

43

<div class="status">${x => x.status}</div>

44

45

<!-- Conditional content binding -->

46

<div class="message">${x => x.showMessage ? x.message : ''}</div>

47

48

<!-- Complex expression binding -->

49

<span class="count">${x => `Items: ${x.items?.length ?? 0}`}</span>

50

51

<!-- Attribute binding -->

52

<input type="text"

53

value="${x => x.currentValue}"

54

?disabled="${x => x.isReadonly}"

55

class="${x => x.isValid ? 'valid' : 'invalid'}"

56

>

57

58

<!-- Event listener binding -->

59

<button @click="${x => x.handleClick}"

60

@mouseenter="${listener((x, e) => x.handleHover(e), { passive: true })}"

61

>

62

${x => x.buttonText}

63

</button>

64

65

<!-- Custom event binding with options -->

66

<custom-element @custom-event="${listener(

67

(x, e) => x.handleCustomEvent(e.detail),

68

{ once: true }

69

)}">

70

</custom-element>

71

`;

72

73

@customElement({

74

name: "my-component",

75

template

76

})

77

export class MyComponent extends FASTElement {

78

@attr status: string = "ready";

79

@attr message: string = "";

80

@attr showMessage: boolean = false;

81

@attr isReadonly: boolean = false;

82

@attr isValid: boolean = true;

83

@attr buttonText: string = "Click me";

84

@attr currentValue: string = "";

85

86

items: string[] = [];

87

88

handleClick() {

89

console.log("Button clicked");

90

this.showMessage = !this.showMessage;

91

}

92

93

handleHover(event: MouseEvent) {

94

console.log("Button hovered", event);

95

}

96

97

handleCustomEvent(detail: any) {

98

console.log("Custom event received", detail);

99

}

100

}

101

102

// Advanced one-way binding with security policy

103

const secureBinding = oneWay(

104

x => x.userContent,

105

myDOMPolicy, // Security policy for sanitization

106

false // Not volatile

107

);

108

109

// Volatile binding that always re-evaluates

110

const volatileBinding = oneWay(

111

x => Math.random(), // Always different

112

undefined,

113

true // Volatile - always re-evaluate

114

);

115

```

116

117

### Two-Way Binding

118

119

Creates bidirectional bindings that synchronize data between source and view, automatically detecting changes in both directions.

120

121

```typescript { .api }

122

/**

123

* Creates a two-way data binding

124

* @param expression - The binding expression to synchronize

125

* @param optionsOrChangeEvent - The binding options or change event name

126

* @param policy - The security policy to associate with the binding

127

* @param isBindingVolatile - Indicates whether the binding is volatile

128

* @returns A two-way binding configuration

129

*/

130

function twoWay<T = any>(

131

expression: Expression<T>,

132

optionsOrChangeEvent?: TwoWayBindingOptions | string,

133

policy?: DOMPolicy,

134

isBindingVolatile?: boolean

135

): Binding<T>;

136

137

/**

138

* Two-way binding configuration options

139

*/

140

interface TwoWayBindingOptions {

141

/** The event name to listen for changes from the view */

142

changeEvent?: string;

143

144

/** Function to transform values coming from the view */

145

fromView?: (value: any) => any;

146

}

147

148

/**

149

* Settings for configuring two-way binding behavior

150

*/

151

interface TwoWaySettings {

152

/**

153

* Determines which event to listen to for detecting view changes

154

* @param bindingSource - The directive to determine the change event for

155

* @param target - The target element to determine the change event for

156

*/

157

determineChangeEvent(bindingSource: BindingDirective, target: HTMLElement): string;

158

}

159

160

/**

161

* Utilities for configuring two-way binding system

162

*/

163

const TwoWaySettings: {

164

/**

165

* Configures global two-way binding behavior

166

* @param settings - The settings to use for the two-way binding system

167

*/

168

configure(settings: TwoWaySettings): void;

169

};

170

```

171

172

**Usage Examples:**

173

174

```typescript

175

import { FASTElement, customElement, html, attr, observable } from "@microsoft/fast-element";

176

import { twoWay } from "@microsoft/fast-element/binding/two-way.js";

177

178

const template = html<FormComponent>`

179

<!-- Basic two-way binding -->

180

<input type="text" :value="${x => x.name}">

181

182

<!-- Two-way binding with custom change event -->

183

<input type="text"

184

:value="${twoWay(x => x.email, 'input')}"

185

placeholder="Email">

186

187

<!-- Two-way binding with value transformation -->

188

<input type="number"

189

:value="${twoWay(

190

x => x.age,

191

{

192

changeEvent: 'input',

193

fromView: (value) => parseInt(value, 10) || 0

194

}

195

)}">

196

197

<!-- Two-way binding with checkbox -->

198

<input type="checkbox"

199

:checked="${twoWay(x => x.isSubscribed, 'change')}">

200

201

<!-- Two-way binding with select -->

202

<select :value="${twoWay(x => x.category)}">

203

<option value="web">Web Development</option>

204

<option value="mobile">Mobile Development</option>

205

<option value="data">Data Science</option>

206

</select>

207

208

<!-- Two-way binding with custom element -->

209

<custom-slider

210

:value="${twoWay(x => x.volume, 'value-changed')}"

211

min="0"

212

max="100">

213

</custom-slider>

214

215

<!-- Display bound values -->

216

<div class="preview">

217

<p>Name: ${x => x.name}</p>

218

<p>Email: ${x => x.email}</p>

219

<p>Age: ${x => x.age}</p>

220

<p>Subscribed: ${x => x.isSubscribed ? 'Yes' : 'No'}</p>

221

<p>Category: ${x => x.category}</p>

222

<p>Volume: ${x => x.volume}%</p>

223

</div>

224

`;

225

226

@customElement({

227

name: "form-component",

228

template

229

})

230

export class FormComponent extends FASTElement {

231

@observable name: string = "";

232

@observable email: string = "";

233

@observable age: number = 0;

234

@observable isSubscribed: boolean = false;

235

@observable category: string = "web";

236

@observable volume: number = 50;

237

}

238

239

// Configure custom two-way binding behavior

240

TwoWaySettings.configure({

241

determineChangeEvent(bindingSource, target) {

242

// Custom logic for determining change events

243

if (target.tagName === 'INPUT') {

244

const type = target.getAttribute('type');

245

switch (type) {

246

case 'text':

247

case 'email':

248

case 'password':

249

return 'input'; // Real-time updates

250

case 'checkbox':

251

case 'radio':

252

return 'change';

253

default:

254

return 'input';

255

}

256

}

257

if (target.tagName === 'SELECT') {

258

return 'change';

259

}

260

if (target.tagName === 'TEXTAREA') {

261

return 'input';

262

}

263

264

// Default for custom elements

265

return 'change';

266

}

267

});

268

269

// Custom two-way binding with complex transformation

270

const currencyBinding = twoWay(

271

x => x.price,

272

{

273

changeEvent: 'input',

274

fromView: (value: string) => {

275

// Transform currency input to number

276

const cleaned = value.replace(/[^0-9.]/g, '');

277

const parsed = parseFloat(cleaned);

278

return isNaN(parsed) ? 0 : parsed;

279

}

280

}

281

);

282

```

283

284

### One-Time Binding

285

286

Creates bindings that evaluate once during initial render and do not update automatically, useful for static content and performance optimization.

287

288

```typescript { .api }

289

/**

290

* Creates a one-time binding that evaluates once

291

* @param expression - The binding expression to evaluate

292

* @param policy - The security policy to associate with the binding

293

* @returns A one-time binding configuration

294

*/

295

function oneTime<T = any>(

296

expression: Expression<T>,

297

policy?: DOMPolicy

298

): Binding<T>;

299

```

300

301

**Usage Examples:**

302

303

```typescript

304

import { FASTElement, customElement, html, oneTime, attr } from "@microsoft/fast-element";

305

306

const template = html<StaticComponent>`

307

<!-- One-time binding for static content -->

308

<div class="header">${oneTime(x => x.appName)}</div>

309

<div class="version">Version: ${oneTime(x => x.version)}</div>

310

311

<!-- One-time binding for configuration -->

312

<div class="config" data-theme="${oneTime(x => x.theme)}">

313

Content that uses theme but doesn't need updates

314

</div>

315

316

<!-- Mixed bindings: static and dynamic -->

317

<div class="status-panel">

318

<h3>${oneTime(x => x.panelTitle)}</h3>

319

<span class="current-status">${x => x.currentStatus}</span>

320

<div class="build-info">

321

Built on: ${oneTime(x => x.buildDate)}

322

</div>

323

</div>

324

325

<!-- Performance optimization for expensive calculations -->

326

<div class="expensive-calc">

327

${oneTime(x => x.performExpensiveCalculation())}

328

</div>

329

`;

330

331

@customElement({

332

name: "static-component",

333

template

334

})

335

export class StaticComponent extends FASTElement {

336

@attr appName: string = "My Application";

337

@attr version: string = "1.0.0";

338

@attr theme: string = "light";

339

@attr panelTitle: string = "System Status";

340

@attr currentStatus: string = "Running";

341

@attr buildDate: string = new Date().toISOString();

342

343

// Expensive calculation that only needs to run once

344

performExpensiveCalculation(): string {

345

console.log("Performing expensive calculation...");

346

// Simulate expensive operation

347

return "Calculated result: " + Math.random().toString(36);

348

}

349

}

350

351

// One-time binding with security policy

352

const secureOneTime = oneTime(

353

x => x.trustedContent,

354

mySecurityPolicy

355

);

356

```

357

358

### Signal Binding

359

360

Creates signal-based bindings that update when specific signals are sent, providing a publish-subscribe pattern for reactive updates.

361

362

```typescript { .api }

363

/**

364

* Creates a signal binding configuration

365

* @param expression - The binding to refresh when signaled

366

* @param options - The signal name or expression to retrieve the signal name

367

* @param policy - The security policy to associate with the binding

368

* @returns A signal binding configuration

369

*/

370

function signal<T = any>(

371

expression: Expression<T>,

372

options: string | Expression<T>,

373

policy?: DOMPolicy

374

): Binding<T>;

375

376

/**

377

* Gateway to signal APIs for publish-subscribe communication

378

*/

379

const Signal: {

380

/**

381

* Subscribes to a signal

382

* @param signal - The signal name to subscribe to

383

* @param subscriber - The subscriber to receive notifications

384

*/

385

subscribe(signal: string, subscriber: Subscriber): void;

386

387

/**

388

* Unsubscribes from a signal

389

* @param signal - The signal name to unsubscribe from

390

* @param subscriber - The subscriber to remove

391

*/

392

unsubscribe(signal: string, subscriber: Subscriber): void;

393

394

/**

395

* Sends the specified signal to all subscribers

396

* @param signal - The signal name to send

397

*/

398

send(signal: string): void;

399

};

400

```

401

402

**Usage Examples:**

403

404

```typescript

405

import { FASTElement, customElement, html, attr } from "@microsoft/fast-element";

406

import { signal, Signal } from "@microsoft/fast-element/binding/signal.js";

407

408

const template = html<SignalComponent>`

409

<!-- Signal binding with static signal name -->

410

<div class="theme-indicator">

411

Current theme: ${signal(x => x.getCurrentTheme(), "theme-changed")}

412

</div>

413

414

<!-- Signal binding with dynamic signal name -->

415

<div class="user-status">

416

Status: ${signal(

417

x => x.getUserStatus(x.userId),

418

x => `user-${x.userId}-changed`

419

)}

420

</div>

421

422

<!-- Multiple signal bindings -->

423

<div class="notifications">

424

Messages: ${signal(x => x.getMessageCount(), "messages-updated")}

425

Alerts: ${signal(x => x.getAlertCount(), "alerts-updated")}

426

</div>

427

428

<!-- Signal binding for real-time data -->

429

<div class="live-data">

430

<span>Price: $${signal(x => x.getCurrentPrice(), "price-update")}</span>

431

<span>Change: ${signal(x => x.getPriceChange(), "price-update")}%</span>

432

</div>

433

434

<button @click="${x => x.refreshData}">Refresh All</button>

435

`;

436

437

@customElement({

438

name: "signal-component",

439

template

440

})

441

export class SignalComponent extends FASTElement {

442

@attr userId: string = "user123";

443

444

private currentTheme: string = "light";

445

private messageCount: number = 0;

446

private alertCount: number = 0;

447

private currentPrice: number = 100.0;

448

private priceChange: number = 0.0;

449

450

connectedCallback() {

451

super.connectedCallback();

452

453

// Setup signal listeners for external updates

454

this.setupSignalListeners();

455

}

456

457

getCurrentTheme(): string {

458

return this.currentTheme;

459

}

460

461

getUserStatus(userId: string): string {

462

// Simulate getting user status

463

return "online";

464

}

465

466

getMessageCount(): number {

467

return this.messageCount;

468

}

469

470

getAlertCount(): number {

471

return this.alertCount;

472

}

473

474

getCurrentPrice(): number {

475

return this.currentPrice;

476

}

477

478

getPriceChange(): number {

479

return this.priceChange;

480

}

481

482

refreshData() {

483

// Manually trigger updates

484

Signal.send("theme-changed");

485

Signal.send(`user-${this.userId}-changed`);

486

Signal.send("messages-updated");

487

Signal.send("alerts-updated");

488

Signal.send("price-update");

489

}

490

491

private setupSignalListeners() {

492

// Listen for external theme changes

493

window.addEventListener('theme-changed', () => {

494

this.currentTheme = document.documentElement.getAttribute('data-theme') || 'light';

495

Signal.send("theme-changed");

496

});

497

498

// Listen for WebSocket updates

499

this.setupWebSocketListeners();

500

501

// Listen for user events

502

document.addEventListener('user-event', (e: CustomEvent) => {

503

Signal.send(`user-${e.detail.userId}-changed`);

504

});

505

}

506

507

private setupWebSocketListeners() {

508

// Simulate WebSocket connection

509

setInterval(() => {

510

// Simulate price updates

511

this.currentPrice += (Math.random() - 0.5) * 2;

512

this.priceChange = (Math.random() - 0.5) * 10;

513

Signal.send("price-update");

514

}, 1000);

515

516

setInterval(() => {

517

// Simulate message updates

518

this.messageCount = Math.floor(Math.random() * 10);

519

Signal.send("messages-updated");

520

}, 5000);

521

}

522

}

523

524

// External signal senders

525

class DataService {

526

static updateTheme(newTheme: string) {

527

// Update theme and signal change

528

document.documentElement.setAttribute('data-theme', newTheme);

529

Signal.send("theme-changed");

530

}

531

532

static updateMessages(count: number) {

533

// Signal message count change

534

Signal.send("messages-updated");

535

}

536

537

static updateAlerts(count: number) {

538

// Signal alert count change

539

Signal.send("alerts-updated");

540

}

541

}

542

```

543

544

### Binding Base Class

545

546

Abstract base class for all binding types, providing common functionality for expression evaluation and observer creation.

547

548

```typescript { .api }

549

/**

550

* Captures a binding expression along with related information and capabilities

551

*/

552

abstract class Binding<TSource = any, TReturn = any, TParent = any> {

553

/** Options associated with the binding */

554

options?: any;

555

556

/**

557

* Creates a binding

558

* @param evaluate - Function that evaluates the binding

559

* @param policy - The security policy to associate with this binding

560

* @param isVolatile - Indicates whether the binding is volatile

561

*/

562

constructor(

563

public evaluate: Expression<TSource, TReturn, TParent>,

564

public policy?: DOMPolicy,

565

public isVolatile: boolean = false

566

);

567

568

/**

569

* Creates an observer capable of notifying a subscriber when the binding output changes

570

* @param subscriber - The subscriber to changes in the binding

571

* @param directive - The binding directive to create the observer for

572

*/

573

abstract createObserver(

574

subscriber: Subscriber,

575

directive: BindingDirective

576

): ExpressionObserver<TSource, TReturn, TParent>;

577

}

578

579

/**

580

* The directive from which a binding originates

581

*/

582

interface BindingDirective {

583

/** The binding */

584

readonly dataBinding: Binding;

585

586

/** The evaluated target aspect */

587

readonly targetAspect?: string;

588

589

/** The type of aspect to target */

590

readonly aspectType?: DOMAspect;

591

}

592

```

593

594

**Usage Examples:**

595

596

```typescript

597

import { Binding, BindingDirective, Subscriber, ExpressionObserver } from "@microsoft/fast-element";

598

599

// Custom binding implementation

600

class CustomBinding<TSource = any, TReturn = any, TParent = any>

601

extends Binding<TSource, TReturn, TParent> {

602

603

createObserver(

604

subscriber: Subscriber,

605

directive: BindingDirective

606

): ExpressionObserver<TSource, TReturn, TParent> {

607

return new CustomObserver(this, subscriber, directive);

608

}

609

}

610

611

// Custom observer implementation

612

class CustomObserver<TSource = any, TReturn = any, TParent = any>

613

implements ExpressionObserver<TSource, TReturn, TParent> {

614

615

constructor(

616

private binding: CustomBinding<TSource, TReturn, TParent>,

617

private subscriber: Subscriber,

618

private directive: BindingDirective

619

) {}

620

621

bind(controller: ExpressionController<TSource, TParent>): TReturn {

622

// Custom binding logic

623

const result = this.binding.evaluate(controller.source, controller.context);

624

625

// Set up custom change detection

626

this.setupChangeDetection(controller);

627

628

return result;

629

}

630

631

unbind(controller: ExpressionController<TSource, TParent>): void {

632

// Custom cleanup logic

633

this.cleanupChangeDetection();

634

}

635

636

private setupChangeDetection(controller: ExpressionController<TSource, TParent>): void {

637

// Custom change detection implementation

638

}

639

640

private cleanupChangeDetection(): void {

641

// Custom cleanup implementation

642

}

643

}

644

645

// Factory function for custom binding

646

function customBinding<T = any>(

647

expression: Expression<T>,

648

options?: any

649

): Binding<T> {

650

const binding = new CustomBinding(expression);

651

binding.options = options;

652

return binding;

653

}

654

```

655

656

### Expression Normalization

657

658

Utilities for normalizing and processing binding expressions to ensure consistent behavior across different binding types.

659

660

```typescript { .api }

661

/**

662

* Normalizes binding expressions for consistent processing

663

* @param expression - The expression to normalize

664

* @returns The normalized binding expression

665

*/

666

function normalizeBinding<T = any>(expression: Expression<T>): Binding<T>;

667

```

668

669

## Types

670

671

```typescript { .api }

672

/**

673

* A function or string that represents a binding expression

674

*/

675

type Expression<TReturn = any, TSource = any, TParent = any> =

676

| ((source: TSource, context: ExecutionContext<TParent>) => TReturn)

677

| string;

678

679

/**

680

* Execution context for binding evaluation

681

*/

682

interface ExecutionContext<TParent = any> {

683

/** Current index in a repeat context */

684

index: number;

685

686

/** Length of the collection in a repeat context */

687

length: number;

688

689

/** Parent data source */

690

parent: TParent;

691

692

/** Parent execution context */

693

parentContext: ExecutionContext<TParent>;

694

}

695

696

/**

697

* Observer interface for expression changes

698

*/

699

interface ExpressionObserver<TSource = any, TReturn = any, TParent = any> {

700

/**

701

* Binds the observer to a controller

702

* @param controller - The expression controller

703

*/

704

bind(controller: ExpressionController<TSource, TParent>): TReturn;

705

706

/**

707

* Unbinds the observer from a controller

708

* @param controller - The expression controller

709

*/

710

unbind?(controller: ExpressionController<TSource, TParent>): void;

711

}

712

713

/**

714

* Controller for managing expression lifecycle

715

*/

716

interface ExpressionController<TSource = any, TParent = any> {

717

/** The data source */

718

source: TSource;

719

720

/** The execution context */

721

context: ExecutionContext<TParent>;

722

723

/**

724

* Registers a callback for when the controller unbinds

725

* @param callback - The callback to register

726

*/

727

onUnbind(callback: any): void;

728

}

729

730

/**

731

* Subscriber interface for change notifications

732

*/

733

interface Subscriber {

734

/**

735

* Handles changes in observed values

736

* @param source - The source of the change

737

* @param args - Arguments related to the change

738

*/

739

handleChange(source: any, args: any): void;

740

}

741

```