【问题标题】:Angular 2 Dynamic MenuAngular 2 动态菜单
【发布时间】:2016-12-25 03:02:51
【问题描述】:

我在玩 Angular 2。 目前我想知道实现动态菜单组件的最佳方式是什么。 这个想法是有一个全局菜单组件,它接收应用程序中每个其他内容组件的菜单项。

一个想法是,有一个 menu.component、一个 menu.service 和其他使用该服务的组件。该服务在 app.module 中被全局引用。 menu.components 订阅一个 observable 并动态添加粘贴的项目。我认为这不是最好的解决方案。

另一个想法是,我将菜单选择器/标签放在每个组件模板中,并将数据直接粘贴到菜单组件。目前我一直在研究如何将项目获取到 menu.component,以便 ngFor 在初始化后生成菜单项。

所以问题是:是否有任何“最佳实践”或通用方法可以做到这一点?

我想我需要更多关于 Angular 2 的基础知识,但如果你能带领我走上正确的道路,那就太好了。这只是一个学习项目:)

这是我尝试过的一些代码:

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HTTP_PROVIDERS } from '@angular/http';

import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';
import { MenuComponent } from './menu.component';

import { HomeComponent } from './home.component';

@NgModule({
    imports: [BrowserModule],
    declarations:
    [
        AppComponent

    ],
    bootstrap: [AppComponent],
    providers:
    [
        MenuComponent,
        appRouterProviders,
        HTTP_PROVIDERS
    ]
})
export class AppModule { }

menu.component.ts

import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';

export class MenuItem {
    name: string;
    path: string;
}

@Component({
    selector: 'component-menu',
    template: `
    <ul>
    <li *ngFor="let item of menuItems"><a [routerLink]="[item.path]">{{item.name}}</a></li>
    </ul>
    `
})
export class MenuComponent {

    public menuItems: Array<MenuItem>;

    constructor() {
    }

}

home.component.ts

import { Component, OnInit } from '@angular/core';
import { MenuComponent, MenuItem } from './menu.component';

@Component({
    selector: 'app-home',
    directives: [],
    template: `<h2>Home Component</h2>
    <component-menu></component-menu>`
})
export class HomeComponent implements OnInit{
    private menuItems:Array<MenuItem>;

    constructor(private menuComponent:MenuComponent) {
        console.log('Home component ctor');
        this.menuItems = [
            {name:'Home', path: '/home'},
            {name:'Content', path: '/content'}
        ];

        this.menuComponent.menuItems = this.menuItems;


    }

    ngOnInit(){

    }
}

【问题讨论】:

  • “动态菜单”非常通用。您没有对菜单提出任何要求。这个问题也是关于在 SO 上不鼓励的意见。
  • OK - 这个想法是有一个单独的菜单组件,每个“内容”组件都有自己的菜单项,这些菜单项被转移到这个菜单组件。听起来真的很简单。我认为这是一个常见的用例。
  • 请编辑您的问题并发布代码,展示您尝试完成的工作以及失败的地方。
  • 更多的是关于什么是实现这样一个全局菜单组件的最佳方式的问题,而不是获取特定代码的帮助。
  • 但是具体的代码可以让你更容易掌握你真正想要完成的事情。请查看帮助菜单,了解要问什么问题(不问)以及如何提出好的问题。 SO 是关于具体的编程问题。好的问题通常包含代码,这些代码展示了尝试过的内容、失败的地方以及适用的具体错误消息。对于讨论,其他渠道更合适。

标签: angular


【解决方案1】:

我已经在我的项目中实现了您想要的。我是这样做的:

1- 我创建了一个名为 ContextMenuService 的服务,它负责实例化上下文菜单

2- 我创建了一个 ContextMenu 组件,这是要显示的实际菜单。

3- 服务动态创建组件,每当在菜单外点击,服务就会销毁菜单组件。

这里是服务:

@Injectable()

export class ContextMenuService {

  private _menuAlreadyOn: boolean = false;

  private _currentContextMenu: ComponentRef<any>;

  viewContainerRef: ViewContainerRef

  showContextMenu(event: MouseEvent, options: ContextMenuOption[]) : boolean {

    event.stopPropagation();

    if (this._menuAlreadyOn) {
      this._currentContextMenu.destroy();
      this._menuAlreadyOn = false;
    }

    let componentRef = this.viewContainerRef.createComponent(this._cfr.resolveComponentFactory(ContextMenuComponent))

    componentRef.instance.options = options;
    componentRef.location.nativeElement.getElementsByTagName('div')[0].style.left = event.clientX
    componentRef.location.nativeElement.getElementsByTagName('div')[0].style.top = event.clientY

    this._currentContextMenu = componentRef;
    this._menuAlreadyOn = true;

    let listener = this._eventManager.addGlobalEventListener('document', 'click',  () => {

      this._currentContextMenu.destroy();
      this._menuAlreadyOn = false;
      listener();
    })

    return false;
  }

  constructor(
    private _cfr: ComponentFactoryResolver,
    private _eventManager: EventManager
  ) { }   
}

现在,我必须使用 providers 数组将这个服务注入到 app 模块中,而且,我需要在 entryComponents 列表中为 app 模块提供 ContextMenuComponent,entryComponents 是所有动态创建的组件的列表:

@NgModule({
  declarations: [
    AppComponent,
    ContextMenuComponent
  ],
  providers: [
    ContextMenuService,
  ],
  bootstrap: [AppComponent],
  entryComponents: [ContextMenuComponent]
})
export class AppModule{}

最后,在主app组件的构造函数中,告诉它上下文菜单服务的ViewContainerRef,就是app组件的视图容器ref:

@Component ({
  selector: 'saugo-viewer',
  templateUrl: 'app/views/app.component.html',
  styleUrls: ['app/css/app.component.css'],
 }
)

export class AppComponent {
  constructor(
    private contextMenuService: ContextMenuService,
    viewContainer: ViewContainerRef
) {
    contextMenuService.viewContainerRef = viewContainer;
  }
}

我的 ContextMenuComponent 看起来像这样:

export interface ContextMenuOption {
  text: string;
  action?: () => void;
  icon?: string;
  disabled?: boolean;
}

@Component({
  selector: 'context-menu',
  templateUrl: 'app/views/helpers/context-menu.service.html',
  styleUrls: ['app/css/helpers/context-menu.service.css']
})

export class ContextMenuComponent {

  options: ContextMenuOption[] = [];

  itemClicked(i: number) {
    if (this.options[i].action) {
      this.options[i].action();
    }
  }
}

使用此模板:

<div id="context-menu"class="custom-menu">
  <ul>
    <li *ngFor="let option of options; let i = index"
    [ngClass]="{'active-item': !option.disabled}" (click)="itemClicked(i)">
      <span [class]="'glyphicon ' + option.icon"></span>
      <a href="#" [ngClass]="{'disabled-text': option.disabled}">{{option.text}}</a>
    </li>
  </ul>
</div>

我希望这可以帮助您创建自己的自定义上下文菜单

【讨论】:

    【解决方案2】:

    读完这个问题后,我首先想到的是routes。 您可以使用嵌套路由来制作这样的菜单。重新加载页面后,您将在同一页面中。 此外,如果您需要此类功能,您可以根据当前路线生成不同的菜单项。

    routes documentation

    要动态更改路线,您可以使用resetConfig 方法。 resetConfig

    对于父组件,使用prefix 类型的pathMatch

    另一个可能的 pathMatch 值是 'prefix',它告诉路由器在剩余 URL 以重定向路由的前缀路径开头时匹配重定向路由。


    要在组件之间同步数据,请使用主应用文件提供的服务。


    如果您需要更动态的方法,请使用Compiler 将您的组件注入到视图中。 Compiler documentation

    constructor(
        private compiler:Compiler,
        private viewContainerRef:ViewContainerRef){}
    
    ...
    
    this.compiler.compileComponentAsync(yourComponentClass).then((cmpFactory)=>{
          const injector = this.viewContainerRef.injector;
          this.viewContainerRef.createComponent(cmpFactory, 0,  injector);
        });
    

    【讨论】:

      猜你喜欢
      • 2017-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-27
      • 1970-01-01
      • 1970-01-01
      • 2016-10-23
      • 2016-09-18
      相关资源
      最近更新 更多