如何解决从JSON结构生成导航菜单项
我有一个JSON示例,我想在其中存储Angular项目的导航菜单:
{
"menus": [{
"name": "nav-menu","style": "nav navbar-toggler","items": [{
"id": "1","name": "Navigation menu","parent_id": null,"style": "btn btn-default w-100"
},{
"id": "2","name": "Home and garden","parent_id": "1",{
"id": "3","name": "Cookers","parent_id": "2",{
"id": "4","name": "Microwave ovens",{
"id": "5","name": "Fridges",{
"id": "6","name": "PC peripherials",{
"id": "7","name": "Head phones","parent_id": "6",{
"id": "8","name": "Monitors",{
"id": "9","name": "Network",{
"id": "10","name": "Laptop bags",{
"id": "11","name": "Web Cams",{
"id": "12","name": "Remote cameras","parent_id": "11",{
"id": "13","name": "Laptops",{
"id": "14","name": "15' Laptops","parent_id": "13",{
"id": "15","name": "17' Laptops","style": "btn btn-default w-100"
}]
}]
}
这个想法是在需要时编辑JSON并根据此数据生成导航菜单。 如何实现呢?
解决方法
这是一个可能的解决方案: https://stackblitz.com/edit/dashjoin-ddx71w
使用常规的角度树(https://v9.material.angular.io/components/tree/overview)实现菜单。 该树使用嵌套的JSON结构,而不是您建议的具有id / parent_id的扁平结构。
如果我们直接采用并编辑此结构,则JSON模式(https://json-schema.org/)是编辑树模型的良好基础。在应用程序组件中检出“ schema”变量。它是树模型结构的简单JSON模式表示形式:
schema: Schema = {
type: "object",properties: {
name: {
type: "string"
},style: {
type: "string"
},children: {
type: "array",...
该示例中的架构仅支持三个嵌套级别。您还可以使用$ ref机制来支持任意嵌套级别。
然后,我正在使用一个JSON模式表单组件,该组件显示基于模型和模式的表单:
<lib-json-schema-form [value]="value" (valueChange)="apply($event)" [schema]="schema"></lib-json-schema-form>
apply($event)
通过首先删除模型,然后将其设置为从表单组件发出的新值来使材料树重绘。
将表单中的样式(应该称为类)应用于树节点,如下所示:
<span [ngClass]="node.style">{{node.name}}</span>
总而言之,我认为这是一个非常优雅的解决方案,只需很少的代码。
,因此,首先您需要将平面列表转换为树状结构。
function unflatten(arr) {
var tree = [],mappedArr = {},arrElem,mappedElem;
// First map the nodes of the array to an object -> create a hash table.
for(var i = 0,len = arr.length; i < len; i++) {
arrElem = arr[i];
mappedArr[arrElem.id] = arrElem;
mappedArr[arrElem.id]['children'] = [];
}
for (var id in mappedArr) {
if (mappedArr.hasOwnProperty(id)) {
mappedElem = mappedArr[id];
// If the element is not at the root level,add it to its parent array of children.
mappedElem.displayName = mappedElem.name;
mappedElem.icon = '';
if (mappedElem.parent_id) {
mappedArr[mappedElem['parent_id']]['children'].push(mappedElem);
}
// If the element is at the root level,add it to first level elements array.
else {
tree.push(mappedElem);
}
}
}
return tree;
}
var arr = [{
"id": "1","name": "Navigation menu","parent_id": null,"style": "btn btn-default w-100"
},{
"id": "2","name": "Home and garden","parent_id": "1",{
"id": "3","name": "Cookers","parent_id": "2",{
"id": "4","name": "Microwave ovens",{
"id": "5","name": "Fridges",{
"id": "6","name": "PC peripherials",{
"id": "7","name": "Head phones","parent_id": "6",{
"id": "8","name": "Monitors",{
"id": "9","name": "Network",{
"id": "10","name": "Laptop bags",{
"id": "11","name": "Web Cams",{
"id": "12","name": "Remote cameras","parent_id": "11",{
"id": "13","name": "Laptops",{
"id": "14","name": "15' Laptops","parent_id": "13",{
"id": "15","name": "17' Laptops","style": "btn btn-default w-100"
}]
var tree = unflatten(arr);
console.log(tree);
为了支持Material UI,在上面的代码中,我添加了额外的字段。 1.displayName 2.icon
一旦获得嵌套结构,就可以在组件的模板中使用它。 其余的Angular实现将在stackblitz
中给出 ,我在新项目中执行此操作,这是部分代码供您参考。 我们在这里用作JSON文件来存储菜单,在应用统计信息完成后使用加载程序加载菜单。然后在我们的html中使用加载的菜单。
我没有使用您的代码或json引用,因此您可以采用这种想法并以自己的方式实现。
sidenav-menu.json
将此文件放在asset/config/sidenav-menu.json
下
[
{
"icon": "dashboard","name": "Dashboard","isShow": false,"userRole": [
"ROLE_USER","ROLE_ADMIN"
],"href": "dashboard","isChildAvailable": true,"child": [
{
"userRole": [
"ROLE_USER","ROLE_ADMIN"
],"icon": "cog","name": "Config","href": "admin/dashboard/config",}
]
}]
menu-loader.service.ts
将此文件放在Shared / Services / loader / MenuLoader.service.ts下
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MenuLoaderService {
sidenavMenu: any[];
constructor(
private http: HttpClient) {
}
load(): Promise<any> {
console.log('MenuLoaderService',`getting menu details`);
const promise = this.http.get('../assets/data/config/sidenav-menu.json')
.toPromise()
.then((menus: any[]) => {
this.sidenavMenu = menus;
return menus;
}).catch(this.handleError());
return promise;
}
private handleError(data?: any) {
return (error: any) => {
console.log(error);
};
}
getMenu() {
return this.sidenavMenu;
}
}
将MenuLoader服务添加到构造器并从中获取菜单
app.component.ts
export class AppComponent implements OnInit {
constructor(
// other inject modules
private menuLoader: MenuLoaderService
) {
this.getMenuList();
}
ngOnInit(): void {
}
getMenuList() {
this.menuLoader.load();
}
}
HTML部分
my-component.component.html 对于显示菜单,我使用了Material design
<!-- Other codes -->
<mat-nav-list style="overflow-y: auto; height: 70.5vh;">
<div *ngFor="let link of sidenavMenu" [@slideInOut]="link.show ? 'out' : 'in'">
<span *ngIf="checkUrlAccessibleOrNot(link?.userRole,link?.loginType)">
<ng-container *ngIf="link.isChildAvailable; else elseTemplate">
<mat-list-item role="link" #links [ngClass]="{'bg-amber-gradient': routeIsActive(link?.href)}"
(click)="showHideNavMenu(link)">
<mat-icon class="component-viewer-nav" matListIcon svgIcon="{{link.icon}}"></mat-icon>
<a matLine [matTooltip]="link.name" matTooltipPosition="after">
{{ link.name }}</a>
<mat-icon *ngIf="!link.isShow">add</mat-icon>
<mat-icon *ngIf="link.isShow">remove</mat-icon>
</mat-list-item>
<mat-divider></mat-divider>
<span *ngIf="link.isShow">
<div *ngFor="let clink of link.child; let last = last;">
<mat-list-item *ngIf="checkUrlAccessibleOrNot(clink?.userRole,link?.loginType)" role="link" #links
routerLinkActive="active-link" routerLink="{{clink.href}}" (click)="onLinkClick()" class="p-l-10">
<mat-icon class="component-viewer-nav" matListIcon svgIcon="{{clink.icon}}"></mat-icon>
<a matLine [matTooltip]="clink.name" matTooltipPosition="after">
{{ clink.name}}</a>
</mat-list-item>
<mat-divider></mat-divider>
</div>
</span>
</ng-container>
<ng-template #elseTemplate>
<mat-list-item role="link" #links routerLinkActive="active-link" routerLink="{{link.href}}"
(click)="onLinkClick()">
<mat-icon class="component-viewer-nav" matListIcon svgIcon="{{link.icon}}"></mat-icon>
<a matLine [matTooltip]="link.name" matTooltipPosition="after">
{{ link.name }}</a>
</mat-list-item>
<mat-divider></mat-divider>
</ng-template>
</span>
</div>
</mat-nav-list>
<!-- Other html codes -->
my-component.component.ts
export class MyComponentComponent implements OnInit {
this.sidenavMenu = []; 构造函数(
menuLoader: MenuLoaderService
) {
this.sidenavMenu = menuLoader.getMenu();
}
ngOnInit() {
}
showHideNavMenu(link: any) {
this.sidenavMenu.forEach(indLink => {
if (indLink.name !== link.name) {
indLink.isShow = false;
}
});
this.sidenavMenu[this.sidenavMenu.indexOf(link)].isShow =
!this.sidenavMenu[this.sidenavMenu.indexOf(link)].isShow;
}
routeIsActive(routePath: string) {
const mainUrl = this.router.url;
const splitUrls = mainUrl.split('/');
return splitUrls[1] === routePath;
}
onLinkClick(): void {
if (this.isMobileView) {
this.menuSidenav.close();
}
}
checkUrlAccessibleOrNot(roleList: string[],loginType: 'USER' | 'ADMIN'): boolean {
// Implement it in your own way
}
}
编辑
动画代码:
animations: [
trigger('slideInOut',[
state('in',style({
transform: 'translate3d(0,0)'
})),state('out',style({
transform: 'translate3d(100%,transition('in => out',animate('400ms ease-in-out')),transition('out => in',animate('400ms ease-in-out'))
]),trigger('detailExpand',[
state('collapsed',style({ height: '0px',minHeight: '0',display: 'none' })),state('expanded',style({ height: '*' })),transition('expanded <=> collapsed',animate('225ms cubic-bezier(0.4,0.0,0.2,1)')),]),]
我在这里放置了一小段代码,因此您可以以自己的方式实现它,这是一个想法,您也可以实现它,但我没有添加任何演示。如果您在理解代码方面遇到任何问题,请在注释中加以解决。 快乐编码...:D
,我需要有关您项目的更多数据,但现在……您可以创建一个接口和一个对象类。需要时,您可以随时将数据添加到对象中。
如果需要对API端点进行POST,则可以发送此对象。实现预定义接口的想法是随时编辑此对象。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。