Skip to content

Support Input/Output spread #14545

Open
Open

Description

@jiayihu

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Currently each Input or Output must be explicitly declared in the component to allow its usage.

Expected behavior

I'd like to have any way to pass Inputs/Outputs to a component in the template without declaring them first. This is allowed in React with props spread and allows techniques like Higher Order Components.

<my-component [prop1]="prop1" {...otherInputs} ></my-component>

What is the motivation / use case for changing the behavior?

Declaring each Input/Output is a problem when wrapping some component to extend its behaviour or to hide the component when dealing with an external library. It requires mapping each of the wrapped component Inputs/Outputs and it's very tedious and error prone. Besides any change in the wrapped component API would require touching the wrapper component.

Activity

  1. lacolaco commented on Feb 17, 2017

    @lacolaco
    Contributor

    related to #11850

  2. DzmitryShylovich commented on Feb 17, 2017

    @DzmitryShylovich
    Contributor

    Don't think they are related. This is a much more complex feature.

  3. jiayihu commented on Feb 17, 2017

    @jiayihu
    Author

    @laco0416 yup not much related. That issue asks for array spread in function calls, whereas this feature is more object spread of Inputs/Outputs.

  4. otodockal commented on Feb 20, 2017

    @otodockal
    Contributor

    @fabiobiondi @manuel-di-iorio sorry guys, but don't spam the thread with "+1", use reactions instead.

  5. fabiobiondi commented on Feb 20, 2017

    @fabiobiondi

    Sorry otodockal I was on mobile phone and it didn't support reactions. Now fixed. Thanx

  6. added
    area: coreIssues related to the framework runtime
    featureLabel used to distinguish feature request from other issues
    on Apr 10, 2017
  7. crysislinux commented on Dec 22, 2017

    @crysislinux

    any updates on this? It's really verbose when you want to wrap some components. for example, if you want to use mat-select from @angular/material but apply some additional styles on it to make it another component(logically). It's not ideal to add a directive on it because:

    1. it seems more readable to use a single component rather than add the directive every time
    2. sometimes you just want wrap mat-select and add styles on the wrapper instead of change the style of mat-select
    3. sometimes it's just impossible to implement all the stuff without adding new wrapper dom elements.

    Higher Order Components solves this problem quite well. I cannot figure out another way to do the same thing without write all the Input on the wrapper again. When dealing with Output, it's even verbose because you have to introduce EventEmitter for each Output again.

  8. added this to the Backlog milestone on Jan 23, 2018
  9. YogliB commented on Aug 12, 2020

    @YogliB

    any chance we'll see this anytime?

  10. pacocom commented on Nov 20, 2020

    @pacocom

    I like spead props! In React is possible!!
    Please add this to Angular.

  11. LinboLen commented on Dec 23, 2020

    @LinboLen

    like this?

    <my-component [prop1]="prop1" [...otherInputs] (...otherOutputs) ></my-component>

    or maybe

    <my-component [prop1]="prop1" [...]="otherInputs" (...)="otherOutputs" ></my-component>
  12. mlc-mlapis commented on Dec 23, 2020

    @mlc-mlapis
    Contributor

    @shybaLen What such a syntax means concretely on the <my-component> side? And what otherInputs and otherOutputs means on the host side?

  13. added
    feature: under considerationFeature request for which voting has completed and the request is now under consideration
    on Jun 4, 2021
  14. 6 remaining items

  15. b-barry commented on Apr 12, 2023

    @b-barry

    Hello all,

    Do we have a workaround at the moment?

  16. xc404 commented on May 25, 2023

    @xc404

    I'm waiting for this feature

  17. tombohub commented on Jul 16, 2023

    @tombohub

    I believe this feature made react what is today. Because it's so easy to extend and build upon something. Just compare number of component libraries in react compared to other frameworks. We people are lazy, don't wanna type every single input to extend a component. This feature is of strategic importance for the benefit of angular ecosystem.

  18. otodockal commented on Jul 16, 2023

    @otodockal
    Contributor

    There is also a new library called Zag, which takes a novel approach to building UI components using FSMs(Finite State Machines) and heavily leverages spreads.
    It would be a great addition to the Angular ecosystem if spreads is supported.
    https://zagjs.com/

  19. tombohub commented on Jul 17, 2023

    @tombohub

    @otodockal how come there is no svelte version of zagjs? does svelte makes it hard to implement?

  20. otodockal commented on Jul 17, 2023

    @otodockal
    Contributor

    @tombohub They gave it a try, but it was removed for some reason chakra-ui/zag@bdb1950
    There seems to be a problem with the lack of support for Events. sveltejs/svelte#5112

  21. victor-assis commented on Oct 8, 2023

    @victor-assis

    Given this new feature, I was thinking, what if Angular had abstract directives that implement interfaces for native elements?
    for example:

    @Directive()
    export abstract class HTMLInputElementDirective extends HTMLElementDirective {
      /** accumulates all inputs */
      protected hostInput: Partial<HTMLInputElement> = {};
    
      /** Sets or retrieves a comma-separated list of content types. */
      @Input()
      accept: string;
    
      @HostBinding('attr.accept')
      private acceptHost = null;
    
      /**
       * Sets or retrieves how the object is aligned with adjacent text.
       * @deprecated
       */
      @Input()
      align: string;
    
      @HostBinding('attr.align')
      private alignHost = null;
    

    I created a complete example using a directive I created a while ago, which binds attributes:
    https://stackblitz.com/edit/angular-ivy-mvhptb?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fcomponents%2Fcheckbox%2Fcheckbox.component.ts

  22. MGREMY commented on Jan 25, 2024

    @MGREMY

    Any update on this ? :)

  23. robinsjovoll commented on Mar 7, 2024

    @robinsjovoll

    Also wondering about this - any updates? It would really help when creating wrapper components around third party components for instance. So that when the third party updates their components we doesn't have to explicitly add or change the params in our wrapper to support them.

  24. ConteClaudioSalvatore commented on Apr 2, 2024

    @ConteClaudioSalvatore

    hi, i was also wondering if this feature is still unplanned...

  25. iwnow commented on May 9, 2024

    @iwnow

    need it

  26. xc404 commented on Feb 2, 2025

    @xc404

    any update on this feature

  27. szacskesz commented on Jul 13, 2025

    @szacskesz

    I believe this would be really nice to have. For now there is no proper way (or I am not aware of it) to do wrapper component in Angular.

    My use case

    Create an app-input-field with a lot of inputs and outputs, and make it look like as the application needs it. Create a specialised app-password-field which is the same as the input field, but its sets type=password and doesnt let the developer change it (not exposed as an input)

    Sound reasonable and should be easy right? As far as I know this is not possible without any flaw:

    • Wrapper component manually forwarding inputs/outputs.

      • This is not DRY, repeating code, possible to break when you change the wrapped component's API.
    • Using composition with ng-content (<app- wrapper> <app-wrapped ... /> </app-wrapper>)

      • Cannot change default values for inputs ( the time you get the wrapped component it has already run ngOnInit)
      • Cannot restrict API of wrapped component. For example you cannot enforce it to have [type]="password" without it being able to be overriden by the "user"
    • Using directive on the component (<app-wrapped appWrapper />)

      • Error prone, you can forget the directive
      • Multiple levels of wrapping might need multiple directives -> error prone
      • Cannot restrict API in a typesafe way. Meaning: it can throw an error or ignore the value you set when you try to change the input, but its all in runtime, you cant enforce it in compile time as with regular inputs.
    • New component extending original

      • Need to copy component metadata, which is error prone, and can break if wrapped component changes.
      • Cannot restrict API.
    • +1 Extending component and using hostDirectives

      • Both problems from directive and extension methods.
  28. gund commented on Apr 24, 2026

    @gund

    Its 2026, you could already vibe code this feature by now, for real what takes so long...

  29. PatrickLarocque commented on Jul 22, 2026

    @PatrickLarocque

    Design exploration: runtime binding sets

    This is related to #14545 and #41993, but proposes a narrower primitive than automatic wrapper forwarding.

    Angular can dynamically compose binding values, but not binding structure. Binding names must be statically present in the template so the compiler can classify them as directive inputs/outputs, DOM properties or attributes, styling, or events.

    Passing an object input does not solve this: the receiving component must know its schema and manually reproduce Angular's renderer, sanitization, styling, and listener behavior.

    Runtime classification is not unusual across frameworks: Vue's v-bind, React and Solid DOM spreads, and Svelte element spreads classify or patch object keys at runtime, although their component and directive resolution models are simpler than Angular's.

    Proposed API

    <button
      {...triggerBindings()}
      [attr.data-analytics-id]="analyticsId()"
    >
      Toggle
    </button>
    readonly triggerBindings = computed(() => ({
      disabled: this.disabled(),
      'attr.aria-expanded': this.open() ? 'true' : 'false',
      'data-state': this.open() ? 'open' : 'closed',
      class: {'is-open': this.open()},
      '(click)': this.handleClick,
    }));

    The same mechanism applies to Angular components:

    <app-field {...fieldBindings()} />
    readonly fieldBindings = computed(() => ({
      value: this.value(),
      disabled: this.disabled(),
      '(valueChange)': this.handleValueChange,
      'attr.aria-describedby': this.errorId(),
    }));

    The prototype currently resolves keys as follows:

    Key Resolution
    value, disabled Matching input first, otherwise DOM property
    attr.* Forced attribute
    aria-*, data-* Matching input first, otherwise attribute
    class, style Existing Angular styling-map semantics
    (event) Angular output/native-listener machinery

    Explicit template bindings always win over the binding set, regardless of source order. Removed keys remove their previous property, attribute, styling, listener, or output-subscription contribution.

    Prototype

    I built a vertical prototype against Angular 22.1-next.6 covering:

    • Parsing, template AST, compiler IR, type-checking, and language-service traversal.
    • Runtime input/property/attribute resolution.
    • Styling precedence and sanitization.
    • Dynamic native listeners and directive outputs, including replacement and teardown.
    • SSR, hydration, and event replay.

    Conceptually, this:

    <button
      {...bindings()}
      [title]="explicitTitle()"
      (click)="explicitClick()"
    />

    compiles to:

    ɵɵbindingSet(ctx.bindings(), excludedTargets);
    ɵɵdomProperty('title', ctx.explicitTitle());

    The compiler records statically explicit targets as exclusions, so the runtime set cannot overwrite them.

    For an opted-in node, Angular identity-checks the binding-set object. When it changes, the runtime diffs its own keys and routes them through existing Angular input, renderer, styling, sanitizer, and listener paths. Templates without binding sets retain their current generated code and runtime path.

    This is not an implicit HostBinding: the binding source belongs to the parent template and is resolved alongside ordinary template bindings. It does not allow a directive to mutate peer inputs after input checking.

    Considerations

    1. Should {...bindings()} accept a plain object, Angular's existing Binding descriptors, or both?
    2. Should keys support Angular's complete scoped target grammar—attr.*, class.*, and style.*[.unit]—or keep styling composition limited to class and style map values?
    3. Should updates be identity-based, or should mutable objects be diffed every change-detection pass?

    I know this is a bit much for a random comment from someone not on the core team. Just wanted to put this idea out there. If anyone cares, I can open a draft PR for eyes on the actual code shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtimecore: inputs / outputsfeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under consideration

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions