Как реализовать ngModel на пользовательских элементах?

Учитывая простой элемент input, я могу сделать это:

<input [(ngModel)]="name" /> {{ name }}

Это не работает для моих пользовательских элементов:

<my-selfmade-combobox [(ngModel)]="name" values="getValues()" required></my-selfmade-combobox>

Как я могу это реализовать?


person Community    schedule 02.02.2016    source источник
comment
stackoverflow.com/a/41353306/2176962   -  person hgoebl    schedule 31.12.2017


Ответы (5)


Думаю, эта ссылка ответит на ваш вопрос:

Для этого нам нужно реализовать две вещи:

  • Компонент, который обеспечивает логику вашего компонента формы. Это не требует ввода, так как это будет предоставлено самим ngModel.
  • Пользовательский ControlValueAccessor, который реализует мост между этим компонентом и ngModel / ngControl

Предыдущая ссылка дает вам полный образец ...

person Thierry Templier    schedule 02.02.2016
comment
На мой взгляд, эта ссылка тоже помогла embed.plnkr.co/nqKUSPWb6w5QXr8a0wEu / а> - person Paulo Henrique; 17.08.2018

[(ngModel)]="item" - это сокращение от [ngModel]="item" (ngModelChange)="item = $event"

Это означает, что если вы хотите добавить свойство двусторонней привязки к своему компоненту, например

<app-my-control [(myProp)]="value"></app-my-control>

Все, что вам нужно сделать в своем компоненте, это добавить

@Input()
myProp: string;

// Output prop name must be Input prop name + 'Change'
// Use in your component to write an updated value back out to the parent
@Output()
myPropChange = new EventEmitter<string>();

@Input будет обрабатывать записи и записать новое значение обратно в родительский элемент, просто вызовите this.myPropChange.emit("Awesome") (вы можете поместить излучение в сеттер для своего свойства, если вы просто хотите, чтобы он обновлялся каждый раз при изменении значения. )

Вы можете прочитать более подробное объяснение того, как / почему это работает здесь.


Если вы хотите использовать имя ngModel (потому что есть дополнительные директивы, которые привязываются к элементам с ngModel), или это для элемента FormControl, а не компонента (AKA, для использования в ngForm), тогда вам нужно будет поиграть с ControlValueAccessor. Подробное объяснение того, как создать свой собственный FormControl и почему он работает, можно прочитать на здесь.

person Tezra    schedule 06.01.2020
comment
Здорово! Спас день! - person Pianoman; 26.09.2020

Я реализовал ngModel один раз для ввода в мои общие компоненты, и с тех пор я могу очень просто расширить его.

Всего две строчки кода:

  1. providers: [createCustomInputControlValueAccessor(MyInputComponent)]

  2. extends InputComponent

my-input.component.ts

import { Component, Input } from '@angular/core';
import { InputComponent, createCustomInputControlValueAccessor } from '../../../shared/components/input.component';
@Component({
   selector: 'my-input',
   templateUrl: './my-input-component.component.html',
   styleUrls: ['./my-input-component.scss'],
   providers: [createCustomInputControlValueAccessor(MyInputComponent)]
})
export class MyInputComponent extends InputComponent {
    @Input() model: string;
}

my-input.component.html

<div class="my-input">
    <input [(ngModel)]="model">
</div>

input.component.ts

import { Component, forwardRef, ViewChild, ElementRef, OnInit } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
export function createCustomInputControlValueAccessor(extendedInputComponent: any) {
    return {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => extendedInputComponent),
        multi: true
    };
}

@Component({
    template: ''
})
export class InputComponent implements ControlValueAccessor, OnInit {
    @ViewChild('input') inputRef: ElementRef;

    // The internal data model
    public innerValue: any = '';

    // Placeholders for the callbacks which are later provided
    // by the Control Value Accessor
    private onChangeCallback: any;

    // implements ControlValueAccessor interface
    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }
    // implements ControlValueAccessor interface
    registerOnChange(fn: any) {
        this.onChangeCallback = fn;
    }

    // implements ControlValueAccessor interface - not used, used for touch input
    registerOnTouched() { }

    // change events from the textarea
    private onChange() {
        const input = <HTMLInputElement>this.inputRef.nativeElement;
        // get value from text area
        const newValue = input.value;

        // update the form
        this.onChangeCallback(newValue);
    }
    ngOnInit() {
        const inputElement = <HTMLInputElement>this.inputRef.nativeElement;
        inputElement.onchange = () => this.onChange();
        inputElement.onkeyup = () => this.onChange();
    }
}
person Shlomi Aharoni    schedule 19.07.2018

Шаг 1. Добавьте свойство providers ниже:

@Component({
    selector: 'my-cool-element',
    templateUrl: './MyCool.component.html',
    styleUrls: ['./MyCool.component.css'],
    providers: [{   // <================================================ ADD THIS
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => MyCoolComponent),
        multi: true
    }]
})

Шаг 2. Выполните ControlValueAccessor:

    export class MyCoolComponent implements ControlValueAccessor {
    
      private _value: string;
      // Whatever name for this (myValue) you choose here, use it in the .html file.
      public get myValue(): string { return this._value }
      public set myValue(v: string) {
        if (v !== this._value) {     
          this._value = v;
          this.onChange(v);
        }
      }
    
      constructor() {}
    
      onChange = (_) => { };
      onTouched = () => { };
    
      writeValue(value: any): void {    
        this.myValue = value;
      }
      registerOnChange(fn: any): void {
        this.onChange = fn;
      }
      registerOnTouched(fn: any): void {
        this.onTouched = fn;
      }
      setDisabledState?(isDisabled: boolean): void {
        throw new Error("Method not implemented.");
      }
    
    }

Шаг 3. В HTML привяжите любой элемент управления, который хотите myValue:


    <my-cool-element [(value)]="myValue">
              <!-- ..... -->
     </my-cool-element>
person A-Sharabiani    schedule 13.09.2019
comment
где часть [(ngModel)] реализуется в пользовательском компоненте? ‹Модель cutom-component = что-то› то в custom-component вы принимаете что-то и привязываете это к [(ngModel)] нет? - person mcha; 04.01.2021
comment
@mcha, шаг 3 - это html для MyCoolComponent. Цель состоит в том, чтобы сделать настраиваемый компонент совместимым со встроенной функциональностью ngModel. Итак, в этом случае они теперь могут писать; ‹My-cool-element [(ngModel)] = значение› ‹/my-cool-element› - person Gavin; 11.06.2021
comment
@Gavin обновил это спасибо - person A-Sharabiani; 13.06.2021

Вы можете самостоятельно реализовать настраиваемую двустороннюю привязку. Для angular 10 см. Официальный пример SizerComponent, здесь [(size)] ведет себя так же, как [(ngModel)]:

<app-sizer [(size)]="fontSizePx"></app-sizer>
person Iceberg    schedule 24.07.2021