【问题标题】:How can i see if there is some sibling projected content?我如何查看是否有一些兄弟投影内容?
【发布时间】:2021-10-03 04:19:54
【问题描述】:

我正在投影两个组件。

<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 内部,例如是否投影了组件一

我需要以某种方式检查组件 2 是否具有兄弟内容投影 - component-one。

我怎样才能用角度做到这一点?

【问题讨论】:

  • 父组件能否通知子组件整个家族?

标签: angular angular-content-projection


【解决方案1】:

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

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

父组件

@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类型,所以这个指令附加到的每个元素,都需要提供这个类型。

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:

  • 我们选择所有具有 SelectorDirective 的 ContentChildren
  • 我们将此元素发送到 ParentService 中的主题上
  • 我们还会查找 QueryList 的变化,如果发生变化,我们会将它们发送到 ParentService 中的主题上

不,我们可以在 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

【讨论】:

  • 感谢您的回答。顺便说一句-提供者的含义是什么:[ {提供:Basecomp,useExisting:forwardRef(()=> Comp2Component)}]
  • 这意味着我们在为该元素创建的 Elementinjector 中提供组件的实例。这是必要的,因为我们想要访问 selectorDirective 中不同类型的组件。这是因为同一元素上的组件和指令共享一个 Elementinjector。您可以在此处阅读更多相关信息:angular.io/guide/hierarchical-dependency-injection。对于forwardRef(),请参阅stackoverflow.com/questions/50894571/…
  • 非常感谢恩诺。还有 - Basecomp - 这应该是指令或只是一个没有指令注释的简单类?
  • 因为当我尝试重新实现您的示例时,我得到 - main.ts:12 NullInjectorError: R3InjectorError(AppModule)[Basecomp -> Basecomp -> Basecomp] 错误
  • 在 selectorDirective 中,我们正在注入 baseComp - 这会产生错误,因为 baseComp 不在 app.module.ts 文件的 providers 数组中。我在你的例子中看过 - 在 stackblitz 上,baseCompo 不在 app.module.ts 文件的 providers 数组中。那么它是如何运作的呢?
【解决方案2】:

一个简单的解决方案是让父组件显式地将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-one 和 component-two 之间建立紧密耦合,但在我看来它们似乎是完全耦合的,所以这种方式是可以的。

【讨论】:

  • 我不明白这是什么想法?当我在 componentTwo 中初始化属性值等于 componentOne - 我如何才能在 componentTwo 中访问该属性?
  • 在 componentTwo 中定义它(我没有在我的答案中显示) - 并从父级分配给它 - 就像我显示的那样。
猜你喜欢
  • 1970-01-01
  • 2021-03-20
  • 1970-01-01
  • 1970-01-01
  • 2013-06-29
  • 2021-08-24
  • 1970-01-01
  • 2022-05-30
  • 2022-05-30
相关资源
最近更新 更多