Skip to content

Conversation

Copilot
Copy link

@Copilot Copilot AI commented Sep 24, 2025

Overview

This PR implements support for Angular's native bindings API in Angular Testing Library, addressing the suggestion in #526 that ATL's current input/output interfaces may be unnecessary with Angular's new approach.

What's New

Angular introduced a native bindings API that allows direct binding of inputs and outputs when creating components with TestBed.createComponent():

// Angular native approach
TestBed.createComponent(MyComponent, {
  bindings: [
    inputBinding('value', () => 'test-value'),
    outputBinding('click', (event) => console.log(event)),
    twoWayBinding('name', signal('initial'))
  ]
});

This PR adds seamless support for this pattern in ATL while maintaining full backward compatibility.

Usage Examples

New Bindings API

import { render, screen } from '@testing-library/angular';
import { inputBinding, outputBinding, twoWayBinding, signal } from '@angular/core';

await render(MyComponent, {
  bindings: [
    inputBinding('value', () => 'test-value'),
    inputBinding('greeting', () => 'hello'),  // supports aliases
    outputBinding('clicked', clickHandler),
    twoWayBinding('name', signal('initial'))  // two-way binding with signals
  ],
});

Traditional API (Still Supported)

await render(MyComponent, {
  inputs: { value: 'test-value', greeting: 'hello' },
  on: { clicked: clickHandler },
});

Mixed Usage

// Both approaches can coexist with intelligent warnings
await render(MyComponent, {
  bindings: [inputBinding('value', () => 'test-value')],
  inputs: { greeting: 'hello' }, // Warning: ignored due to Angular limitations
  on: { clicked: clickHandler }, // Works alongside bindings
});

Benefits

  1. Native Angular Integration - Uses Angular's official bindings API
  2. Better Performance - Bindings are handled natively by Angular, avoiding manual setup overhead
  3. Improved Type Safety - Leverages Angular's built-in type checking for bindings
  4. Future-Proof - Aligns with Angular's recommended testing patterns
  5. Zero Breaking Changes - Existing code continues to work unchanged
  6. Two-Way Data Binding - Full support for bidirectional data flow with signals

Implementation Details

  • Added bindings?: Binding[] option to RenderComponentOptions
  • Modified createComponent() to use TestBed.createComponent(component, { bindings }) when bindings are provided
  • Smart logic handles mixed usage scenarios with helpful warnings
  • Users import binding functions directly from @angular/core
  • Added comprehensive tests validating input binding, output binding, two-way binding, and mixed usage scenarios
  • Support for writable signals with re-rendering scenarios

Binding Types Supported

  • inputBinding() - Bind input values, including with writable signals for dynamic updates
  • outputBinding() - Listen to component outputs
  • twoWayBinding() - Bidirectional data binding with writable signals

Mixed Usage Behavior

Due to Angular's limitations, inputs and outputs behave differently when mixing approaches:

  • Inputs: Angular prevents mixing setInput() with inputBinding(), so bindings take complete precedence (with warning)
  • Outputs: Both outputBinding() and traditional on listeners work together
  • Warnings: Helpful messages guide users toward consistent usage patterns

Migration Path

Users can gradually adopt the new bindings API:

  • Immediate: Continue using existing inputs/on APIs - no changes required
  • Gradual: Start using bindings for new tests while keeping existing tests unchanged
  • Future: Eventually migrate to bindings API for consistency with Angular's patterns

This provides a smooth transition path while giving users access to Angular's modern testing capabilities.

Fixes #546

Fixes #546


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot AI and others added 2 commits September 24, 2025 16:55
Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] Bindings API Add support for Angular v20 bindings API Sep 24, 2025
@Copilot Copilot AI requested a review from timdeschryver September 24, 2025 16:57
Copilot finished work on behalf of timdeschryver September 24, 2025 16:57
export * from './lib/testing-library';

// Re-export Angular's binding functions for convenience
export { inputBinding, outputBinding, twoWayBinding, type Binding } from '@angular/core';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot don't do this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the re-exports. Users now import inputBinding, outputBinding, and twoWayBinding directly from @angular/core as requested. Commit ec079c2

Comment on lines 199 to 209
// Only use traditional input/output setting if no bindings are provided
// When bindings are used, they handle inputs and outputs natively
if (!bindings || bindings.length === 0) {
setComponentProperties(createdFixture, properties);
setComponentInputs(createdFixture, inputs);
setComponentOutputs(createdFixture, outputs);
subscribedOutputs = subscribeToComponentOutputs(createdFixture, subscribeTo);
} else {
// With bindings, we still need to handle componentProperties for non-input properties
setComponentProperties(createdFixture, properties);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot is it possible to use the current syntax together with bindings? If a binding is used with the same name as an input/output/property then the binding should take preference, ideally we can log a warning when this happens.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented mixed usage support with warnings. Angular doesn't allow mixing setInput() with inputBinding(), so bindings take full precedence for inputs (with warning). Traditional output listeners work alongside outputBinding(). Added comprehensive test coverage for this behavior. Commit ec079c2

Comment on lines 313 to 314
* An array of bindings to apply to the component using Angular v20+'s native bindings API.
* This provides a more direct way to bind inputs and outputs compared to the `inputs` and `on` options.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot remove the mention of v20

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed version references. Documentation now refers to "Angular's native bindings API" without specific version mentions. Commit ec079c2

@timdeschryver
Copy link
Member

@copilot please look at my review comments

…arnings, remove v20 references

Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
Copy link
Author

Copilot AI commented Sep 24, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cloud.nx.app
    • Triggering command: node ./bin/post-install (dns block)
    • Triggering command: node /home/REDACTED/work/angular-testing-library/angular-testing-library/node_modules/.bin/nx test testing-library (dns block)
    • Triggering command: node /home/REDACTED/work/angular-testing-library/angular-testing-library/node_modules/.bin/nx run-many --target=build --projects=testing-library (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@Copilot Copilot AI changed the title Add support for Angular v20 bindings API Add support for Angular bindings API Sep 24, 2025
@Copilot Copilot AI requested a review from timdeschryver September 24, 2025 18:35
Copilot finished work on behalf of timdeschryver September 24, 2025 18:35
@timdeschryver
Copy link
Member

@lacolaco is this what you had in mind?

@lacolaco
Copy link
Contributor

@timdeschryver Thanks. I'm gonna review in this week.

Copy link
Contributor

@lacolaco lacolaco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, this is definitely what I wanted! I left some comments and suggestions on the implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing apps/example-app/src/app/examples/23-bindings-api.component.spec.ts. It should explain the bindings usage.

clicked = output<string>();
}

it('should support inputBinding for regular inputs', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to add a case for a re-rendering scenario which uses inputBinding with a writable signal.

const valueSignal = signal('test-value');

await render(BindingsTestComponent, {
      bindings: [inputBinding('value', valueSignal), inputBinding('greet', () => 'hi there')],
    });

// <= assert 'test-value' is rendered.

valueSignal.set('updated-value');

// <= assert 'updated-value' is rendered


expect(clickHandler).toHaveBeenCalledWith('clicked: bound-value');
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

twoWayBinding scenario also should be tested.

https://angular.dev/api/core/twoWayBinding

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review and input @lacolaco ! Let's see how copilot will handle this 👀

README.md Outdated

```typescript
import { render, screen } from '@testing-library/angular';
import { inputBinding, outputBinding } from '@angular/core';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

twoWayBinding too.

@timdeschryver
Copy link
Member

@copilot also add the functionality for twoWayBinding

Example:

const checkboxValue = signal('');
createComponent(MyCheckbox, {
bindings: [
twoWayBinding('value', checkboxValue),
],
});

And resolve this comment:

It would be better to add a case for a re-rendering scenario which uses inputBinding with a writable signal.

const valueSignal = signal('test-value');

await render(BindingsTestComponent, {
      bindings: [inputBinding('value', valueSignal), inputBinding('greet', () => 'hi there')],
    });

// <= assert 'test-value' is rendered.

valueSignal.set('updated-value');

// <= assert 'updated-value' is rendered

Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
Copy link
Author

Copilot AI commented Sep 27, 2025

@copilot also add the functionality for twoWayBinding

Example:
...

Added twoWayBinding functionality with comprehensive test coverage and documentation. Also added the requested test case using inputBinding with a writable signal that demonstrates re-rendering scenarios. The implementation supports signal-based two-way data binding where external signal changes update the component and component changes update the external signal. Commit 39fe933

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cloud.nx.app
    • Triggering command: node ./bin/post-install (dns block)
    • Triggering command: node /home/REDACTED/work/angular-testing-library/angular-testing-library/node_modules/.bin/nx test testing-library (dns block)
    • Triggering command: node /home/REDACTED/work/angular-testing-library/angular-testing-library/node_modules/.bin/nx run-many --target=build --projects=testing-library (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@Copilot Copilot AI changed the title Add support for Angular bindings API Add support for Angular bindings API with twoWayBinding Sep 27, 2025
Copilot finished work on behalf of timdeschryver September 27, 2025 12:39
@timdeschryver timdeschryver marked this pull request as ready for review September 27, 2025 12:46
expect(await screen.findByText('updated-value')).toBeInTheDocument();
});

it('should support twoWayBinding for model signals', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks perfect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bindings API
3 participants