How to access the host component from a directive ? #8277
Comments
|
You could just inject that component into the directive. On Wed, Apr 27, 2016 at 4:34 AM Frank Pepermans notifications@github.com
|
|
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? |
|
@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? |
|
@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. |
|
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).. |
|
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? |
|
@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: 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. |
|
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). |
|
@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.. |
|
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 :) |
|
I have same issue described. This code works for me:
explanation: it is a short run fix, till angular team provide better solution. |
|
This doesn't work for Dart though because in Dart access to private members is effectively prevented. |
|
Hi, @tbosch this is a feature needed by some folks (including myself) as you can see on StackOverflow: Best, |
|
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. |
|
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... |
|
/cc @tbosch |
|
Any news on this? The only one @dqweek approach has been patched in 2.2.0. |
|
@DzmitryShylovich that doesn't seem to be related to me |
|
yeah, nvm |
|
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. |
|
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. |
|
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. |
|
@yoan-asdrubal I used your method in my project. But when I update my project to ng9, this method is no longer available. Is there anyother method to get the host component instance from a directive, in ng9? |
|
@RockNHawk Your method is very good, but need a base class for every component. In my scene, there is no base class for components, alse I can not modify them. Do you have some other ideas? Thanks! |
|
Depending on the use case, you can just invert the relationship and inject the directive into the component. E.g. @Directive({
selector: '[some]'
})
export class SomeDirective() {
public someComponentFunction = () => {};
public something() {
//....
// call something dynamically set by a component
this.someComponentFunction();
// ....
// do other things maybe
}
}
@Component({
selector: 'example',
template: '<div>Example</div>'
})
export class ExampleComponent implements OnInit {
public ngOnInit() {
if (this._someDirective) {
this._someDirective.someComponentFunction = () => {
// ... do whatever you gotta do
};
}
}
constructor(@Optional() private readonly _someDirective: SomeDirective) {}
} |
|
My workaround solution Tested on Angular 9.1
EDIT: |
|
Any solution for this one yet? Even if its a bit hacky, but i dont have access of the parent (but not for debug only either) Used to use this one for non ivy: (this.viewContainer as any)._view.componentBasically all i need it for is to execute a function in correct context (this) const hostComponent = (this.viewContainer as any)._view.component;
// execute is coming from input
const execFn = this.execute.bind(hostComponent); |
|
Look at: #10448 (comment) |
Thats having an abstraction over 1 parent component, so its not good for my case. I need the actual view host of the component is in (not specified anywhere) e.g. if you have 5 pages (so 5 components) the directive should pick its parent depending where it is in. For now what i've did I just pass the host via an input as well e.g. |
|
My workaround solution Tested on Angular
Used by my lib here |
|
@valorkin isn't |
Use an |
@cbejensen You are correct. |
|
I will rephrase ng.getComponent. This works in production - tested function _getComponent(nativeElement): any {
const ngDevMode = true;
const MONKEY_PATCH_KEY_NAME = '__ngContext__';
const TVIEW = 1;
const HEADER_OFFSET = 20;
const HOST = 0;
function findViaNativeElement(lView, target) {
const tView = lView[TVIEW];
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
if (unwrapRNode(lView[i]) === target) {
return i;
}
}
return -1;
}
/**
* Returns `RNode`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
function unwrapRNode(value) {
while (Array.isArray(value)) {
value = value[HOST];
}
return value;
}
function readPatchedData(target) {
return target[MONKEY_PATCH_KEY_NAME] || null;
}
function getLContext(target) {
const mpValue = readPatchedData(target);
if (mpValue) {
// only when it's an array is it considered an LView instance
// ... otherwise it's an already constructed LContext instance
if (Array.isArray(mpValue)) {
const lView = mpValue;
let nodeIndex;
nodeIndex = findViaNativeElement(lView, target);
if (nodeIndex === -1) {
return null;
}
return {nodeIndex, lView};
}
}
}
function getComponentAtNodeIndex(nodeIndex, lView) {
const tNode = lView[TVIEW].data[nodeIndex];
let directiveStartIndex = tNode.directiveStart;
return tNode.flags & 2 /* isComponentHost */ ? lView[directiveStartIndex] : null;
}
function loadLContext(target, throwOnNotFound = true) {
const context = getLContext(target);
return context;
}
function getComponent(element) {
const context = loadLContext(element, false) as any;
if (context === null) {
return null;
}
if (context.component === undefined) {
context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView);
}
return context.component;
}
return getComponent(nativeElement);
} |
I actually like this solution. It seems much cleaner than everything else proposed. |
This seems like it would work well for many scenarios, but what if I want to use the directive on multiple child components in the same template? |
@cbejensen did you check out the StackBlitz example? With the solution you should be able to use the directive on multiple child components in the same template. |
Each directive will be injected with its own parent, via the token. |
|
I find that this code works just fine in angular 11
|
|
I just wish the angular team would give us an easy method of doing this, that's not using an underscored variable that could break in any upgrade :P It has been 5 years now... |
|
@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 |
I am using angular 11. This solution worked for me to get the component. However the changes done to component in the directive are not reflected in host component. is there a way to get this working? |
I agree that it's not difficult to implement a token-based solution, but I'd argue these 2 points: a) It adds unnecessary (and messy) lines of code. The point is - it's there, it shouldn't be hard to make it a permanent, public reference. Easy fix that would make a lot of people happy, and reduce danger. I'm not seeing a down-side. |
|
I think the token based approach is completely against what this issue is about. If you know what the host is, then there is no problem. If you don't know and don't want to need to know statically, then there is no proper way of accomplishing the task, but there should be. |
My way is to proxy the ngOnchanged hook, but yes if there's official API to achieve then that would be nice. |
|
Any news? |
|
My workaround was to pass the template ref of component as @input to the directive. It worked well in my case which was to debounce events. Type safety is still missing in this case but it works fine if used correctly. @Directive({selector: '[debounceEvent]'})
export class DebounceEventDirective implements OnInit {
@Input() componentRef: any;
...
constructor(private el: ElementRef, private renderer: Renderer2) { }
ngOnInit(){
if (this.componentRef != null) {
// Subscribe to event emitter in component
this.componentRef[this.eventName].subscribe();
} else {
// Add DOM event bindings using Renderer2
this.renderer.listen(this.el.nativeElement, this.eventName, (e) => {});
}
}
}Usage in view: <!-- With native elements -->
<button debounceEvent [eventName]="'click'" ... ></button>
<!-- With components -->
<app-sidebar #appSidebarRef debounceEvent [componentRef]="appSidebarRef " [eventName]="'sidebarOpened'" .... > |
would upper form of usage work? @amninderchahal |
|
@pillsilly Yes it should work. |



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.getComponentto obtain a Component reference from an ElementRef before, how would I do this now?What I need to achieve is the following:
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
The text was updated successfully, but these errors were encountered: