Angular пользовательский выпадающий компонент - ngModel не обновляется должным образом при изменении параметров - PullRequest
0 голосов
/ 10 марта 2020

Я пытаюсь создать пользовательский раскрывающийся компонент, который реализует ControlValueAccessor, но я хочу иметь возможность определять параметры из родительского компонента вместо настраиваемого раскрывающегося списка.

В идеале я хотел бы реализовать его, используя ngModel в моем выборе и ngValue в моих опциях, но по какой-то причине, когда я использую ngValue в опциях, мой ngModel не получает объект, связанный с ngValue для текущий выбранный вариант.

Ниже приведен упрощенный пример использования, которое я пытаюсь выполнить sh:

Примечание. В этом случае я знаю, что могу просто использовать поле id и go о моем пути, но у меня есть случаи, когда я хотел бы использовать ngModel и ngValue.

User: { id: string, name: string, address string }
<!-- This does NOT work like I expected, and selectedUser receives the label in between the options tags -->
<custom-dropdown [(ngModel)]="selectedUser" (change)="onUserChange($event)">
  <option *ngFor="let user of users;" [ngValue]="user">
    ID#{{user.id}} - {{user.name}}
  </option>
</custom-dropdown>

Печать вывода пользователя перед внесением каких-либо изменений является объектом пользователя и ожидается. Однако, как только я изменю параметры, значением объекта selectedUser является строка между тегом параметра выбранной опции (т. Е. ID # ... - ...) вместо объекта.

Если я использую [value]="user.id" вместо [ngValue]="user", все будет работать как положено:

<!-- This works as expected and selectUser gets the user object that ngValue is associated with -->
<custom-dropdown [(ngModel)]="selectedUser" (change)="onUserChange($event)">
  <option *ngFor="let user of users;" [value]="user.id">
    ID#{{user.id}} - {{user.name}}
  </option>
</custom-dropdown>

Если я использую обычный выбор вместо custom-dropdown, все будет работать так, как ожидается:

<!-- This works as expected and selectUser gets the user object that ngValue is associated with -->
<select [(ngModel)]="selectedUser" (change)="onUserChange($event)">
  <option *ngFor="let user of users;" [ngValue]="user">
    ID#{{user.id}} - {{user.name}}
  </option>
</select>

Вот раскрывающиеся списки и раскрывающиеся списки. html реализации:

раскрывающиеся списки. html

<select [ngClass]="[sizeClass]" [(ngModel)]="value">
  <ng-content></ng-content>
</select>

dropdown.ts

@Component({
  selector: 'dropdown',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DropdownComponent),
      multi: true
    }
  ],
  templateUrl: './dropdown.component.html',
  styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements ControlValueAccessor {

    private innerValue: any;

    get value(): any {
        return this.innerValue;
    }

    set value(value: any) {
        if (this.innerValue !== value) {
            this.innerValue = value;
            this.onChange(value);
        }
    }

    writeValue(value: any) {
      this.innerValue = value;
    }

    onChange = (_) => {};
    onTouched = () => {};
    registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
    registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

Если вы хотите увидеть полную реализацию, у меня есть простая настройка stackblitz:

https://stackblitz.com/edit/angular-6v8ajp

Я не уверен, что именно я не прав, и любая помощь приветствуется ..

1 Ответ

0 голосов
/ 11 марта 2020

Боюсь, что вы не можете использовать в ng-контенте тег html <option>, но вы можете создать директиву

@Directive(
{
  selector:'[option]'
})
export class OptionDirective implements AfterViewInit{
  @Input() ngValue:any;
  innerHTML:string="";
  constructor(private el:ElementRef)
  {
  }
  ngAfterViewInit()
  {
     this.innerHTML=this.el.nativeElement.innerHTML

  }
}

Таким образом, ваш. html становится похожим, смотрите которые используют <div option..>

<dropdown [(ngModel)]="selectedUser" (ngModelChange)="onUserChange($event)">
  <div option *ngFor="let user of users;" [ngValue]="user">
    ID#{{user.id}} - {{user.name}}
  </div>
</dropdown>

Ваш компонент имеет ng-контент в div с отображением none

<select [ngClass]="[sizeClass]" [(ngModel)]="value">
  <option *ngFor="let option of options" [ngValue]="option.ngValue">
        {{option.innerHTML}}
   </option>
</select>
<div [style.display]="'none'">
    <ng-content></ng-content>
</div>

И вы получаете параметры и получаете директиву, используя ContentChildren

 @ContentChildren(OptionDirective) options:QueryList<OptionDirective>

Вы можете видеть в stackblitz

ПРИМЕЧАНИЕ. Я помещаю в тот же файл dropdown.component, директиву OptionDirective (которую необходимо объявить в модуле)

Обновление с помощью директивы, мы можем использовать в качестве селектора директивы "option"

@Directive(
{
  selector:'option' //<--use 'option'
})
export class OptionDirective implements AfterViewInit{
  ...
}

И тогда наш. html становится как

<dropdown [(ngModel)]="selectedUser" (ngModelChange)="onUserChange($event)">
  <option *ngFor="let user of users;" [ngValue]="user">
    ID#{{user.id}} - {{user.name}}
  </option>
</dropdown>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...