0
# Observable System
1
2
Memory-efficient observable system with automatic dependency tracking, batch updates, and array observation capabilities for building reactive applications with minimal overhead.
3
4
## Capabilities
5
6
### Observable Decorator
7
8
Decorator function for making class properties observable, enabling automatic change detection and notification when property values change.
9
10
```typescript { .api }
11
/**
12
* Decorator: Defines an observable property on the target
13
* @param target - The target to define the observable on
14
* @param nameOrAccessor - The property name or accessor to define the observable as
15
*/
16
function observable(target: {}, nameOrAccessor: string | Accessor): void;
17
18
/**
19
* Overloaded signature for use with custom accessors
20
* @param target - The target object
21
* @param nameOrAccessor - The property accessor
22
* @param descriptor - The property descriptor
23
*/
24
function observable<T, K extends keyof T>(
25
target: T,
26
nameOrAccessor: K
27
): void;
28
function observable<T, K extends keyof T>(
29
target: T,
30
nameOrAccessor: K,
31
descriptor: PropertyDescriptor
32
): PropertyDescriptor;
33
```
34
35
**Usage Examples:**
36
37
```typescript
38
import { FASTElement, customElement, html, observable, attr } from "@microsoft/fast-element";
39
40
@customElement("observable-example")
41
export class ObservableExample extends FASTElement {
42
// Basic observable property
43
@observable firstName: string = "";
44
@observable lastName: string = "";
45
@observable age: number = 0;
46
47
// Observable arrays
48
@observable items: string[] = [];
49
@observable users: User[] = [];
50
51
// Observable objects
52
@observable settings: UserSettings = {
53
theme: "light",
54
notifications: true
55
};
56
57
// Computed properties (getters that depend on observables)
58
get fullName(): string {
59
return `${this.firstName} ${this.lastName}`.trim();
60
}
61
62
get isAdult(): boolean {
63
return this.age >= 18;
64
}
65
66
get itemCount(): number {
67
return this.items?.length ?? 0;
68
}
69
70
// Methods that modify observable properties
71
addItem(item: string) {
72
this.items.push(item);
73
// Observable array mutation is automatically tracked
74
}
75
76
updateSettings(newSettings: Partial<UserSettings>) {
77
Object.assign(this.settings, newSettings);
78
// Object property changes are tracked when accessed through observables
79
}
80
81
clearAll() {
82
this.firstName = "";
83
this.lastName = "";
84
this.age = 0;
85
this.items = [];
86
this.users = [];
87
}
88
}
89
90
// Template that uses observable properties
91
const template = html<ObservableExample>`
92
<div class="user-info">
93
<h2>${x => x.fullName || 'No name'}</h2>
94
<p>Age: ${x => x.age} ${x => x.isAdult ? '(Adult)' : '(Minor)'}</p>
95
<p>Items: ${x => x.itemCount}</p>
96
</div>
97
98
<div class="items">
99
${x => x.items.map(item => `<div class="item">${item}</div>`).join('')}
100
</div>
101
102
<div class="settings">
103
Theme: ${x => x.settings.theme}
104
Notifications: ${x => x.settings.notifications ? 'On' : 'Off'}
105
</div>
106
`;
107
108
// Observable properties in nested objects
109
export class NestedObservables {
110
@observable user = {
111
profile: {
112
name: "John",
113
email: "john@example.com"
114
},
115
preferences: {
116
theme: "dark",
117
language: "en"
118
}
119
};
120
121
// Method to update nested properties
122
updateUserName(name: string) {
123
// This will trigger change notifications
124
this.user.profile.name = name;
125
}
126
127
updateTheme(theme: string) {
128
// This will trigger change notifications
129
this.user.preferences.theme = theme;
130
}
131
}
132
```
133
134
### Observable Core APIs
135
136
Central object providing utilities for observable creation, tracking, and notification management.
137
138
```typescript { .api }
139
/**
140
* Common Observable APIs for managing reactive properties and expressions
141
*/
142
const Observable: {
143
/**
144
* Defines an observable property on a target object
145
* @param target - The object to define the property on
146
* @param nameOrAccessor - The property name or accessor
147
*/
148
defineProperty(target: any, nameOrAccessor: string | Accessor): void;
149
150
/**
151
* Gets the notifier for a source object
152
* @param source - The object to get the notifier for
153
* @returns The notifier for change notifications
154
*/
155
getNotifier(source: any): Notifier;
156
157
/**
158
* Creates a binding observer for an expression
159
* @param evaluate - The expression to observe
160
* @param observer - The observer to notify on changes
161
* @param isVolatile - Whether the binding is volatile
162
* @returns An expression observer
163
*/
164
binding<T>(
165
evaluate: Expression<T>,
166
observer: ExpressionObserver,
167
isVolatile?: boolean
168
): ExpressionObserver<any, T>;
169
170
/**
171
* Tracks property access for dependency collection
172
* @param target - The target object
173
* @param propertyName - The property being accessed
174
*/
175
track(target: any, propertyName: PropertyKey): void;
176
177
/**
178
* Notifies observers of property changes
179
* @param source - The source object that changed
180
* @param args - Arguments describing the change
181
*/
182
notify(source: any, args: any): void;
183
184
/**
185
* Determines if an expression should be treated as volatile
186
* @param expression - The expression to check
187
* @returns True if the expression is volatile
188
*/
189
isVolatileBinding(expression: Expression<any>): boolean;
190
191
/**
192
* Marks the current evaluation as volatile
193
*/
194
trackVolatile(): void;
195
196
/**
197
* Sets the array observer factory
198
* @param factory - Function that creates array observers
199
*/
200
setArrayObserverFactory(factory: (array: any[]) => Notifier): void;
201
};
202
```
203
204
**Usage Examples:**
205
206
```typescript
207
import { Observable, Notifier, Subscriber } from "@microsoft/fast-element";
208
209
// Manual observable property definition
210
class ManualObservable {
211
private _value: string = "";
212
213
constructor() {
214
// Define observable property manually
215
Observable.defineProperty(this, "value");
216
}
217
218
get value(): string {
219
return this._value;
220
}
221
222
set value(newValue: string) {
223
if (this._value !== newValue) {
224
const oldValue = this._value;
225
this._value = newValue;
226
// Notify observers of the change
227
Observable.notify(this, { oldValue, newValue });
228
}
229
}
230
}
231
232
// Custom subscriber for observable changes
233
class CustomSubscriber implements Subscriber {
234
handleChange(source: any, args: any): void {
235
console.log("Property changed:", args);
236
}
237
}
238
239
// Track property access and subscribe to changes
240
const obj = new ManualObservable();
241
const notifier = Observable.getNotifier(obj);
242
const subscriber = new CustomSubscriber();
243
244
notifier.subscribe(subscriber, "value");
245
246
obj.value = "new value"; // Triggers subscriber notification
247
248
// Custom expression observer
249
class LoggingObserver implements Subscriber {
250
handleChange(source: any, args: any): void {
251
console.log("Expression changed:", source, args);
252
}
253
}
254
255
const target = { name: "John", age: 30 };
256
Observable.defineProperty(target, "name");
257
Observable.defineProperty(target, "age");
258
259
// Create expression observer
260
const fullNameExpression = (source: typeof target) => `${source.name} (${source.age})`;
261
const observer = new LoggingObserver();
262
const binding = Observable.binding(fullNameExpression, observer, false);
263
264
// Simulate binding
265
const controller = {
266
source: target,
267
context: { index: 0, length: 1, parent: null, parentContext: null },
268
isBound: true,
269
onUnbind: () => {},
270
sourceLifetime: undefined
271
};
272
273
binding.bind(controller); // Sets up observation
274
275
target.name = "Jane"; // Triggers expression re-evaluation and logging
276
target.age = 31; // Triggers expression re-evaluation and logging
277
```
278
279
### Property Change Notifier
280
281
Implementation that manages change notifications for individual properties on objects.
282
283
```typescript { .api }
284
/**
285
* Manages property change notifications for an object
286
*/
287
class PropertyChangeNotifier implements Notifier {
288
/**
289
* Creates a notifier for the specified source object
290
* @param source - The object to create notifications for
291
*/
292
constructor(source: any);
293
294
/**
295
* Subscribes to changes for a specific property
296
* @param subscriber - The subscriber to notify of changes
297
* @param propertyToWatch - The property name to watch
298
*/
299
subscribe(subscriber: Subscriber, propertyToWatch?: string): void;
300
301
/**
302
* Unsubscribes from changes for a specific property
303
* @param subscriber - The subscriber to remove
304
* @param propertyToWatch - The property name to stop watching
305
*/
306
unsubscribe(subscriber: Subscriber, propertyToWatch?: string): void;
307
308
/**
309
* Notifies subscribers of property changes
310
* @param args - Arguments describing the change
311
*/
312
notify(args: any): void;
313
}
314
315
/**
316
* Set of subscribers for efficient notification management
317
*/
318
class SubscriberSet {
319
/**
320
* Adds a subscriber to the set
321
* @param subscriber - The subscriber to add
322
*/
323
add(subscriber: Subscriber): boolean;
324
325
/**
326
* Removes a subscriber from the set
327
* @param subscriber - The subscriber to remove
328
*/
329
remove(subscriber: Subscriber): boolean;
330
331
/**
332
* Notifies all subscribers in the set
333
* @param args - Arguments to pass to subscribers
334
*/
335
notify(args: any): void;
336
}
337
```
338
339
**Usage Examples:**
340
341
```typescript
342
import { PropertyChangeNotifier, Subscriber, Observable } from "@microsoft/fast-element";
343
344
// Custom object with manual change notifications
345
class DataModel {
346
private _name: string = "";
347
private _status: string = "pending";
348
private notifier: PropertyChangeNotifier;
349
350
constructor() {
351
this.notifier = new PropertyChangeNotifier(this);
352
}
353
354
get name(): string {
355
Observable.track(this, "name");
356
return this._name;
357
}
358
359
set name(value: string) {
360
if (this._name !== value) {
361
const oldValue = this._name;
362
this._name = value;
363
this.notifier.notify({
364
type: "change",
365
propertyName: "name",
366
oldValue,
367
newValue: value
368
});
369
}
370
}
371
372
get status(): string {
373
Observable.track(this, "status");
374
return this._status;
375
}
376
377
set status(value: string) {
378
if (this._status !== value) {
379
const oldValue = this._status;
380
this._status = value;
381
this.notifier.notify({
382
type: "change",
383
propertyName: "status",
384
oldValue,
385
newValue: value
386
});
387
}
388
}
389
390
// Subscribe to specific property changes
391
onNameChanged(callback: (oldValue: string, newValue: string) => void): void {
392
const subscriber: Subscriber = {
393
handleChange: (source, args) => {
394
if (args.propertyName === "name") {
395
callback(args.oldValue, args.newValue);
396
}
397
}
398
};
399
this.notifier.subscribe(subscriber, "name");
400
}
401
402
onStatusChanged(callback: (oldValue: string, newValue: string) => void): void {
403
const subscriber: Subscriber = {
404
handleChange: (source, args) => {
405
if (args.propertyName === "status") {
406
callback(args.oldValue, args.newValue);
407
}
408
}
409
};
410
this.notifier.subscribe(subscriber, "status");
411
}
412
}
413
414
// Usage
415
const model = new DataModel();
416
417
model.onNameChanged((oldVal, newVal) => {
418
console.log(`Name changed from ${oldVal} to ${newVal}`);
419
});
420
421
model.onStatusChanged((oldVal, newVal) => {
422
console.log(`Status changed from ${oldVal} to ${newVal}`);
423
});
424
425
model.name = "John"; // Logs: "Name changed from to John"
426
model.status = "active"; // Logs: "Status changed from pending to active"
427
```
428
429
### Array Observation
430
431
Specialized system for tracking changes in arrays, including splice operations, sorting, and length changes.
432
433
```typescript { .api }
434
/**
435
* Observes changes to arrays, tracking splices and mutations
436
*/
437
class ArrayObserver implements Notifier {
438
/**
439
* Creates an array observer for the specified array
440
* @param array - The array to observe
441
*/
442
constructor(array: any[]);
443
444
/** Subscribes to array change notifications */
445
subscribe(subscriber: Subscriber): void;
446
447
/** Unsubscribes from array change notifications */
448
unsubscribe(subscriber: Subscriber): void;
449
450
/** Notifies subscribers of array changes */
451
notify(args: any): void;
452
}
453
454
/**
455
* Observes array length changes specifically
456
*/
457
class LengthObserver {
458
/**
459
* Creates a length observer for the specified array
460
* @param array - The array to observe length changes for
461
*/
462
constructor(array: any[]);
463
464
/** Gets the current length of the observed array */
465
getValue(): number;
466
}
467
468
/**
469
* Gets an observable version of array length
470
* @param array - The array to get the length of
471
* @returns Observable length value
472
*/
473
function lengthOf(array: any[]): number;
474
475
/**
476
* Represents a splice operation on an array
477
*/
478
class Splice {
479
/** Indicates that this splice represents a complete array reset */
480
reset?: boolean;
481
482
/**
483
* Creates a splice record
484
* @param index - The index where the splice occurs
485
* @param removed - The items that were removed
486
* @param addedCount - The number of items that were added
487
*/
488
constructor(
489
public index: number,
490
public removed: any[],
491
public addedCount: number
492
);
493
494
/**
495
* Adjusts the splice index based on the provided array
496
* @param array - The array to adjust to
497
* @returns The same splice, mutated based on the reference array
498
*/
499
adjustTo(array: any[]): this;
500
}
501
502
/**
503
* Represents a sort operation on an array
504
*/
505
class Sort {
506
/**
507
* Creates a sort update record
508
* @param sorted - The updated index positions of sorted items
509
*/
510
constructor(public sorted?: number[]);
511
}
512
513
/**
514
* Strategy for handling array splice operations
515
*/
516
interface SpliceStrategy {
517
/** The level of support provided by this strategy */
518
readonly support: SpliceStrategySupport;
519
520
/**
521
* Processes array changes and returns splice records
522
* @param array - The array that changed
523
* @param oldArray - The previous state of the array
524
* @returns Array of splice records describing the changes
525
*/
526
process(array: any[], oldArray?: any[]): Splice[];
527
528
/** Resets the strategy state */
529
reset(): void;
530
}
531
532
/** Available splice strategy support levels */
533
const SpliceStrategySupport: {
534
readonly reset: 1;
535
readonly splice: 2;
536
readonly optimized: 3;
537
};
538
```
539
540
**Usage Examples:**
541
542
```typescript
543
import {
544
ArrayObserver,
545
lengthOf,
546
Splice,
547
Sort,
548
Subscriber,
549
Observable
550
} from "@microsoft/fast-element";
551
552
// Observable array in a component
553
@customElement("array-example")
554
export class ArrayExample extends FASTElement {
555
@observable items: string[] = ["apple", "banana", "orange"];
556
@observable numbers: number[] = [1, 2, 3, 4, 5];
557
558
// Computed property using lengthOf
559
get itemCount(): number {
560
return lengthOf(this.items);
561
}
562
563
// Methods that modify arrays
564
addItem(item: string) {
565
this.items.push(item); // Triggers array change notification
566
}
567
568
removeItem(index: number) {
569
this.items.splice(index, 1); // Triggers splice notification
570
}
571
572
sortItems() {
573
this.items.sort(); // Triggers sort notification
574
}
575
576
clearItems() {
577
this.items.length = 0; // Triggers array reset
578
}
579
580
replaceItems(newItems: string[]) {
581
this.items = newItems; // Triggers complete array replacement
582
}
583
584
// Bulk operations
585
addMultipleItems(items: string[]) {
586
this.items.push(...items); // Single splice operation
587
}
588
589
insertItem(index: number, item: string) {
590
this.items.splice(index, 0, item); // Insert at specific index
591
}
592
}
593
594
// Custom array subscriber
595
class ArrayChangeLogger implements Subscriber {
596
handleChange(source: any[], args: Splice | Sort): void {
597
if (args instanceof Splice) {
598
console.log("Array splice:", {
599
index: args.index,
600
removed: args.removed,
601
added: args.addedCount,
602
reset: args.reset
603
});
604
} else if (args instanceof Sort) {
605
console.log("Array sorted:", args.sorted);
606
}
607
}
608
}
609
610
// Manual array observation
611
const observableArray = ["a", "b", "c"];
612
Observable.setArrayObserverFactory((array) => new ArrayObserver(array));
613
614
const arrayNotifier = Observable.getNotifier(observableArray);
615
const logger = new ArrayChangeLogger();
616
arrayNotifier.subscribe(logger);
617
618
observableArray.push("d"); // Logs splice operation
619
observableArray.sort(); // Logs sort operation
620
observableArray.splice(1, 2, "x", "y"); // Logs splice with removal and addition
621
622
// Template that reacts to array changes
623
const arrayTemplate = html<ArrayExample>`
624
<div class="summary">
625
Total items: ${x => lengthOf(x.items)}
626
</div>
627
628
<ul class="items">
629
${x => x.items.map((item, index) =>
630
`<li data-index="${index}">${item}</li>`
631
).join('')}
632
</ul>
633
634
<div class="numbers">
635
Sum: ${x => x.numbers.reduce((sum, num) => sum + num, 0)}
636
</div>
637
638
<button @click="${x => x.addItem('new item')}">Add Item</button>
639
<button @click="${x => x.sortItems()}">Sort Items</button>
640
<button @click="${x => x.clearItems()}">Clear All</button>
641
`;
642
643
// Advanced array operations with custom splice strategy
644
class CustomSpliceStrategy implements SpliceStrategy {
645
readonly support = SpliceStrategySupport.optimized;
646
647
process(array: any[], oldArray?: any[]): Splice[] {
648
// Custom logic for optimizing splice detection
649
if (!oldArray) {
650
return [new Splice(0, [], array.length)];
651
}
652
653
// Implement custom diffing algorithm
654
const splices: Splice[] = [];
655
// ... custom splice detection logic
656
return splices;
657
}
658
659
reset(): void {
660
// Reset strategy state
661
}
662
}
663
```
664
665
### Volatile Properties
666
667
System for marking properties as volatile, ensuring they are always re-evaluated even when dependencies haven't changed.
668
669
```typescript { .api }
670
/**
671
* Decorator: Marks a property getter as having volatile observable dependencies
672
* @param target - The target that the property is defined on
673
* @param name - The property name or accessor
674
* @param descriptor - The existing property descriptor
675
* @returns Modified property descriptor that tracks volatility
676
*/
677
function volatile(
678
target: {},
679
name: string | Accessor,
680
descriptor: PropertyDescriptor
681
): PropertyDescriptor;
682
```
683
684
**Usage Examples:**
685
686
```typescript
687
import { FASTElement, customElement, html, observable, volatile } from "@microsoft/fast-element";
688
689
@customElement("volatile-example")
690
export class VolatileExample extends FASTElement {
691
@observable currentTime: Date = new Date();
692
@observable counter: number = 0;
693
694
// Volatile property - always re-evaluates
695
@volatile
696
get timestamp(): string {
697
return new Date().toISOString(); // Always returns current time
698
}
699
700
// Volatile computed property
701
@volatile
702
get randomValue(): number {
703
return Math.random(); // Always returns a new random number
704
}
705
706
// Volatile property that depends on external state
707
@volatile
708
get windowWidth(): number {
709
return window.innerWidth; // Always gets current window width
710
}
711
712
// Non-volatile computed property (for comparison)
713
get formattedCounter(): string {
714
return `Count: ${this.counter}`;
715
}
716
717
// Volatile property with complex logic
718
@volatile
719
get systemStatus(): string {
720
// This might depend on external factors that can't be tracked
721
const isOnline = navigator.onLine;
722
const memoryUsage = (performance as any).memory?.usedJSHeapSize;
723
const currentLoad = Math.random(); // Simulated system load
724
725
if (!isOnline) return "offline";
726
if (memoryUsage > 50000000) return "high-memory";
727
if (currentLoad > 0.8) return "high-load";
728
return "normal";
729
}
730
731
connectedCallback() {
732
super.connectedCallback();
733
734
// Update non-volatile observable periodically
735
setInterval(() => {
736
this.currentTime = new Date();
737
this.counter++;
738
}, 1000);
739
}
740
}
741
742
// Template that uses volatile properties
743
const volatileTemplate = html<VolatileExample>`
744
<div class="timestamps">
745
<p>Stored Time: ${x => x.currentTime.toISOString()}</p>
746
<p>Current Time: ${x => x.timestamp}</p>
747
</div>
748
749
<div class="values">
750
<p>Counter: ${x => x.formattedCounter}</p>
751
<p>Random: ${x => x.randomValue}</p>
752
<p>Window Width: ${x => x.windowWidth}px</p>
753
</div>
754
755
<div class="status">
756
System Status: ${x => x.systemStatus}
757
</div>
758
`;
759
760
// Example showing difference between volatile and non-volatile
761
@customElement("comparison-example")
762
export class ComparisonExample extends FASTElement {
763
@observable triggerUpdate: number = 0;
764
765
// Non-volatile: only re-evaluates when triggerUpdate changes
766
get nonVolatileRandom(): number {
767
console.log("Non-volatile random calculated");
768
return Math.random();
769
}
770
771
// Volatile: re-evaluates on every template update
772
@volatile
773
get volatileRandom(): number {
774
console.log("Volatile random calculated");
775
return Math.random();
776
}
777
778
triggerChange() {
779
this.triggerUpdate++;
780
}
781
}
782
783
const comparisonTemplate = html<ComparisonExample>`
784
<div>
785
<p>Trigger: ${x => x.triggerUpdate}</p>
786
<p>Non-volatile: ${x => x.nonVolatileRandom}</p>
787
<p>Volatile: ${x => x.volatileRandom}</p>
788
<button @click="${x => x.triggerChange()}">Update</button>
789
</div>
790
`;
791
792
// Custom volatile behavior
793
class CustomVolatileClass {
794
@observable baseValue: number = 10;
795
796
// Volatile getter that depends on external timing
797
@volatile
798
get timeBasedValue(): number {
799
const seconds = new Date().getSeconds();
800
return this.baseValue * (seconds % 10);
801
}
802
803
// Volatile getter for external API calls
804
@volatile
805
get externalData(): Promise<any> {
806
// Always fetches fresh data
807
return fetch('/api/current-data').then(r => r.json());
808
}
809
}
810
```
811
812
### Update Queue System
813
814
Batch update system that manages asynchronous property change notifications for optimal performance.
815
816
```typescript { .api }
817
/**
818
* Manages batched updates for optimal performance
819
*/
820
const Updates: {
821
/**
822
* Enqueues a task for batch processing
823
* @param callable - The task to enqueue
824
*/
825
enqueue(callable: Callable): void;
826
827
/**
828
* Processes all enqueued updates immediately
829
*/
830
process(): void;
831
832
/**
833
* Sets the update mode
834
* @param isAsync - Whether updates should be processed asynchronously
835
*/
836
setMode(isAsync: boolean): void;
837
};
838
839
/**
840
* Update queue for managing batched notifications
841
*/
842
const UpdateQueue: {
843
/**
844
* Enqueues an observer for update processing
845
* @param observer - The observer to enqueue
846
*/
847
enqueue(observer: any): void;
848
849
/**
850
* Processes the update queue
851
*/
852
process(): void;
853
};
854
```
855
856
## Types
857
858
```typescript { .api }
859
/**
860
* Represents a getter/setter property accessor on an object
861
*/
862
interface Accessor {
863
/** The name of the property */
864
name: string;
865
866
/**
867
* Gets the value of the property on the source object
868
* @param source - The source object to access
869
*/
870
getValue(source: any): any;
871
872
/**
873
* Sets the value of the property on the source object
874
* @param source - The source object to access
875
* @param value - The value to set the property to
876
*/
877
setValue(source: any, value: any): void;
878
}
879
880
/**
881
* A record of observable property access
882
*/
883
interface ObservationRecord {
884
/** The source object with an observable property that was accessed */
885
propertySource: any;
886
887
/** The name of the observable property that was accessed */
888
propertyName: string;
889
}
890
891
/**
892
* Describes how the source's lifetime relates to its controller's lifetime
893
*/
894
type SourceLifetime = undefined | 1; // unknown | coupled
895
896
/**
897
* Controls the lifecycle of an expression and provides relevant context
898
*/
899
interface ExpressionController<TSource = any, TParent = any> {
900
/** The source the expression is evaluated against */
901
readonly source: TSource;
902
903
/** Indicates how the source's lifetime relates to the controller's lifetime */
904
readonly sourceLifetime?: SourceLifetime;
905
906
/** The context the expression is evaluated against */
907
readonly context: ExecutionContext<TParent>;
908
909
/** Indicates whether the controller is bound */
910
readonly isBound: boolean;
911
912
/**
913
* Registers an unbind handler with the controller
914
* @param behavior - An object to call when the controller unbinds
915
*/
916
onUnbind(behavior: { unbind(controller: ExpressionController<TSource, TParent>): void }): void;
917
}
918
919
/**
920
* Observes an expression for changes
921
*/
922
interface ExpressionObserver<TSource = any, TReturn = any, TParent = any> {
923
/**
924
* Binds the expression to the source
925
* @param controller - The controller that manages the lifecycle and context
926
*/
927
bind(controller: ExpressionController<TSource, TParent>): TReturn;
928
}
929
930
/**
931
* Provides additional contextual information available to behaviors and expressions
932
*/
933
interface ExecutionContext<TParent = any> {
934
/** The index of the current item within a repeat context */
935
index: number;
936
937
/** The length of the current collection within a repeat context */
938
length: number;
939
940
/** The parent data source within a nested context */
941
parent: TParent;
942
943
/** The parent execution context when in nested context scenarios */
944
parentContext: ExecutionContext<TParent>;
945
946
/** The current event within an event handler */
947
readonly event: Event;
948
949
/** Indicates whether the current item has an even index */
950
readonly isEven: boolean;
951
952
/** Indicates whether the current item has an odd index */
953
readonly isOdd: boolean;
954
955
/** Indicates whether the current item is the first item */
956
readonly isFirst: boolean;
957
958
/** Indicates whether the current item is in the middle */
959
readonly isInMiddle: boolean;
960
961
/** Indicates whether the current item is the last item */
962
readonly isLast: boolean;
963
964
/** Returns the typed event detail of a custom event */
965
eventDetail<TDetail>(): TDetail;
966
967
/** Returns the typed event target of the event */
968
eventTarget<TTarget extends EventTarget>(): TTarget;
969
}
970
```