Angular material <mat-selection-list> 和 <mat-list-option> 跨模板边界

Angular material <mat-selection-list> and <mat-list-option> across template boundaries

如何制作 <mat-list-option> -- 在组件 sub 中声明 -- "subscribe" 到 <mat-selection-list> 外部组件 sub (比如说,组件 app)?

(不幸的是,我无法让 Stackblitz 与我公司的代理一起工作,所以我只放我的示例的相关部分)

示例 1(有效,无组件 sub

(app.component.html)

<div>{{selections | json}}</div>
<mat-selection-list [(ngModel)]="selections">
  <mat-list-option [value]="{x: 1}">x: 1</mat-list-option>
  <mat-list-option [value]="{x: 2}">x: 2</mat-list-option>
  <mat-list-option [value]="{y: 1}">y: 1</mat-list-option>
  <mat-list-option [value]="{y: 2}">y: 2</mat-list-option>
</mat-selection-list>

(app.component.ts)

@Component({
  templateUrl: './app.component.html',
})
export class AppComponent {
  selections = [];
}

此示例按预期工作。所有 4 个选项都被渲染,它们的选择反映在 selections 属性 中(所有选项的值都是 javascript 对象,并且它们从 selections数组).

示例 2(不起作用,在 <mat-selection-list><mat-list-option> 之间添加了组件 sub

(app.component.html)

<div>{{selections | json}}</div>
<mat-selection-list [(ngModel)]="selections">
  <sub></sub>
</mat-selection-list>

(sub.component.html)

  <mat-list-option [value]="{x: 1}">x: 1</mat-list-option>
  <mat-list-option [value]="{x: 2}">x: 2</mat-list-option>
  <mat-list-option [value]="{y: 1}">y: 1</mat-list-option>
  <mat-list-option [value]="{y: 2}">y: 2</mat-list-option>

(app.component.ts 不变;sub.component.ts 微不足道)

此示例确实按预期呈现了所有选项。他们的选择不会反映回 selections 属性(正如预期的那样,因为在每个 <mat-list-option> 被实例化时,没有可见的 <mat-selection-list> 供他们订阅) .

例3(尝试转发<mat-selection-list>,无果)

(app.component.ts)

@Component({
  ...
  viewProviders: [{ provide: MatSelectionList, useExisting: MatSelectionList }]
})
export class AppComponent {
  selections = [];
}

我预计这个会起作用。但我只能得到一个错误:

Cannot instantiate cyclic dependency! MatSelectionList (...)

如何进行这项工作?

备注

我想观察 ngModel 指令(与 ngForm and/or ngModelGroup 协同工作)只需添加适当的模板边界即可完美工作viewProviders 到实例化 ngFormngModelGroup(含)和实例化 ngModel(不含)之间的所有中间组件。

虽然它们的注入路径不一样:ngModel 通过 获取其父 ControlContainer(包含 ngFormngModelGroup) [source]:

constructor(@Optional() @Host() parent: ControlContainer, ...) {...}

<mat-list-option> 通过 [source]:

constructor(..., @Inject(forwardRef(() => MatSelectionList)) public selectionList: MatSelectionList) {...}

我 angular 不了解这些差异及其含义。

MatSelectList 的内置 SelectionModel 与示例 2 方法配合使用效果很好。问题在于 NgModel(这可能是错误或固有限制)。您可以通过监听列表的选择更改并直接管理模型来解决此问题。例如:

HTML

<mat-selection-list [ngModel]="selections" (selectionChange)="handleSelectionChange($event.option)">
  <sub></sub>
</mat-selection-list>    

TS

selections = [];
handleSelectionChange(option: MatSelectionListChange) {
  if (option.selected) {
    this.selections.push(option.value);
  }
  else {
    const index = this.selections.indexOf(option.value);
    this.selections.splice(index, 1);
  }
}