【问题标题】:Create a dropdown component创建下拉组件
【发布时间】:2016-01-12 05:25:35
【问题描述】:

我想使用 Angular 2 创建一个下拉菜单,但我不确定如何以“Angular 2 方式”进行操作。

我可以创建一个这样使用的下拉组件:

<dropdown>
    <li (click)="action('item 1')">Item 1</li>
    <li (click)="action('item 2')">Item 2</li>
</dropdown>

这看起来不错,但是需要在包含 &lt;dropdown&gt; 的组件上定义 action 方法,并且 &lt;li&gt; 元素不会从 &lt;dropdown&gt; 组件中的样式中应用样式,这有点奇怪。

另一种选择是创建这样使用的组件:

<dropdown>
    <dropdown-item (click)="action('item 1')">Item 1</dropdown-item>
    <dropdown-item (click)="action('item 2')">Item 2</dropdown-item>
<dropdown>

这更详细,dropdown-item 组件处理点击动作,并且项目的样式也由 dropdown-item 组件定义。

在 Angular 2 中是否有更规范的方法来做到这一点?

编辑:我不是在谈论表单的自定义选择输入。更像是带有选项的菜单,或右键单击上下文菜单。

【问题讨论】:

  • 只在下拉组件的模板中渲染
  • 不是一种选择?
  • 这是一个选项,但它不是一个非常可重用的组件,因为
  • 将被硬编码。
  • 您可以在
  • 中使用 ngFor 并从列表中设置属性,而不必对每个属性进行硬编码
  • 您可以将字符串列表初始化为组件的属性,例如options:string[] = [ 'option1', 'option2' ] ,并将它们绑定到&lt;li&gt;,例如:&lt;li *ngFor="#option of options" (click)="select(option)"&gt;{{value.label}}&lt;/li&gt;,或者您可以从父组件传递列表,例如蒂埃里的例子。
  • 这取决于你想做什么,如果你想重用整个下拉列表是的,只需将选项数组作为组件的输入传递并使用它来渲染每个 &lt;li&gt; 里面
  • 标签: angular


    【解决方案1】:

    我会说这取决于你想做什么。

    如果您的下拉列表是管理状态的表单的组件,我会利用 Angular2 的双向绑定。为此,我将使用两个属性:一个用于获取关联对象的输入,一个用于在状态更改时通知的输出。

    这是一个示例:

    export class DropdownValue {
      value:string;
      label:string;
    
      constructor(value:string,label:string) {
        this.value = value;
        this.label = label;
      }
    }
    
    @Component({
      selector: 'dropdown',
      template: `
        <ul>
          <li *ngFor="let value of values" (click)="select(value.value)">{{value.label}}</li>
        </ul>
      `
    })
    export class DropdownComponent {
      @Input()
      values: DropdownValue[];
    
      @Input()
      value: string[];
    
      @Output()
      valueChange: EventEmitter;
    
      constructor(private elementRef:ElementRef) {
        this.valueChange = new EventEmitter();
      }
    
      select(value) {
        this.valueChange.emit(value);
      }
    }
    

    这允许您以这种方式使用它:

    <dropdown [values]="dropdownValues" [(value)]="value"></dropdown>
    

    您可以在组件内构建下拉列表、应用样式并在内部管理选择。

    编辑

    您会注意到,您可以简单地利用组件中的自定义事件来触发下拉菜单的选择。所以组件现在应该是这样的:

    export class DropdownValue {
      value:string;
      label:string;
    
      constructor(value:string,label:string) {
        this.value = value;
        this.label = label;
      }
    }
    
    @Component({
      selector: 'dropdown',
      template: `
        <ul>
          <li *ngFor="let value of values" (click)="selectItem(value.value)">{{value.label}}</li>
        </ul>
      `
    })
    export class DropdownComponent {
      @Input()
      values: DropdownValue[];
    
      @Output()
      select: EventEmitter;
    
      constructor() {
        this.select = new EventEmitter();
      }
    
      selectItem(value) {
        this.select.emit(value);
      }
    }
    

    然后你可以像这样使用组件:

    <dropdown [values]="dropdownValues" (select)="action($event.value)"></dropdown>
    

    注意action 方法是父组件之一(不是下拉组件)。

    【讨论】:

    • 当我说“下拉组件”时,我不是指选择框,而是指下拉菜单或右键单击上下文菜单。
    • 好吧,我明白了!当为下拉列表选择一个元素时,我通过添加自定义事件来更新我的答案...
    • 为什么你说你的选择器是tags 并在你的HTML 标记中使用dropdown? Angular 2 新手可能会感到困惑。
    • "#value of values" 是 *ngFor 表达式的弃用语法。使用“让值的值”。
    • 嗨。正是我正在寻找的,当用户使用箭头并使用回车键选择一个 li 值时,我可以添加相同的事件吗?
    【解决方案2】:

    希望这对某人有所帮助。在具有反应形式的 Angular 6 中工作正常。也可以通过键盘操作。

    dropdown.component.html

    <div class="dropdown-wrapper {{className}} {{isFocused ? 'focus':''}}" [ngClass]="{'is-open':isOpen, 'disabled':isReadOnly}" *ngIf="options" (contextmenu)="$event.stopPropagation();">
      <div class="box" (click)="toggle($event)">
        <ng-container>
          <div class="dropdown-selected" *ngIf="isSelectedValue" l10nTranslate><span>{{options[selected]}}</span></div>
          <div class="dropdown-selected" *ngIf="!isSelectedValue" l10nTranslate><span>{{placeholder}}</span></div>
        </ng-container>
      </div>
    
      <ul class="dropdown-options" *ngIf="options">
        <li *ngIf="placeholder" (click)="$event.stopPropagation()">{{placeholder}}</li>
        <ng-container>
          <li id="li{{i}}"
            *ngFor="let option of options; let i = index"
            [class.active]="selected === i"
            (click)="optionSelect(option, i, $event)"
            l10nTranslate
          >
            {{option}}
          </li>
        </ng-container>
    
      </ul>
    </div>
    

    dropdown.component.scss

    @import "../../../assets/scss/variables";
    
    // DROPDOWN STYLES
    .dropdown-wrapper {
        display: -webkit-box;
        display: -ms-flexbox;
        display: flex;
        border: 1px solid #DDDDDD;
        border-radius: 3px;
        cursor: pointer;
        position: relative;
        &.focus{
            border: 1px solid #a8a8a8;
        }
        .box {
            display: -webkit-box;
            display: -ms-flexbox;
            display: flex;
            width: 100%;
        } 
    
        // SELECTED
        .dropdown-selected {
            height: 30px;
            position: relative;
            padding: 10px 30px 10px 10px;
            display: -webkit-box;
            display: -ms-flexbox;
            display: flex;
            -webkit-box-align: center;
                -ms-flex-align: center;
                    align-items: center;
            width: 100%;
            font-size: 12px;
            color: #666666;
            overflow: hidden;
            background-color: #fff;
            &::before {
                content: "";
                position: absolute;
                top: 50%;
                right: 5px;
                -webkit-transform: translateY(-50%);
                        transform: translateY(-50%);
                width: 22px;
                height: 22px;
                background: url('/assets/i/dropdown-open-selector.svg');
                background-size: 22px 22px;
            }
            span {
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
        } 
    
        // DROPDOWN OPTIONS
        .dropdown-options {
            display: none;
            position: absolute;
            padding: 8px 6px 9px 5px;
            max-height: 261px;
            overflow-y: auto;
            z-index: 999;
            li {
                padding: 10px 25px 10px 10px;
                font-size: $regular-font-size;
                color: $content-text-black;
                position: relative;
                line-height: 10px;
                &:last-child {
                    border-bottom: none;
                }
                &:hover {
                    background-color: #245A88;
                    border-radius: 3px;
                    color: #fff;
                    border-bottom-color: transparent;
                }
                &:focus{
                    background-color: #245A88;
                    border-radius: 3px;
                    color: #fff;
                }
                &.active {
                    background-color: #245A88;
                    border-radius: 3px;
                    color: #fff;
                    border-bottom-color: transparent;
                }
                &:hover {
                    background-color: #7898B3
                }
                &.active {
                    font-weight: 600;
                }
            }
        }
        &.is-open {
            .dropdown-selected {
                &::before {
                    content: "";
                    position: absolute;
                    top: 50%;
                    right: 5px;
                    -webkit-transform: translateY(-50%);
                            transform: translateY(-50%);
                    width: 22px;
                    height: 22px;
                    background: url('/assets/i/dropdown-close-selector.svg');
                    background-size: 22px 22px;
                }
            }
            .dropdown-options {
                display: -webkit-box;
                display: -ms-flexbox;
                display: flex;
                -webkit-box-orient: vertical;
                -webkit-box-direction: normal;
                    -ms-flex-direction: column;
                        flex-direction: column;
                width: 100%;
                top: 32px;
                border-radius: 3px;
                background-color: #ffffff;
                border: 1px solid #DDDDDD;
                -webkit-box-shadow: 0px 3px 11px 0 rgba(1, 2, 2, 0.14);
                        box-shadow: 0px 3px 11px 0 rgba(1, 2, 2, 0.14);
            }
        }
        &.data-input-fields {
            .box {
                height: 35px;
            }
        }
        &.send-email-table-select {
            min-width: 140px;
            border: none;
        }
        &.persoanal-settings {
            width: 80px;
        }
    }
    
    div.dropdown-wrapper.disabled
    {
      pointer-events: none;
      background-color: #F1F1F1;
      opacity: 0.7;
    }
    

    dropdown.component.ts

    import { Component, OnInit, Input, Output, EventEmitter, HostListener, forwardRef } from '@angular/core';
    import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
    
    const noop = () => {
    };
    
    export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DropdownComponent),
      multi: true
    };
    
    @Component({
      selector: 'app-dropdown',
      templateUrl: './dropdown.component.html',
      styleUrls: ['./dropdown.component.scss'],
      providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
    })
    export class DropdownComponent implements OnInit, ControlValueAccessor {
    
      @Input() options: Array<string>;
      @Input() selected: number;
      @Input() className: string;
      @Input() placeholder: string;
      @Input() isReadOnly = false;
      @Output() optSelect = new EventEmitter();
      isOpen = false;
      selectedOption;
    
    
      private onTouchedCallback: () => void = noop;
      private onChangeCallback: (_: any) => void = noop;
      isSelectedValue: boolean;
      key: string;
      isFocused: boolean;
    
      /**
       *Creates an instance of DropdownComponent.
       * @memberof DropdownComponent
       */
    
      ngOnInit() {
        // Place default value in dropdown
        if (this.selected) {
          this.placeholder = '';
          this.isOpen = false;
        }
      }
    
      @HostListener('focus')
      focusHandler() {
        this.selected = 0;
        this.isFocused = true;
      }
    
      @HostListener('focusout')
      focusOutHandler() {
        this.isFocused = false;
      }
    
      @HostListener('document:keydown', ['$event'])
      keyPressHandle(event: KeyboardEvent) {
    
        if (this.isFocused) {
          this.key = event.code;
          switch (this.key) {
            case 'Space':
              this.isOpen = true;
              break;
            case 'ArrowDown':
              if (this.options.length - 1 > this.selected) {
                this.selected = this.selected + 1;
              }
              break;
            case 'ArrowUp':
              if (this.selected > 0) {
                this.selected = this.selected - 1;
              }
              break;
            case 'Enter':
              if (this.selected > 0) {
                this.isSelectedValue = true;
                this.isOpen = false;
                this.onChangeCallback(this.selected);
                this.optSelect.emit(this.options[this.selected]);
              }
              break;
          }
        }
    
      }
    
      /**
      * option selection
      * @param {string} selectedOption - text
      * @param {number} idx - current index of item
      * @param {any} event - object
      */
      optionSelect(selectedOption: string, idx, e: any) {
        e.stopPropagation();
        this.selected = idx;
        this.isSelectedValue = true;
        // this.placeholder = '';
        this.isOpen = false;
        this.onChangeCallback(this.selected);
        this.optSelect.emit(selectedOption);
      }
    
      /**
      * toggle the dropdown
      * @param {any} event object
      */
      toggle(e: any) {
        e.stopPropagation();
        // close all previously opened dropdowns, before open
        const allElems = document.querySelectorAll('.dropdown-wrapper');
        for (let i = 0; i < allElems.length; i++) {
          allElems[i].classList.remove('is-open');
        }
        this.isOpen = !this.isOpen;
        if (this.selected >= 0) {
          document.querySelector('#li' + this.selected).scrollIntoView(true);
        }
      }
    
      /**
      * dropdown click on outside
      */
      @HostListener('document: click', ['$event'])
      onClick() {
        this.isOpen = false;
      }
    
      /**
       * Method implemented from ControlValueAccessor and set default selected value
       * @param {*} obj
       * @memberof DropdownComponent
       */
      writeValue(obj: any): void {
        if (obj && obj !== '') {
          this.isSelectedValue = true;
          this.selected = obj;
        } else {
          this.isSelectedValue = false;
        }
      }
    
      // From ControlValueAccessor interface
      registerOnChange(fn: any) {
        this.onChangeCallback = fn;
      }
    
      // From ControlValueAccessor interface
      registerOnTouched(fn: any) {
        this.onTouchedCallback = fn;
      }
    
      setDisabledState?(isDisabled: boolean): void {
    
      }
    
    }
    

    用法

    <app-dropdown formControlName="type" [options]="types" [placeholder]="captureData.type" [isReadOnly]="isReadOnly">
    </app-dropdown>
    

    选项必须按如下方式绑定数组。它可以根据需要更改。

    types= [
            {
                "id": "1",
                "value": "Type 1"
            },
            {
                 "id": "2",
                 "value": "Type 2"
            },
            {
                  "id": "3",
                  "value": "Type 3"
            }] 
    

    【讨论】:

    • 在这个例子中你在哪里定义了'../../../assets/scss/variables'和其他assets svg
    • 我将所有 scss 变量保存在 ../../../assets/scss/_variables.scss 中,并将所有 svg 文件保存在 ../../../assets/i 文件夹中.如上所述,根据您的主题使用 cscc 变量和 svg。
    【解决方案3】:

    这是在 Angular 7、8、9 中创建下拉菜单的代码

    .html文件代码

    <div>
    <label>Summary: </label>
    <select (change)="SelectItem($event.target.value)" class="select">
      <option value="0">--All--</option>
      <option *ngFor="let item of items" value="{{item.Id.Value}}">
        {{item.Name}}
      </option>
    </select>
    </div>
    

    .ts文件代码

    SelectItem(filterVal: any)
    {
        var id=filterVal;
        //code
    }
    

    items 是一个数组,应该在.ts 文件中初始化。

    【讨论】:

      【解决方案4】:

      如果你想使用引导下拉菜单,我会为 angular2 推荐这个:

      ngx-dropdown

      【讨论】:

        【解决方案5】:

        这可能不是你想要的,但我使用 jquery smartmenu(https://github.com/vadikom/smartmenus) 构建了一个 ng2 下拉菜单。

         $('#main-menu').smartmenus();
        

        http://plnkr.co/edit/wLqLUoBQYgcDwOgSoRfF?p=preview https://github.com/Longfld/DynamicaLoadMultiLevelDropDownMenu

        【讨论】:

        • 是否推荐jquery和angular 2一起使用?
        • 我认为这不适用于任何 jquery 插件,因为在这种情况下,jqurey 插件与 ng2 应用程序的其余部分完全无关
        • 我在生产中准确地使用了它,我找不到像我在这里展示的那样好的另一个大规模下拉菜单,并且可以很好地用于移动或桌面,甚至不是商业组件,ngx 也没有- 上面的下拉菜单。是的,我正在寻找 angular 的替代品,但找不到像这样好的替代品
        【解决方案6】:

        如果您想要带有下拉列表(一些值列表)和用户指定值的东西,该值也可以填充到所选输入中。 这个自定义的 Angular 下拉列表也有一个关于输入键值的过滤器下拉列表。 请检查这个 stackblitzlink -> https://stackblitz.com/edit/angular-l9guzo?embed=1&file=src/app/custom-textarea.component.ts

        【讨论】:

          猜你喜欢
          相关资源
          最近更新 更多
          热门标签