createModel#
function |
Package: package:preact_signals
Function: createModel#
SignalModelConstructor<T> createModel(T Function() factory, {SignalModelOptions options = const SignalModelOptions()})
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();
}
References#
The createModel type is referenced and used in the following pages: