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

Created on 23 May 2016  ·  114Comments  ·  Source: angular/angular

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.

core directive matching host and host bindings feature

Most helpful comment

@IgorMinar the Ivy work makes this more feasible. But yes past v6.

All 114 comments

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

@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.

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?

I've proposed a solution to this issue (albeit for angular version 1) here: https://github.com/angular/angular.js/issues/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?).

+1

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.

@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?

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.

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

+1

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.

I'd like to add another use case for this. It would allow ng2-mobx (https://github.com/500tech/ng2-mobx) to get rid of the wrapping component and look much cleaner.

I would love to have this too. Currently I need it to make custom routerLink directive. I'd love to reuse angular one and just provide it with parameters prepared by mi directive.

So instead of <a [routerLink]="repeatedCodeToGetLink()"> I would have <a [myRouterLink]> and it would dynamically apply [routerLink] with resolved parameters.

Really excited about the prospects of this!

We have needed this for a while. In fact, a while back before I knew there was an open issue for it, I asked on stack overflow about essentially this very feature.

I have provided an elaborate example of how we'd be able to use this feature to solve https://github.com/angular/flex-layout/issues/162 which we have had open for a while. (See the example and explanation here)

We are really looking forward to any feedback, I see that this issue is the third most thumbed-up of all open issues in this repo. Hopefully we can see this in the next release or sooner (fingers crossed)!

/cc @tbosch @IgorMinar @mhevery @jelbourn @hansl @ThomasBurleson

@jjstreet I think your suggestion

<ng-host myDirective="foo"></ng-host> 

... would go well with another separate proposal that was made a while back for seperate reasons than we are discussing here.

See https://github.com/angular/angular/issues/7297

Currently I work around this by adding directive in the parent component and then add listener in the host with the @HostListener.

Parent.html
<my-component myDirective>

Component.ts
@HostListener('myEvent') handler() { // do stuff }

But it would be cleaner if we can add attributes directly in the host...

Here is how I have been dealing with this, but I really think implementing this feature from the ground would be the best solution.

Just a monthly reminder that we're waiting for positive or negative comment on this from the Angular team.

@tbosch - Any public thoughts on the priority of this issue. It also impacts @angular/flex-layout.

@fadzic can you not just add the directive to the host element by doing this...

Component.ts
@HostBinding('attr.myHilite') myHilite = new myHiliteDirective()

or like this if you need parameters like ElementRef or Renderer2
@HostBinding('attr.myHilite') myHilite = new myHiliteDirective(this.elementRef, this.renderer)

I also have the need to add a directive to the host element and I was redirected to this issue. I was able to do what I needed using the code above. I am in no way an expert using angular but this workaround seems to work so far, if someone had issues with this approach let me know. Thanks.

@btinoco that doesn't work because no lifecycle methods get called. You'd have to manually wire everything up in each component that uses the directive rather than just having Angular wire it up for you.

@hccampos thanks for the heads up. I have just tried it and the ngOnInit of my directive did not execute (unless I use the directive in my component manually) or I call the directive's ngOnInit() on my component'sngOnInit(). Again thank you for letting me know that.

@btinoco - yes. it is a subtle but nasty issue. One that @angular/flex-layout hopes will get fixed soon. ;-)

Any news from the Angular team about this? It's been 1 year since the issue was opened...

Finding this detailed description on this issue was super cool,
then finding no feedback from the Angular team was super uncool :(

Regarding the already working solutions:

This feature request sounds very much like mixins. In fact, bullet no 2 in the description
of this feature actually matches the official
documentation of TypeScript, see here.
In angular, this becomes a little worse though, as to mixin a class with @Input()s, you
have to redeclare them on the base class.

Anther solution that already works today would be to make the component contain a wrapper element and apply the directives there.
E.g. if the component contained a template like <wrapper g-touch>...

Regarding "Create a helper function that does it through a service directly on elementRef.nativeElement":
Yes, that seems like a good idea as well. I would not care about WebWorkers for now,
as they are still experimental and are missing some bigger features for production,
and almost no library out there would work on WebWorkers.
See e.g. also our material library which accesses the DOM directly.

Regarding Option 1) of the proposal:

The current semantics for host property bindings are,
that they set a property myDir on the underlying HTML element,
but not any directive. However, if host can also introduce directives, users could write the following
and would be confused why this does not update the property in the directive myDir:

@Component({
  host: {
    '[myDir]': true
  },
  template: '...'
})
class MyComp {}

Regarding Option 1) and Option 3):
Introducing some kind of host bindings between directives on the same element can:

  • lead to a cycle in the data binding graph, which Angular does not support, and therefore
    lead to hard to debug errors because of stale values / "Expression has changed after checked" errors.
  • lead to additional perf overhead, compared to directives injecting each other
    and communicating directly.

Regarding Option 2) of the proposal:

  • yes, having to refer to the gTouch class seems odd, as all other directives
    are triggered via NgModules.

@ThomasBurleson let's talk offline about your use case in more details...

@tbosch I'd like to propose another option: Introduce a native angular tag, let's call it <ng-host>.

Note: @mhevery proposed the introduction of an <ng-host> tag in https://github.com/angular/angular/issues/7546, however, even though I'm using the same tag name here, what I am proposing is separate and meant specifically to address the issue that has been raised here.

The ng-host tag would not be implemented as a regular directive/component class but would instead be "magic" framework code... similar to ng-content, ng-container, etc...,
The tag would simply serve as a "pointer" to the component host in a way that is analogous to the css :host selector.

It avoid ambiguous scenarios, each component would only be allowed to have, at most, one <ng-host> block and it would have to be the root tag/node of that component's template.

Here is how one would use it:

// Option 5: Use `<ng-host>`.. Very declarative and intuitive
@Component({
  selector: 'g-btn',
  template: `
    <!-- 
      Besides comments, having dom code inside the template but outside of a declared 
      ng-host code block would raise an error (hopefully at compile-time) .
    -->

    <ng-host role="button" g-touch> 
      <ng-content></ng-content>
    </ng-host>
  `
})

By the way @tbosch, Thank you for responding at all. We really appreciate your engagement and feedback on this issue.

Are everyone else's thoughts on this functionality being specific to Components, or would it also make sense if a Directive could apply a different directive to it's host? The use case I started subscribing to this issue for involved a few third-party directives that A) we wanted to isolate from our code in case we wanted to change later and B) wanted to apply some default functionality to each instance without having to duplicate the setup every time we used it.

For example, a tooltip directive, which is going to be applied on a large number of elements throughout our application, and we want to default the delay and appendToBody (it doesn't support a centralized config object). Because it didn't support a central config object, we had to put three or four attributes everywhere we wanted to use it. And then later, we did end up moving away from that library (started using Material tooltips) and we had to manually replace every tooltip. Had we been able to create our own directive that "wrapped" it, it would have been as simple as changing that directive to apply [mdTooltip] to its host instead of the other library.

@MikeMatusz Looks like I had that in mind as well, here is my snippet from https://github.com/angular/flex-layout/issues/162#issuecomment-290350270.

@Directive({
  selector: 'fxLayoutFullPage',
  hostDirectives: [LayoutDirective],
  host: { 
    'fxLayout': 'column', 
    'style': 'min-height:100vh; background-color:yellow'
  }, 
}) class LayoutFullPageDirective {}

Would it be possible to create a property decorator that instantiates a directive?
For example:
@HostDirective(LayoutDirective) myLayoutDirective: LayoutDirective;

This would work for Components as well as Directives, provide an instance-reference for interaction and wouldn't get lost when inheriting from the Component/Directive.
I guess it gets more complicated if you also want to provide input and event bindings.

Where does this stand? I'm fairly new to Angular2/4, and what I want to do is create a directive that just applies several other directives at once. So that instead of:

<button directiveA directiveB directiveC>BUTTON TEXT</button>

I can just write:

<button customDirectiveABC>BUTTON TEXT</button>

Feels like this should be easy--basic composition/DRYness. But as far as I can tell it's not possible?

@soynog, I feel exactly the same. I'd also like to know where this stands.

I was hoping to be able to make draggable dialogs using Angular Material and angular2-draggable (since angular/material#1206 is not yet supported) where I'd like to dynamically add a directive to the md-dialog-container that MdDialog service creates, but it seems much harder to get the behavior of the Angular 1.x compiler here for dynamic directives.

@tbosch , @ThomasBurleson, was the offline use case you discussed related to the issues or goals Thomas raised in angular/material#1206 by chance? I'm just trying to wrap my head around the behavior changes between the 1.6.x and 2+ frameworks.

Are there any updates on this issue? It gained traction in the beginning but I think its not receiving any attention anymore.

Yup any update on that?

This is something I need so badly, hope this proposal gets his way upstream.

This would be nice, realized today that I can't apply directives programmatically/dynamically, got sad.

+1
Same for me :)
I'm searching for a way to wrap multiple directives binding into a custom directive that do all I need. For example :

<my-cmp [myDirective]="content"
        [isOpen]="myCondition"
        customProp2="customClass"
        customProp1="customText">
 ...
</my-cmp>

It would be nice if I could create a directive which wrap all that things so I can reuse them without copy/paste all the lines.

<my-cmp myCustomDirective>
</my-cmp>

And into my custom directive

<ng-host [myDirective]="content"
        [isOpen]="myCondition"
        customProp2="customClass"
        customProp1="customText">
</ng-host>

coming up on the second anniversary of this issue! honestly this feature would be so, so useful, it lets us create highly composable components and directives without having to create a million wrappers. just compose the component you need from the directives you have. simple, clean, effective.

@IgorMinar - Anyway we can get this feature on the radar for upcoming enhancements?

I would like to know if such a feature would be considered a bad pattern or not. Anyone?

@darkbasic - AFAIU, without this feature, a developer would need to introduce a wrapper element (on ng-container) simply to add parent directives to the template view and content.

No, I don't think being able to have full control of your own component without having to use wrappers is a bad pattern. It's a necessity.

@bradlygreen any comment?

This feature is the most popular request (if not top 5) among all open issues of this repo... Across the internet we are seeing reports (backed by empirical data) of the decline of Angular as the defacto framework ... I think one of the things driving that is a sentiment that the community is not being heard. The competition; vue.js and react, are gaining ground and have surpassed angular because even though they may not necessarily implement every little thing that every one wants they atleast provide ongoing feedback about the most popular requested items. It is so frustrating to wait so long and hear nothing.. not even a simple "no, we won't do it".

(See "Angular slips" Js frameworks section)

... although I think that some opinions about Angular / Vue / React / ... are influenced by different factors ... this concrete feature would really be worthy of some form of implementation (even circumstances are a little more complicated than just a solution with a simple list of applied directives) ... so the concrete position of Angular core team would be very welcome ... 🥇

No specific ETA, but we're working towards making this category of things much easier in the renderer in 2018.

Hopefully things get drastically better in 2018. We are losing

See:

@somombo this article was confirmed to be bullshit long time ago

People who actually know their stuff made fun of the author and none of them took him seriously, the likes are from react, vue fanboys, naturally.

So the fact of the matter is that this issue here is a very low priority for the angular team, in fact it is the lowest possibly priority.

See published priority List at AngularHQ (look for Issue number 8785)

This is the case despite this issue having generated alot of discussion and interest from the community as is shown by the number of comments.

If you are someone who cares about this issue and would really like to see it implemented, then instead of waiting for.. well honestly _potentially never_, perhaps you can fill out the Official Annual Angular Survey, and make known that you feel like this issue should be a higher priority and would appreciate seeing it fufilled sooner rather than later.

Don't forget to thank our Angular Team for all the great work they have done!

I would also like to cast my vote for this feature. This has been the cause of too much grief trying to work around this issue.

@somombo please don't read too much into the priority in AngularHQ yet. the priority formula is not fully fleshed out. having said that, I think we should revisit this feature request after v6 ships. I'm afraid that we don't have the bandwidth for it sooner and working on this would conflict with an already ongoing work in the compiler/core area.

This is not a quick fix request. I suspect that it's going to take a considerable effort to get it done properly, but the stuff we are working on for v6 should make this one much easier to implement.

@IgorMinar the Ivy work makes this more feasible. But yes past v6.

@IgorMinar and @mhevery I can not stress enough how thankful I am (and the rest of us also, i'm sure) that you have given us this concrete feedback on what your thoughts are and what needs to happen first before this issue can be properly addressed.

It's not always clear to us laymen what's a "quick fix" and what's not. However, barring the fact that this is not a quick fix type of thing and has to be done right, I am especially appreciative that it seems you also feel that this will be a useful feature for angular to have.

We know you are both very busy and can't possibility respond like this to every single issue.
So you have our sincere gratitude whenever you do. We are excited and looking forward to angular v6 and beyond!

Thank you for all the great work!

You can have you component class extend or implement the directive class. If your're trying to apply the directive under the hood, then it should probably just be logic in the component.

export class gBtn extends gTouch

@NateMay, that only allows you to extend a single class. This issue is more about composition of multiple pieces of functionality using directives.

@NateMay two problems with that - first, you can only extend a single class, and second, you just broke dependency injection.

Just adding my two cents. I am building a multi-layer SPA with angular, material and flex-layout, using nested states of @uirouter/angular. Then inability to easily apply flex directives to component elements is very limiting.

So a vote for this feature please.

+1 for this added feature

It's possible to add a directive to an ng-container, which won't appear in the DOM.

I had a need for this for the intersection-observer API (which raises events when elements enter/leave the viewport). I have an intersector directive, which has enter() and leave() events when the element becomes visible/hidden. I have certain components that need to use this API internally, but I didn't want to add an extra DIV in the template.

So what I did was the following in component.html :

<ng-container intersector (enter)="weCameOnScreen()" (leave)="byeBye()">
     ... components normal template ...
</ng-container>

Then the intersector.directive.ts directive constructor injects the ElementRef.

    constructor(private intersectorElementRef: ElementRef) { ... }

For a normal DOM element you would just operate on intersectorElementRef.nativeElement, but for an ng-container the node is actually a comment node. So I just check to see if it's a comment, and if it is then I go up a level.

public ngAfterViewInit(): void 
{
    // if the directive is applied to an ng-container must go a level up
    this.domElement = (this.intersectorElementRef.nativeElement.nodeType == 8) ? this.intersectorElementRef.nativeElement.parentElement : this.intersectorElementRef.nativeElement;

   registerIntersector(this.domElement ...);

This isn't going to work for all situations, but I'm fine with this for now. I believe that in IVY compiler they may not be using comments anymore - so this may break. The important thing was I have a single directive I can use on DOM nodes or in what is effectively as a fake '@HostBinding' for the directive.

I was really hoping it was possible to add directives dynamically. I want to be able to encapsulate lower level directives in "higher order", more abstract, directives. I asked the following question on stack overflow, and I was wondering whether there will be a solution for this in the future : https://stackoverflow.com/questions/51608645/abstract-away-leaflet-directive-in-custom-directive

as @mhevery said.. we need to be patient and wait for the complete version ivy (ng v7.0.0 ?) to land. Apparently, it will be much easier for them to implement with ivy... After ivy, we must remind the team of how important this feature is to us, so they dont forget about it 😉

Subscribing to this. I also need to be able to dynamically attach a directive to a component that I created with resolveComponentFactory/createComponent.

const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentItem.component);

const componentRef = viewContainerRef.createComponent(componentFactory);
(<DynamicComponent>componentRef.instance).data = componentItem.data;
(<DynamicComponent>componentRef.instance).cssClassList = componentItem.cssClassList;
// Add directive to new component here
// componentRef.addDirective(someDirective)

Any update???
I ran into another use-case where I'm using third party directive.
In some scenario, I need to remove/add directive on an HTML element dynamically.
is this possible in any way or yet pending the solution?

@micronyks ... actually it's not possible to add a directive dynamically. We have to wait for Ivy which should add the possibility to create such a feature.

@mlc-mlapis but any plan when will IVY come ? in which version?

@micronyks ... Angular 7 by schedule.

Guys let's be reasonable here, Angular Team is trying hard to work on several huge features which are highly demanded, (PWAs, SSR, Ivy, and especially Custom Elements) the last one being very high priority feature as I could understand, because a lot of big enterprises (like Microsoft) been asking for it forever, and there is a reason for that. To achieve efficient Custom Elements they need Ivy, as soon as they're done with Ivy, as @mhevery said, the engine will allow dynamic directives more easily.

In the meanwhile, instead of keep demanding for this feature (which I also hopelessly need btw), we can help the Angular team to accelerate the process, by testing the betas, reporting bugs, helping with the docs, etc.

Let's remember that Angular Team is not even that big, it's just a dozen of people trying to make an awesome framework for everybody, but that takes time.

... yep, it's necessary to be a bit patient now and wait till the moment when we can help more with Ivy ... when the compiler will be completed and some detail design docs available.

@avatsaev I can agree with everythinh you said. You shouldn’t demand things in here. But you can explain the issues you’re dealing with when working with Angular.

I’m nowhere near being a very experienced Angular developer, but some things feel wrong or not explained clearly enough.

I’m came across this issue because I want to encapsulate a third party component/directive, without having a leaky abstraction. Part of this is making it possible to have dynamic directives. What suprises me is that it’s quite complicated to achieve such a thing.

I am building a form generator, using Angular Material and Flex-Layout, which takes a JSON configuration and generates a form. This feature would help me apply the flex-layout directives to the host component at runtime. I feel like one of Angular's biggest assets is the ability to generate code at runtime from a configuration, this will go a long way to make that code more versatile. Just wanted to drop in a good use case. I'm patient ;)

That’s my exact use case

@NateMay here's my implementation if you'd like to check it out.

@NateMay here's my implementation if you'd like to check it out.

could you please explain? I guess you mean dynamic-field.directive

The dynamic-field.directive does the fancy stuff but there's a lot of other stuff happening as well. I just added CONTRIBUTING.md in the root folder, which has instructions to setup locally. Be wary of using in anything that's in production for a couple months. I am making big changes as I work toward a stable implementation.

+1

By far, my workarounds are, they all have downsides.

  1. has-it, define new a member property as that directive inside my component class, pass all needed constructor arguments to that directive when call its constructor function (e.g. ElementRef, ViewContainerRef, TemplateRef...any injectable variables it asks for ), and manually call its lifecycle callback if it has, like ngInit() ngAfterViewInit() at current component's corresponding lifecycle callback function.
@component(...)
class MyComponent {
   theDirective: TargetDirective;
   constructor(el: ElementRef) {
       this.theDirective = new TargeDirective(el);
   }

  ngOnInit() {
     this.theDirective.ngOnInit();
  }
  ...
}
  1. Wrapper everything in components template within a top-level ng-template,
    <ng-template><div targetDirective>....</div></ng-template> render them in ngAfterViewInit() like:
const vf = this.viewContainerRef.createEmbeddedView(this.templateRef);
vf.detectChanges();

This way, Angular creates another element with that directive on it and my component's actual content inside it right after the original component element in DOM tree.

<my-component></my-component>
<div targetDirective>....</div>

This way is like what <route-outlet> does.

There are obvious side effects

Can someone confirm if this is now possible with Ivy? If so, does anyone have an example?

Let's remember that Angular Team is not even that big, it's just a dozen of people trying to make an awesome framework for everybody, but that takes time.

It could be bigger by having a community of contributors.

However, the chance of a contributed fix for this being accepted is very low.

So instead we're back to a dozen people.

Can someone confirm if this is now possible with Ivy? If so, does anyone have an example?

Since no word yet, thought I'd provide the closest thing I was able to find, which is an article from a while back about implementing mixins with Ivy: https://blog.nrwl.io/metaprogramming-higher-order-components-and-mixins-with-angular-ivy-75748fcbc310

According to the article, I think a possible solution for the original issue of this thread is to use the new feature called... "features".

...You can imagine it's a bit of a nightmare to try to Google anything about this feature. Hoping they release some official Ivy documentation soon! :)

@nayfin also building visual form designer/builder
And after few months of working to get stuck on fact I have no way to deploy directive to dynamically added div makes me crazy .... Should be so oblivious to call some MyDirectiveFactory::apply(HTMLElement)

This feature would be overwhelmingly welcomed as I find myself always making a single div to attach top-level directives. Additionally, If I want any flex-layout directives, I have to make that one-off div as well and it would be nice if I can attach them directly to the host element instead of having to do this.

Would be super cool to be able to add directives dynamically such as:

const msked = componentFactory.createDirective(MaskedInputDirective);
msked.textMaskConfig = {};
this.elementRef.directives.add(msked);

Additionally, If I want any flex-layout directives, I have to make that one-off div as well and it would be nice if I can attach them directly to the host element instead of having to do this.

@tsteuwer You could always just use the :host selector in your scss to apply style properties to the host element.

But yeah, I also would like the ability to apply directives to the host element. would be useful for making the host element scrollable and applying CdkScrollable from Angular Material CDK.

Wrapper everything in components template within a top-level ng-template

A slightly slicker alternative to this is to use https://github.com/trotyl/angular-contrib and add

host: { ngNoHost: '' }

This project shims the renderer and renders children of elements with the ngNoHost attribute, sans parent.

It has many of the same drawbacks of course.

Pity this is still open after 3 years. Directives bound to host element would really improve code reuse-ability.

Additionally, If I want any flex-layout directives, I have to make that one-off div as well and it would be nice if I can attach them directly to the host element instead of having to do this.

@tsteuwer You could always just use the :host selector in your scss to apply style properties to the host element.

But yeah, I also would like the ability to apply directives to the host element. would be useful for making the host element scrollable and applying CdkScrollable from Angular Material CDK.

Not ideal but you can create the CdkScrollable programmatically this way :
this.scrollable = new CdkScrollable(this.elementRef, this.scrollDispatcher, this.zone);
this.scrollable.ngOnInit();

You have to destroy it manually too :
if (this.scrollable) {
this.scrollable.ngOnDestroy();
}

https://github.com/angular/angular/issues/8785#issuecomment-361004682 IgorMinar the Ivy work makes this more feasible. But yes past v6.

@mhevery Following up on your comment :point_up_2:, now that Ivy has finally fully landed, can we pretty please have this feature at (or before) v10's release? If not, kindly update us on what other considerations may hold this back further.

Are any changes?

If this specific feature was on Angular survey https://twitter.com/angular/status/1252646001162088448?s=20, I bet it'd be the top voted entry.

There are tons of features which would be top voted, this one for sure but also using Observables for @output and many others. Unfortunately at the current pace they will never get implemented.

If this specific feature was on Angular survey, I bet it'd be the top voted entry.

Great idea @princemaple!

Though not ideal, this can actually be addressed in the survey's "extra comments" section (of question 2)
Where it says:

"How else should we improve Angular for you?"

So basically, everyone interested in seeing this feature, please just answer the survey and make it explicitly known, that you care alot about seeing "Issue #8785" implemented and resolved.

Here is the direct link to the survey:
https://goo.gle/angular-survey-2020

May the force be with you! 🙂

I too have struggled with how to programatically add more functionality to components, and honestly I think some of the proposals here seem like the BEST ways of approaching those specific problems.

I've spoken with angular team members regarding that article

Can someone confirm if this is now possible with Ivy? If so, does anyone have an example?

Since no word yet, thought I'd provide the closest thing I was able to find, which is an article from a while back about implementing mixins with Ivy: https://blog.nrwl.io/metaprogramming-higher-order-components-and-mixins-with-angular-ivy-75748fcbc310

And basically was led to the impression this is hacking with angular's internals, and it was in fact not designed for typical user consumption.

I'm not sure what if any technical reason exists that prevents us from being able to do this, but I think if we had the capabilities to do this, it would DRASTICALLY improve my day to day with angular.

“We’ve dramatically increased our investment in working with the community. In the last three weeks our open issue count has decreased by over 700 issues across framework, tooling, and components. We’ve touched over 2,000 issues, and we plan to make large investments over the next few months, working with the community to do even more.” — @StephenFluin
from Angular 10 Announcement

So I guess this means we'll see this issue quashed in v11? 🤞😏

What better a way is there to "work with the community" (and appease them) than to work on adding one of their most requested features!? (this one 😉)

Listen to them!

Just to set the expectations, what you are asking is not trivial amount of work and the current data structures are not really designed for this. So to support something like this would require some major engineering.

@mhevery how it's different from applying them from parent in template?

@k3nsei It's necessary to see it from NgModule point of view, which is actually the key element that creates the infrastructure for all of its components.

@mlc-mlapis We have @HostBinding and @HostListener so maybe @HostDirective would be good choice for that functionality. I have seen talks that Ivy apis enables such functionalities.

For me key point is to have some composition API that would allow us to have more decoupled classes with ability to have extensions/traits with reusable junks of functionality. For example like selectable, expandable/collapsable.

@k3nsei It could be, but I am not sure if it's not too dynamic, so less performant than strictly static structures.

"Just to set the expectations, what you are asking is not trivial amount of work and the current data structures are not really designed for this. So to support something like this would require some major engineering." — https://github.com/angular/angular/issues/8785#issuecomment-654391378

Thanks for your timely response @mhevery.

I think I'll be speaking for community in saying that it is not at all lost on us that this will be a very big challenge. If it weren't, I am sure by now we would have made some third-party libraries that achieves this properly (somehow). [to my knowledge there isn't any].

Also, goes without saying but, please let us know if there are some low hanging fruit (or otherwise) that we can assist with contributing towards this.

We sincerely thank you and value your earnest communication and hope we will continue to be a part of the dialogue on what we need vs. what is realistic/pragmatic to add.

Though my understanding is that Ivy makes this easier than before.

@mhevery

@IgorMinar the Ivy work makes this more feasible. But yes past v6.

Though my understanding is that Ivy makes this easier than before

My new understanding is "easier" still doesn't mean "easy"

My new understanding is "easier" still doesn't mean "easy"

Ivy: The thing you spent two years on and still doesn't address any of the most popular Angular issues.

Ivy: The thing you spent two years on and still doesn't address any of the most popular Angular issues.

@pauldraper I guess our issues are not their "issues" seeing as this particular one isn't even on their radar (See Roadmap https://angular.io/guide/roadmap).

For me personally, I think its time for me to look elsewhere for a project that's not only open source but a project whose direction the community (and users) have a real influence on.

@pauldraper I guess our issues are not their "issues" seeing as this particular one isn't even on their radar (See Roadmap https://angular.io/guide/roadmap).

@somombo I am disappointed by the fact that this issue is still open after all these years, but I cannot agree with you The first point on the roadmap is explicitly about handling open github issues. Listing all of them on the roadmap wouldn't make much sense, would it? This issue is one of the most upvoted (or the most upvoted) and I hope it will be finally taken resolved.

The first point on the roadmap is explicitly about handling open github issues. Listing all of them on the roadmap wouldn't make much sense, would it? This issue is one of the most upvoted (or the most upvoted) and I hope it will be finally taken resolved.

nope, this is just wishful thinking, read thru https://github.com/angular/angular/issues/5689 there is absolutely no indication they want to tackle any of the most upvoted issues, apart from "strongly typed forms" in the future

@pauldraper I guess our issues are not their "issues" seeing as this particular one isn't even on their radar (See Roadmap https://angular.io/guide/roadmap).

@somombo I am disappointed by the fact that this issue is still open after all these years, but I cannot agree with you The first point on the roadmap is explicitly about handling open github issues. Listing all of them on the roadmap wouldn't make much sense, would it? This issue is one of the most upvoted (or the most upvoted) and I hope it will be finally taken resolved.

All the same .. I am done waiting.. this issue has litterally been a huge blocker for me. So the fact that it doesn't seem like it's even being planned for any time soon means it's time for me personally to move on. Best of luck to everyone else.

I would like to see this renamed to "Support for adding directives to directives". While that name might be confusing, I think it's important that this feature work on directives, and not be limited to components. Other names for the feature might be "implied directives" or "attached directives", meaning when you use a given component or directive in a template, it pulls in the implied/attached directives on the host element.

I've wanted this many times, mainly because composition is potentially a cleaner form of re-use in Angular, compared to inheritance. Inheritance can be clunky because there's no multiple inheritance, constructor parameters have to be passed down, and some Angular features work when inherited, and others have to be "re-connected" in each leaf class.

I imagine "implied/attached directives" working like a component-local or directive-local form of directive instantiation, which results in the directive being instantiated on the host element without requiring the directive selector to exist in the template markup.

Here's an example:

@Directive({
  selector: 'app-popup',
  attachDirectives: [
    FocusAreaDirective
  ]
})
export class PopupDirective {

  // Attached directives can be injected, just like template-declared directives today
  constructor(public focusArea: FocusAreaDirective) {
  }

}

The @Input() and @Output() properties on the attached directive can be used in the template, and the lifecycle methods on the attached directive should be called as part of the host component lifecycle. The attached directive can also bind to the host element. Basically, it acts exactly like a template-declared directive today, but doesn't need to be declared in the template.

I think this feature would provide a significant benefit to Angular, enabling cleaner/simpler component architectures via directive composition.

Today, you have 2 choices if you want a similar form of directive re-use:

  • Require that a set of related directives always be declared together in template markup; and detect and throw errors if the required directives aren't present in the constructor. There's no way to require that the required directives are declared on the same element. This is messy from a template authoring and documentation standpoint, and is not a strong API or contract due to redundancy, but is otherwise not horrible.
  • Manually instantiate the attached directives within the host directive, and forward constructor parameters, @Input/@Output properties, host bindings, and lifecycle methods to the internal directives. This is a fragile mess for component authors, but is doable given a simple set of related components. It is much nicer for template authoring.

To put it another way, the absence of the feature sometimes creates an unnecessary tradeoff between clean+simple component use, and clean+simple component authoring.

@johncrim
Note that in a real world case your custom directive would have inputs, and you would want to transform them and pass as inputs to attached directive. Perhaps this could be done with a syntax similar to host attributes in Directive decorator options.

@amakhrov : Good point - I excluded the inputs from my example for clarity. In most of the cases where I need this, I don't need to transform the inputs (or outputs) for the attached directives - they (ideally) act as composable units and their input (or output) values could be bound from the template using the parent directive (or component).

In cases where there are either naming conflicts or naming clarity issues (which I would try to avoid when designing directives for composition), or when inputs or outputs needs to be transformed, that could be handled pretty easily by injecting the attached directive in the parent directive, and create new input or output properties on the parent that delegate to the attached directives.

I stand corrected.
This issue is now listed under the "Future" section of the official roadmap. See https://angular.io/guide/roadmap#support-adding-directives-to-host-elements

Support adding directives to host elements

A long-standing feature request is to add the ability to add directives to host elements. The feature will allow developers to augment their own components with additional behaviors without using inheritance. The project will require substantial effort in terms of the definition of APIs, semantics, and implementation.

Since I have just noticed it, I'm not sure when it was added, but I will admit that this is great news, and a meaningful and reassuring gesture. I'll keep crossing my fingers.

Thank you to the team for putting it there! 🙏🏾

Was this page helpful?
0 / 5 - 0 ratings