首页 文章

Angular2:根据主导航显示不同的“工具栏”组件

提问于
浏览
13

这是一个关于如何使用Angular 2以“正确”方式实现所需功能的概念性问题 .

我的应用程序有一个导航菜单,一个工具栏和一个内容区域 . 后者包含主 <router-outlet> 并显示不同的视图,如列表和详细信息 .

我想要实现的是工具栏显示不同的组件,具体取决于在内容区域中呈现的组件/视图 . 例如,列表组件需要工具栏中的搜索控件,而详细信息组件需要保存按钮 .

A) 我的第一次尝试是将另一个(命名的) <router-outlet> 添加到工具栏并显示基于静态路由的工具栏组件 . 这有什么不妥之处:

  • 静态内容 - 工具栏关系过于松散,符合我的口味 .

  • 该关系在URL中可见(并且可更改) .

  • 即使用户导航,工具栏插座也会保留此路径 .

B) 我的第二次尝试是命令性地导航到主视图组件的 ngOnInit 中的工具栏组件(也使用命名的工具栏插座),它将它更紧密地耦合 . 什么闻起来很糟糕:

  • A2

  • A3,为了防止这种情况我可以 ngOnDestroy 上的工具栏插座,但我还没有发现如何 .

C) 给路由器一个最后的机会,因为我发现这种作用:

const ROUTES: Routes = [
    {path: "buildings", children: [
        {path: "", component: BuildingListComponent, pathMatch: "full", outlet: "primary"},
        {path: "", component: BuildingListToolbarComponent, pathMatch: "full", outlet: "toolbar"},
        {path: ":id", component: BuildingDashboardComponent, outlet: "primary"}
    ]}
];

这个想法是路由器会选择每个插座的匹配路径 . 但是(不,它可能很容易)不幸的是,这不起作用:

const ROUTES: Routes = [
    {path: "buildings", children: [
        {path: "list", component: BuildingListComponent, pathMatch: "full", outlet: "primary"},
        {path: "list", component: BuildingListToolbarComponent, pathMatch: "full", outlet: "toolbar"},
        {path: ":id", component: BuildingDashboardComponent, outlet: "primary"}
    ]}
];

它显然(并且可能是意外地)仅适用于空路径 . 为什么,为什么?

D) 一个完全不同的策略是重新修改我的组件层次结构,以便每个主视图组件都包含一个合适的工具栏,并使用multi-slot content projection . Haven 't tried this, but I'恐怕遇到了多个工具栏实例的问题 .

有时,这似乎是一个常见的用例,我想知道Angular 2专家将如何解决这个问题 . 有任何想法吗?

2 回答

  • 12

    正如GünterZöchbauer所建议的那样(谢谢!),我最终添加并删除了工具栏中的动态组件 . 所需的工具栏组件在路径的data属性中指定,并由包含工具栏的中央组件(navbar)进行评估 .
    请注意,导航栏组件无需了解有关工具栏组件(在feauture模块中定义)的任何信息 .
    希望这有助于某人 .

    buildings-routing.module.ts

    const ROUTES: Routes = [
        {path: "buildings", children: [
            {
                path: "",
                component: BuildingListComponent,
                pathMatch: "full",
                data: {toolbar: BuildingListToolbarComponent}
            },
            {
                path: ":id",
                component: BuildingDashboardComponent,
                data: {toolbar: BuildingDashboardToolbarComponent}
            }
        ]}
    ];
    
    @NgModule({
        imports: [
            RouterModule.forChild(ROUTES)
        ],
        exports: [
            RouterModule
        ]
    })
    export class BuildingsRoutingModule {
    }
    

    navbar.component.html

    <div class="navbar navbar-default navbar-static-top">
        <div class="container-fluid">
            <form class="navbar-form navbar-right">
                <div #toolbarTarget></div>
            </form>
        </div>
    </div>
    

    navbar.component.ts

    @Component({
        selector: 'navbar',
        templateUrl: './navbar.component.html',
        styleUrls: ['./navbar.component.scss']
    })
    export class NavbarComponent implements OnInit, OnDestroy {
    
        @ViewChild("toolbarTarget", {read: ViewContainerRef})
        toolbarTarget: ViewContainerRef;
    
        toolbarComponents: ComponentRef<Component>[] = new Array<ComponentRef<Component>>();
        routerEventSubscription: ISubscription;
    
    
        constructor(private router: Router,
                    private componentFactoryResolver: ComponentFactoryResolver) {
        }
    
        ngOnInit(): void {
            this.routerEventSubscription = this.router.events.subscribe(
                (event: Event) => {
                    if (event instanceof NavigationEnd) {
                        this.updateToolbarContent(this.router.routerState.snapshot.root);
                    }
                }
            );
        }
    
        ngOnDestroy(): void {
            this.routerEventSubscription.unsubscribe();
        }
    
        private updateToolbarContent(snapshot: ActivatedRouteSnapshot): void {
            this.clearToolbar();
            let toolbar: any = (snapshot.data as {toolbar: Type<Component>}).toolbar;
            if (toolbar instanceof Type) {
                let factory: ComponentFactory<Component> = this.componentFactoryResolver.resolveComponentFactory(toolbar);
                let componentRef: ComponentRef<Component> = this.toolbarTarget.createComponent(factory);
                this.toolbarComponents.push(componentRef);
            }
            for (let childSnapshot of snapshot.children) {
                this.updateToolbarContent(childSnapshot);
            }
        }
    
        private clearToolbar() {
            this.toolbarTarget.clear();
            for (let toolbarComponent of this.toolbarComponents) {
                toolbarComponent.destroy();
            }
        }
    }
    

    参考文献:
    https://vsavkin.com/angular-router-understanding-router-state-7b5b95a12eab
    https://engineering-game-dev.com/2016/08/19/angular-2-dynamically-injecting-components
    Angular 2 dynamic tabs with user-click chosen components
    Changing the page title using the Angular 2 new router

  • 0

    My solution for Angular 6

    我有一些延迟加载模块的问题,最终通过将我的动态组件添加到我可以加载到应用程序的共享模块来解决 . 这可能不是非常重要的组成部分,但我没有尝试解决它 . 这似乎是我在SO和Github上看到的一个常见问题 . 我不得不通读herehere,但没有回答上面的回答和GünterZöchbauer答案的链接,我能够在项目中实现这一点 .

    无论如何,我的主要问题是我无法加载到List-Detail路由中的动态组件 . 我有一条像 /events 的路线,然后像 /events/:id 这样的孩子 . 当我通过直接在URL栏中输入 localhost:4200/events/1234 导航到 /events/1234 之类的东西时,动态组件不会立即加载 . 我必须单击一个不同的列表项才能加载我的工具栏 . 例如,我必须导航到 localhost:4200/events/4321 然后工具栏将加载 .

    我的修复如下:我使用 ngOnInit() 立即用 this.updateToolbar 调用 this.updateToolbar ,这允许我立即使用 ActivatedRoute 路由 . 由于 ngOnInit 仅被调用一次,因此第一次调用 this.updateToolbar 仅被调用一次,然后我的 Subscription 在后续导航中被调用 . 出于任何我不完全理解的原因,我的第一次导航没有触发 .subscribe() ,因此我使用 subscribe 来管理对子路径的后续更改 . 自 .pipe(take(1))... 以来,我的 Subscription 只更新了一次 . 如果您只是使用 .subscribe() ,则每次更改路线时都会继续更新 .

    我有一个List-Detail视图,需要List来获取我当前的路线 .

    import { ParamMap } from '@angular/router';
    
    import { SEvent, SService } from '../s-service.service';
    
    import { Observable } from 'rxjs';
    import { switchMap, tap } from 'rxjs/operators';
    
    import { Component, OnInit, OnDestroy, ViewChild, ViewContainerRef, ComponentRef, ComponentFactory, ComponentFactoryResolver, Type  } from '@angular/core';
    
    import { SubscriptionLike } from 'rxjs';
    import { Router, NavigationEnd, ActivatedRouteSnapshot, ResolveStart, ChildActivationEnd, ActivatedRoute } from '@angular/router';
    import { Event } from '@angular/router';
    import { filter, take } from 'rxjs/operators';
    
    @Component({
      selector: 'app-list',
      templateUrl: './s-list.component.html',
      styleUrls: ['./s-list.component.scss']
    })
    export class SListComponent implements OnInit, OnDestroy {
    
      isActive = false;
    
      @ViewChild("toolbarTarget", {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
    
      toolbarComponents: ComponentRef<Component>[] = new Array<ComponentRef<Component>>();
      routerEventSubscription: SubscriptionLike;
    
        seismicEvents: Observable<SEvent[]>;
        selectedId: number;
    
       constructor(private service: SService,
        private router: Router,
        private route: ActivatedRoute,
        private componentFactoryResolver: ComponentFactoryResolver) { }
    
        ngOnInit() {
            this.sEvents = this.route.paramMap.pipe(
                switchMap((params: ParamMap) => {
                        this.selectedId = +params.get('id');
                        return this.service.getSEvents();
                    })
          );
    
          // used this on component init to trigger updateToolbarContent 
          this.updateToolbarContent(this.route.snapshot);
    
          // kept this like above (with minor modification) to trigger subsequent changes
          this.routerEventSubscription = this.router.events.pipe(
            filter(e => e instanceof ChildActivationEnd),
            take(1)
          ).subscribe(
            (event: Event) => {
                if (event instanceof ChildActivationEnd) {
                  this.updateToolbarContent(this.route.snapshot);
                }
            }
          );
       }
    
      ngOnDestroy() {
        this.routerEventSubscription.unsubscribe();
      }
    
      private clearToolbar() {
        this.toolbarTarget.clear();
        for (let toolbarComponent of this.toolbarComponents) {
            toolbarComponent.destroy();
        }
      }
    
      private updateToolbarContent(snapshot: ActivatedRouteSnapshot) {
    
        // some minor modifications here from above for my use case
    
        this.clearToolbar();
        console.log(snapshot);
        let toolbar: any = (snapshot.data as {toolbar: Type<Component>}).toolbar;
        if (toolbar instanceof Type) {
          let factory: ComponentFactory<Component> = this.componentFactoryResolver.resolveComponentFactory(toolbar);
          let componentRef: ComponentRef<Component> = this.toolbarTarget.createComponent(factory);
          this.toolbarComponents.push(componentRef);
        }
      }
    }
    

相关问题