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

Joh*_*ohn 5 angular-routing angular

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

{ path: '', component: HomeComponent, data: {breadcrumb: 'home'}}
Run Code Online (Sandbox Code Playgroud)

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

{ path: '', component: HomeComponent, data: {breadcrumb: CustomViewComponent}}
Run Code Online (Sandbox Code Playgroud)

按照 angular Dynamic Component Loader 指南有用的博客文章,我尝试让我的面包屑渲染组件执行以下操作:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

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

如果任何人的方式知道,使此代码的工作,或者有什么建议一种更好的方式来实现我建立一个面包屑成分消耗其它视图组成部分,我会的目标,极大地欣赏它!

PS:如果我跳过该data属性,而只是手动要求面包屑组件将 a 添加MainLogoComponent到页面,如下所示:

ngAfterViewInit() {
    const logoComponent = this.componentFactoryResolver.resolveComponentFactory(MainLogoComponent);
    this.parent.createComponent(logoComponent);
  }
Run Code Online (Sandbox Code Playgroud)

有用

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

const routes: Routes = [
  {
    path: 'calendar',
    data: {
      breadcrumb: MainLogoComponent
    },
    canActivate: [ AuthGuard ],
    children: [
      {
        path: '',
        component: CalendarComponent
      }
    ]
  }
];
Run Code Online (Sandbox Code Playgroud)

更新

这是相关的 NgModule:

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

Joh*_*ohn 3

所以我让它按照我想要的方式工作(非常感谢 @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));
          }
        });
      });
  }
}

Run Code Online (Sandbox Code Playgroud)

面包屑视图组件通过路由器的 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'
      }
    ]
  }
];
Run Code Online (Sandbox Code Playgroud)

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

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) {}
}
Run Code Online (Sandbox Code Playgroud)

以及关联的视图文件(我现在意识到,它确实不需要自己的文件......)

<md-icon>event</md-icon><h3>Calendar</h3>
Run Code Online (Sandbox Code Playgroud)