createModel#
Creates a new model constructor with an instanced factory.
A SignalModel is a highly powerful architectural primitive designed to manage cohesive packages of related state, business logic, actions, and side effects.
Under the hood, SignalModel automatically tracks, scopes, and manages the lifecycle of any
Effects
instantiated during its factory execution. When the model is disposed (by calling
model.dispose()),
all nested/captured effects are clean up automatically, ensuring complete prevention of memory leaks.
Furthermore, if the factory returns a standard Dart Map, and wrapInAction
is enabled (default),
all nested function properties are automatically wrapped in batched action
transactions to optimize updates.
1. Type-Safe Models using Dart 3+ Records (Recommended)#
The simplest and most built-in way to define a compile-safe model is to return a Dart record from your factory. Records provide immediate type safety, autocomplete, and compile-time verification without any wrapper boilerplates.
import 'package:signals/signals.dart';
// Define the reactive model constructor returning a Record
final counterModel = createModel(() {
final count = signal(0);
// Captured nested side-effect (e.g. logging or syncing to local storage)
effect(() {
print('Nested logger: count is ${count.value}');
});
return (
count: count,
increment: () => count.value++,
);
});
void main() {
// Instantiate the model
final model = counterModel();
// Access properties type-safely via .value
print(model.value.count.value); // Prints: 0 (and registers effect print: Nested logger: count is 0)
model.value.increment(); // Prints: Nested logger: count is 1
// Dispose when done to clean up all captured nested effects
model.dispose();
}
2. Object-Oriented Style: Type-Safe Wrappers using Dart 3+ Extension Types#
While records are great for lightweight structures, you can wrap the returned Map-based SignalModel
in a standard Dart 3 extension type when you prefer a class-like API (e.g. implementing getters/setters or hiding subscript lookups).
import 'package:signals/signals.dart';
// 1. Define the reactive model constructor returning a Map
final counterModel = createModel(() {
final count = signal(0);
// Captured nested side-effect (e.g. logging or syncing to local storage)
effect(() {
print('Nested logger: count is ${count.value}');
});
return <String, dynamic>{
'count': count,
'increment': () => count.value++,
};
});
// 2. Create a premium, compile-safe extension type wrapper
extension type TypeSafeCounter(SignalModel<Map<String, dynamic>> _model) {
int get count => (_model['count'] as Signal<int>).value;
set count(int val) => (_model['count'] as Signal<int>).value = val;
void increment() => (_model['increment'] as Function)();
void dispose() => _model.dispose();
}
void main() {
// 3. Instantiate and wrap the model
final counter = TypeSafeCounter(counterModel());
// Now you have a beautifully autocomplete-friendly, compile-safe API!
print(counter.count); // Prints: 0 (and registers effect print: Nested logger: count is 0)
counter.increment(); // Prints: Nested logger: count is 1
// Dispose when done to clean up all captured nested effects
counter.dispose();
}
SignalModel#
A premium wrapper for cohesive state packages constructed with createModel.
It holds the instanced model value and all the Effects that were captured during its construction. Disposing the SignalModel automatically disposes of all nested/captured effects, completely avoiding memory leaks.
Premium Pattern: Dart 3+ Extension Type Wrappers#
To avoid unchecked subscript access like model['count'].value, wrap your model in an extension type:
extension type TypeSafeCounter(SignalModel<Map<String, dynamic>> _model) {
int get count => (_model['count'] as Signal<int>).value;
set count(int val) => (_model['count'] as Signal<int>).value = val;
void increment() => (_model['increment'] as Function)();
void dispose() => _model.dispose();
}
Constructors#
View Constructors
SignalModel(this.value, this._effects, {this.options = const SignalModelOptions()})
Creates a new model instance.
Properties#
View Properties
T value
The instanced model value.
SignalModelOptions options
Options used to configure this model.
Methods#
View Methods
dynamic [](Object? key)
Access properties dynamically if the underlying value is a Map.
void []=(dynamic key, dynamic val)
Set properties dynamically if the underlying value is a Map.
T call()
Returns the value of this model. Alias for [.value]
void dispose()
Disposes of all captured effects.
SignalModelOptions#
Options for configuring a SignalModel.
Provides configuration for debug labeling (name) and whether to automatically wrap Map functions in transaction-safe, batched actions (wrapInAction).
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final options = const SignalModelOptions(
name: 'user-profile-model',
wrapInAction: true,
);
Constructors#
View Constructors
SignalModelOptions({this.name, this.wrapInAction = true})
Creates a new instance of SignalModelOptions.
Properties#
View Properties
String? name
The name or debug label of the model.
bool wrapInAction
Whether to automatically wrap returned Map functions in actions. Defaults to true.
Methods#
View Methods
SignalModelOptions copyWith({String? name, bool? wrapInAction})
Copy options with new values.
bool ==(Object other)
int hashCode
SignalModelConstructor#
A constructor for models that manages nested effects.
The model constructor starts capturing effects when called, storing them inside the returned SignalModel.
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final myModel = SignalModelConstructor(() => 'data');
final model = myModel();
print(model.value); // Prints: data
Constructors#
View Constructors
SignalModelConstructor(this._factory, {this.options = const SignalModelOptions()})
Creates a new instance of SignalModelConstructor.