3
votes

I'm using Angular5 and primeng library. Particularly, I have problem with p-dropdown. I have one big entity - Consignment - with many fields, I open page with this saved object. And several p-dropdown elements(here it is contract component) dont' display selected value. But, I'm sure that there are no object's empty fields. It seems, like array, which linked with p-dropdown options property, is still empty when @Input() field related with p-dropdown ngModel is not. How I can fill in options array before ngModel field will initiated?Or may be there is another problem? My code below:

Html template:

<div class="form-group ui-float-label">
  <p-dropdown [options]="selectItems" [(ngModel)]="contract" [filter]="true" (onChange)="onContractSelect()"
              [style]="{'width':'100%'}" id="contract" placeholder="Select a contract" [required]="required" ></p-dropdown>
  <label for="contract" [hidden]="contract == null">Contract</label>
</div>

Component code:

export class SelectContractComponent implements OnChanges{
  public contracts : Contract[] = [];
  @Input() contract: Contract;
  @Output() contractChange = new EventEmitter();
  @Input() contractor: Contractor;
  selectItems: SelectItem[]= [];

  @Input() disabled: boolean;
  @Input() required: boolean;

  constructor(
    private contractService: ContractService) {
  }


  ngOnChanges(changes: SimpleChanges): void {
    this.getAllContracts();
    const contractor: SimpleChange = changes.contractor;
    this.selectItems = [];
    if (contractor != null && contractor.currentValue != null){
      this.getContractsByContractor();
    }
  }

  private fillSelectItems(contract: Contract){
    this.selectItems.push({label: contract.name, value: contract});
  }

  getContractsByContractor():void {
    this.contractService.getContractsByContractor(this.contractor.id)
      .subscribe(contracts => {
        this.contracts = contracts;
        this.contracts.forEach(c => this.fillSelectItems(c));
      });
  }

  getAllContracts():void{
    this.contractService.getContracts()
      .subscribe(contracts => {
        this.contracts = contracts;
        this.contracts.forEach(c => this.fillSelectItems(c));
      });
  }

  public onContractSelect(){
    this.contractChange.emit(this.contract);
  }

}

Fragment from Consignment template:

<app-select-contract [(contract)]="consignment.costPayerContract" [contractor]="consignment.costPayer"
                                     *ngIf="consignment.costPayer"></app-select-contract>
2

2 Answers

0
votes

I found solution. I had to create else one field for ngModel and assigned this field to contract's field after selectItems was filled in.

Html template:

<div class="form-group ui-float-label">
  <p-dropdown [options]="selectItems" **[(ngModel)]="selectedItem"** [filter]="true" (onChange)="onContractSelect()"
              [style]="{'width':'100%'}" id="contract" placeholder="Select a contract" [required]="required" ></p-dropdown>
  <label for="contract" [hidden]="contract == null">Contract</label>
</div>

Component class:

export class SelectContractComponent implements OnChanges{
  public contracts : Contract[] = [];
  @Input() contract: Contract;
  @Output() contractChange = new EventEmitter();
  @Input() contractor: Contractor;
  selectItems: SelectItem[] = [];
  **selectedItem: Contract;**

  @Input() disabled: boolean;
  @Input() required: boolean;

  constructor(
    private contractService: ContractService) {
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log('select contract on changes');
    const contractor: SimpleChange = changes.contractor;
    if (contractor != null && contractor.currentValue != null){
      this.getContractsByContractor();
    } else {
      this.getAllContracts();
    }
    console.log('select contract on changes end');
  }

  getContractsByContractor():void {
    this.contractService.getContractsByContractor(this.contractor.id)
      .subscribe(contracts => {
        this.contracts = contracts;
        this.fillSelectItems();
        console.log('before assign selected item');
        **this.selectedItem = this.contract;**
      });
  }

  getAllContracts():void{
    this.contractService.getContracts()
      .subscribe(contracts => {
        this.contracts = contracts;
        this.fillSelectItems();
        console.log('before assign selected item');
        **this.selectedItem = this.contract;**
      });
  }

  public onContractSelect(){
    this.contractChange.emit(this.selectedItem);
  }

  fillSelectItems():void{
    console.log('filling select items');
    this.selectItems = [];
    this.contracts.forEach(c => this.selectItems.push({label: c.name, value: c}));
    console.log('filling select items end');
  }

}
0
votes

I found a race condition in primeng's code, where if you set your options, then set your selectedOption right after(in the same thread/function) It would fail to update the display with your selectedOption. A quick workaround is to add an observable after you have set the options(giving primeng time to update their data, before you set the selectedOption)

public randomSetFunction(options: any, selectedOption: any) {
    this.options = options;
    of(this.options).subscribe(opts => { this.selectedOption = selectedOption });
}

This issue may be be fixed in later versions of Primeng(I tested this with 7.1.3)