LogoSignals.dart
Copy Markdown
rodydavis/signals.dart 999999

Type: createModel

API reference and details for createModel from signals.dart.

createModel#

Kind: 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.

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();
}
Favor using Dart 3 records or extension types when defining models. They cost zero runtime overhead while granting complete compile-safe parameters and autocomplete functionality.

References#

The createModel type is referenced and used in the following pages:

  • Model (signals_flutter/utilities)
  • Model (signals_core/utilities)
  • Model (signals/utilities)
  • Model (preact_signals/utilities)