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

How to access the host component from a directive ? #8277

Open
frankpepermans opened this Issue Apr 27, 2016 · 70 comments

Comments

Projects
None yet
@frankpepermans
Copy link

frankpepermans commented Apr 27, 2016

According to the change log (https://github.com/angular/angular/blob/master/CHANGELOG.md),
AppViewManager is renamed into ViewUtils and is a mere private utility service.

However, I was using AppViewManager.getComponent to obtain a Component reference from an ElementRef before, how would I do this now?

What I need to achieve is the following:

  • I have a Component with a selector (say: selector my-component)
  • A directive that can be placed upon Components (my-directive)

In HTML, it looks as following
<my-component my-directive></my-component>

From my-directive, I need to have a reference to the my-component Component instance.

If there is another (better) way of doing this, I'm happy to adapt my code

@tbosch

This comment has been minimized.

Copy link
Member

tbosch commented Apr 27, 2016

You could just inject that component into the directive.

On Wed, Apr 27, 2016 at 4:34 AM Frank Pepermans notifications@github.com
wrote:

According to the change log (
https://github.com/angular/angular/blob/master/CHANGELOG.md),
AppViewManager is renamed into ViewUtils and is a mere private utility
service.

However, I was using AppViewManager.getComponent to obtain a Component
reference from an ElementRef before, how would I do this now?

What I need to achieve is the following:

  • I have a Component with a selector (say: selector my-component)
  • A directive that can be placed upon Components (my-directive)

In HTML, it looks as following

From my-directive, I need to have a reference to the my-component
Component instance.

If there is another (better) way of doing this, I'm happy to adapt my code


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
#8277

@frankpepermans

This comment has been minimized.

Copy link

frankpepermans commented Apr 27, 2016

How would that work?

The component could be anything really, or should in that case the component resolve a provider with its own instance as value, so that the directive can inject it dynamically?

@zoechi

This comment has been minimized.

Copy link
Contributor

zoechi commented Apr 27, 2016

@tbosch I think that would only work when the type is statically known by the directive but if the directive is added to different components, how would one inject it?

@frankpepermans

This comment has been minimized.

Copy link

frankpepermans commented Apr 28, 2016

@tbosch can you comment an alternative? If getting a component from an ElementRef will be dropped, let me know so I can start reworking my solution.

@bennylut

This comment has been minimized.

Copy link

bennylut commented Apr 30, 2016

Any workaround yet? I have a directive that assumes that the host component will have some specific interface. Without this feature (or some workaround) I will have to develop this directive for each component that implements this interface (which is what I will do if no other option)..

@CaptainCodeman

This comment has been minimized.

Copy link

CaptainCodeman commented Apr 30, 2016

If the component has to be static, doesn't it kind of defeat the whole point of directives - why wouldn't you just move whatever the directive does right into the known component?

@bennylut

This comment has been minimized.

Copy link

bennylut commented Apr 30, 2016

@CaptainCodeman, The idea was that instead of repeating the implementation of the same functionality in every component that follows a specific interface, I would have a directive that add this functionality.

Here is a simplified example:
Say I have a selection-info component that is just a simple panel that display information about some selected data.
Now I have three components: list-view, tree-view and combo-box, each of these components has the following properties: selection:T, selectionChange:EventEmitter and valueToString: (T)=>string,
in the previous version I could write a simple directive track-selection that receive an instance of selection-info as input, and take care of binding all the needed properties and even supply some additional services for debugging. In the new version it of course still possible but in a less elegant way in my eyes.

This is of course a relatively simplified scenario but I hope it will be able to show the rational behind receiving the component in a directive without actually knowing it exact class but only it interface.

@CaptainCodeman

This comment has been minimized.

Copy link

CaptainCodeman commented Apr 30, 2016

Yes, that is what a directive is supposed to do - add behavior to whatever component it's attached to.

If that can no longer be done then it makes them kind of pointless - the behavior could be moved to a class that is pulled into each component instead (if things have to be statically defined).

@bennylut

This comment has been minimized.

Copy link

bennylut commented Apr 30, 2016

@CaptainCodeman right, for now this is what I will do. But I hope that the angular team will provide an alternative as the directive approach was much more elegant..

@frankpepermans

This comment has been minimized.

Copy link

frankpepermans commented Apr 30, 2016

This is exactly what I've been using directives for, a way to elegantly allow composition and enhance components.

In my case state management, the component must implement a certain interface, the directive asserts that the component indeed implements it and then does its thing.

I do hope this will still be possible :)

@dqweek

This comment has been minimized.

Copy link

dqweek commented May 2, 2016

I have same issue described.
I have found a dirty workaround, till official fix will be provided.

This code works for me:

@Directive({
    selector: 'foo'
})
class FooDirective
{
   constructor(private _view: ViewContainerRef) {
  }
  ngOnInit() {
      let component = (<any>this._view)._element.component

     //TODO: add initialization code here
 }
}

explanation:
AppElement initialized with component instance on compilation.
AppElement is private to angular, so we can't DI it.
Fortunately ViewContainerRef has a reference to it on initialization (it is private).

it is a short run fix, till angular team provide better solution.

@zoechi

This comment has been minimized.

Copy link
Contributor

zoechi commented May 2, 2016

This doesn't work for Dart though because in Dart access to private members is effectively prevented.

@jpsfs

This comment has been minimized.

Copy link

jpsfs commented May 10, 2016

Hi,

@tbosch this is a feature needed by some folks (including myself) as you can see on StackOverflow:
http://stackoverflow.com/questions/31748575/how-to-access-the-component-on-a-angular2-directive/

Best,
José

Elvynia added a commit to Elvynia/trilliangular that referenced this issue May 16, 2016

- Upgraded to Angular2 beta17
  - Upgraded ts/js dependencies
  - AppViewManager was removed, using ViewRefContainer by accessing its private element property. Waiting for angular/angular#8277 issue to bring a better solution.
  - DynamicComponentLoader changed. Removed renderer/camera default loadings for now. Waiting angular/angular#6071 to bring a good solution with anchors
- Added Mousetrap library for keyboard events
- Added retyped mousetrap typings
- Added camera controls with zqsdae
- Added konami code in index.html as a working witness
- Added '+' in front of number assignments to avoid the use of strings
@syndicatedshannon

This comment has been minimized.

Copy link

syndicatedshannon commented May 24, 2016

We attempted to use this today, but as part of a workaround for a workaround for another need. The use-case may still be relevant, so I'll describe it briefly.

Our hierarchical tree needs to Observe the result of a late-determined function, displaying resulting children. However, an Angular 2 function in a view, even one returning an Observable piped to async, never settles. To work-around, we passed a function reference from the view instead, which also required the view-containing component's context.

@giancarloa

This comment has been minimized.

Copy link

giancarloa commented Jun 16, 2016

anyone know if a supported solution to this problem is available now? I too have a similar requirement... I have a child component that can literally be used inside of any type of parent component... and the child component needs to access the parent component...

@jpsfs

This comment has been minimized.

Copy link

jpsfs commented Jul 14, 2016

@tbosch @mhevery
Any plan to include this shortly?

Or at least an indication of how to tackle this problem using another approach?

@vicb

This comment has been minimized.

Copy link
Contributor

vicb commented Sep 27, 2016

/cc @tbosch

@vicb vicb changed the title [beta.16] AppViewManager replacement? How to access the host component from a directive ? Oct 6, 2016

@Luke265

This comment has been minimized.

Copy link

Luke265 commented Nov 19, 2016

Any news on this? The only one @dqweek approach has been patched in 2.2.0.

@zoechi

This comment has been minimized.

Copy link
Contributor

zoechi commented Nov 19, 2016

@DzmitryShylovich that doesn't seem to be related to me

@DzmitryShylovich

This comment has been minimized.

Copy link
Contributor

DzmitryShylovich commented Nov 19, 2016

yeah, nvm

@dominique-mueller

This comment has been minimized.

Copy link

dominique-mueller commented Nov 25, 2016

We're having the same issue here. The workaround worked so far, yet got removed with the Angualr 2.2 release. In general, I think Angular should provide a proper way for directives getting a reference to the components they're attached to. Only this way, directives and components can interact directly, thus allowing directives to enhance components (like others mentioned above).

Are there any news on this? With this issue unsolved, directives are not as powerful and thus helpful as they could be.

@ghetolay

This comment has been minimized.

Copy link
Contributor

ghetolay commented Nov 25, 2016

What's wrong about using a base class or a token on your component and use that to inject it into the directive like that https://plnkr.co/edit/xSyOseYau8YFXnsxnsMA?p=preview ?

If you want to inject a component into a directive you must certainly know something about that component, class/interface or you won't be able to interact with it.

@sam-s4s

This comment has been minimized.

Copy link

sam-s4s commented Aug 7, 2017

@Serjster problem with that is, because you're using private members, it could change with any angular update (not listed as breaking changes), because you're not supposed to be accessing that :)

This is why I decided to use the token technique. Safety first! lol

@shlomiassaf

This comment has been minimized.

Copy link
Contributor

shlomiassaf commented Aug 7, 2017

@sam-s4s This is a very specific solution to the problem that does not scale. It is a directive that is not ergonomic and to top that the main slot (@Input) is taken, additional slots will require more attributes, not good for component published by libraries or even in an app if they are used frequently.

@Serjster I would strongly recommend avoid that and any other use you make with private members. Not only for @angular but also in general within you'r app and with other 3rd party libraries. Its been already said that private members does not obey semver and it is not guaranteed that next version will have them and you might say, well i'll revise.... so you are facing a greater risk, now with ES7 private members proposal in with TypeScript transformers its even possible to randomly create different identifiers for these private members, given them shorter names... don't do that.

@Serjster

This comment has been minimized.

Copy link

Serjster commented Aug 8, 2017

@shlomiassaf Your advice is unwelcome unless you provide a method that does work. I have a problem to solve, this solution solves it. Your comment is arrogant, as if you are the creator of programming and only you know the best way to do things... instead, how about a bit of humility and show us the "correct" way, and if we agree, we will use your advice, if not we'll skip... "don't do that"? Your arrogance is beyond abhorent.

@sam-s4s

This comment has been minimized.

Copy link

sam-s4s commented Aug 8, 2017

@shlomiassaf Sorry, which solution are you referring to (specific and doesn't scale)? I think you're agreeing with me about the #variable and @input method not being particularly elegant?

@Serjster I feel like you've taken @shlomiassaf 's comment as an attack, but I don't think it was meant in that way.

I was considering doing what you have done myself, but decided it was a bit too risky, for those same reasons.

May I ask - what is your use case? Is there any reason the token approach would not work for you?

(are you just trying to pull a parent component from a child component?)

@shlomiassaf

This comment has been minimized.

Copy link
Contributor

shlomiassaf commented Aug 8, 2017

There was no arrogance nor intent for it.

There is no solution at the moment.

If you're happy with your solution use it, it is your code.

However, it is considered a bad practice, not by me by the principles of OOP and CS. Maybe they are arrogant.

Just because you think there is no solution doesn't mean you should use bad practices. Sometime the solution isn't direct, maybe a change of approach, architecture etc...

My comment will stay in place as it might prevent a developer from using bad practices.

If you were offended I'm sorry, it's was not the intent. Don't act with your ego learn to accept criticism.

Good luck

@matheo

This comment has been minimized.

Copy link

matheo commented Aug 19, 2017

@ghetolay Thanks a lot!!
With tokens it was easy to bind Reactive FormGroups on different Components:

export interface BindForm {
  form: FormGroup;
}

export const BINDFORM_TOKEN = new InjectionToken<BindForm>('BindFormToken');

@Directive({
  selector: '[bindForm]'
})
export class BindFormDirective {
  constructor(
    @Inject(BINDFORM_TOKEN) @SkipSelf()  @Optional() private parent: BindForm,
    @Inject(BINDFORM_TOKEN) @Self() @Optional() private child: BindForm
  ) {
    console.log('parent', parent.form);
    console.log('child', child.form);
    parent.form.addControl('child', child.form);
  }
}
@ghetolay

This comment has been minimized.

Copy link
Contributor

ghetolay commented Aug 20, 2017

@matheo

I don't know precisely your case and this is isn't the place to discuss about it.
But you can probably directly inject the form group directives using ControlContainer token and form control directives using NgControl.

@CramericaIndustries

This comment has been minimized.

Copy link

CramericaIndustries commented Aug 21, 2017

In Angular 4.3.3 I'm getting the host component in my directive like so:

import { Host, Self, Optional } from '@angular/core';
...
	constructor(
		 @Host() @Self() @Optional() public hostCheckboxComponent : MdlCheckboxComponent
		,@Host() @Self() @Optional() public hostSliderComponent   : MdlSliderComponent
	){
                if(this.hostCheckboxComponent) {
                       console.log("host is a checkbox");
                } else if(this.hostSliderComponent) {
                       console.log("host is a slider");
                }
         }
...

Of course you need to know at compile time which host-components are possible. Benefit is that it works with every component, even those that you can't modify because they are part of a 3rd party module.

@Lovrenc

This comment has been minimized.

Copy link

Lovrenc commented Dec 13, 2017

@CramericaIndustries i tried your approach and declared the directives (even tried it on 4.3.3) and I never get any component injected.

Is there anything special with injection into a directive?

@CramericaIndustries

This comment has been minimized.

Copy link

CramericaIndustries commented Dec 13, 2017

@Lovrenc could you provide some code please? The approach seems to work, at least for the 11 people who upvoted it. You are using it on a directive, right?

@Lovrenc

This comment has been minimized.

Copy link

Lovrenc commented Dec 14, 2017

@CramericaIndustries yeah sorry noob mistake not posting any code.

Yes i am injecting it into a directive.

@Directive({
    selector: '[appModal]'
})
export class ModalDirective implements OnInit {

   ....
 
    constructor(
        private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef,
        private cfResolver: ComponentFactoryResolver,
        private modalService: ModalService,
        @Host() @Self() @Optional() public test: DialogComponent,
        @Host() @Self() @Optional() public test2: ConfigRestoreComponent
    ) {}
   ...

This is how i use my directive: (example with DialogComponent)

<app-dialog *appModal="'modalConfigRestoreDialog', TitleKey:'CONFIG.RESTART.TITLE'"></app-dialog>

I felt like i misunderstand how Angular DI is working. I (currently) have both (component and a directive) in the same module.

@CramericaIndustries

This comment has been minimized.

Copy link

CramericaIndustries commented Dec 14, 2017

@Lovrenc My guess is that this doesn't work with structural directives (those with the asterisk *). According to the docs your code will be translated at runtime from this:
<app-dialog *appModal="'modalConfigRestoreDialog', TitleKey:'CONFIG.RESTART.TITLE'"></app-dialog>
to something like this:

<ng-template [appModal]="'modalConfigRestoreDialog', TitleKey:'CONFIG.RESTART.TITLE'">
	<app-dialog></app-dialog>
</ng-template>

And as you can see the <app-dialog> component is no longer the host of your directive.

@brinzi

This comment has been minimized.

Copy link

brinzi commented Dec 14, 2017

Is there still no elegant solution for this issue?

I would like to the host component regardless of it's type.

@sam-s4s

This comment has been minimized.

Copy link

sam-s4s commented Dec 14, 2017

Well that's a funny thing... because if you don't know what the host component is - then you can't do anything to/with it. You'd need to know at least a sub-type... and if that's the case, you can use the token method (above, by ghetolay) to get those :)

For example, in my system, I have a bunch of custom controls, and a tooltip directive. The tooltip directive can be placed on any HTML element - but it has special behaviour if placed on one of my custom controls. So the token system lets it get the first custom control it find (because the custom controls all have the token).

I do wish there was a BETTER way of doing this... but I don't think there is. Still, this does work.

@eavestn

This comment has been minimized.

Copy link

eavestn commented Dec 18, 2017

@CramericaIndustries have you ever seen you solution complain about

No provider for MdlSliderComponent

(In my case, the class is different; however, am getting the above error). Is this what @Optional solves?

@Lovrenc

This comment has been minimized.

Copy link

Lovrenc commented Dec 18, 2017

@eavestn Yes, @Optional would save you from getting this error

When you declare parameters in the constructor, the angular injector creates/finds instances of the classes you want and passes them to the constructor for you. It does so for every parameter you have in your constructor.

But what if the injector cannot find/create an instance of the object you want? Then you get the No provider error. You can, however, add @Optional to the parameter which means "TRY to inject this object". Instead of getting the error (in case the injection fails) the parameter will simply result to undefined.

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018

@rbrzoska

This comment has been minimized.

Copy link

rbrzoska commented May 10, 2018

There is a solution for that:

you need to define some abstract class or a token

export abstract class MyAbstractComponent {
// some public methods that should be available in directive
}

then in component you should extend abstract class and provide it

@Component({
...
providers:[{provide: MyAbstractComponent, useExisting:  forwardRef(() => MyComponent ) ]
})
export class MyComponent extends MyAbstractComponent {
... implement  abstract class
}

and finally in directive just inject component instance as abstract class

@Directive({
})
export class MyDirective implements OnInit {

  constructor(private component: MyAbstractComponent ) {}

  ngOnInit() {
    this.component.....
  }
}
@muratcorlu

This comment has been minimized.

Copy link

muratcorlu commented May 24, 2018

I have a component and a structured directive(visible) like this:

<my-dialog *visible="someObservable">
...
</my-dialog>

VisibleDirective binds to an observable and when it's resolved, it initializes to dialog. Dialog component also has a public subject property and it's resolving when it's closed. Now I'm trying to reach that observable inside VisibleDirective. When I tried to suggested approaches to reach my-dialog component instance inside visible directive, I'm getting No provider for DialogComponent error. My VisibleDirective is something like this:

@Directive({ selector: '[visible]'})
export class VisibleDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    @Host() @Self() @Optional() private dialog: DialogComponent) { }

  private show() {
    this.viewContainer.createEmbeddedView(this.templateRef);
    // this.dialog is null here
    this.dialog.subject.subscribe(() => {
      this.hide();
    })
  }

  private hide() {
    this.viewContainer.clear();
  }

  @Input() set visible(condition: boolean | Subject<any>) {
    if (condition instanceof Subject) {
      condition.subscribe(() => {
        this.show();
      }, () => {
        this.hide()
      });
    } else {
      if (condition) {
        this.show();
      } else {
        this.hide();
      }
    }
  }
}

Is it possible to get the reference of dialog component in visible directive when it's exist?

@sam-s4s

This comment has been minimized.

Copy link

sam-s4s commented May 24, 2018

@muratcorlu I don't see any reason why this shouldn't work, in general - however I don't think you're supposed to use both @Host and @Self at the same time - but instead, use just one or the other.

@muratcorlu

This comment has been minimized.

Copy link

muratcorlu commented May 25, 2018

@sam-s4s I think it's because of visible removes dialog component depending on its condition. And by default the condition is false so at the time of visible directive initialize, there is no DialogComponent exist. Here is working example: https://stackblitz.com/edit/angular-dialog-proposal?file=src%2Fapp%2Fdialog%2Fvisible.directive.ts

@fxck

This comment has been minimized.

Copy link

fxck commented Jul 17, 2018

So is there any way to inject host component to a structural directive?

@noddycha

This comment has been minimized.

Copy link

noddycha commented Sep 4, 2018

I was looking for a solution to the same problem in Angular 6. After a lot of research and experimentation, the below approach is working perfectly fine for me:

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[yourDirective]'
})
export class yourDirective {

  constructor(
    private vcRef: ViewContainerRef
  ) { }

  ngOnInit() {
  }

  yourFunction = (): void => {
    console.log(this.vcRef['_view'].component);
  }

}
@sam-s4s

This comment has been minimized.

Copy link

sam-s4s commented Sep 4, 2018

Yeah, this sort of method works, it's just "not supported", in that it's accessing things that aren't meant to be accessed. And thus the Angular team can, at any time (including non-breaking versions) change how it works, and your app will break. Use with caution :)

@cwayfinder

This comment has been minimized.

Copy link

cwayfinder commented Sep 7, 2018

this.vcRef['_view'].component is the parent component; not the one we put the directive on :(

@sbkpilot1

This comment has been minimized.

Copy link

sbkpilot1 commented Sep 28, 2018

I am trying to find a solution to this as well that works with Structural Directives. Looks like the @Host() method does not work with Structural Directives.

@mlc-mlapis

This comment has been minimized.

Copy link

mlc-mlapis commented Sep 28, 2018

@sbkpilot1 ... if you de-sugar the syntax, you can see that using @Host on the DI level leads to <ng-template>:

<ng-container *myDirective>
	...
</ng-container>

is transformed to ...

<ng-template myDirective>
	<ng-container>...</ng-container>
</ng-template>
@sbkpilot1

This comment has been minimized.

Copy link

sbkpilot1 commented Oct 1, 2018

@mlc-mlapis I defined a new Injection Token and forwardRef'd to the host component then used @Inject in my directive. Works good!

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