-
Notifications
You must be signed in to change notification settings - Fork 25.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Need ability to add directives to host elements in component declaration. #8785
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. 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. |
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?). |
+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 |
@jjstreet your proposal sounds similar to @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 So instead of |
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 angular/flex-layout#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 |
Currently I work around this by adding directive in the parent component and then add listener in the host with the @HostListener. Parent.html Component.ts 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 |
@fadzic can you not just add the directive to the host element by doing this... Component.ts or like this if you need parameters like ElementRef or Renderer2 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 |
Moved from PR @crisbeto thank you for the response. I figured it'd be the case. In that case, should we update the PR description to call it out for Structural Directives? I guess it makes sense that this feature only targets Attribute Directive. Structural should be handled on the consuming (component's users) side of things rather than authoring (component's author). Question 1: is it possible to have TypeScript generics supported? @Directive()
export abstract class NgtInstance<TObject> {
@Output() ready = new EventEmitter<TObject>();
}
@Component()
export class NgtMesh extends NgtInstance<THREE.Mesh> {} Here's a sample inheritance example. In reality, the inheritance level might be more than 1 level deep. You can see here that I can have generics with inheritance, and Let's try with Directive Composition @Directive({
selector: '[ngt-instance]'
standalone: true
})
export class NgtInstance<TObject> {
@Output() ready = new EventEmitter<TObject>();
}
@Component({
hostDirectives: [
// not sure how to provide THREE.Mesh here for (ready) to be strongly typed
// maybe something like NgtInstance as NgtInstance<THREE.Mesh> and the compiler can figure it out?
{ directive: NgtInstance, outputs: ['ready'] }
]
})
export class NgtMesh {} Question 2: assuming everything is @Component({
hostDirectives: [DirectiveA, {directive: DirectiveB, inputs: ['foo: fooAlias'], outputs: ['bar']}]
}) based on this example, where do we import |
Regarding structural directives: I'm not sure why they need to be called out specifically. You can still apply a host directive to them, but it's going to work on the template instance, the same as if you were to apply a directive to the Regarding generics: this is one of the limitations we are aware of, but I'm not sure if there's a good way around it given that the host directives are referenced by value. We could introduce some way of inferring the generic type in the future, but it's not something we've considered yet for v1. Regarding the standalone question: only the directives referenced in the |
@crisbeto I just realized that we can probably have Thanks for the concise answers. |
@crisbeto Regarding genrics - won't Instantiation expressions solve it somehow? |
@Harpush do you mean something like |
@crisbeto I meant the new typescript 4.7 feature allowing to assign generic params without const Component = <T>() => ({hostDirectives: [HostDir<T>]}); That is assuming you somehow have access to that hostDirectives: [HostDir<number>] |
Ah, then me way be able to get it to work since the compiler should have access to the generic type. I would still defer this for after the initial implementation is done. |
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to angular#8785.
This is the compile-time implementation of the `hostDirectives` feature plus a little bit of runtime code to illustrate how the newly-generated code will plug into the runtime. It works by creating a call to the new `ɵɵHostDirectivesFeature` feature whenever a directive has a `hostDirectives` field. Afterwards `ɵɵHostDirectivesFeature` will patch a new function onto the directive definition that will be invoked during directive matching. For example, if we take the following definition: ```ts @directive({ hostDirectives: [HostA, {directive: HostB, inputs: ['input: alias']}] }) class MyDir {} ``` Will compile to: ```js MyDir.ɵdir = ɵɵdefineComponent({ features: [ɵɵHostDirectivesFeature([HostA, { directive: HostB, inputs: { input: "alias" } }])] }); ``` The template type checking is implemented during directive matching by adding the host directives applied on the host to the array of matched directives whenever the host is matched in a template. Relates to #8785. PR Close #46868
Have you considered using a @HostDirective(FooDirective)
@Component()
class FooComponent {}
@HostDirective(FooDirective)
@Directive()
class BarDirective {}
@HostDirective(DirectiveA)
@HostDirective(DirectiveB)
@Directive()
class DirectiveC {} |
Enables the new directive composition API by exposing the `hostDirectives` property on the `Directive` and `Component` decorators. Also cleans up some casts that were put in place while the feature was being developed. Fixes angular#8785.
@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. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
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:
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.
Some ideas of how this could work:
Thanks everyone, Angular 2 is looking great!. Let me know if theres something I'm missing.
The text was updated successfully, but these errors were encountered: