Skip to content

DynamicProperty

Overview

Creates a Property that does synchronization of values with a swappable Property that itself can change. Handles the case where you need a Property that can switch between acting like multiple other Properties.

With no other options specified, the value of this Property is: - null, if valuePropertyProperty.value === null - valuePropertyProperty.value.value otherwise

The value of this Property (generalized, with the options available) is: - derive( defaultValue ), if valuePropertyProperty.value === null - map( derive( valuePropertyProperty.value ).value ) otherwise

Generally, this DynamicProperty uses one-way synchronization (it only listens to the source), but if the 'bidirectional' option is true, it will use two-way synchronization (changes to this Property will change the active source). Thus when this Property changes value (when bidirectional is true), it will set: - derive( valuePropertyProperty.value ).value = inverseMap( this.value ), if valuePropertyProperty.value !== null


General example


const firstProperty = new Property( Color.RED ); const secondProperty = new Property( Color.BLUE ); const currentProperty = new Property( firstProperty ); // {Property.<Property.<Color>>}

const backgroundFill = new DynamicProperty( currentProperty ) // Turns into a {Property.<Color>} backgroundFill.value; // Color.RED, since: currentProperty.value === firstProperty and firstProperty.value === Color.RED firstProperty.value = Color.YELLOW; backgroundFill.value; // Color.YELLOW - It's connected to firstProperty right now

currentProperty.value = secondProperty; backgroundFill.value; // Color.BLUE - It's the secondProperty's value

secondProperty.value = Color.MAGENTA; backgroundFill.value; // Color.MAGENTA - Yes, it's listening to the other Property now.

Also supports falling back to null if our main Property is set to null: currentProperty.value = null; backgroundFill.value; // null


'derive' option


Additionally, DynamicProperty supports the ability to derive the Property value from our main Property's value. For example, say you have multiple scenes each with the type: scene: { backgroundColorProperty: {Property.<Color>} } and you have a currentSceneProperty: {Property.<Scene>}, you may want to create: const currentBackgroundColorProperty = new DynamicProperty( currentSceneProperty, { derive: 'backgroundColorProperty' } ); This would always report the current scene's current background color. What if you sometimes don't have a scene active, e.g. {Property.<Scene|null>}? You can provide a default value: new DynamicProperty( currentSceneProperty, { derive: 'backgroundColorProperty', defaultValue: Color.BLACK } ); So that if the currentSceneProperty's value is null, the value of our DynamicProperty will be Color.BLACK. NOTE there are constraints using derive: 'string' when using parametric type parameters. See https://github.com/phetsims/projectile-data-lab/issues/10


'bidirectional' option


If you would like for direct changes to this Property to change the original source (bidirectional synchronization), then pass bidirectional:true: const firstProperty = new Property( 5 ); const secondProperty = new Property( 10 ); const numberPropertyProperty = new Property( firstProperty ); const dynamicProperty = new DynamicProperty( numberPropertyProperty, { bidirectional: true } ); dynamicProperty.value = 2; // allowed now that it is bidirectional, otherwise prohibited firstProperty.value; // 2 numberPropertyProperty.value = secondProperty; // change which Property is active dynamicProperty.value; // 10, from the new Property dynamicProperty.value = 0; secondProperty.value; // 0, set above. firstProperty.value; // still 2 from above, since our dynamic Property switched to the other Property


'map' and 'inverseMap' options


DynamicProperty also supports mapping values to different types. For example, say we have a numberPropertyProperty {Property.<Property.<number>>}, but want to have a {Property.<string>} as the output. Then: new DynamicProperty( numberPropertyProperty, { map: function( number ) { return '' + number; } } ); will do the trick. If this needs to be done with a bidirectional DynamicProperty, also include inverseMap: new DynamicProperty( numberPropertyProperty, { bidirectional: true, map: function( number ) { return '' + number; }, inverseMap: function( string ) { return Number.parseFloat( string ); } } ); so that changes to the dynamic Property will result in a change in the numberPropertyProperty's value.

@author Jonathan Olson <jonathan.olson@colorado.edu>

Class DynamicProperty

ThisValueType: The value type of the resulting DynamicProperty InnerValueType: The value type of the inner (derived) Property, whose value gets mapped to ThisValueType and back OuterValueType: The value type of the main passed-in Property (whose value may be derived to the InnerValueType) e.g.: class Foo { colorProperty: Property<Color> } new DynamicProperty<number, Color, Foo>( someFooProperty, { derive: 'colorProperty', map: ( color: Color ) => color.alpha } ); Here, ThisValueType=number (we're a Property<number>). You've passed in a Property<Foo>, so OuterValueType is a Foo. InnerValueType is what we get from our derive (Color), and what the parameter of our map is.

import { DynamicProperty } from 'scenerystack/axon';

Constructor

new DynamicProperty( valuePropertyProperty : TNullableProperty<OuterValueType> | TReadOnlyProperty<OuterValueType>, providedOptions? : DynamicPropertyOptions<ThisValueType, InnerValueType, OuterValueType> )

Instance Methods

dispose()

Disposes this Property

reset()

Resets the current property (if it's a Property instead of a TinyProperty)

set( value : ThisValueType )

Prevent setting this Property manually if it is not marked as bidirectional.

isSettable() : boolean

Returns true if this Property value can be set externally, by set() or .value =

Instance Properties

derive : ( u: OuterValueType ) => TReadOnlyProperty<InnerValueType>

(protected, readonly)

map : ( v: InnerValueType ) => ThisValueType

(protected, readonly)

inverseMap : ( t: ThisValueType ) => InnerValueType

(protected, readonly)

bidirectional : boolean

(protected, readonly)

Type DynamicPropertyOptions

import type { DynamicPropertyOptions } from 'scenerystack/axon';
  • bidirectional?: boolean
    If set to true then changes to this Property (if valuePropertyProperty.value is non-null at the time) will also be made to derive( valuePropertyProperty.value ).
  • defaultValue?: InnerValueType
    If valuePropertyProperty.value === null, this dynamicProperty will act instead like derive( valuePropertyProperty.value ) === new Property( defaultValue ). Note that if a custom map function is provided, it will be applied to this defaultValue to determine our Property's value.
  • derive?: ( ( outerValue: OuterValueType ) => TReadOnlyProperty<InnerValueType> ) | KeysMatching<OuterValueType, TReadOnlyProperty<InnerValueType>>
    Maps a non-null valuePropertyProperty.value into the Property to be used. See top-level documentation for usage. If it's a string, it will grab that named property out (e.g. it's like passing u => u[ derive ]) NOTE: This accepts TReadOnlyProperty, but if you have bidirectional:true it must be a full TProperty. This is not currently type checked. NOTE there are constraints using derive: 'string' when using parametric type parameters. See https://github.com/phetsims/projectile-data-lab/issues/10
  • map?: ( ( innerValue: InnerValueType ) => ThisValueType ) | KeysMatching<InnerValueType, ThisValueType>
    Maps our input Property value to/from this Property's value. See top-level documentation for usage. If it's a string, it will grab that named property out (e.g. it's like passing u => u[ derive ])
  • inverseMap?: ( ( value: ThisValueType ) => InnerValueType ) | KeysMatching<ThisValueType, InnerValueType>
  • & PropertyOptions<ThisValueType>

Type TNullableProperty

import type { TNullableProperty } from 'scenerystack/axon';

TReadOnlyProperty<T | null> | TReadOnlyProperty<T>

Source Code

See the source for DynamicProperty.ts in the axon repository.