Skip to content

Proposal: Need ability to add directives to host elements in component declaration. #8785

Closed

Description

@jamesmfriedman

I've been digging into Angular 2 and have run into a potential road block for extending certain kinds of components.

In the the following example, I have a button component, and a directive that will apply styles based on touch events. There will be many other objects other than just the button that will inherit the exact same touch behavior. I've explored my options, and I'm at a loss:

  • Directly extend a TouchClass. This seems less than ideal since typescript doesn't support multiple class inheritance, and I'd also like to expose the behavior to consumers for use in their own classes.
  • Fake multiple class inheritance through an interface. This seems like a hack and requires me to redeclare a shim api on every class I'm trying to mix into. https://www.stevefenton.co.uk/2014/02/TypeScript-Mixins-Part-One/
  • Create a helper function that does it through a service directly on elementRef.nativeElement in the component constructor. I really don't want to do this since it states in the docs that nativeElement will be null when running in a worker, and that capability is something I'm the most excited about.

Without getting too deep into the guts, I'd presume that the componentMetadata is available during the components compile time, and that the host property could be scanned for additional directives that could be added dynamically and compiled at the same time. This would allow you to do mixins the angular way: using composable directives to extend functionality, and doing it without breaking view projection. Short example below.

Current behavior
Declaring a directive in componentMetadata.host treats it as a regular attribute

Expected/desired behavior
The directive declared in host would be processed at compile time.

/**
 * App
 */
@Component({
    selector: 'app-component',
    template: '<g-btn>TEST</g-btn>',
    directives: [gBtn, gTouch]
})

export class AppComponent {
    constructor() {

    }
}

/**
 * Touch Directive
 * Will be used in lots and lots of components
 */
@Directive({
    selector: '[g-touch]',
    host: { 
        '(touchstart)': '...',
        '(touchend)': '...',
        '(touchmove)': '...',
        '(touchcancel)': '...'
    }
})

export class gTouch {
    constructor() {

    }
}

/**
 * Simple button component
 */
@Component({
    selector: 'g-btn',
    template: '<ng-content></ng-content>',
    host: {
        'role': 'button',
        // WOULD LOVE FOR THIS TO COMPILE THE DIRECTIVE!
        // right now it just adds an attribute called g-touch
        'g-touch': ' ' 
    }
})

export class gBtn {

    constructor() {

    }
}

Some ideas of how this could work:

// Option 1: just scan the host properties for directives.
// This would be my ideal, simple and understandable
@Component({
    selector: 'g-btn',
    template: '<ng-content></ng-content>',
    host: {
        'role': 'button',
        'g-touch': true // or {prop: 'foo'} or string
    }
})

// Option 2: definitely more declarative using a hostDirectives property
// more declarative, albeit more annoying to have to reimport the touch class
@Component({
    selector: 'g-btn',
    template: '<ng-content></ng-content>',
    hostDirectives: gTouch,
    host: {
        'role': 'button',
        'g-touch': true
    }
})

// Option 3: declare host directives as its own thing, still just
// use keys pointing to bool, obj, or string
@Component({
    selector: 'g-btn',
    template: '<ng-content></ng-content>',
    hostDirectives: {
        'g-touch': {someOption: someOption}
    },
    host: {
        'role': 'button',
    }
});

// Option 4: Not a huge fan of this one, but understandable if
// people want to keep one host property
@Component({
    selector: 'g-btn',
    template: '<ng-content></ng-content>',
    host: {
        'role': 'button',
        _directives: {
            'g-touch': true
        }
    }
});

Thanks everyone, Angular 2 is looking great!. Let me know if theres something I'm missing.

Activity

  1. Andy1605 commented on Jul 7, 2016

    @Andy1605

    I am currently developing a large client and am therefore trying to break down all GUI related problems into reusable Angular2 directives. This always leads me to that same problem, as james pointed out perfectly.
    This really has to work somehow for the sake of good modular and dynamic architecture. The touch example is only one of many scenarios where this is needed. e.g. Drag&Drop, Resize observing, etc. etc. etc.
    Made another example as plunker:
    https://plnkr.co/edit/J65THEMic0yhObt1LkCu?p=info

    Is there any chance this functionality will be added any time soon?

    Here is a StackOverflow question related to this: http://stackoverflow.com/questions/37148080/use-angular2-directive-in-host-of-another-directive

  2. added
    area: coreIssues related to the framework runtime
    and removed on Sep 7, 2016
  3. jamesmfriedman commented on Sep 16, 2016

    @jamesmfriedman
    Author

    @Andy1605 did you ever find a way around this? I kind of tabled working with NG2 because of this during the RCs. Would love to pick it back up, but this particular issue prevents me from building extensible UI patterns.

  4. jofran73 commented on Sep 24, 2016

    @jofran73

    I also feel that Angular is missing an essential feature here. It should be possible for a component to declare (multiple) attribute-directives for its host. Not being able to do that is a major hindrance for my project, too.
    Does anybody know if this will be implemented in the future or if there are reasons why it cannot be done?

  5. Aaron-Pool commented on Oct 16, 2016

    @Aaron-Pool

    I've proposed a solution to this issue (albeit for angular version 1) here: angular/angular.js#15270.

    My idea is, instead of just having the ability to add directives, the compilation framework would have a new extensibility point called "hostTransforms" (in the case of angular 1, "nodeTransforms") which would have access to the unmodified, unfiltered component declaration and the original, uncompiled component host node whenever a component is first encountered by the compiler and being prepared for insertion into the DOM. This way a developer could extend component decorators with custom properties, and then use nodeTransforms to convert those custom properties into something the angular framework is familiar with, just before compilation. Check the feature request thread for examples.

    I'm more familiar with the angular source code than the angular 2 source code, so I'm not sure if the implementation process would be the same here. But since this seems to be a pretty popular request, I'd love to see it either implemented in angular 2 and backported, or implemented in angularjs and forwardported (is that a thing?).

  6. oddlyfunctional commented on Nov 1, 2016

    @oddlyfunctional

    +1

  7. jjstreet commented on Nov 1, 2016

    @jjstreet

    I must agree, a feature that lets us add contributing attribute directives to the host would be great. I know I could use such a feature right now implementing a more "angular" like way adding drag/drop functionality to my UI components.

    What about creating a new tag similar to <ng-container> that lets you apply them in the component's template instead of the host metadata property? Something like <ng-host [attributeDirective]> to indicate the directives are added to the host component.

  8. Aaron-Pool commented on Nov 1, 2016

    @Aaron-Pool

    @jjstreet your proposal sounds similar to replace: true (obviously not identical, but similar), which was deprecated a while ago. But perhaps replace: true was deprecated for a reason that wouldn't apply here.

    @pkozlowski-opensource Could we get a response of any kind from the ng2 team on this?

  9. jamesmfriedman commented on Nov 1, 2016

    @jamesmfriedman
    Author

    I'm game for any way to achieve this. I suggested the host property because it it has access to the local scope of the component, and it already adds attributes to the component itself. Directives seem like a natural extension of this behavior.

  10. k3nsei commented on Nov 14, 2016

    @k3nsei

    +1 this feature is required to have clean and reusable code in UI components

  11. vlodko commented on Dec 14, 2016

    @vlodko

    +1

  12. Aaron-Pool commented on Dec 14, 2016

    @Aaron-Pool

    Could we please get some sort of response from the ng2 team on this? Even if it's just to say you're not going to do it, or to say it's a good idea but not a current priority, I'd just like to hear some sort of input.

  13. 149 remaining items

  14. framasev commented on Aug 31, 2022

    @framasev

    Have you considered using a @HostDirective decorator and augmenting a directive through decorators composition?

    @HostDirective(FooDirective)
    @Component()
    class FooComponent {}
    
    @HostDirective(FooDirective)
    @Directive()
    class BarDirective {}
    
    @HostDirective(DirectiveA)
    @HostDirective(DirectiveB)
    @Directive()
    class DirectiveC {}
  15. added a commit that references this issue on Oct 5, 2022
    6a8bd9f
  16. johncrim commented on Nov 3, 2022

    @johncrim

    @crisbeto - This is an amazing addition to angular, thank you very much. I'll be porting our component libraries to use it, I look forward to the improvements in code simplicity and code size.

  17. angular-automatic-lock-bot commented on Dec 4, 2022

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  18. locked and limited conversation to collaborators on Dec 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions