简体   繁体   中英

How can i see if there is some sibling projected content?

I am projecting two components.

<app-form-field>
   <component-one/>
   <component-two/>
</app-form-field>

So if i want to know for example if component one is projected inside app-form-field i will do:

  @ContentChild(ComponentOne) componentOne;

ngAfterContentInit() {
   if(this.componentOne) {
      alert('it is projected');
   } else {
      alert('it is NOT projected');
   }
}

but i need to check inside my component-two if for example component-one is projected

i need to check somehow if component-two has sibling content projection - component-one .

How can i do that in angular ?

It is possible to check if there is a sibling-component projected, but the way to go might be hacky.

First of all we need the component that projects the "child"-components.

ParentComponent

@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css'],
  providers: [ParentService]
})
export class ParentComponent implements AfterContentInit {
  @ContentChildren(SelectorDirective) public refs: QueryList<SelectorDirective>;
  constructor(private p: ParentService) {}
  ngAfterContentInit(): void {
    this.p.selectorDirectives.next(this.refs.toArray());
    this.refs.changes.subscribe(x => {
      this.p.selectorDirectives.next(this.refs.toArray());
    });
  }

  ngOnInit() {}
}

Note:

  • We inject a Service ParentService on component level, so child and projected components can receive this instance.

  • For components of differents types we need a uniform Selector ( SelectorDirective ) to pass to @ContentChildren

ParentService:

@Injectable()
export class ParentService {
  selectorDirectives: BehaviorSubject<
    SelectorDirective[]
  > = new BehaviorSubject([]);

  constructor() {}
}

SelectorDirective:

@Directive({
  selector: '[appSelector]'
})
export class SelectorDirective {
  constructor(public componentRef: Basecomp) {}
}

Note:

  • In the constructor we inject the Basecomp type, so each element this directive is attached to, need to provide this type.

Basecomp

export class Basecomp {}

Note:

  • This is only used as injection token

Now we need to provide the intances of our child-components for our uniform token:

Comp1Component

@Component({
  selector: 'app-comp1',
  templateUrl: './comp1.component.html',
  styleUrls: ['./comp1.component.css'],
  providers: [
    { provide: Basecomp, useExisting: forwardRef(() => Comp1Component) }
  ]
})
export class Comp1Component implements OnInit {
  constructor() {}

  ngOnInit() {}
}

Note:

  • We provide the instance of this component also with the Basecomp-Token. If the selectorDirective is attached to a component which provides this token we can access the component-instance inside of the directive.

Now look back at ngAfterContentInit of ParentComponent:

  • We select all ContentChildren which have the SelectorDirective
  • We take this Elements an emit them on the Subject in our ParentService
  • Also we look for changes of the QueryList, if changes occour we emit them on the Subject in our ParentService

No we can inject ParentService in comp2 and have access to all siblings:

Comp2Component

@Component({
  selector: 'app-comp2',
  templateUrl: './comp2.component.html',
  styleUrls: ['./comp2.component.css'],
  providers: [
    { provide: Basecomp, useExisting: forwardRef(() => Comp2Component) }
  ]
})
export class Comp2Component implements OnInit {
  constructor(private p: ParentService) {}
  c1 = false;
  ngOnInit() {
    this.p.selectorDirectives.pipe().subscribe(x => {
      this.c1 = !!x.find(a => {
        return a.componentRef instanceof Comp1Component;
      });
    });
  }
}

Working Example: https://stackblitz.com/edit/angular-ivy-t3qes6?file=src/app/comp2/comp2.component.ts

A simple solution would be to have the parent component introduce component-one to component-two explicitly:

So in your app-form-field like you write something along the lines of:

@ContentChild(ComponentOne) componentOne;
@ContentChild(ComponentTwo) componentTwo;

ngAfterContentInit() {
   this.componentTwo.componentOne = this.componentOne;
}

And then you can react to that in component-two .

I know this creates a tight coupling between app-form-field component-one and component-two but it looks to me as if they are quite coupled anyway, so this way is ok.

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