Angular- Prevent selection of same options from a dropdown

1.1k Views Asked by At

I made this dropdown with a textarea for input in angular material dialog. There I have only three options in the dropdown(so far)-'English','French' and 'Canadian French'. I have already disabled 'English' by default. Now, for the rest of the remaining options, when I click 'Add new Language' button and select an option(french ; say) and add text I am able to disable the selected option so that when the user adds the third language they cannot select it again. It works fine. Like this (This is without hitting the save button) enter image description here Now the problem starts here. When I select 'French'(say) and hit the save button. And again open the dialog-box. I am again seeing 'French' along with 'Canadian French' in the options for the adding a third language. What do I do to make it inactive and grey-ish like 'English'?enter image description here

This the ts code:

export class ModalAllComponent implements OnInit {

  dialogData: DialogDataModel;

  languages: any[];

  rows: any[];

  item!:any[];

  constructor(
     public dialogRef:MatDialogRef<ModalAllComponent>,
     @Inject(MAT_DIALOG_DATA) public data: DialogDataModel) {
      this.dialogData = data;
      this.rows = this.dialogData.localisedEntities.filter(lang => lang.value,)
      this.languages = this.dialogData.localisedEntities.map(item => ({ code: item.code, title: item.title, canEdit: item.canEdit }))
      console.log(this.dialogData)
    }

  ngOnInit(): void {
  }

  addNewLanguage() {
    this.rows.push({
      code: '',
      title: '',
      value: '',
      canEdit: true
    });
  }

  onChangeValue(ev: any){
    this.rows = this.rows.map(row => {
      if (row.code == ev.value) {
        const lang = this.languages.find(lang => lang.code == ev.value);
        row.title =lang.title;
      }
      return row;
    })

    console.log(this.rows)

    this.languages = this.languages.map(lang => {
      if (lang.code == ev.value) {
        lang.canEdit = false;
        console.log(lang);
      }
      return lang;
    });
    this.isDisabled()
  }

  isDisabled(){
    return this.rows.filter((item) => item.value == '' || item.code == '')
        .length > 0
        ? true
        : false;
  }

  submit(ev:any){
    this.dialogRef.close({data: this.rows});
  }

  back(){
    this.dialogRef.close()
  }

  removeBtn(index:number){
    this.rows.splice(index, 1);
  }
}

I was console-logging at a lot of places and finally manage to draw down to these place where the problem might be happening. In console.log(lang), I saw that, when I selected 'French',the flag canEdit turned to false.But in console.log(this.rows), when I selected 'French',the flag canEdit did not turned to false

How to solve the issue?

The HTML code:

<div>
  <table class="justify-content-between">
    <tr *ngFor="let entity of rows; let i = index">
      <td class="col-1" *ngIf="entity.value!=null">
        <mat-select [(ngModel)]="entity.code" [disabled]="!entity.canEdit"  (selectionChange)="onChangeValue($event)">
          <mat-option *ngFor="let lang of languages" [disabled]="!lang.canEdit" [value]="lang.code">{{ lang.title }}</mat-option>
        </mat-select>
        <!-- <mat-error *ngIf="entity.code.hasError('required')">Please choose an language</mat-error> -->
      </td>
      <td class="col-1" *ngIf="entity.value!=null">
        <textarea style="height: 2rem" class="pl-5" [disabled]="!entity.canEdit" [(ngModel)]="entity.value">{{ entity.value }}</textarea>
        <mat-icon class="pl-2" style="color: red;font-size: 2rem;cursor: pointer;" (click)="removeBtn(i)">close</mat-icon>
      </td>
    </tr>
  </table>
  <div class="d-flex flex-column align-items-center mt-2">
    <button class="form-control" (click)="addNewLanguage()" *ngIf="rows.length < dialogData.localisedEntities.length" [disabled]="isDisabled()">Add new language</button>
      <div class="d-flex pt-2">
        <button class="form-control" [disabled]="isDisabled()" (click)="back()">Discard</button>
        <button class="form-control ml-4 pl-4 pr-4" [disabled]="isDisabled()" (click)="submit($event)">Save</button>
      </div>
  </div>
</div>

This is where the modal is opening:

localiseFoodName() {
    const dialogData = < DialogDataModel > {
      localisedEntities: this.foodModel.localisedName.map((item: any) => {
        if (item.code == 'en') {
          item.canEdit = false;
        } else {
          item.canEdit = true;
        }
        return item;
      }),
    };

    let dialogRef = this.dialog.open(ModalAllComponent, { width: '26.5rem', data: dialogData });

    dialogRef.afterClosed().subscribe(res => {

      if (res && res.data) {
        console.log(res)
        console.log(res.data)
        let temp:any
        this.foodModel.localisedName.map((item:any)=>{
          temp = res.data.find((element:any)=> element.code === item.code);
          if(temp){
            item.value = temp.value
            item.canEdit = temp.canEdit = false
          }
          //console.log(temp)
        })
        const food  = this.foodModel.localisedName
        console.log(food)
      }
    })
  }

When I console.log(food) I could see the canEdit:false. But when I open the modal again, in console.log(this.dialogData) the canEdit is again "True" for the selected item enter image description here

Thanks in advance for the help!

3

There are 3 best solutions below

3
On

Before diving in...

Because, like you said, it's a big project, I feel like it might be hard to diagnose, but I'll point out what I think could be the culprit based on things I've seen before, and I'll update this answer if more details/context come(s) to light.

In the meantime, please check this out: https://stackoverflow.com/help/minimal-reproducible-example.

Look, I know, it's all obvious stuff, I don't mean to insult, but us programmers will often forget these things when we're zeroed-in on a problem and a reminder is often helpful. Bottom line, a minimal reproducible example goes miles for helping the answerers and the question-asker in turn. Whatever progress you can make on that front is deeply appreciated!


What might be going on

This problem feels much better suited to reactive forms than template-driven forms, but you can use template forms. You'd want some logic in the setter that's attached to your 2-way ngModel binding. That's where you'll need to update the canEdit status.

Also I'm suspicious of your subscription. Depending what dialogRef.afterClosed() returns (in terms of how many emissions, what its source is, etc), you might have a lingering subscription problem here. The subscription does not die with the component. And even if it doesn't cause a logic bug, it's going to bloat your memory, slowing down your app.

And in general, I'd recommend finding a way to not be subscribing explicitly, but seeing if you can instead leverage the async pipe and just push any changes via a subject that you .next at the specific point in your event-listening code (i.e. your ngModel-bound setter). The other benefit is that async pipe handles subscription for you, but it also will unsubscribe when the component dies.

I'll come back to update if this doesn't solve your problem and if you are able to provide more context. Sorry, it's just a lot of guesswork right now. More context would be really helpful!

10
On

On selection, get the selected item inside a function and filter the languages array.

1
On

Instead of just hard coding item.code == 'en',so that whenever it sees the en it will turn the flag false:

localiseFoodName() {
    const dialogData = < DialogDataModel > {
      localisedEntities: this.foodModel.localisedName.map((item: any) => {
        if (item.code == 'en') {
          item.canEdit = false;
        } else {
          item.canEdit = true;
        }
        return item;
      }),
    };

I just checked, if the value of item is null(empty) with this item.value != null:

localiseFoodName() {
    const dialogData = < DialogDataModel > {
      localisedEntities: this.foodModel.localisedName.map((item: any) => {
        if (item.value != null) {
          item.canEdit = false;
        } else {
          item.canEdit = true;
        }
        return item;
      }),
    };
    console.log(dialogData);


    let dialogRef = this.dialog.open(ModalAllComponent, {
      width: '26.5rem',
      data: dialogData
    });


    dialogRef.afterClosed().subscribe(res => {

      if (res && res.data) {
        console.log(res.data)
         this.foodModel.localisedName.map((item:any)=>{
          this.temp = res.data.find((element:any)=> element.code === item.code);
          if(this.temp){
            item.value = this.temp.value
          }
        })
      }
    })
  }