Angular4:通过路由器的`data`属性传递面包屑视图组件

时间:2017-04-27 03:58:16

标签: angular angular-routing

在我的Angular(v4)应用程序中,我正在尝试创建一个breadcrumb模块。我发现this S.O. question有帮助,建议我将痕迹信息存储在路由器的data属性中,如此

{ path: '', component: HomeComponent, data: {breadcrumb: 'home'}}

但我想要做的是在数据属性中存储组件,然后让breadcrumb模块解压缩并呈现它。允许我像这样与面包屑模块​​进行交互

{ path: '', component: HomeComponent, data: {breadcrumb: CustomViewComponent}}

在角度Dynamic Component Loader guidehelpful blog post之后,我试图让我的面包屑渲染组件执行此操作:

import { Component, Input, OnInit, AfterViewInit,
  ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import 'rxjs/add/operator/filter';

import { MainLogoComponent } from '../my-material/main-logo/main-logo.component';

@Component({
  selector: 'app-breadcrumbs',
  template: `<div #parent></div>`,
  styleUrls: ['./breadcrumbs.component.scss']
})    
export class BreadcrumbsComponent implements OnInit, AfterViewInit {
  breadcrumbs: Array<any>;
  @ViewChild('parent', {read: ViewContainerRef}) parent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver,
    private router:Router, private route:ActivatedRoute) { }

  ngOnInit() {
    this.router.events
      .filter(event => event instanceof NavigationEnd)
      .subscribe(event => {  // note, we don't use event
        this.breadcrumbs = [];
        let currentRoute = this.route.root,
            url = '';
        do {
          let childrenRoutes = currentRoute.children;
          currentRoute = null;
          childrenRoutes.forEach(route => {
            if(route.outlet === 'primary') {
              let routeSnapshot = route.snapshot;
              url += '/' + routeSnapshot.url.map(segment => segment.path).join('/');
              this.breadcrumbs.push({ 
                label: route.snapshot.data.breadcrumb,
                url:   url });
              currentRoute = route;
            }
          })
        } while(currentRoute);
      })
  }

  ngAfterViewInit() {
    const logoComponent = this.componentFactoryResolver
      .resolveComponentFactory(this.breadcrumbs[0]); // at the moment just using breadcrumbs[0] for simplicity
    this.parent.createComponent(logoComponent);
  }
}

不幸的是,似乎路由数据属性不能/不会存储组件。深入调试控制台后,data属性将保存为{data: {breadcrumb: }}

如果有人知道如何使这段代码有效,或者有更好的方法来实现构建消耗其他视图组件的breadcrumbs组件的目标,那么我 非常 欣赏它!!

PS:如果我跳过data属性,只需手动要求breadcrumb组件向页面添加MainLogoComponent,如下所示:

ngAfterViewInit() {
    const logoComponent = this.componentFactoryResolver.resolveComponentFactory(MainLogoComponent);
    this.parent.createComponent(logoComponent);
  }

它有效

并且,为了完整性,相关路线的代码。

const routes: Routes = [
  {
    path: 'calendar',
    data: {
      breadcrumb: MainLogoComponent
    },
    canActivate: [ AuthGuard ],
    children: [
      {
        path: '',
        component: CalendarComponent
      }
    ]
  }
];

更新

以下是相关的NgModule:

@NgModule({
  imports: [
    CommonModule,
    MyMaterialModule
  ],
  exports: [
    BreadcrumbsComponent
  ],
  entryComponents: [BreadcrumbsComponent, MainLogoComponent],
  declarations: [BreadcrumbsComponent] // MainLogoComponent is declared in MyMaterialModule
})
export class BreadcrumbsModule { }

1 个答案:

答案 0 :(得分:1)

所以我得到它的工作,因为我希望它工作(非常感谢@yurzui 花时间创建一个plnkr显示它应该工作,在概念!!!!!那真的 真的 有帮助,我非常高兴!!)。我仍然不确定我做错了什么,但我创建了一个新的应用程序来制作breadcrumbs模块,让它工作,然后将它移植到我的真实应用程序。我发现,一个可能的问题是ng serve似乎错过了重新编译我已经改变的文件(或者我的浏览器可能是缓存错误)。好几次,编译器吐出了有关我删除的变量的错误,当我重新启动ng serve(没有改变任何东西)时,错误消失了。警告其他人解决问题(虽然它可能与我的开发设置隔离)。

这是最终的,正在运行的 breadcrumbs 代码(注意,如果您尝试复制我的方法,也请阅读原始问题中的信息/链接)。

import { Component, AfterViewInit, ViewChild, ComponentFactoryResolver, ViewContainerRef } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import 'rxjs/add/operator/filter';

import { BreadDividerComponent } from './bread-divider/bread-divider.component';

@Component({
  selector: 'app-breadcrumbs',
  template: `
    <div #breadContainer></div>
    <div #iconContainer></div>
    <div #lastBreadContainer></div>`
})
export class BreadcrumbsComponent implements AfterViewInit {
  breadcrumbs: Array<any>;
  storedBreadcrumbs: Array<Component>;
  @ViewChild('breadContainer', {read: ViewContainerRef}) breadContainer: ViewContainerRef;
  @ViewChild('iconContainer', {read: ViewContainerRef}) iconContainer: ViewContainerRef;
  @ViewChild('lastBreadContainer', {read: ViewContainerRef}) lastBreadContainer: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver,
    private router: Router) { }

  ngAfterViewInit() {
    this.router.events
      .filter(event => event instanceof NavigationEnd)
      .subscribe(event => {
        // Reset variables and clear breadcrumb component on view change
        this.breadcrumbs = [];
        this.storedBreadcrumbs = [];
        this.breadContainer.clear();
        this.iconContainer.clear();
        this.lastBreadContainer.clear();

        // Drill down through child routes
        let currentRoute = this.router.routerState.snapshot.root.firstChild;
        if (currentRoute) {
          do {
            this.breadcrumbs.push(currentRoute.data.breadcrumb);
            currentRoute = currentRoute.firstChild;
          } while (currentRoute)
        }

        // Because each route's home path is actually itself a child route
        // e.g. { path: 'two', children: [ { path: '', component: ... } ] }
        if (this.breadcrumbs.length === 2 
          && this.breadcrumbs[0] === this.breadcrumbs[1]) {
            this.breadcrumbs = [this.breadcrumbs[0]];
        }

        this.breadcrumbs.forEach((breadcrumb, index, array) => {
          if (array.length > 1) {
            if (index < array.length - 1) {
              const breadFactory = this.componentFactoryResolver
                .resolveComponentFactory(breadcrumb);
              this.storedBreadcrumbs.push(this.breadContainer.createComponent(breadFactory));

              const iconFactory = this.componentFactoryResolver
                .resolveComponentFactory(BreadDividerComponent);
              this.storedBreadcrumbs.push(this.iconContainer.createComponent(iconFactory));
            } else {
              const breadFactory = this.componentFactoryResolver
                .resolveComponentFactory(breadcrumb);
              this.storedBreadcrumbs.push(this.lastBreadContainer.createComponent(breadFactory));
            }
          } else {
            const breadFactory = this.componentFactoryResolver
              .resolveComponentFactory(breadcrumb);
            this.storedBreadcrumbs.push(this.lastBreadContainer.createComponent(breadFactory));
          }
        });
      });
  }
}

Breadcrumb视图组件通过路由器的data属性传递,如下所示:

const routes: Routes = [
  {
    path: 'calendar',
    data: {
      breadcrumb: CalendarBreadcrumbComponent,
      menu: CalendarMenuComponent
    },
    canActivate: [ AuthGuard ],
    children: [
      {
        path: 'events',
        data: {
          breadcrumb: EventsBreadcrumbComponent
        },
        component: EventsComponent
      },
      {
        path: '',
        pathMatch: 'full',
        redirectTo: 'events'
      }
    ]
  }
];

我也使用这种策略来构建我的应用程序菜单。这是一个面包屑视图组件的示例

import { Component, HostBinding } from '@angular/core';
import { DomSanitizer  } from '@angular/platform-browser';

@Component({
  selector: 'app-calendar-breadcrumb',
  templateUrl: './calendar-breadcrumb.component.html',
  styleUrls: ['./calendar-breadcrumb.component.scss']
})
export class CalendarBreadcrumbComponent {
  // Modify this components parent element so that it is displayed with flexbox
  @HostBinding('attr.style') style = this.sanitizer
    .bypassSecurityTrustStyle('display: flex; flex-direction: row;');

  constructor(private sanitizer: DomSanitizer) {}
}

相关的视图文件(我现在意识到,实际上并不需要自己的文件......)

<md-icon>event</md-icon><h3>Calendar</h3>