【问题标题】:How to create a container for all selected items from dropdown?如何从下拉列表中为所有选定项目创建容器?
【发布时间】:2021-05-04 13:24:09
【问题描述】:

我正在创建一个容器来存储从下拉列表中选择的所有项目。我想设置容器的样式,使其可以显示在下拉列表的正上方。

这是我的代码:LIVE DEMO

<mat-form-field>
  <mat-select [(ngModel)]="listOfItems" (selectionChange)="someChange($event)"     (openedChange)="comboChange($event)" placeholder="Toppings" [formControl]="toppings"     disableOptionCentering panelClass="demo-mat-select-container" multiple>
    <mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
  </mat-select>
</mat-form-field>

<div *ngIf="toppings.value && toppings.value.length" class="items-container">
  <div class = 'parent' *ngFor="let item of toppings.value">
      <button class = 'clear-icon' (click)="deleteItem(item)">x</button>
      <div class = "circle">{{ item }}</div>
  </div>
</div>

注意:

如果一个选定的项目有长文本,只需将其放在前一个选定项目的下方(永远不要增加选定项目容器的宽度大小

【问题讨论】:

  • 这里第一个也是主要问题的解决方案是从数组中过滤掉已删除的项目:this.listOfItems = this.listOfItems.filter(searchedItem =&gt; searchedItem !== item); 而不是使用splice(),这样不会改变原始数组。您需要将其分配给左侧 this.listOfItems = this.listOfItems.splice(...) 但是即使您这样做也不起作用。
  • 因为这是两个问题,我建议您将第二个问题提升为一个新的单独问题,因为这将有助于其他人进行搜索。
  • @maljukan 感谢您对过滤问题的意见!我更新了我的问题,使其专注于所选项目的容器。如果您对样式有任何想法,我将不胜感激。
  • 你确定你不只是想要 mat-chip-list 吗? stackblitz.com/angular/…

标签: javascript css angular typescript angular-material


【解决方案1】:

嗯,还有另一种方法是使用 cdk 制作我们自己的“菜单”。先看看SO I wrote in comments

要打开菜单,我们将考虑两个元素,“原点”(被点击的 div)和“锚点”,即叠加层要附加的元素

代码是这样的

  constructor(
    private overlay: Overlay,
    private viewContainerRef: ViewContainerRef
  ) {}
  openMenu(origin: any, anchor: any, menu: any) {
    this.close(null);
    this.overlayRef = this.overlay.create(this.getOverlayConfig(anchor,origin));
    this.overlayRef.attach(
      new TemplatePortal(menu, this.viewContainerRef, {
        $implicit: this
      })
    );
    setTimeout(() => {
      this.sub = fromEvent<MouseEvent>(document, "click")
        .pipe(
          filter(event => {
            const clickTarget = event.target as HTMLElement;
            return (
              clickTarget != origin &&
              (!!this.overlayRef &&
                !this.overlayRef.overlayElement.contains(clickTarget))
            );
          }),
          take(1)
        )
        .subscribe(() => {
          this.close(null);
        });
    });
  }
  close = (data: any) => {
    this.sub && this.sub.unsubscribe();
    if (this.overlayRef) {
      this.overlayRef.dispose();
      this.overlayRef = null;
    }
  };


  change(value: any, isChecked: boolean) {
    if (!isChecked)
      this.value = (this.value || []).filter(
        (x: any) => x != value
      );

    if (isChecked)
      this.value = this.toppingList.filter(
        (x: any) =>
          x == value || (this.value && this.value.indexOf(x) >= 0)
      );
    //when some change, we reposition the overLayRef
    setTimeout(() => {
      this.overlayRef && this.overlayRef.updatePosition();
    });
  }
  private getOverlayPosition(origin: any): PositionStrategy {
    const positionStrategy = this.overlay
      .position()
      .flexibleConnectedTo(origin)
      .withPositions(this.getPositions())
      .withPush(false);

    return positionStrategy;
  }

  private getOverlayConfig(anchor: any,origin:any): OverlayConfig {
    return new OverlayConfig({
      hasBackdrop: false,
      backdropClass: "popover-backdrop",
      positionStrategy: this.getOverlayPosition(anchor),
      scrollStrategy: this.overlay.scrollStrategies.close(),
      width:origin.getBoundingClientRect().width,
      panelClass:"mat-elevation-z8"
      //you can add anothers properties, see
      //https://material.angular.io/cdk/overlay/api#OverlayConfig
    });
  }
  private getPositions(): ConnectionPositionPair[] {
    return [
      {
        originX: "center",
        originY: "bottom",
        overlayX: "center",
        overlayY: "top"
      },
      {
        originX: "center",
        originY: "top",
        overlayX: "center",
        overlayY: "bottom"
      }
    ];
  }

还有.html

<div class="container-all">
    <div #field class="container-input" (click)="openMenu(field,anchor,tpl)">
        <div>
            <span>{{value || []}}</span>
        </div>
        <mat-icon matSuffix>keyboard_arrow_down</mat-icon>
    </div>
    <div #anchor [ngClass]="{'chip-list':value && value.length}">
        <mat-chip-list aria-label="Fruit selection">
            <mat-chip *ngFor="let fruit of value" removable="true" (removed)="change(fruit,false)">
                {{fruit}}
                <mat-icon matChipRemove>cancel</mat-icon>
            </mat-chip>
        </mat-chip-list>
    </div>
</div>
<ng-template #tpl>
    <div class="checkbox-list">
        <div *ngFor="let topping of toppingList">
            <mat-checkbox [checked]="value && value.indexOf(topping)>=0" (change)="change(topping,$event.checked)">
                {{topping}}
            </mat-checkbox>
        </div>
    </div>
</ng-template>

堆栈闪电战,像往常一样here

另一个看起来“更多材料”的堆栈闪电战here

【讨论】:

    【解决方案2】:

    我认为您最好的选择是使用 mat-menu,而不是 mat-select。有些人喜欢

    <form class="example-form">
        <mat-form-field class="example-full-width" appearance="outline">
            <mat-label>Favorite food</mat-label>
            <input matInput readonly [value]="listOfItems || []"  [matMenuTriggerFor]="menu"/>
            <mat-icon matSuffix>keyboard_arrow_down</mat-icon>
            <ng-container *ngTemplateOutlet="chipList">
            </ng-container>
        </mat-form-field>
    </form>
    <mat-menu #menu="matMenu" class="menu-select">
        <div (click)="$event.stopPropagation()">
            <ng-container *ngTemplateOutlet="chipList">
            </ng-container>
            <div *ngFor="let topping of toppingList">
                <mat-checkbox [checked]="listOfItems && listOfItems.indexOf(topping)>=0"
                    (change)="change(topping,$event.checked)">
                    {{topping}}
                </mat-checkbox>
            </div>
        </div>
    </mat-menu>
    <ng-template #chipList>
        <mat-chip-list aria-label="Fruit selection">
            <mat-chip *ngFor="let fruit of listOfItems" removable="true" (removed)="change(fruit,false)">
                {{fruit}}
                <mat-icon matChipRemove>cancel</mat-icon>
            </mat-chip>
        </mat-chip-list>
    </ng-template>
    

    看到菜单被 &lt;div (click)="$event.stopPropagation()"&gt; 包围,可以检查复选框

    复选框的代码是将变量更改为数组的典型代码

    change(value:any,isChecked:boolean){
        if (!isChecked)
           this.listOfItems=(this.listOfItems || []).filter((x:any)=>x!=value)
    
        if (isChecked)
          this.listOfItems=this.toppingList.filter((x:any)=>x==value ||(this.listOfItems && this.listOfItems.indexOf(x)>=0))
    
      }
    

    这是ugly stackblitz

    更新来控制菜单,我们可以为 MenuTrigger 分配一个模板变量,看到我们将#menuTrigger="matMenuTrigger 添加到输入中,这样我们就可以使用matMenuTrigger 的方法和属性

    <input #menuTrigger="matMenuTrigger" matInput readonly 
          [value]="listOfItems || []"  [matMenuTriggerFor]="menu"/>
    

    所以,我们可以更好地控制菜单,例如我们想在点击图标时打开,如果 isOpen 则更改图标

    <mat-icon matSuffix (click)="menuTrigger.openMenu()">
        {{menuTrigger.menuOpen?'keyboard_arrow_up':'keyboard_arrow_down'}}
    </mat-icon>
    

    甚至使用(焦点)打开菜单

    <input (focus)="menuTrigger.openMenu()" #menuTrigger="matMenuTrigger" ...>
    

    如果我们不想要表单字段的标签,我们可以使用类似

    <ng-container *ngIf="!listOfItems?.length">
        <mat-label >Favorite food</mat-label>
    </ng-container>
    

    【讨论】:

    • 我注意到带有 matChipRemove 的选定项目与 mat-menu 上的选定项目位于同一 div 中。我想将所选项目放在不同的 div 中,就像我在图片中一样。我还注意到单击它时箭头根本不起作用。关于如何让它与典型的下拉菜单一起工作的任何其他想法?谢谢
    • 您可以将 [matMenuTriggerFor]="menu" 添加到 mat-form-field 而不是 mat-input。材料角度的问题在于没有“下拉菜单”。制作一个像stackoverflow.com/questions/59199540/… 这样的东西并不是很困难。正如我所见,您不能使用 mat-select 因为您不能“覆盖”复选框列表
    • 所以假设我尝试通过单击外部来关闭菜单,如果我之前选择了项目,我如何在菜单中只显示那些选定的项目(mat-chip 项目)而不必关闭整个菜单?基本上我如何告诉菜单“如果您选择了项目并且如果用户在菜单外点击,请不要关闭菜单但隐藏复选框。否则,如果用户没有选择任何内容,只需隐藏菜单”任何想法如何做到这一点?我在阻止菜单关闭时遇到问题。谢谢
    • 我喜欢你使用 mat-menu 的想法,但你有其他想法让它看起来像我的照片,而不是像你做的那样绑定 mat-chips 两次吗?
    • @progx,我写了另一个使用我指出的链接的方法。我知道这有点复杂,但我无法想象在添加浇头时“重新定位”“菜单”的另一种方式
    猜你喜欢
    • 2013-06-18
    • 1970-01-01
    • 2018-03-01
    • 2017-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-13
    • 1970-01-01
    相关资源
    最近更新 更多