Angular4:通过路由器的“数据”传递面包屑视图组件 属性

Angular4: passing breadcrumb view components through the router's `data` property

在我的 Angular (v4) 应用程序中,我正在尝试创建一个面包屑导航模块。我发现 很有帮助,建议我将面包屑信息存储在路由器的 data 属性 中,就像这样

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

但我想做的是在数据 属性 中存储 一个组件 ,然后让面包屑模块​​提取并呈现它。允许我像这样与面包屑模块​​交互

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

在 angular Dynamic Component Loader guide and a helpful 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: {breadcrumb: }}

如果有人知道让这段代码工作的方法,或者有更好的方法来实现我构建使用其他视图组件的面包屑组件的目标,我会 非常非常感谢!!

PS:如果我跳过 data 属性,只是手动要求面包屑组件向页面添加 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 { }

所以我按照我想要的方式让它工作(非常感谢@yurzui 花时间 创建一个 plnkr 显示它应该工作,在概念上! !!!! 这真的 真的 帮了大忙,我非常感激!!)。我仍然不确定我哪里做错了,但我创建了一个新应用程序来制作面包屑模块​​,使其正常工作,然后将其移植到我的真实应用程序中。我发现,一个可能的问题是 ng serve 似乎偶尔会错过重新编译我更改过的文件(或者我的浏览器可能存在缓存错误)。现在有好几次,编译器已经吐出有关我删除的变量的错误,当我重新启动 ng serve(不更改任何内容)时,错误消失了。对其他人解决问题的警告(尽管它可能与我的开发设置无关)。

这是最终的工作 面包屑 代码(请注意,如果您要复制我的方法,请通读原始问题中的信息/链接)。

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));
          }
        });
      });
  }
}

面包屑视图组件通过路由器的数据传递 属性,像这样:

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>