父组件中的 NgModel 应该反映子组件中的状态

NgModel in parent-component supposed to reflect state in child-component

我有一个库,其中包含一个组件,它是一个 primeng 组件的包装器。一切以Angular10.

为准

primeng 组件有一个 ngModel。我想在访问包装器组件的父组件中设置 ngModel 并且我希望 ngModel 在 primeng 中向下更改时 'reflected' 回到父组件-组件。

父级包含这样的包装器:

parent.component.html

    <div class="col">
      <wrapper-autocomplete
        [suggestions]="projects"
        ngDefaultControl
        [ngModel]="selectedProject"
        (ngModelChange)="changeSelectedProject($event)"
        [field]="'name'"
        (completeMethod)="getProjects($event)"
        [forceSelection]="true">
        
        ...

      </wrapper-autocomplete>
    </div>

在父组件中,我将 [ngModel] 设置为名称为 selectedProject 的变量,该变量是父组件的一部分。该组件通过使用我插入自己的函数的 ngModelChange 对更改做出反应:

parent.component.ts

 changeSelectedProject(event: any) {
   this.selectedProject = event;
 }

在包装器组件中,我像这样包含了 primeng 组件(抽象的,为了便于阅读,不显示所有属性):

wrapper.component.html

<p-autoComplete
  ...
  [ngModel]="ngModel"
  (ngModelChange)="ngModelChangeCallback($event)">

  ...

</p-autoComplete>

代码的 ts 部分如下所示(为了便于阅读也进行了删减):

wrapper.component.ts

@Component({
  selector: 'wrapper-autocomplete',
  templateUrl: './autocomplete-list.component.html',
  styleUrls: ['./autocomplete-list.component.css']
})
export class AutocompleteListComponent {

  @Input() ngModel: any;
  @Output() ngModelChange: EventEmitter<any> = new EventEmitter<any>();

  ngModelChangeCallback(event: any): void {
    this.ngModelChange.emit(event);
  }
}

因此,这有效并且确实将更改反映回父级,但这并不是我真正想要实现的。该组件应该在未来被其他人使用,我希望他们能够仅使用 [(ngModel)] 而不是 [ngModel](ngModelChange)=....[=24= 来绑定模型]

你对我做错了什么有什么建议吗?我发现 ControlValueAccessor 似乎是我需要的,但我无法在我的代码中正确实现它。

提前致谢!

是的,ControlValueAccessor 是您所需要的,但是对于这个问题还有另一个技巧,代码更少。

只需将子组件中的 ngModel 重命名为 Smth 或任何您想要的名称,输出将是 SmthChange。然后就可以神奇地绑定[(Smth)]了。

如您所知,您可以实现自定义 ControlValueAccessor。所以让我在这里为你做吧

<p-autoComplete
  ...
  [ngModel]="value"
  (ngModelChange)="ngModelChangeCallback($event)">

  ...

</p-autoComplete>

@Component({
  selector: 'wrapper-autocomplete',
  templateUrl: './autocomplete-list.component.html',
  styleUrls: ['./autocomplete-list.component.css'],
  providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => AutocompleteListComponent ),
            multi: true,
        }
    ],
  })
export class AutocompleteListComponent implements ControlValueAccessor {

    onValueChange: any; 
    value: any;

    writeValue(val: any): void {
        this.value = val;
    }
    registerOnChange(fn: any): void {
        this.onValueChange = fn;
    }
    registerOnTouched(fn: any): void {}
    setDisabledState?(isDisabled: boolean): void {}

    ngModelChangeCallback(event: any): void {
      this.onValueChange(event);
    }

}


// then in use it like this

  <wrapper-autocomplete
     [suggestions]="projects"
     ngDefaultControl
     [(ngModel)]="selectedProject"
     [field]="'name'"
     (completeMethod)="getProjects($event)"
     [forceSelection]="true">
       
     ...

  </wrapper-autocomplete>

我上面所做的是实现一个自定义控件值处理器。它有 4 个方法

  1. writeValue(val: any) --> 它会在父 mgModel change/update something
  2. 时调用
  3. registerOnChange(fn: any) --> 它将在组件初始化时调用,你可以将该函数存储在某个变量中,然后每次你想简单地向父级发送一些东西时像上面那样将数据传递给该函数。
  4. registerOnTouched(fn: any): void {} and setDisabledState?(isDisabled: boolean): void {} 您现在可以忽略它,因为现在可能不需要它。但当您想注册触摸或禁用状态时,它们很有用。

另一个简单的方法是在盒子里使用香蕉技巧

只需将您的输入输出替换为其他名称而不是 ngModel

export class AutocompleteListComponent {

  @Input() input: any;
  @Output() inputChange: EventEmitter<any> = new EventEmitter<any>();

  ngModelChangeCallback(event: any): void {
    this.inputChange.emit(event);
  }
}

 <wrapper-autocomplete
   [suggestions]="projects"
   [(input)]="selectedProject">  </wrapper-autocomplete>