Как реализовать определенное поведение с помощью mat-select

У меня есть стекблиц здесь:

https://stackblitz.com/edit/angular-mat-select-multi-with-formcontrol-uxtev9?file=app%2Fselect-overview-example.html,app%2Fselect-overview-example.ts

Как это можно изменить, чтобы, если пользователь выберет опцию «все», все флажки становились отмеченными, И текст выбора мата просто говорил «все». Аналогично, если пользователь устанавливает все флажки, кроме опции «все», опция «все» должна стать отмеченной, и, опять же, в тексте опции mat должно быть указано «все».

Спасибо за любую помощь.

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
0
0
103
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вот решение для вас:

    //HTML
    <mat-form-field>
      <mat-select
        #drop
        (selectionChange) = "doSomething($event)"
        placeholder = "Select numbers"
        multiple
        [(value)] = "selected"
      >
        <mat-option
          *ngFor = "let num of allNums"
          [value] = "num"
          (click) = "onClick(num)"
        >
          {{ num }}
        </mat-option>
      </mat-select>
    </mat-form-field>

    //TS
    import { Component, ViewChild } from '@angular/core';
    import { FormControl } from '@angular/forms';
    
    /**
     * @title Mat-Select Multiple initialisation with ngModel
     */
    @Component({
      selector: 'select-overview-example',
      templateUrl: 'select-overview-example.html',
      styleUrls: ['select-overview-example.css'],
    })
    export class SelectOverviewExample {
      @ViewChild('drop') dropControl: any;
    
      allNums: string[] = ['all', 'one', 'two', 'three', 'four', 'five'];
      selected: string[] = [];
    
      onClick(value: string) {
        if (value === 'all') {
          if (this.selected.length + 1 === this.allNums.length) {
            this.selected = [];
          } else {
            this.selected = this.allNums;
          }
        }
      }
    
      doSomething(e: any) {
        // console.info(e);
      }
    }

Если вам нужна дополнительная помощь, дайте мне знать.

Я надеюсь, что это покажет всем пользователям самый простой способ: Есть и другие варианты использования, но я думаю, вы поймете идею.

выберите-обзор-example.comComponent.ts

import { Component, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';

type User = {
  id: number;
  name: string;
};

@Component({
  selector: 'select-overview-example',
  templateUrl: 'select-overview-example.html',
  styleUrls: ['select-overview-example.css'],
})
export class SelectOverviewExample {
  // form
  usersCtrl = new FormControl([]);

  // public to template
  users: User[] = [
    {
      id: 1,
      name: 'stack',
    },
    {
      id: 2,
      name: 'over',
    },
    {
      id: 3,
      name: 'flow',
    },
  ];

  // helper
  private isAllSelect = false;

  selectedUsers() {
    return this.usersCtrl.value.length == this.users.length + 1 // add 1 coz the first option is just a helper
      ? 'All Users'
      : this.usersCtrl.value;
  }

  selectAllState() {
    if (this.isAllSelect) {
      this.usersCtrl.patchValue([]);
    } else {
      this.usersCtrl.patchValue(this.getIds());
    }
    this.isAllSelect = !this.isAllSelect;
  }

  selectedOption(id: number) {
    // log one id
    console.info(id);

    /**
     * Here you can add the business logic for other use cases:
     *
     *
     * 1- If all selected without fist option, the first option then should be added
     * 2- If all options are true but one option de-selected, the first option should de-selected too.
     */
  }

  submitToServer() {
    // dont forget to exclude the first option if all selected
  }

  private getIds() {
    return [...this.users.map((user) => user.id), 0];
  }
}

select-overview-example.comComponent.html

<mat-form-field>
  <mat-select [formControl] = "usersCtrl" placeholder = "Users" multiple>
    <mat-select-trigger *ngIf = "this.usersCtrl.value.length > 0">
      {{selectedUsers()}}
    </mat-select-trigger>
    <mat-option (click) = "selectAllState()" [value] = "0"> {{'All'}} </mat-option>
    <mat-option
      *ngFor = "let user of users;"
      [value] = "user.id"
      (click) = "selectedOption(user.id)"
    >
      {{user.name}}
    </mat-option>
  </mat-select>
</mat-form-field>

Чтобы избежать ошибок при множественном выборе, значение FormControl всегда должно иметь массив.

Ответ принят как подходящий

Еще один, который независимо от того, есть ли у вас [(ngModel)] или FormControl

Мы можем получить параметры с помощью ViewChildren, и я выбираю отдельные параметры в массиве и выбираю All.

@ViewChildren(MatOption) options: QueryList<MatOption>;
allNums: string[] = ['one', 'two', 'three', 'four', 'five'];

Наш HTML

<mat-form-field>
  <mat-select 
    #drop
    placeholder = "Select numbers"
    multiple
  >
  <mat-select-trigger *ngIf = "drop.value && drop.value.length">
    {{drop.value.length>allNums.length?'All':
      drop.value[0]===null?drop.value.slice(1):
      drop.value}}
  </mat-select-trigger>
    <mat-option #all (click) = "onClick(all)" [value] = "null">Select all</mat-option>
    <mat-option *ngFor = "let num of allNums" [value] = "num" (click) = "onClick()">
      {{num}}
    </mat-option>
  </mat-select>
</mat-form-field>

И функция «onClick»

  onClick(el: any = null) {
    if (el == this.options.first) {
      this.options.forEach((x) => {
        x[el.selected ? 'select' : 'deselect']();
      });
    } else {
      const selected = this.options.filter((x, index) => index && x.selected);
      if (selected.length == this.allNums.length) this.options.first.select();
      if (selected.length == 0) this.options.first.deselect();
    }
  }

стекблиц

Обновление, предложенное gnisut, мы можем перейти к функции «щелкнуть» параметры (и нам не нужно использовать ViewChild). Я меняю функцию и аргументы для отображения. Таким образом, .html становится похожим

<mat-form-field>
  <mat-select [(ngModel)] = "value"
    #drop
    placeholder = "Select numbers"
    multiple
  >
  <mat-select-trigger *ngIf = "drop.value && drop.value.length">
    {{drop.value.length>allNums.length?'All':
      drop.value[0]===null?drop.value.slice(1):
      drop.value}}
  </mat-select-trigger>
    <!--see that we pass "drop.options" and "true"-->
    <mat-option (click) = "onClick(drop.options,true)" [value] = "null">Select all</mat-option>

    <!--see that we pass "drop.options"-->
    <mat-option *ngFor = "let num of allNums" [value] = "num" (click) = "onClick(drop.options)">
      {{num}}
    </mat-option>
  </mat-select>
</mat-form-field>

И функция onClick очень «похожа» на предыдущую:

  onClick(options:QueryList<MatOption>,isFirst:boolean=false) {
    if (isFirst) {
      const el=options.first;
      options.forEach((x) => {
        x[el.selected ? 'select' : 'deselect']();
      });
    } else {
      const selected = options.filter((x, index) => index && x.selected);
      if (selected.length == this.allNums.length) options.first.select();
      if (selected.length == 0) options.first.deselect();
    }
  }

Большое спасибо за это, очень ценно. Можно ли изменить его так, чтобы функцию onClick можно было сделать «автономной», приняв в качестве дополнительного аргумента переменную, которая в данный момент хранится в this.options? Чтобы не было необходимости иметь параметры @ViewChildren(MatOption): QueryList<MatOption>; в файле ts, поскольку мы будем передавать список объектов MatOption в качестве аргумента функции onClick, когда она вызывается в html-коде.

gnitsuk 04.07.2024 11:26

@gnitsuk, конечно!. Поскольку у нас есть ссылка на шаблон «#drop» на mat-select, а у matSelect есть свойство: «options» (вы можете увидеть в github вы можете перейти к функции onClick(drop.options). Я просто обновляю ответ и stackblitz с вашей идеей.

Eliseo 04.07.2024 11:40

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

gnitsuk 04.07.2024 11:55

(Я добавил еще один вопрос): stackoverflow.com/questions/78706735/…

gnitsuk 04.07.2024 13:06

Вот решение вашей проблемы. Я надеюсь, что это позволит выполнить вашу работу с минимальными изменениями. Пожалуйста, дайте мне знать, соответствует ли это вашим требованиям. Спасибо

import { Component } from '@angular/core';
import { MatSelectChange } from '@angular/material';

/** @title Select with multiple selection */
@Component({
  selector: 'select-multiple-example',
  templateUrl: 'select-multiple-example.html',
  styleUrls: ['select-multiple-example.css'],
})
export class SelectMultipleExample {
  modules = [
    { value: 'module1', viewValue: 'Module 1' },
    { value: 'module2', viewValue: 'Module 2' },
  ];
  selected: string[] = [];

  onSelectionChange(event: MatSelectChange) {
    if (event.value.includes('all')) {
      event.value = ['all'];
      this.selected = ['all'];
    }
    console.info('Event', event.value, this.selected);

    // const allIndex = event.value.indexOf('all');
    // if (allIndex > -1 && this.allAlreadySelected == false)
    //   {
    //     //if all is selected, select all browsers
    //     this.selectedModules = ['all', ...this.modules.map(b => b.value)];
    //     this.allAlreadySelected = !this.allAlreadySelected;
    //   }
    // else if (allIndex > -1 && this.allAlreadySelected) {
    //   this.selectedModules = [];
    //   this.allAlreadySelected = !this.allAlreadySelected;
    // }
  }
}
<mat-form-field>
  <mat-select
    placeholder = "Select Modules"
    multiple
    [(ngModel)] = "selected"
    (selectionChange) = "onSelectionChange($event)"
  >
    <mat-option [value] = "'all'">All</mat-option>
    <mat-option
      *ngFor = "let module of modules"
      [value] = "module.value"
      [disabled] = "selected.includes('all')"
      >{{module.viewValue}}</mat-option
    >
  </mat-select>
</mat-form-field>

Благодарим вас за вклад в сообщество Stack Overflow. Возможно, это правильный ответ, но было бы очень полезно предоставить дополнительные пояснения к вашему коду, чтобы разработчики могли понять ваши рассуждения. Это особенно полезно для новых разработчиков, которые не так хорошо знакомы с синтаксисом или пытаются понять концепции. Не могли бы вы отредактировать свой ответ, включив в него дополнительную информацию на благо сообщества?

Jeremy Caney 09.07.2024 08:56

Да, конечно, я только начал отвечать в сообществе, по мере того, как я прогрессирую каждый день, я буду улучшать себя с объяснениями, большое спасибо за предложение.

Ashish Hulwan 09.07.2024 11:17

Другие вопросы по теме