Skip to content
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

Closed
frankpepermans opened this issue Apr 27, 2016 · 132 comments
Closed

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

frankpepermans opened this issue Apr 27, 2016 · 132 comments
Assignees
Labels
area: core Issues related to the framework runtime core: di feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Milestone

Comments

@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
Copy link
Contributor

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
Copy link
Author

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
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
Copy link
Author

@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
Copy link

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
Copy link

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
Copy link

@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
Copy link

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
Copy link

@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
Copy link
Author

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
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
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
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é

@syndicatedshannon
Copy link

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
Copy link

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
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 vicb added feature Issue that requests a new feature area: core Issues related to the framework runtime labels Sep 27, 2016
@vicb
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
Copy link

Luke265 commented Nov 19, 2016

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

@zoechi
Copy link
Contributor

zoechi commented Nov 19, 2016

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

@DzmitryShylovich
Copy link
Contributor

yeah, nvm

@dominique-mueller
Copy link

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

@dominique-mueller
Copy link

Thanks so much, your solution(s) work perfectly! I didn't know about the @self decorator, the Angular documentation should probably be updated to include this approach.

@pillsilly
Copy link

pillsilly commented May 20, 2021

<app-sidebar [debounceEvent]="{component: appSidebarRef,eventName: 'sidebarOpened'}"  .... >

would upper form of usage work? @amninderchahal

@amninderchahal
Copy link

@pillsilly Yes it should work.

@angular-robot angular-robot bot added the feature: under consideration Feature request for which voting has completed and the request is now under consideration label May 21, 2021
@badrbilal
Copy link

Until a permanent solution is available, a workaround could be to pass reference of the hosting component to the directive:

image

image

image

of course, this is just a temporary solution which requires binding on each element and a permanent solution will be appreciated.

@pillsilly
Copy link

@sam-s4s InjectionToken approach is not a rocket science. Even code example was provided. Imho there is no need to improve this part by angular team at all

what if the target component is not in your repository?

@pillsilly
Copy link

pillsilly commented Aug 10, 2021

@pillsilly Yes it should work.

since reference id can now only give in an static manner, if the target is in an ngFor(see #33233) then it becomes problematic...

I've voted 33233 as well anyway...

Hope this feature can be prioritized since demands here are much more than the 33233... @angular

@amninderchahal
Copy link

@pillsilly If I remember it correcrly, template reference variables are scoped so this should still work in ngFor. Each #ref would reference the current element in the list. I could be wrong though, I haven't tested this.

@pillsilly
Copy link

pillsilly commented Aug 11, 2021

@pillsilly If I remember it correcrly, template reference variables are scoped so this should still work in ngFor. Each #ref would reference the current element in the list. I could be wrong though, I haven't tested this.

thank you for correcting me, I tested and it works for ngFor.

@metasong
Copy link

what's the current status?
this feature is needed!
in dev mode we have the global ng tools, but in prod mode it is not available

we need a way to dynamically know the host component, where the directive is applied.

declare interface NG {
  getComponent<T>(element: Element): T | null;
}

export function getComponent<T>(elementOrRef: Element | ElementRef): T | null {
  const element = elementOrRef instanceof ElementRef ? elementOrRef.nativeElement : elementOrRef;
  const ng = globalThis.ng as NG;
  return ng.getComponent<T>(element);
}

@bglamadrid
Copy link

bglamadrid commented Sep 14, 2021

(Using Angular 11)

For my specific use case, I needed to open a dialog and dynamically generate a component with a FormGroup in it. I had 7 variants of this dynamic component. Obviously somewhere I'm going to tell Angular which component I'm going to instantiate, but getting the instance itself from within the dialog was the pain point.

In the end, I found out I can get the component instance at the moment of creation through a ViewContainerRef. Comes off as "any" type and works for me.
I used a directive with a ViewContainerRef, but components may work as well.

The relevant bits follow below (this is all inside the dialog component class):

@ViewChild(MyDirective, { static: true }) formHost: MyDirective; // has a viewContainerRef
constructor(
    @Inject(MAT_DIALOG_DATA) public data: MyDialogData<T>, // holds the component type
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
    
}

ngOnInit(): void {
  this.loadComponent(); // will not work if called within constructor
}

private loadComponent(): void {
    const factory = this.componentFactoryResolver
        .resolveComponentFactory(this.data.formComponent); // pass component type
    const component = this.formHost.viewContainerRef
        .createComponent(factory)
        .instance; // get the instantiated component
    if ('formGroup' in component && // monkey js typechecking, can do better
        component.formGroup instanceof FormGroup) { 
      // do stuff with the component
    }
}

@AndrewKushnir AndrewKushnir added this to Inbox in Feature Requests via automation Sep 15, 2021
@AndrewKushnir AndrewKushnir moved this from Inbox to Needs Discussion in Feature Requests Sep 15, 2021
@AndrewKushnir AndrewKushnir self-assigned this Sep 16, 2021
@AndrewKushnir AndrewKushnir moved this from Needs Discussion to Close with Followup in Feature Requests Sep 16, 2021
@swseverance
Copy link
Contributor

swseverance commented Oct 13, 2021

I could not get some of these approaches to work, and others seemed too hacky. So... I turned my directive into a "shell" component and used a ContentChild ref. Yes, you do need to know the type of component you need to reference in advance.

@Component({
  selector: 'my-component',
  template: '<ng-content></ng-content>'
})
export class MyComponentThatWasOriginallySupposedToBeADirective implements AfterContentInit {
  @ContentChild(MyComponentINeedToReference) comp: MyComponentINeedToReference;

  ngAfterContentInit(): void {
    // use this.comp
  }
}
<my-component>
  <component-that-i-need-to-reference></component-that-i-need-to-reference>
</my-component>

@LickIt
Copy link

LickIt commented Oct 28, 2021

What about structural directives? Is there an easier way to get the component from the EmbeddedViewRef?

@MQpeng
Copy link

MQpeng commented Dec 1, 2021

try this

@Directive({
  selector: '[shortcut-form]',
})
export class ShortcutFormDirective implements OnInit, AfterViewInit {
  hostComponent:any;
  constructor(public el: ElementRef, private view: ViewContainerRef) {}

  ngOnInit(): void{
  }

  ngAfterViewInit(): void{
    this.hostComponent = this.view.injector.get(NzSelectComponent, null) || this.view.injector.get(CoSelectComponent, this.el.nativeElement);
  }
}

@kayahr
Copy link

kayahr commented Jan 13, 2022

With current Angular 13 this workaround with template ref and Input property no longer works:

<app-sidebar #appSidebarRef  debounceEvent [componentRef]="appSidebarRef " ... >

The input property is now undefined. Other input properties which reference other components are working so looks like Angular 13 only broke this self-reference.

I had to downgrade to Angular 12 to get it working again... Does someone know a workaround which doesn't hack into private properties and which works with Angular 13?

@amninderchahal
Copy link

@kayahr Seems to be working in angular 13.1.2. Are you sure your property name is correct.

@kayahr
Copy link

kayahr commented Jan 14, 2022

@kayahr Seems to be working in angular 13.1.2. Are you sure your property name is correct.

Huh, strange... Yes, the property name is correct. Without changing the code it works with Angular 12 but not with 13. At what time do you access the property in your code? I tried on init and on after-view-init, in both cases the property is undefined with Angular 13.

Or maybe there is a difference if angular-cli is used to compile the beast and Angular 13 now has even more black magic which forces me into accepting their compiler? I don't use that, my project is a standard TypeScript environment.

@amninderchahal
Copy link

amninderchahal commented Jan 16, 2022

@kayahr I can access component ref in both on-init and after-view-init hooks. I did try it in a new project in v13 just for testing this. Not sure why it won't work in your application if it's a typical application.

Also I think if the component/directive has exportAs option used to name it then you have to use the name specified for that option to grab the reference.

@AndrewKushnir
Copy link
Contributor

We've discussed this feature request as a part of the Feature Request process.

There are few solutions mentioned in this thread:

  • using DI to inject a host component instance reference (when the component class is known)
  • declaring a provider on a host component (with a reference to the component itself) and injecting that token into a directive (when there are different types of components that should be handled by a directive)
  • using a local ref to a component and pass that as an input to a directive

Those solutions provide a way to access a host element in an explicit and type-safe way, so we encourage using one of those options.

We are closing this ticket, please create a new one if there are some use-cases that can not be covered by one of the options mentioned above (and described in this thread).

Feature Requests automation moved this from Close with Followup to Closed Jan 21, 2022
@platon-rov
Copy link

declaring a provider on a host component (with a reference to the component itself) and injecting that token into a directive (when there are different types of components that should be handled by a directive)

it won't work for structural directives btw, is it intended? Once you turn it into attribute directive it works.

@Totati
Copy link
Contributor

Totati commented Feb 11, 2022

it won't work for structural directives btw, is it intended? Once you turn it into attribute directive it works.

Yes, <my-comp *ngIf="true"> is a syntactic sugar for <ng-template [ngIf]="true"><my-comp>.... As you can see the component is not the host.

@Nanges
Copy link

Nanges commented Feb 22, 2022

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

To prevent this, I use a third party directive that actes as proxy and provides a common interface.

e.g:

app.component.html

<kendo-dropdownlist appMyDataProvider></kendo-dropdownlist>

The common interface used by directives that will handle the dropdown (or another kind of component)

export interface DataComponentAccessor {
    populate(data: any[]);
}

export const DATA_COMPONENT_ACCESSOR = new InjectionToken('Data component accessor');

The proxy. Only this guy knows how work the third party component.

The selector is the same as kendo-dropdownlist to make implementation more friendly.

@Directive({
    selector: 'kendo-dropdownlist',
    providers: [
        {
            provide: DATA_COMPONENT_ACCESSOR,
            useExisting: forwardRef(() => DropDownListAccessorDirective),
        },
    ],
})
export class DropDownListAccessorDirective implements DataComponentAccessor {
    constructor(private component: DropDownListComponent) {}

    populate(data: any[]) {
        this.component.data = data;
    }
}

The data provider. As you can see, there is no reference to the kendo-component.

@Directive({
    selector: '[appMyDataProvider]',
})
export class MyDataProviderDirective implements OnInit {
    constructor(@Inject(DATA_COMPONENT_ACCESSOR) private component: DataComponentAccessor) {}

    ngOnInit(): void {
        this.component.populate(['foo', 'bar', 'baz']);
    }
}

@Lonli-Lokli
Copy link

@Nanges there is no strict types here so it becomes useless

@angular-automatic-lock-bot
Copy link

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

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime core: di feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Projects
No open projects
Development

No branches or pull requests