#html #angular-material #menu
Вопрос:
Я работаю над шаблонным проектом с настраиваемой структурой меню. Меню в нем привязано к рекурсивному типу данных. Я хочу решить эту проблему с помощью ng-контейнера и ng-шаблона, так как мне кажется, что это единственный способ рекурсивно выполнять определенные шаги.
Я уже разработал несколько компонентов с использованием материала, поэтому я хотел бы также придерживаться компонентов материала и не использовать другой компонент меню.
Проблема, с которой я сталкиваюсь, заключается в том, что если пункты меню mat определены в шаблоне ng, меню не отображается должным образом. [https://i.imgur.com/WQed1yb.gif]
Элемент меню-триггер для пункта меню, похоже, отображается не так, как должно. В нем отсутствует значок подменю, и при наведении на него подменю не отображается. Вместо этого требуется щелчок. Кроме того, родительское меню исчезает при открытии подменю, что может затруднить навигацию по меню.
Помимо упомянутой проблемы, меню само по себе, похоже, работает.
Я попробовал ленивую загрузку на основе примера здесь: https://www.angularjswiki.com/material/menu/ но это не решило проблему.
Мой HTML-шаблон выглядит следующим образом:
<button mat-button [matMenuTriggerFor]="itemMenu">
{{ startMenuItem.description }}
</button>
<mat-menu #itemMenu="matMenu">
<ng-container *ngFor="let childItem of startMenuItem.children">
<ng-container
*ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem }"
>
</ng-container>
</ng-container>
</mat-menu>
<ng-template #recursiveMenuTmpl let-parentItem>
<ng-container *ngIf="parentItem.children">
<ng-container
*ngTemplateOutlet="menuItem; context: { $implicit: parentItem }"
></ng-container>
</ng-container>
<ng-container *ngIf="!parentItem.children">
<ng-container
*ngTemplateOutlet="singleItem; context: { $implicit: parentItem }"
></ng-container>
</ng-container>
</ng-template>
<ng-template #menuItem let-rootItem>
<button [matMenuTriggerFor]="itemMenu" mat-menu-item>
{{ rootItem.description }}
</button>
<mat-menu #itemMenu="matMenu">
<ng-container *ngFor="let childItem of rootItem.children">
<ng-container
*ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem }"
>
</ng-container>
</ng-container>
</mat-menu>
</ng-template>
<ng-template #singleItem let-item>
<button *ngIf="item.route" mat-menu-item [routerLink]="item.uri">
{{ item.description }}
</button>
<button *ngIf="!item.route" mat-menu-item (click)="onOpenUrl(item.uri)">
{{ item.description }}
</button>
</ng-template>
и мой компонент
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-dynamic-menu',
templateUrl: './dynamic-menu.component.html',
styleUrls: ['./dynamic-menu.component.css'],
})
export class DynamicMenuComponent implements OnInit {
public startMenuItem: MenuItem = {
description: 'Menu',
uri: '',
route: false,
children: [
{
description: 'Welcome',
uri: '',
route: false,
children: [],
},
{
description: 'Other',
uri: '',
route: false,
children: [],
},
{
description: 'Sign on/off',
uri: '',
route: false,
children: [
{
description: 'Login',
uri: '',
route: false,
children: [],
},
{
description: 'Logout',
uri: '',
route: false,
children: [],
},
],
},
],
};
constructor() {}
public onOpenUrl(url: string) {
document.location.href = url;
}
ngOnInit(): void {
}
}
Ответ №1:
Проведя некоторые дополнительные исследования, я обнаружил, что в моем коде действительно была ошибка. ngIf в моем шаблоне, который проверяет, есть ли в элементе меню дочерние элементы, проверяет наличие дочернего массива, а не наличие в массиве фактического содержимого. Я исправил это, но, к сожалению, это не решило мою первоначальную проблему.
Дальнейший анализ поведения меню материалов показал, что отношения между родителями и детьми вели себя не так, как ожидалось. Это в конечном итоге привело меня к проблеме, уже поднятой для Angular (https://github.com/angular/angular/issues/14842). Я очень подозреваю, что эта проблема вызывает неправильное поведение меню. Понимая этот недостаток, я переработал свой шаблон таким образом, чтобы использовался только один ng-шаблон.
Проблема меню материалов заключается в том, что в нем отсутствуют дочерние элементы контента (по крайней мере, в моем случае, поскольку дети находятся в ng-шаблоне). Однако, если ссылка из mat-меню передается вместе с шаблоном, то пользовательская директива (или, по крайней мере, так я ее исправил) в шаблоне может выполнять дочерние элементы содержимого и устанавливать результат в родительском mat-меню. Достаточно было установить _allItems в родительском мат-меню с дочерними элементами содержимого и вызвать _updateDirectDescendants.
К сожалению, внедрение зависимостей с директивами в шаблонах также проблематично (см.: https://github.com/angular/angular/issues/2907), который предотвращает установку родительских отношений в пунктах меню и триггере меню. Если родительское меню всех элементов меню установлено в соответствующее родительское меню (посредством передачи ссылки через контекст шаблона), эта проблема также решается для элементов меню. В триггере меню необходимо установить _parentMaterialMenu. Наконец, для элементов matMenuItems, которые запускают подменю, подменю triggerSubMenu должно быть установлено значение true.
Так что заканчивайте с этим:
- Используйте один шаблон или, по крайней мере, убедитесь, что меню и пункты меню-потомки находятся в одном шаблоне.
- Получите дочерние элементы меню (например, с помощью пользовательской директивы), установите их в _allItems в меню. После этого вызовите _updateDirectDescendants в меню.
- Установите для всех элементов меню значение родительского меню в фактическое родительское меню.
- В меню меню _parentMaterialMenu необходимо установить в фактическое родительское меню.
- Элементы меню, которые запускают подменю, должны иметь значение _triggerSubMenu, равное true.
И все, меню должно работать в обычном режиме. Обратите внимание, что это исправление не зависит только от углового api. Он работает для угловой версии материала 12, но он также может работать и в других версиях.