Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: Get reference to parent component when being content-projected

Tags:

angular

In my situation, I have a component that should behave differently if it is inside a specific component. So I want to look through the parents to find the component of the correct type, which works well through dependency injection in the simple case:

Child Component

@Component({
    selector: 'spike-child',
    template: `<div>I am the child, trying to find my parent. 
        <button (click)="log()">Test</button></div>`
})
export class ChildComponent {
    // Get Parent through dependency injection; or null if not present.
    public constructor(@Optional() private parent: ParentComponent) { }

    public log(): void {
        console.log('Parent', this.parent);
        console.log('Parent.Id', this.parent && this.parent.id);
    }
}

Parent Component

@Component({
    selector: 'spike-parent',
    template: `
    <div>
        <div>I am the parent of the content below, ID = {{ id }}:</div>
        <ng-content></ng-content>
    </div>`
})
export class ParentComponent {
  @Input()
  public id: number;
}

Usage, works

<spike-parent [id]="1">
  <spike-child></spike-child>
</spike-parent>

Unfortunately, this does not work anymore if we add one more indirection through content projection like this:

ProjectedContent Component

@Component({
    selector: 'spike-projected-content',
    template: '<spike-parent [id]="2"><ng-content></ng-content></spike-parent>'
})
export class ProjectedContentComponent { }

Usage, does not work

  <spike-projected-content>
    <spike-child></spike-child>
  </spike-projected-content>

Obviously, the child will again be inside a parent at runtime, but now it always gets null as the injected parameter. I understand that content that is projected keeps the original context (including the injector chain), which is definitely almost always helpful, but is there some way to tackle this situation? I read about ngComponentOutlet which looks like it might help, but I didn't exactly see how it could fit. I also didn't find any questions that take this last step from this situation. I guess I could query the DOM to achieve the result, but I obviously would like to avoid that and use Angular mechanics for this.

Thank you so much!

like image 696
Dennis Avatar asked Jul 12 '18 06:07

Dennis


People also ask

How do you communicate between parent and child components?

@Input() and @Output() give a child component a way to communicate with its parent component. @Input() lets a parent component update data in the child component. Conversely, @Output() lets the child send data to a parent component.

How do you pass data from child component to parent component in Angular?

Otherwise, remember the three steps: Prepare Child component to emit data. Bind Property in Parent Component template. Use Property in Parent Component class.

What is one way you can pass data from a parent component to a child component?

@Input Decorator: This is used to define an input property and it is used to send data from parent component to child component.


1 Answers

I can only see two ways (except DOM hacking):

  1. Switch to using templates. Ticket is still open: 14935. Currently it is impossible to override injector in viewContainerRef.createEmbeddedView, however there is option to pass context information (example context-parameter-in-angular, or use NgTemplateOutlet).
  2. Use DI to be able to find projected child components and call their methods. Each child component provides itself using common token {provide: Token, useExisting: forwardRef(() => Child)}, it allows to use @ContentChildren(Token) and get list of all projected children, and call any method/setter.
like image 87
kemsky Avatar answered Oct 19 '22 11:10

kemsky