我如何查看是否有一些兄弟投影内容?

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

我正在投影两个组件。

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

因此,如果我想知道组件一是否投影在内部 app-form-field 我会做:

  @ContentChild(ComponentOne) componentOne;

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

但我需要检查我的 component-two 是否投影了组件一

我需要以某种方式检查组件二是否有同级内容投影 - component-one.

我如何在 angular 中做到这一点?

可以检查是否有投影的兄弟组件,但要走的路可能很麻烦。

首先我们需要投影“子”组件的组件。

父组件

@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() {}
}

注:

  • 我们在组件级别注入服务 ParentService,因此子组件和投影组件可以接收此实例。

  • 对于不同类型的组件,我们需要一个统一的选择器(SelectorDirective)传递给@ContentChildren

父服务:

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

  constructor() {}
}

选择器指令:

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

注:

  • 在构造函数中我们注入了Basecomp类型,所以这个指令附加到的每个元素都需要提供这个类型。

基础合成

export class Basecomp {}

注:

  • 这仅用作注入令牌

现在我们需要为我们的统一令牌提供子组件的实例:

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() {}
}

注:

  • 我们还为该组件的实例提供 Basecomp-Token。如果 selectorDirective 附加到提供此令牌的组件,我们可以访问指令内部的组件实例。

现在回头看看ParentComponent的ngAfterContentInit:

  • 我们 select 所有具有 SelectorDirective
  • 的 ContentChildren
  • 我们采用这些元素并在我们的 ParentService 中的主题上发出它们
  • 我们还会查找 QueryList 的更改,如果发生更改,我们会在 ParentService 的 Subject 上发出它们

不,我们可以在 comp2 中注入 ParentService 并访问所有兄弟姐妹:

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;
      });
    });
  }
}

工作示例:https://stackblitz.com/edit/angular-ivy-t3qes6?file=src/app/comp2/comp2.component.ts

一个简单的解决方案是让父组件显式地将 component-one 引入 component-two

所以在你的 app-form-field 中你写了一些类似的东西:

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

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

然后您可以在 component-two 中对此做出反应。

我知道这会在 app-form-field component-onecomponent-two 之间造成紧密耦合,但在我看来它们似乎是完全耦合的,所以这种方式是可以的。