Mutable vector

Mutable<T> is awesome if you are storing single values. But if you have a Mutable<Vec<T>>, we must always update the entire vec when we wish to change it. Consider this example.

let data = Mutable::new(vec![1,2,3]);
let dom_vec_signal = data.signal_ref(|values: &Vec<i32>| {
    values.iter().map(|value: &i32| {
        html!("div", {
            .text(&format!("{value}"))
        })
    }).collect::<Vec<_>>()
});

let mut changed_vec = data.get_cloned();

changed_vec.pop();
changed_vec[0] = 666;
data.set(changed_vec);

When an element is added to, removed from or changed in the vector, we have to redraw all the elements in our list. This is perhaps ok for very small vectors, but it is a big performance hit when dealing with bigger lists, since it will cause massive unnecessary layout recalculations.

But we can do better. In fact, we can achieve near perfect granularity when updating the dom by using MutableVec<T> and the associated SignalVec<Item=T>.

let data = MutableVec::new_with_values(vec![1,2,3]);
let dom_signal_vec = data.signal_vec().map(|value: i32| {
    html!("div", {
        .text(&format!("{value}"))
    })
});

data.lock_mut().pop();
data.lock_mut().set(0, 42);

html!("div", {
    .children_signal_vec(dom_signal_vec)
})

As you see in the latter example, we can change individual elements of a vec. This propagates through the SignalVec we get from the MutableVec instance by simply forwarding the diff produced by the mutation.

Notice how the DomBuilder has a children_signal_vec() method. This accepts a SignalVec<Dom>, and will ensure optimal DOM updates based on the changes to the signal.

It's the signal counterpart to the .children() method we saw in the introductory chapter!

This means we can map the SignalVec on a per-element basis, and dominator in turn can optimally update only the parts of the DOM that require changing as a result of a diff! This ensures that we don't cause any needless layout shifts or recalculations, which is essential for writing performant web applications.

SignalVec in SignalVec

Sometimes, we run into the "sibling problem" when composing UIs. This typical if we have a component that wishes to have a sibling dependent on some internal state. For instance, if we are rendering a table view, and a row wishes to have a following containing more details if it is " expanded".

We can solve this by nesting SignalVec inside of SignalVec, and then use the .flatten() method to convert into a single dimension SignalVec.

The following example shows how we convert a SignalVec<Item=i32> to a SignalVec<Item=SignalVec<Item=String>>, where the inner signal vec has a number of rows equal to its input value. We then use .flatten() to convert it into a SignalVec<Item=String> before we finally map it to a SignalVec<Item=Dom>.

let items = MutableVec::new_with_values(vec![1, 2, 3]);
let nested_signal_vec = items
    .signal_vec()
    .map(|item: i32| {

    futures_signals::signal_vec::always(
        (0..item).map(|value| {
            format!("{item}:{value}")
        })
        .collect()
    )
});

let flattened_dom_signal_vec = nested_signal_vec.flatten().map(|v: String|{
    html!("span", { .text(&v)})
});

items.lock_mut().set(1, 5);

html!("div", {
    .children_signal_vec(flattened_dom_signal_vec)
})

Pay attention to what happens when we replace the element at index 1 with the number 5. The signal vec at position 1 will receive an update, re-evaluate and produce an output containing 5 instead of 3 rows.

Signal<Vec<> -> SignalVec -> Signal<Vec>

Sometimes, we want to fit a square peg into a round hole. We may wish to convert between Signal<Item=Vec<T>> and SignalVec<Item=T> for reasons outside our control.

Luckily, this is trivially done using the .to_signal_vec() and to_signal_cloned() methods.

fn consume_signal_vec(sig: impl SignalVec<Item = ()>) {}
fn consume_signal_cloned(sig: impl Signal<Item = Vec<()>>) {}

fn conversion() {
    let v1 = Mutable::new(vec![()]);
    let v2 = MutableVec::new_with_values(vec![()]);

    // Call a function expecting SignalVec<Item=()>
    consume_signal_vec(v1.signal_cloned().to_signal_vec());

    // Call a function expecting Signal<Item=Vec<()>>
    consume_signal_cloned(v2.signal_vec().to_signal_cloned());
}

Note that if you have a SignalVec made this way, any update in the root signal will cause the entire vec to be replaced; this is simply a type coercion and not a magical incantation.

If you need fine-grained updates, make sure you convert your data model into using a MutableVec properly!

Enumerate

We can call .enumerate() on a signal vec to convert it into a SignalVec<Item=(ReadOnlyMutalbe<Option<usize>>, T)>. The read only mutable contains the index of each element, so this behaves similar to the standard iterator enumerate() function.

The reason the index is a mutable rather than just a number is that when reordering or removing elements in a vector, we can get a signal to the changed index (or lack thereof) for each element.

let data = MutableVec::new_with_values(vec![1, 2, 3]);
data.signal_vec()
    .enumerate()
    .map(|(index, value): (ReadOnlyMutable<Option<usize>>, i32)| {
        html!("div", {
            .text_signal(index.signal_ref(|idx| {
                match idx {
                    Some(idx) => format!("I am at index {idx}"),
                    _ => format!("I am removed!")
                }
            }))
        })
});