Skip to main content
Neural UIv2.0.0Documentation
View v1 GitHub

State ownership

Decide who changes a value and distinguish inputs, models, proposals and events.

The Aโ€“F contract map

Read the component API to choose a binding. The same Change suffix does not always mean a model. This table describes the six responsibilities with real Neural examples.

Contract
Who owns state
Can the component apply a new value?
Application receives
Neural example
A ยท INPUTApplicationNo: it reads the supplied value.Nothing automatically.Button.disabled
B ยท MODELShared editable valueYes, through the documented model.The updated value.Input.value / valueChange
C ยท PROPOSALApplicationNo: application accepts or rejects.A request, not an applied result.Dialog.open / closeRequested
D ยท DOMAIN EVENTApplication owns the consequence.No authoritative state update implied.Interaction and payload.Button.neuClick: MouseEvent
E ยท LIFECYCLEThe observed lifecycleNot an editable value.Notification after the documented transition.Dialog.opened / closed
F ยท INTERNAL DERIVEDComponent internalsDerives behavior from public state.No extra public binding.Button blocks activation when disabled or loading.

A ยท Configure with an input

The application supplies Button.disabled. Toggling the application signal changes whether the button can activate; Button does not write a new disabled value back. This example is synchronous and only teaches input ownership.

example.ts
import { Component, signal } from '@angular/core';
import { NeuButtonComponent } from '@neural-ui/core/button';
import { NeuSwitchComponent } from '@neural-ui/core/switch';

@Component({
  selector: 'app-input-example',
  imports: [NeuButtonComponent, NeuSwitchComponent],
  template: `
    <neu-switch
      label="Block action"
      [(checked)]="blocked"
    />
    <button
      neu-button
      type="button"
      [disabled]="blocked()"
      (neuClick)="count.update(increment)"
    >
      Run
    </button>
    <output aria-live="polite">{{ count() }}</output>
  `,
})
export class InputExample {
  readonly blocked = signal(false);
  readonly count = signal(0);
  readonly increment = (value: number) => value + 1;
}

B ยท Edit one shared value

Input emits valueChange after an edit. The handler updates the same signal that feeds value. Reset updates that signal from the application; there is no second local value. [(value)]="name" is the equivalent two-way form: choose one binding style for a control.

B ยท Edit one shared value
Value: Neural ยท 0

C ยท Accept or reject a proposal

Open Dialog and press Escape. With acceptance enabled, the handler sets open to false. With acceptance disabled, closeRequested still fires but open remains true. Use Finish and close for an explicit application action. Core restores focus when the dialog actually closes.

Do not use [(open)] here: Dialog exposes an input and a request, not an open model.

C ยท Accept or reject a proposal
ยท opened: 0 ยท closed: 0

D ยท React to an interaction

Button.neuClick reports activation with a MouseEvent. The application decides the consequence. In the first-component example, that consequence is incrementing a counter; it could instead open a task or submit application work.

E ยท Observe lifecycle changes

opened and closed in the Dialog example observe applied open-state transitions. They do not request a change and must not become another open binding. The counters start at zero: Core suppresses an initial lifecycle emission during the first render.

F ยท Leave derived behavior to the component

Button derives its blocked activation from disabled and loading. Configure those public inputs; do not bind internal helpers or manually reproduce its disabled semantics.