Angular 6/7库组件-提供formGroup输入的正确方法?

时间:2019-01-22 16:43:02

标签: angular angular-library

更新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的.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 ...仅举几例,我怀疑答案就在这里,但我的理解仍处于起步阶段。

感谢所有帮助和说明。

更新

我在上面提供的第二个链接似乎表明输入应该是无状态的……

  

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

     

诸如组件和管道的声明应设计为   无状态,表示他们不依赖或更改外部变量

(添加了强调)。我一直在做的工作是将此输入作为表单的一部分,然后在提交时从表单中获取值。听起来好像我应该将其反转,以便库组件发出事件,如this answer中所示。问题是这是否是最好的方法。通过事件处理库组件的值似乎更合逻辑。

1 个答案:

答案 0 :(得分: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)

将名称查找迁移到原始项目之外,然后只需要进行一些小的更改。我从[html]中删除了[formGroup] =“ parent”部分,因为它不再是控件上下文的一部分。

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

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

在namelookup.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实现as outlined in many examples。 其显着点是:

在模板中为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({});
}

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