parametros - ¿Cómo creo un componente desplegable en Angular 2?
formularios en angular 5 (4)
Es posible que esto no sea exactamente lo que desea, pero he usado jquery smartmenu ( https://github.com/vadikom/smartmenus ) para crear un menú desplegable ng2.
$(''#main-menu'').smartmenus();
http://plnkr.co/edit/wLqLUoBQYgcDwOgSoRfF?p=preview https://github.com/Longfld/DynamicaLoadMultiLevelDropDownMenu
Quiero crear un menú desplegable utilizando Angular 2, pero no sé cómo hacerlo de la "forma Angular 2".
Podría crear un componente desplegable que se usa así:
<dropdown>
<li (click)="action(''item 1'')">Item 1</li>
<li (click)="action(''item 2'')">Item 2</li>
</dropdown>
Esto parece bueno, pero entonces el método de action
debe definirse en el componente que contiene los elementos <dropdown>
y <li>
que los estilos no se apliquen desde los estilos en el componente <dropdown>
, lo cual es algo extraño.
Otra opción es crear componentes que se utilizan de esta manera:
<dropdown>
<dropdown-item (click)="action(''item 1'')">Item 1</dropdown-item>
<dropdown-item (click)="action(''item 2'')">Item 2</dropdown-item>
<dropdown>
Esto es más detallado, el componente del elemento desplegable controla la acción de hacer clic y los estilos de los elementos también se definen por el componente del elemento desplegable.
¿Hay una forma más canónica de hacer esto en Angular 2?
Edición: no estoy hablando de una entrada de selección personalizada para un formulario. Más como un menú con opciones, o un menú contextual de clic derecho.
Espero que esto ayude a alguien. Funciona bien en Angular 6 con formas reactivas. Puede funcionar con el teclado también.
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 {
}
}
Uso
<app-dropdown formControlName="type" [options]="types" [placeholder]="captureData.type" [isReadOnly]="isReadOnly">
</app-dropdown>
Las opciones deben enlazar una matriz de la siguiente manera. Puede cambiar en función del requisito.
types= [
{
"id": "1",
"value": "Type 1"
},
{
"id": "2",
"value": "Type 2"
},
{
"id": "3",
"value": "Type 3"
}]
Si desea utilizar los menús desplegables de arranque, lo recomendaré para angular2:
Yo diría que depende de lo que quieras hacer.
Si su menú desplegable es un componente de un formulario que gestiona un estado, aprovecharía el enlace bidireccional de Angular2. Para esto, usaría dos atributos: uno de entrada para obtener el objeto asociado y uno de salida para notificar cuando cambia el estado.
Aquí hay una muestra:
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);
}
}
Esto te permite usarlo de esta manera:
<dropdown [values]="dropdownValues" [(value)]="value"></dropdown>
Puede crear su menú desplegable dentro del componente, aplicar estilos y administrar selecciones internamente.
Editar
Puede observar que puede simplemente aprovechar un evento personalizado en su componente para activar la selección de un menú desplegable. Entonces el componente ahora sería algo como esto:
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);
}
}
Entonces puedes usar el componente así:
<dropdown [values]="dropdownValues" (select)="action($event.value)"></dropdown>
Observe que el método de action
es el del componente principal (no el desplegable).