Choosing an operator

Decision tables that map what you want a collection view to do onto the operator that does it incrementally.

With more than 200 operator names (the rustdoc of v1.1.0 lists over 260 distinct method names across the collection operator traits), the hard part is picking one. Find the row that describes what you want; the right-hand column names the operator and the page that explains it.

#Start from the data

You haveUseWhy
One value that changesSignal<T>Signals
A value derived from one to three signalsComputed<T>glitch-free, synchronous
A moment, not state (“saved”)EventBus<T>late subscribers do not see it
Items with a stable idSourceCache<K, T>keys, keyed sorting, joins, watch
Items where order is the data, or duplicates are meaningfulSourceList<T>positions, AddRange, Move
A full snapshot from a poll or queryedit_diff on a source, or edit_diff(key) on a streamminimal changes, row identity kept
A stream of single items or batchespopulate_from*, to_*_change_set*Materialization
An imperative producer (socket, callback)create_cache_change_streamthe factory owns start and stop

#Which items appear

You wantUse
a fixed conditionfilter
a condition from a signal (search text, toggle)filter_with_state(signal.values(), ...)
a whole predicate chosen at runtimefilter_dynamic
each item to say whether it is visible (visible: Signal<bool> on the item)filter_on_observable
a filter to re-test items that changed in placerefresh_key / auto_refresh before the filter
to ignore trivial updates (jitter, identical values)ignore_update_when, ignore_same_value_update, or add_or_update_if_changed at the source
only items of one concrete typeof_type::<U>()
the union / intersection / difference of collectionsor / and / except (static), or_dynamic (changing set)
items that satisfy a per-item stream for all / anytrue_for_all*, true_for_any*

#What each item looks like

You wantUse
a pure projectiontransform
to keep a row ViewModel’s UI state across replacementslist transform_with_previous; cache transform_inline
a failure in one item not to reset the viewtransform_safe
to recompute when a theme, locale or rate changestransform_with_forced_all
the latest value of each item’s own streamtransform_on_observable
an I/O-bound projectiontransform_async (worker thread)
several children per itemtransform_many
a different keychange_key, add_key (list to cache), remove_key (cache to list)

#In what order

You wantUse
a sorted list from a listsort_by_key, sort_by
a sorted grid that keeps row identitycache sort_keyed_by_key, sort_keyed_by
user-selectable columnssort_keyed_dynamic(comparers)
items to re-sort when an item’s key mutates in placerefresh_key, or sort_keyed_with_resort
each row to know its positionupdate_index
newest firstlist reverse()

#How many are visible

You wantUse
paged results with a page signalsort_keyed_* then page_keyed_dynamic(signal.values())
page counts for a pagerpage_keyed_dynamic_with_response (total_count, total_pages)
a scroll window over a long listvirtualize_keyed_dynamic_with_response
only visible values, no keys neededsort_and_page_*, sort_and_virtualize_*
the first ntop(n), top_keyed(n) (n must be above zero)
a bounded feed (last 100 events)to_list_change_set_with_options(limit_size_to(100)), or limit_size_to on a source
items that disappear on a timerexpire_after* (source or stream)

#Summaries and structure

You wantUse
a count, sum, average, deviationcount, sum_by, avg_by, std_dev_by (incremental)
a maximum or minimummax_by, min_by (snapshot-based), or sort_keyed_* then top_keyed(1) for large data
a whole-collection queryquery_when_changed_with (clones per batch)
a button enabled when a collection is not emptyComputed::from_stream(&stream.is_not_empty(), false) into Command::with_can_execute
sections with independent live contentsgroup (live groups)
plain group snapshotsgroup_immutable
“group by” chosen at runtimegroup_dynamic
facets or tagsdistinct_values
left join / inner join by keyleft_join, inner_join, …
parent with a live set of childrenleft_join_many, inner_join_many, …
a tree from parent idstransform_to_tree
one value’s changes by keywatch_value, watch_optional

#When it is delivered

You wantUse
to hold updates while the user drags or importsbatch_if(paused.values(), false)
one delivery per framebatch(frame) or batch_with_scheduler
the startup burst delivered oncebuffer_initial(duration)
a loading indicatormonitor_status(), with start_with_empty()
live changes without the snapshotskip_initial()
an initial “empty” notificationstart_with_empty(), count().start_with(0)
to drop Refresh or Move changessuppress_refresh(), treat_moves_as_remove_add()

#Lifetimes and sharing

You wantUse
work to start and stop with each itemsubscribe_many
item resources released when items leavedispose_many*
the view to react to a field inside itemsauto_refresh(|item, key| item.field.changes())
one stream of every item’s valuesmerge_many*, when_value_changed*
a read-only handle to expose from a serviceas_observable_list, as_observable_cache
several consumers sharing one expensive pipelineref_count() or a materialized wrapper
a view of whichever source is currently selectedswitch()
a log of what a pipeline emitsfor_each_batch, for_each_change
statistics about a pipeline (feature diagnostics)collect_update_stats

#A worked pipeline

A searchable, sorted, paged grid row by row: filter from a signal, keyed sort, keyed page from a signal, then a single materialization the view binds.

use dxn_reactive::collections::prelude::*;
use dxn_reactive::prelude::Signal;

let people = SourceCache::new(|person: &(u32, &'static str)| person.0);
people.add_or_update_range([(1, "Grace"), (2, "Alan"), (3, "Ada"), (4, "Barbara")])?;

let search = Signal::new(String::new());
let page = Signal::new(PageRequest::new(0, 2));

let view = people
    .connect()
    .filter_with_state(search.values(), |text: &String, person: &(u32, &str)| {
        person.1.to_lowercase().contains(&text.to_lowercase())
    })
    .sort_keyed_by_key(|person| person.1)
    .page_keyed_dynamic(page.values())
    .as_observable_list()?;

let names = |view: &ObservableList<(u32, &'static str)>| -> Vec<&'static str> {
    view.items().unwrap().into_iter().map(|person| person.1).collect()
};
assert_eq!(names(&view), ["Ada", "Alan"]);

search.set("a".into())?;
page.set(PageRequest::new(1, 2))?;
assert_eq!(names(&view), ["Barbara", "Grace"]);

Next: Application patterns, then the Recipes.