在Angular 4中为不同页面设置不同的布局的最佳方法是什么?

92

我是 Angular 4 的新手。我想要实现的是在我的应用程序中为不同页面设置不同的布局头部和底部。我有三种情况:

  1. 登录、注册页面(没有头部,没有底部)

路由:['login','register']

  1. 市场推广网站页面(这是根路径,它有一个头部和底部,大多数情况下在登录之前显示这些部分)

路由:['','about','contact']

  1. 已登录的应用程序页面(在所有应用程序页面中,我有一个不同的头部和底部,但此头部和底部与市场推广网站的头部和底部不同)

路由:['dashboard','profile']

我通过将头部和底部添加到我的路由器组件 HTML 中临时运行应用程序。

请给我建议更好的方法。

以下是我的代码:

app\app.routing.ts

   const appRoutes: Routes = [
        { path: '', component: HomeComponent},
        { path: 'about', component: AboutComponent},
        { path: 'contact', component: ContactComponent},
        { path: 'login', component: LoginComponent },
        { path: 'register', component: RegisterComponent },
        { path: 'dashboard', component: DashboardComponent },
        { path: 'profile', component: ProfileComponent },


        // otherwise redirect to home
        { path: '**', redirectTo: '' }
    ];

    export const routing = RouterModule.forRoot(appRoutes);

应用组件模板文件

<router-outlet></router-outlet>

app/home/home.component.html

<site-header></site-header>
<div class="container">
    <p>Here goes my home html</p>
</div>
<site-footer></site-footer>

应用/关于/about.component.html

<site-header></site-header>
<div class="container">
    <p>Here goes my about html</p>
</div>
<site-footer></site-footer>

应用/登录/登录组件.html

<div class="login-container">
    <p>Here goes my login html</p>
</div>

应用程序/仪表板/dashboard.component.html

<app-header></app-header>
<div class="container">
    <p>Here goes my dashboard html</p>
</div>
<app-footer></app-footer>

我在 stack-overflow 上看到了这个问题, 但是我没有从那个答案中得到清晰的说明。


这个链接有几种方法可以实现这个功能: https://blog.angularindepth.com/angular-routing-reusing-common-layout-for-pages-from-different-modules-440a23f86b57 - Rakeshk Khanapure
4个回答

175

您可以使用子路由来解决问题。

请参见示例演示:https://angular-multi-layout-example.stackblitz.io/,或编辑示例:https://stackblitz.com/edit/angular-multi-layout-example

设置您的路由如下:

const appRoutes: Routes = [
    
    // Site routes goes here 
    { 
        path: '', 
        component: SiteLayoutComponent,
        children: [
          { path: '', component: HomeComponent, pathMatch: 'full'},
          { path: 'about', component: AboutComponent }
        ]
    },
    
    // App routes goes here
    { 
        path: '',
        component: AppLayoutComponent, 
        children: [
          { path: 'dashboard', component: DashboardComponent },
          { path: 'profile', component: ProfileComponent }
        ]
    },

    // no layout routes
    { path: 'login', component: LoginComponent},
    { path: 'register', component: RegisterComponent },
    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);

6
Demo对我帮助很大,但有点难理解,主要是我不明白空路径''的含义。如果路径为'admin',生成的链接将匹配'admin/profile',但由于它是空的,它会匹配主URL链接,如'/profile',如果子路径也是路径'',它会匹配主页''。另外,那些component: components到底是什么并不是很清楚,我只需要在原来的<router-outlet></router-outlet>保留所有内容,并将所有内容发布到新生成的components中即可。非常感谢 :) - Jiro Matchonson
当模块被懒加载时,如何使其正常工作? - ksh
@KarthikHande 即使在惰性加载上也可以正常工作(我所有的工作都是在惰性加载上完成的)。我会尝试使用模块加载制作演示,但无法保证时间。如果您仍然在惰性加载模块上实现时遇到困难,请在此处提醒我。 - Rameez Rami
作为 Angular 的新手,@RameezRami 我有些困惑:当上述布局都以 path: '' 空路径开始时,浏览器如何知道要走哪个路由?即使它能够正常工作,您能否请解释一下呢? - Ahsan Alii
1
简单来说,不要考虑浏览器如何处理这个问题。所有的路由路径都是由Angular解析的。{path: '',}并不意味着路径就在那里结束了,它可以有子路由。但是如果你使用{path: '', pathMatch: 'full',},它意味着你的路由级别的范围就在那里结束了。尝试一下我分享的stack-bits演示,并通过将pathMatch移动到不同的路由定义级别进行一些实验。 - Rameez Rami
谢谢你的回答和评论,它们对我非常有帮助。然而,我有一个后续问题:在我的布局中,我想要一个按钮,在不同的页面加载时调用不同的“点击”函数。这种动态内容在这种方法中是否可行? - werwuifi

10

你可以使用子节点,例如:

const appRoutes: Routes = [
    { path: '', component: MainComponent,
        children:{
            { path: 'home'  component:HomeComponent},
            { path: 'about', component: AboutComponent},
            { path: 'contact', component: ContactComponent},
               ..others that share the same footer and header...

        }
    },
    { path: 'login', component: LoginComponent },
    { path: 'register', component: RegisterComponent },
    { path: 'admin', component:AdminComponent, 
         children{
            { path: 'dashboard', component: DashboardComponent },
            { path: 'profile', component: ProfileComponent }
               ..others that share the same footer and header...
         }
    }
    { path: '**', redirectTo: '' }
];

主组件和管理员组件类似

<app-header-main></app-header-main>
<router-outlet></router-outlet>
<app-footer-main></app-footer-main>

这篇文章讲述了如何将路由分别存储在不同的文件中。


根据答案,URL将是“admin/dashboard”,“admin/profile”,我不希望发生这种情况...我想将URL设置为“dashboard”,“profile”。有什么办法吗? - ninja dev
我想说的是,如果你有两个不同的页脚,你可以使用一个@input来制作一个页脚,并使用*ngIf来显示其中一个视图,或者制作两个页脚。无论如何,这只是一个例子。你可以把你的DashboardComponent和profile作为路径:'',component:MainComponent的“children”,并忘记路径:admin。 - Eliseo
如果您正在使用管理员路由,则以下内容将非常有用。感谢为我提供这个想法。 - Gurpreet Singh
1
如何覆盖 app-header-main 和 app-footer-main 的内容? - Ievgen

3
有些情况下,布局和共享元素与路由结构并不完全匹配,或者某些元素必须根据每个路由的情况隐藏/显示。对于这种情况,我可以想到以下策略(以app-header-main组件为例 - 但显然适用于任何共享页面元素):

输入和CSS类

您可以提供输入或CSS类来控制共享元素的内部外观,例如:
  1. <app-header-main [showUserTools]="false"></app-header-main>
  1. <app-header-main class="no-user-tools"></app-header-main> 然后使用:host(.no-user-tools)来显示/隐藏需要的内容
  1. at a route level (child or not):

    {
      path: 'home',
      component: HomeComponent,
      data: {
        header: {showUserTools: true},
      },
    },
    

并通过ActivatedRoute访问它,像这样:this.route.data.header.showUserTools

TemplateRef输入

app-header-main组件内:

@Input() rightSide: TemplateRef<any>;

类型为TemplateRef<any>的输入,您可以直接提供ng-template元素

<app-header-main [rightSide]="rightside"></app-header-main>
<ng-template #rightside>your content here</ng-template>

命名插槽传递

您可以编写app-header-main,使其使用命名插槽传递。

在app-header-main模板内:

<ng-content select="[rightSide]"></ng-content>

用法:

<app-header-main class="no-user-tools">
  <div rightSide>your content here</div>
</app-header-main>

0

您可以使用ng-content + ViewChild将布局注入到每个使用特定布局的页面组件中,以解决此问题。

对于这种常见用例,使用路由器似乎总是一种变通方法。您想要的类似于Asp.Net MVC中的布局或WebForm中的MasterPages等。

在努力尝试后,我最终得到了以下结果:

查看演示:https://stackblitz.com/edit/angular-yrul9f

shared.component-layout.ts

import { Component } from '@angular/core';

@Component({
  selector: 'shared-component-layout',
  template: `
  <div *ngIf="!hideLayoutHeader" style="font-size: 2rem;margin-bottom: 10px;">
    Layout title: {{layoutHeader}}
    <ng-content select=".layout-header">    
    </ng-content>
  </div>
  <ng-content select=".layout-body">
  </ng-content>
  `
})
export class SharedComponentLayout {
  layoutHeader: string;
  hideLayoutHeader: boolean;
}

page.component-base.ts

import { Component, ViewChild } from '@angular/core';
import { SharedComponentLayout } from './shared.component-layout';

export abstract class PageComponentBase {
    @ViewChild('layout') protected layout: SharedComponentLayout;
}

login.component.ts - 不包含头部

import { Component } from '@angular/core';
import { PageComponentBase } from './page.component-base';

@Component({
  selector: 'login-component',
  template: `
  <shared-component-layout #layout>
    <div class="layout-body">
      LOGIN BODY
    </div>
  </shared-component-layout>
  `
})
export class LoginComponent extends PageComponentBase {

  ngOnInit() {
    this.layout.hideLayoutHeader = true;    
  }
}

home.component.ts - 带有标题

import { Component } from '@angular/core';
import { PageComponentBase } from './page.component-base';

@Component({
  selector: 'home-component',
  template: `
  <shared-component-layout #layout>
    <div class="layout-body">
      HOME BODY
    </div>
  </shared-component-layout>
  `
})
export class HomeComponent extends PageComponentBase {

  ngOnInit() {    
    this.layout.layoutHeader = 'Home component header';
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接