How do you support [(value)] on an Angular component without model(), and what do the NG8007 and NG1054 errors mean for such a pair?
answer
- input named x, output named xChange
- the child emits, the parent writes
- halves bound to different targets
- one output name claimed twice
basics
~20 sDeclare an input value and an output valueChange; the parent's [(value)] then binds both. NG8007 means the two halves went to different targets, usually a missing or misnamed output; NG1054 means two members claim the same binding name.
solid answer
~40 sTwo-way binding only needs the naming convention, so `value = input(0)` plus `valueChange = output<number>()` (or `@Input()` plus `@Output()`) supports `[(value)]`. The child cannot write its own signal input; it emits `valueChange` and waits for the parent to push the new value back through the input. Under strict template checking, if the input half is claimed by the component but the event half is not, for example because the output is called `valueChanged`, the compiler reports NG8007: the property and event halves are not bound to the same target. Without the check, the event half listens for a native `valueChange` event that never fires. NG1054 is a declaration error: adding `value = model(0)` while keeping `valueChange = output()` makes two members claim the output `valueChange`.
code
ts · 15 linesimport { Component, input, output } from '@angular/core';
@Component({
selector: 'app-volume-slider',
template: `
<input type="range" min="0" max="100"
[value]="value()"
(input)="valueChange.emit($event.target.valueAsNumber)" />
`,
})
export class VolumeSlider {
value = input(50);
// Must be named valueChange; valueChanged would trigger NG8007 at [(value)] call sites
valueChange = output<number>();
}go deeper
Recall that any input x plus output xChange pair supports [(x)], not only model().
Explain the flow of a plain pair and what NG8007 and NG1054 each detect.
Describe migrating pairs to model() safely and when a parent-controlled pair is the better design.
Set a component-library rule for two-way APIs: model() by default, explicit pairs where the parent must validate.
## The contract behind `[(value)]` `[(value)]="volume"` expands to `[value]="volume"` plus `(valueChange)="volume = $event"` (or `volume.set($event)` for a signal). Angular does not require `model()`; it only requires that the **same component or directive** has an input named `value` and an output named `valueChange`. Three ways to meet that contract: | Declaration style | Input | Output | Can the child change its own value? | |---|---|---|---| | `value = model(0)` | `value` | `valueChange` (generated) | yes, `value.set()` updates locally and emits | | `value = input(0)` + `valueChange = output<number>()` | `value` | `valueChange` | no, it emits and waits for the parent | | `@Input() value = 0` + `@Output() valueChange = new EventEmitter<number>()` | `value` | `valueChange` | yes, by assigning the field; the parent only learns of it when the child emits | The plain pair is common in code written before `model()` existed and in components that want the parent to stay in charge: with a read-only `input()`, the component's `value()` only changes when the parent accepts the emitted value and pushes it back. ## A volume slider with a plain pair ```ts @Component({ selector: 'app-volume-slider', template: ` <input type="range" min="0" max="100" [value]="value()" (input)="valueChange.emit($event.target.valueAsNumber)" /> `, }) export class VolumeSlider { value = input(50); valueChange = output<number>(); } ``` With `<app-volume-slider [(value)]="volume" />`, the flow is: the parent pushes `volume` into `value`; the user drags; the child emits; the parent's event half writes `volume`; the next pass pushes the new value back into `value`. ## NG8007: the halves went to different targets The template type checker (with output event checking on, as under `strictTemplates`) verifies that both halves of a two-way binding land on the **same directive**. It reports **NG8007**, *The property and event halves of the two-way binding 'value' are not bound to the same target*, when: 1. the input `value` is claimed by the component but **no output `valueChange` exists**, typically because it is named `valueChanged`, `change` or `onValueChange`. The event half then falls through to the element, and the diagnostic asks whether you are missing an output declaration called `valueChange`; or 2. the input is claimed by one directive on the element and the output by **another** directive. Without strict checking, case 1 compiles and silently fails: the event half becomes a listener for a native DOM event called `valueChange` on the host element, which nothing dispatches, so the parent never updates. ## NG1054: one binding name, two members **NG1054** is raised when the compiler analyses the component class, not the template. It means two class members claim the same input or output name. The usual route there is a half-finished migration: ```ts export class VolumeSlider { value = model(50); // generates the output valueChange valueChange = output<number>(); // NG1054: output 'valueChange' bound twice } ``` `model()` already creates an output named `value` + `Change`, so the leftover `output()` duplicates it. The fix is to delete the old output and replace `valueChange.emit(x)` calls with `value.set(x)`, which updates the model and emits in one step. The same code appears for two inputs with the same alias. ## Catching the silent failure early Because the non-strict failure mode produces no error at all, it tends to surface as a vague bug report ("the slider does not remember its value"). Three habits catch it sooner: - Keep **`strictTemplates`** on, so NG8007 turns a misnamed output into a build error at every call site. - Give each reusable two-way component a **test that binds `[(value)]` from a host component**, emits from the child and asserts on the host's state. It fails immediately if the output is renamed. - When renaming an input, rename its `Change` output in the **same commit**; the two names are one public contract, and changing only one breaks every `[( )]` caller. ## Choosing between the pair and `model()` - Use **`model()`** when the component owns the interaction and should update immediately, like most custom form controls. - Keep a **plain input and output pair** when the parent must be able to reject or adjust a value before it is shown. - In both cases the parent's template is identical, so the choice can change later without touching callers.
- Without strictTemplates, what happens in Angular when [(value)] targets a component whose output is named valueChanged?The property half still binds to the `value` input, but the event half does not match any output, so Angular registers a native DOM listener for an event called `valueChange` on the host element. Nothing dispatches that event, so the parent never updates and no error appears.
- When migrating an Angular input+output pair to model(), what must you change besides the declaration?Delete the old `valueChange` output, since `model()` generates it and keeping both triggers NG1054, and replace `valueChange.emit(x)` with `value.set(x)`. Parents that use `[(value)]` or `(valueChange)` need no change, because the public names stay the same.
saying these in an interview costs you the question
- Two-way binding requires the child to use model()
- Any output name works as long as it contains the input name
- A child with a read-only input() can set it directly before emitting
- NG8007 means the output emits the wrong type
- Keeping the old output next to model() is harmless duplication