简体   繁体   中英

Angular parent component “scoping” child component

Suppose I have components Parent and Child . Child can be used either as a standalone component, or within a Parent component. However, I want Child to have different behavior based upon where it lives.

Suppose Child has an optional @Input [isHappy] , which is either true or false . However , whenever a Child component is hosted by a Parent component, suppose isHappy must always be true.

AFAICT there are two ways of doing this:

1) User must just know to always specify [isHappy]="true" whenever a Child is hosted by a Parent .

<parent>
  <child [isHappy]="true"></child>
</parent>

2) Parent manually sets this.child.isHappy = true within its ngOnInit lifecycle hook.

Which approach is preferred? In my opinion, approach #2 makes more sense, users don't have to know to set [isHappy]="true" when a Child is hosted by a Parent . On the other hand, I'm aware that it's frowned upon in Angular for components to programmatically change one another, especially if all components are OnPush (please correct me if I'm wrong here).

I want Child to have different behavior based upon where it lives.

How about letting Child component know where it lives with ElementRef :

export class ChildComponent {
  hasParent: boolean;

  constructor (
    private elRef: ElementRef
  ) {}

  ngOnInit() {
    const el = this.elRef.nativeElement.parentElement as HTMLElement
    this.hasParent = el.localName === 'app-parent'
  }
}

stackblitz: https://stackblitz.com/edit/angular-kagdsu

In my opinion 2nd way would work, but that could add couple of things into your solution

  1. Tight coupling between two component
  2. What if parent component don't have child component?

To solve it by better way, I'd suggest you to use Host decorator, that will ask for Parent component dependency from Child component. If that exists the make isHappy property to be true

@Component({...})
export class Child {
   @Input() isHappy: boolean = false;

   constructor(@Optional() @Host() private parent: Parent) {} 

   ngOnInit() {
      // Do only if parent exists
      if (this.parent) {
         this.parent.isHappy = true
      }
   }
} 

I understand even the way suggested above has tight coupling between the Parent component. We should think of removing that dependency from the inner child component.

Yes, we can do that by little hackish way, where we would be checking current component's immediate parent component name like below. For achieving the same you had to add ViewContainerRef dependency to get hold of parent/host component.

constructor(private viewContainer: ViewContainerRef) {}

ngOnInit() {
    if(this.viewContainer[ '_data' ].componentView.parent.component.constructor.name === 'Parent') {
      this.parent.isHappy = true
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM