Angular 2动态菜单

时间:2016-08-18 08:53:36

标签: angular

我正在玩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(){

    }
}

2 个答案:

答案 0 :(得分:6)

我已经在我的项目中实现了你想要的。以下是我的表现方式:

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组件的视图容器引用:

@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>

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

答案 1 :(得分:1)

在阅读这个问题后,我首先想到的是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);
    });