javascripthtmlangulartypescriptangular-signals

How to wrap an object contains a metric unit e.g. length= 10 m with Angular signals?


What is the correct implementation for an object in the signal world of angular?

A) all wrapped with signal as:

length = signal({ 
    nominalValue: signal(0),
    unit: signal ("mm")
})

B) only the key value wrapped with signals as:

length = {
    nominalValue: signal(0),
    unit: signal ("mm")
}

C) only the object itself wrapped with signal as:

length = signal( {
    nominalValue: 0,
    unit: "mm" 
})

By the html <input> field and <button>, I have to manipulate the objects' key : values.

The following example is without signals:

<input [(ngModel)] = 'length.nominalValue' (keyup) = 'keyup (length)'>

Note: the triggered keyup() function will handle the changes by spread the object and at least invoke the related post calculations.

<button (click) = 'unitSwitch(length)'>
    {{ length.unit }}
</button>

The same for the units, but manipulated by a button.


Solution

  • Option A:

    This approach is a NO, because there is no benefit in nesting signals, since the signal update from inside are not propagated to the outer signal.


    It is just a matter of convenience for the other two. Both have their advantages and disadvantages.

    Option B:

    length = {
      nominalValue: signal(0),
      unit: signal ("mm") 
    }
    

    In this method it is similar to defining the individual signals for each property that you want reactive behavior, the advantage is that the signals are neatly wrapped inside an object (So that we know what is the scope of the signals and that they are related to each other).

    But if you want reactivity, you must read the individual signals just the object length, does not have any inbuilt reactivity.

    Might be a good option when:

    1. When there are less number of properties that you want to track for changes. Maintaining inner signals for complex objects can become a pain.

    2. The advantage is that we can directly binding to [(ngModel)] when using signals directly, we need to worry about updating the root, when the inner signal changes. Since the root is an object.

    // No Reactivity
    // finalValue = computed(() => {
    //   return this.length; // is not reactive, will not update based on inner signal changes.
    // })
    // Has Reactivity
    finalValue = computed(() => {
      return `${this.length.nominalValue()} ${this.length.unit()}`; // is not reactive, will not update based on inner signal changes.
    })
    

    Option C:

    length=signal({
      nominalValue: 0,
      unit: "mm"
    })
    

    In this method, the advantage is that all related properties are grouped in a single signal. So there is no need to maintain individual signals for each property. Compared to Option B, there is more code, because we have to use update method (on ngModelChange and (cick) of button) to trigger the signal update when the inner properties changes. But the tradeoff is that, when it is a complex object. Managing the state is much easier instead of having many signals inside a complex object.

    Might be a good option when:

    1. You should prefer this methodology when there are more number of inner properties, if there are many inner properties defining too many signals can be a hassle.

    2. When the object structure is complex, you can trigger the signal update using .update, which provides the inner state of the signal, just update the inner property and return a new memory reference, which indicates that the signal has changed when the inner property is updated.