Skip to content

Utilities

CascadingValueProvider

Bit.BlazorUI

BitCascadingValueProvider replaces a stack of nested CascadingValue components with a single list, so a whole set of values reaches the descendant components from one place. Each value carries its own cascaded type, an optional name and the IsFixed and Enabled flags, and can be given eagerly, deferred until it is needed, re-read on every render or told to watch itself for changes. The list is written inline with the Values parameter or built up with the ValueList parameter, a later value shadows an earlier one of the same type or name, and changing a value refreshes the consumers on its own.

Notes

Every value here becomes a real CascadingValue component, so everything that holds for a hand-written one holds here too: consumers match by type and by name, a value that changes re-renders only the components that consume it, and an IsFixed value is never watched. Reach for a root-level cascading value (AddCascadingValue on the service collection) instead when the same value has to reach every component of the app rather than one subtree.

Usage

Every example is live. Open its code to see exactly what produced the component running underneath.

Basic

The Values parameter accepts any collection of BitCascadingValue items, and every item becomes a CascadingValue component wrapped around the child content, in the order they are listed. Thanks to the implicit conversions of BitCascadingValue, a value can be written as the bare value itself (cascaded by its type) or as a (value, name) tuple (cascaded by that name), so neither nesting nor explicit type arguments are needed. The consumer below reads all five of them with plain [CascadingParameter] properties.


Child component with cascading parameters:

Theme: Light
Notifications: 2
Authenticated: True
User (named parameter): Saleh Xafan [CTO]
User (typed parameter): Yaser Moradi [CEO]

Values

Cascading values are live: rebuilding the Values collection from the current state on each render pushes the new values down to every consumer automatically, exactly like a hand-written CascadingValue would. Only the consumers of a value that actually changed are re-rendered, so a set of unrelated values can share one provider. Play with the controls below and watch the consumer update:







Changing cascading values:

Theme: Light
Notifications: 2
Authenticated: True
User (named parameter): Saleh Xafan [CTO]
User (typed parameter): Ava Smith [Product manager]

ValueList

The ValueList parameter takes a BitCascadingValueList, whose generic Add<T> method captures the static type of each value. That makes it the right choice for null values and nullable value types, which carry no usable runtime type of their own, and it adds one method per kind of entry - AddFixed<T>, AddIf<T>, AddLazy<T>, AddComputed<T> and AddObserved<T> - as well as Find<T>, Contains<T>, Remove<T> and Set<T> for a list that is kept around and revised. Setting both parameters is allowed: the ValueList items are provided first and the Values items after them, so Values wins on a conflict, which is what makes the pair a natural defaults-plus-overrides split.


ValueList cascading values:

Theme: null
Notifications: null
Authenticated: null
User (named parameter): null
User (typed parameter): null

Nesting

Providers nest and shadow like any other cascading value: an inner provider overrides only the types and names it redefines, and everything else keeps flowing down from the outer one. The same rule applies inside a single provider, where a later value shadows an earlier one of the same type or name, so a set of defaults can be spread first and then selectively overridden. A shadowed value is dropped from the rendered chain instead of being cascaded and then hidden, so overriding costs nothing at render time.


Outer provider:

Theme: Light
Notifications: 7
Authenticated: null
User (named parameter): null
User (typed parameter): Ava Smith [Product manager]

Inner provider (only Theme is overridden):

Theme: Dark
Notifications: 7
Authenticated: null
User (named parameter): null
User (typed parameter): Ava Smith [Product manager]

ValueType

Every value is cascaded as its ValueType, which is what decides the TValue of the generated CascadingValue and therefore which cascading parameters it can reach. It defaults to the runtime type of the value, so a null value has none of its own and the constructor throws unless a type is passed explicitly. Use new BitCascadingValue(value, typeof(T)), or the BitCascadingValue.From<T> factory that reads the static type of its argument, to cascade a value as a nullable type, a base type or an interface. Cascading a null under the same ValueType is also how an outer value is deliberately cleared for a subtree, which is what the inner provider below does to the [CascadingParameter] int? Count of the consumer:


Outer provider cascades 5 as int?: 5
Inner provider cascades null as the same int?: null

IsFixed

IsFixed tells Blazor that a value never changes, so its consumers are not subscribed for change notifications. That removes the per-consumer subscription bookkeeping and is the recommended setting for services, state containers and other values that are created once and only mutated in place. Use the BitCascadingValue.Fixed<T> factory, or the AddFixed<T> method of the list, as its shorthand. The flag can still be toggled later: the provider re-creates the underlying CascadingValue rather than letting the framework reject the change, and it does the same for a value that is renamed, so the consumers below it are created again and matched against the new name.


Fixed cascading values:

Theme: Light
Notifications: 3
Authenticated: null
User (named parameter): null
User (typed parameter): Yaser Moradi [CEO]

Enabled

Setting Enabled to false on a value drops it from the rendered chain as if it had never been added, which is how a cascading value is provided conditionally without rebuilding the collection or wrapping the markup in an @if block. A disabled value also stops shadowing, so any outer provider or root level cascading value of the same type or name shows through again. Toggle the switches and watch the consumer fall back to null:



Conditional cascading values:

Theme: Dark
Notifications: null
Authenticated: null
User (named parameter): null
User (typed parameter): Ava Smith [Product manager]

Notifications

A BitCascadingValue that is held onto is observable: assigning its Value, Name, IsFixed or Enabled raises its Changed event, the provider re-renders itself in response, and the consumers see the new value even though the component that owns the values never re-rendered. For an object that is cascaded once and then mutated in place there is nothing to assign, so NotifyChanged() raises the same event on demand, and NotifyChangedAsync() is its awaitable form - the counterpart of CascadingValueSource.NotifyChangedAsync - which completes only once every provider listening to the value has re-rendered. The button below starts a background job that uses both, off the UI thread and without a single call to StateHasChanged on this page:




Self-refreshing cascading values:

Status: Idle
Progress: null

Auto notifications

A value that already reports its own mutations does not need to be notified about by hand. Turning AutoNotify on, with the BitCascadingValue.Observed<T> factory or the AddObserved<T> method of the list, makes the cascading value watch the cascaded object itself: an INotifyPropertyChanged value is watched for its property changes and an INotifyCollectionChanged one (an ObservableCollection<T>, typically) for its collection changes, and each of them raises Changed on its own. The subscription only lives while a provider is listening, so a long lived state object never keeps a torn down page alive. The job below mutates the status object from a background thread and never calls NotifyChanged at all:




Self-watching cascading values:

Status: Idle
Count: 0

Lazy values

BitCascadingValue.Lazy<T>, and the AddLazy<T> method of the list, take a factory instead of a value and run it at most once, the first time the value is actually needed. A value that is disabled, that a later value of the same type and name shadows, or that belongs to a provider which is never rendered therefore costs nothing to build, and IsValueCreated reports whether the factory has run yet. The two values below share the same factory; the counter is read inside the provider, after the factories of that render have run, and the second one only shows up once the toggle enables it:



Lazy cascading values:

Theme: null
Notifications: null
Authenticated: null
User (named parameter): null
User (typed parameter): Ava Smith [Product manager]

Factory invocations so far: 1

Computed values

BitCascadingValue.Computed<T>, and the AddComputed<T> method of the list, also take a factory, but they run it every time the value is provided rather than once. That is what lets a single collection be built once, in a field or in the constructor, and still track the state its values are derived from, instead of being rebuilt from scratch on every render the way the Values example does it. It is also how one value is cascaded a second time under another type or name: a computed value that reads another one's Value stays in step with it, since both are read again on the same render. The collection below never changes; only what its two factories read does:




Computed cascading values:

Theme: Light
Notifications: 0
Authenticated: null
User (named parameter): null
User (typed parameter): null

API

Every parameter, public member, sub-class and enum this component exposes.

BitCascadingValueProvider parameters

Name Type Default value Description
ChildContent RenderFragment? null The content to which the values should be provided.
Values IEnumerable<BitCascadingValue>? null The cascading values to be provided for the children. These values are provided after (so they take precedence over) the ones of the ValueList parameter.
ValueList BitCascadingValueList? null The cascading value list to be provided for the children. These values are provided before (so they can be overridden by) the ones of the Values parameter.

BitCascadingValue properties

Defines a value that can be cascaded to descendant components.

Name Type Default value Description
Value object? null The value to be provided. Assigning a value that is not compatible with the ValueType throws an ArgumentException, and assigning a different value raises the Changed event. A lazy factory runs the first time it is read, a computed one on every read.
Name string? null The optional name of the cascading value. An empty or white-space name is treated as no name at all, and the consumers match it case-insensitively. Renaming a live value re-creates the underlying CascadingValue component so that the consumers are matched again under the new name.
IsFixed bool false If true, indicates that Value will not change, so consumers are not subscribed for change notifications. Toggling it re-creates the underlying CascadingValue component.
Enabled bool true Determines whether this cascading value is provided to the children. A disabled value is skipped as if it was never added, so an outer or root level cascading value of the same type or name shows through.
AutoNotify bool false Watches the cascaded value itself, so an INotifyPropertyChanged or INotifyCollectionChanged value raises Changed on its own. The subscription is only held while a provider is listening, so the cascaded object never keeps this value alive.
ValueType Type Value?.GetType() The type to use as the TValue of the CascadingValue component. It is read-only and defaults to the runtime type of the value, so it must be provided explicitly for null values, nullable value types, base types and interfaces.
IsValueCreated bool true Whether the value is already available. It is only false for a lazily created value whose factory has not run yet.
IsComputed bool false Whether the value is produced by a factory that runs on every read rather than being stored once, which is what the Computed factory methods create.
Changed event Action<BitCascadingValue>? Raised whenever the value changes, which is what lets the hosting BitCascadingValueProvider re-render and push the new value down to the consumers on its own.
ChangedAsync event Func<BitCascadingValue, Task>? The awaitable counterpart of Changed, which is what the provider subscribes to and what makes NotifyChangedAsync complete only once the re-render is done.
NotifyChanged() void Raises the Changed and ChangedAsync events on demand, which is how a cascaded object that is mutated in place is pushed down to the consumers.
NotifyChangedAsync() Task The awaitable form of NotifyChanged, whose task completes once every listening provider has re-rendered, like CascadingValueSource.NotifyChangedAsync does.
From<T>(T value, string? name = null, bool isFixed = false, bool enabled = true) BitCascadingValue Creates a cascading value whose ValueType is the static type of T.
Fixed<T>(T value, string? name = null, bool enabled = true) BitCascadingValue Creates a fixed (IsFixed) cascading value whose ValueType is the static type of T.
Lazy<T>(Func<T> valueFactory, string? name = null, bool isFixed = false, bool enabled = true) BitCascadingValue Creates a cascading value whose value is produced by the factory the first time it is actually needed, so a disabled or shadowed value is never built. The factory runs at most once.
Computed<T>(Func<T> valueFactory, string? name = null, bool isFixed = false) BitCascadingValue Creates a cascading value that is re-read from the factory every time it is provided, so one long lived value keeps tracking the state it is derived from.
Observed<T>(T value, string? name = null, bool enabled = true) BitCascadingValue Creates a cascading value with AutoNotify turned on, so a value reporting its own mutations refreshes the consumers without any call to NotifyChanged.

BitCascadingValueList properties

A helper class to ease the using of a list of the BitCascadingValue.

Name Type Default value Description
Add<T>(T value, string? name = null, bool isFixed = false, bool enabled = true) void Adds a typed BitCascadingValue to the list, cascading the value as the static type of T.
Add(BitCascadingValue? value) void Adds an already created BitCascadingValue to the list. A null item is ignored.
Add(object? value, Type valueType, string? name = null, bool isFixed = false, bool enabled = true) void Adds a BitCascadingValue with an explicit ValueType to the list, for when the cascaded type is only known at runtime.
AddIf<T>(bool condition, T value, string? name = null, bool isFixed = false, bool enabled = true) void Adds a typed BitCascadingValue to the list only when the given condition is true.
AddIf(bool condition, BitCascadingValue? value) void Adds an already created BitCascadingValue to the list only when the given condition is true, which paired with a lazy value keeps the value of a conditional entry from being built at all.
AddFixed<T>(T value, string? name = null) void Adds a fixed (IsFixed) typed BitCascadingValue to the list.
AddFixed(object? value, Type valueType, string? name = null) void Adds a fixed (IsFixed) BitCascadingValue with an explicit ValueType to the list.
AddLazy<T>(Func<T> valueFactory, string? name = null, bool isFixed = false, bool enabled = true) void Adds a typed BitCascadingValue whose value is produced by the factory the first time it is actually needed. The factory runs at most once. An overload taking an explicit ValueType is available as well.
AddComputed<T>(Func<T> valueFactory, string? name = null, bool isFixed = false) void Adds a typed BitCascadingValue that is re-read from the factory on every render, so a list built once keeps tracking the state its values are derived from. An overload taking an explicit ValueType is available as well.
AddObserved<T>(T value, string? name = null, bool enabled = true) void Adds a typed BitCascadingValue that watches the value itself, so an INotifyPropertyChanged or INotifyCollectionChanged object refreshes the consumers on its own.
Find<T>(string? name = null) BitCascadingValue? Finds the entry that the given type and name resolve to, which is the last one matching both, since that is the one shadowing all the others. An overload taking an explicit ValueType is available as well.
Contains<T>(string? name = null) bool Whether the list holds an entry of the static type of T carrying the given name, regardless of whether it is enabled.
Remove<T>(string? name = null) bool Removes every entry of the static type of T carrying the given name, and reports whether anything was removed. An overload taking an explicit ValueType is available as well.
Set<T>(T value, string? name = null, bool isFixed = false, bool enabled = true) void Replaces every entry of the static type of T carrying the given name with a new one, or adds it when the list has none, so the list ends up with exactly one entry per type and name.

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.