0

更新 3

我按照@Ingo 的建议使我的库组件实现了CVA,但我发现由于我的组件包装了@ng-select/ng-select,一些必要的功能破坏了ng-select 的基本功能。也就是说,当从项目列表中选择一个值时,它会填充该值,但它会保持隐藏状态。删除所有必需的 CVA 方法使库组件成为一个工作库组件,但在调用表单中显示所选值的挑战仍然存在(仍在努力)。

或者,添加一个事件发射器并定义一个 @Output 似乎遇到了类似的情况,即试图拦截 ng-select 中的更改事件会导致破坏它。很可能 ng-select 不适合嵌入到库组件中。

更新 2

从大量来源来看,使用 @Output 和 EventEmitter 似乎是对此的正确答案。如果没有其他人愿意提供答案,我会在我完成所有工作并发布后写一个。


原始问题

我决定开始尝试 Angular 6 库功能,但我已经升级到 Angular 7 CLI。我认为这并不重要,但我对 Angular 开发还很熟悉。

我想制作成库的组件是一个基于 ng-select 的表单控件。它通过父输入从父表单组件接收 formGroup。

<div [formGroup]="parent" _ngcontent-ikt-1 class="container">
  <ng-select 
    [items]="peopleBuffer"  
    bindLabel="text"
    bindValue="id"  
    [typeahead]="input$"     
    formControlName="assocNumber" 
    #select>
      <ng-template ng-option-tmp let-item="item" let-search="searchTerm">
        <span [ngOptionHighlight]="search">{{item?.text}}</span>
      </ng-template>
  </ng-select>

  </div>

.ts 文件的父级为 @Input()

@Input() parent: FormGroup;

在父表单组件中,我像这样安装控件

<app-lookuplist _nghost-ikt-1
    [parent]="form">
</app-lookuplist>

在父表单 .ts 文件中,表单的类型为 FormGroup,并在调用 @ngOnInit 时构造。在一个自包含的项目中,这很好用。

不过,我最大的问题是,抽象这种关系的正确方法是什么

我应该使用原理图而不是库吗?或者是否有适当的方法来公开此输入以便最初构建?我可以将它放入测试工具中,并像自包含应用程序一样提供输入,但在构建时,它会产生一个你可能都非常熟悉的错误:

无法绑定到“formGroup”,因为它不是“div”的已知属性。

我遵循了几个不同的教程,它们似乎都遵循相同的一般结构。

https://blog.angularindepth.com/creating-a-library-in-angular-6-87799552e7e5

https://angular.io/guide/creating-libraries ...仅举两个例子,我怀疑答案就在这里,但我对这个主题的理解仍处于起步阶段。

所有帮助和指导表示赞赏。

更新

我在上面提供的第二个链接似乎提供了输入应该是无状态的观点......

为了使您的解决方案可重用,您需要对其进行调整,使其不依赖于特定于应用程序的代码。以下是将应用程序功能迁移到库时需要考虑的一些事项。

组件和管道等声明应设计为无状态的,这意味着它们不依赖或更改外部变量

(强调补充)。我一直在做的是将此输入作为表单的一部分,然后在提交时从表单中获取值。听起来我应该反转这个,以便库组件发出一个事件,就像在这个答案中一样。如果这是最好的方法,问题仍然存在。通过事件处理库组件的值似乎更合乎逻辑。

4

1 回答 1

0

好吧,我想我明白了。或者至少,我有一个可行的解决方案,以我目前的理解水平对我来说是有意义的。如果您看到优化的方法,请告诉我。

部分问题是让 ControlValueAccessor 与 ng-select 一起工作,而不会破坏其本机功能。具体来说,当您使用 ng-select 作为类型提前控件并选择一个值时,它应该添加一个标签,显示您到目前为止选择的值。尝试实现 ControlValueAccessor 接口破坏了这一点,可能是因为它覆盖了 ng-select 提供的接口 onChange 处理程序。

因此,我采取的路径结合了我在问题中看到和提到的两种方法。我的 ng-select 实现现在存在于一个嵌套组件中,该组件包含在一个包含 ControlValueAccessor 的包装组件中。

这使得以下粗略的结构:

app.component  (test harness)
    people-search.component  (ControlValueAccessor)
        namelookup.component (@Outputs an EventEmitter)

将 namelookup 从原始项目中迁移出来只需要进行一些小的更改。我从其 html 中编辑了 [formGroup]="parent" 部分,因为这不再是控件上下文的一部分。

在 namelookup.component.html 中,我添加了:

<ng-select
    ...
    (change)="handleSelection($event)"
    ...>

在名为lookup.component.ts

@Component({
  selector: 'ps-namelookup',
  templateUrl: './namelookup.component.html',
  styleUrls: ['./namelookup.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush, // new code
})

export class NameLookupComponent implements OnInit {
...
    @Output() selectedValue = new EventEmitter<string>();

     handleSelection(event) {
         console.log('handleSelection in NameLookupComponent fired');
         this.selectedValue.emit(event);
    }
...
}

然后创建了一个新的包装器 people-search.component,它具有许多示例中概述 的基本 ControlValueAccessor 实现。其中的重点是:

在模板中为从 CVA 包装器中的子组件发出的事件 (selectedValue) 提供输出挂钩。

@Component({
    selector: 'ps-people-search',
    template: `
      <ps-namelookup (selectedValue)="handleselectedvalue($event)"></ps-namelookup>
    `,
...

向类定义添加方法并实现 CVA。

export class PeopleSearchComponent implements ControlValueAccessor {
    ....
    private propagateChange = (_: any) => { };

    handleselectedvalue($event) {
        console.log('handleselectedvalue fired');
        console.log($event);
        this.data = $event[0].id;
        this.propagateChange(this.data);
        console.log('handling change event and propagating ' + this.data);
    }

至少也不能实现 registerOnChange 和 registerOnTouched。编译器可能会坚持其他一些。

最后,在测试工具 app.component.html 中,为了在表单上显示该值,以便我知道选择的值已被传回,我添加了:

{{form.value | json}}

并且在 app.component.ts 中提供了一个替代的 FormGroup 对象。

public form: FormGroup;

constructor(private fb: FormBuilder){
    console.log('constructing test harness appComponent');

   this.form = this.fb.group({});
}

这是一个回答我的根本问题的答案,可能还有其他好的方法可以回答这个问题。反馈、优化和更正当然是受欢迎的。

于 2019-01-23T19:56:07.500 回答