😲 具有动态布局的 Angular 页面!

2025-06-09

😲 具有动态布局的 Angular 页面!

⏳ 几个月前,我写了一篇关于 Vue 中动态布局的文章。
目前,我遇到了同样的问题,只不过是用 Angular 实现的。我在网上找不到一个令人满意的解决方案。对我来说,大多数解决方案都不够清晰,而且有点混乱。

😄 所以这是一个我满意的解决方案。

➡ 顺便说一下,Vue 文章可以在这里找到

简介

首先,我们需要设置一个新的 Angular 项目。为此,我们将使用 Angular CLI。如果您尚未安装 Angular CLI,可以使用以下命令执行此操作:

npm install -g @angular/cli

我们现在将使用以下内容创建我们的项目:

ng new dynamicLayouts

现在 CLI 将询问您是否要添加 Angular 路由器,您需要按 Y 键回答“是”。

选择 CSS 作为样式表格式。
按下 Enter 键后,Angular CLI 将安装所有 NPM 包。这可能需要一些时间。

我们还需要以下包:

  • @angular/material
  • @angular/cdk
  • @angular/flex-layout

@angular/material是一个基于类似名称的 Google 设计系统的包含大量材料组件的组件库。

我们还想使用 flexbox。@angular/flex-layout将帮助我们实现这一点。

我们可以使用以下命令安装所有这些包:

npm i -s @angular/cdk @angular/flex-layout @angular/material

现在我们可以启动我们的开发服务器。

npm start

我首先想做的一件事就是将以下几行添加到您的tsconfig.json"compilerOptions"

  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@app/*": ["app/*"],
      "@layout/*": ["app/layout/*"]
    }
  }

这样,我们可以更轻松地导入组件和模块,然后记住实际路径。

我们需要进行@angular/material一些设置。
首先,将以下内容添加到src/style.css

html,
body {
  height: 100%;
}

body {
  margin: 0;
  font-family: Roboto, "Helvetica Neue", sans-serif;
}

第二,src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>DynamicLayouts</title>
    <base href="/" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/x-icon" href="favicon.ico" />
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap"
      rel="stylesheet"
    />
    <link
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
      rel="stylesheet"
    />
  </head>
  <body class="mat-typography">
    <app-root></app-root>
  </body>
</html>

我还喜欢为所有需要的材质组件创建一个单独的文件。我们需要在名为的文件夹
中创建一个名为 的新文件src/appmaterial-modules.ts

import { NgModule } from '@angular/core';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatCardModule } from '@angular/material/card';

@NgModule({
  exports: [
    MatToolbarModule,
    MatSidenavModule,
    MatButtonModule,
    MatIconModule,
    MatListModule,
    MatInputModule,
    MatFormFieldModule,
    MatCardModule,
  ],
})
export class MaterialModule {}

现在我们可以开始生成该项目所需的模块和组件。

第一个组件是dashboard

ng g c dashboard
ng g m dashboard

接下来,我们可以创建login模块和组件。

ng g c login
ng g m login

我们需要最后一个模块和两个组件。

ng g m layout
ng g c layout/main-layout
ng g c layout/centred-content-layout

app.module.ts现在需要更新

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule, Routes } from '@angular/router';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginModule } from './login/login.module';
import { RegisterModule } from './register/register.module';
import { DashboardModule } from './dashboard/dashboard.module';
import { LayoutModule } from './layout/layout.module';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    LayoutModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    LoginModule,
    RegisterModule,
    DashboardModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

这只是将一切缝合在一起。

我们还需要创建一个app-routing.module.ts设置来使我们的路由器工作。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    redirectTo: '/dashboard',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

这里最重要的几行是Routes
我们在这里定义,如果你在浏览器中输入,localhost:4200/你将会被重定向到该dashboard页面。

我们需要更新的下一个文件是app.component.ts

import { Component } from '@angular/core';
import { Router, RoutesRecognized } from '@angular/router';

export enum Layouts {
  centredContent,
  Main,
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  Layouts = Layouts;
  layout: Layouts;

  constructor(private router: Router) {}

  // We can't use `ActivatedRoute` here since we are not within a `router-outlet` context yet.
  ngOnInit() {
    this.router.events.subscribe((data) => {
      if (data instanceof RoutesRecognized) {
        this.layout = data.state.root.firstChild.data.layout;
      }
    });
  }
}

我们在这里为不同的元素创建一个枚举Layouts,并在其中ngOnInit()设置我们想要使用的正确布局。就是这样!

我们需要更新的应用程序文件夹中的最后一个文件是app.component.html

<ng-container [ngSwitch]="layout">
  <!-- Alternativerly use the main layout as the default switch case -->
  <app-main-layout *ngSwitchCase="Layouts.Main"></app-main-layout>
  <app-centred-content-layout
    *ngSwitchCase="Layouts.centredContent"
  ></app-centred-content-layout>
</ng-container>

此文件是我们所有布局的占位符,我们使用ngSwitch/ngSwitchCase功能来设置正确的布局。在实际的 HTML 代码中,我们需要从枚举中设置正确的值。主应用文件就是这样。

现在我们可以开始实现布局了。
文件src/app/layout/layout.module.ts应该像这样

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MainLayoutComponent } from './main-layout/main-layout.component';
import { CentredContentLayoutComponent } from './centred-content-layout/centred-content-layout.component';
import { RouterModule } from '@angular/router';
import { MaterialModule } from '@app/material-modules';
import { FlexLayoutModule } from '@angular/flex-layout';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([]),
    MaterialModule,
    FlexLayoutModule,
  ],
  exports: [MainLayoutComponent, CentredContentLayoutComponent],
  declarations: [MainLayoutComponent, CentredContentLayoutComponent],
})
export class LayoutModule {}

这里最大的收获是我们需要自己声明并导出布局。其余的都是标准的 Angular 样板代码。

现在让我们实现布局 HTML。
src/app/layout/main-layout/main-layout.component.html应该看起来像这样

<div fxFlex fxLayout="column" fxLayoutGap="10px" style="height: 100vh;">
  <mat-sidenav-container class="sidenav-container">
    <mat-sidenav
      #sidenav
      mode="over"
      [(opened)]="opened"
      (closed)="events.push('close!')"
    >
      <mat-nav-list>
        <a mat-list-item [routerLink]="'/dashboard'"> Dashboard </a>
        <a mat-list-item [routerLink]="'/login'"> Login </a>
      </mat-nav-list>
    </mat-sidenav>

    <mat-sidenav-content style="height: 100vh;">
      <mat-toolbar color="primary">
        <button
          aria-hidden="false"
          aria-label="sidebar toogle button"
          mat-icon-button
          (click)="sidenav.toggle()"
        >
          <mat-icon>menu</mat-icon>
        </button>
      </mat-toolbar>
      <div fxLayout="column">
        App Content
        <router-outlet></router-outlet>
      </div>
    </mat-sidenav-content>
  </mat-sidenav-container>
</div>

这个布局是典型的 Materialapp布局,带有一个滑出的导航抽屉和一个顶栏。导航栏也包含一个指向login页面的路由。
我们在这里放置@angular/material所有组件并@angular/flex-layout进行布局。这里没有什么特别之处。

第二个布局称为centred-content-layout。我们在这里需要更改的唯一文件是centred-content-layout.component.html

<div fxFlex fxLayout="row" fxLayoutAlign="center center" style="height: 100vh;">
  <router-outlet></router-outlet>
</div>

布局非常简短,因为它唯一要做的就是将接收的内容垂直和水平居中。

就是这样!我们已经设置好了布局,现在可以使用它们了。

现在让我们先设置仪表板。在仪表板组件文件夹中,我们需要创建一个名为dashboard-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { Layouts } from '@app/app.component';

const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    data: { layout: Layouts.Main },
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class DashboardRoutingModule {}

我们正在为设置路线dashboard。我们正在告诉我们的应用程序使用Main布局。

dashboard.module.ts我们需要导入DashboardRoutingModule

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardComponent } from './dashboard.component';
import { DashboardRoutingModule } from './dashboard-routing.module';

@NgModule({
  imports: [CommonModule, DashboardRoutingModule],
  declarations: [DashboardComponent],
})
export class DashboardModule {}

现在我们只需要实现我们的login页面。
让我们首先更新login.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './login.component';
import { LoginRoutingModule } from './login-routing.module';
import { MaterialModule } from '@app/material-modules';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';

@NgModule({
  declarations: [LoginComponent],
  imports: [
    CommonModule,
    LoginRoutingModule,
    MaterialModule,
    FormsModule,
    ReactiveFormsModule,
    FlexLayoutModule,
  ],
})
export class LoginModule {}

同样,这里没什么特别的,只是我们标准的 Angular 样板代码。
这里唯一新的东西是我们将使用FormModuleReactiveFormsModule。我们需要它来处理表单和验证。我们现在就来实现它。

下一个要更改的文件是login.component.html

<mat-card>
  <mat-card-content>
    <form>
      <h2>Log In</h2>
      <mat-form-field>
        <mat-label>Enter your email</mat-label>
        <input
          matInput
          placeholder="pat@example.com"
          [formControl]="email"
          required
        />
        <mat-error *ngIf="email.invalid">
          {{ getEmailErrorMessage() }}
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Enter your password</mat-label>
        <input
          matInput
          placeholder="My Secret password"
          [formControl]="password"
          required
        />
        <mat-error *ngIf="password.invalid">
          {{ getPasswordErrorMessage() }}
        </mat-error>
      </mat-form-field>
      <button mat-raised-button color="primary">Login</button>
    </form>
  </mat-card-content>
</mat-card>

这是一个用于登录界面的标准表单。同样,这里没有什么特别的。我们将进行一些表单验证和错误消息。为了使验证工作正常,我们需要更新login.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css'],
})
export class LoginComponent implements OnInit {
  constructor() {}

  email = new FormControl('', [Validators.required, Validators.email]);
  password = new FormControl('', [
    Validators.required,
    Validators.minLength(8),
  ]);
  getEmailErrorMessage() {
    if (this.email.hasError('required')) {
      return 'You must enter a email';
    }

    return this.email.hasError('email') ? 'Not a valid email' : '';
  }

  getPasswordErrorMessage() {
    if (this.password.hasError('required')) {
      return 'You must enter a password';
    }

    return this.password.hasError('password') ? 'Not a valid password' : '';
  }

  ngOnInit(): void {}
}

我们正在设置电子邮件验证。用户现在需要输入有效的电子邮件。
此外,密码必须至少包含 8 个字符。其余部分只是一些用于设置消息的样板代码。

我们需要做的最后一件事是创建一个login-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Layouts } from '@app/app.component';
import { LoginComponent } from './login.component';
const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent,
    data: { layout: Layouts.centeredContent },
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class LoginRoutingModule {}

该文件与我们仪表板示例中的文件几乎相同,但现在将使用centredContent布局。如果您复制并粘贴了login.module.ts,则该文件LoginRoutingModule已被导入。

就是这样!现在我们可以创建任意数量的布局了。我们还可以扩展它们,添加更多功能,而无需修改页面组件。

我还创建了一个包含代码的 GitHub repo。链接

如果您有任何疑问,请在下面的评论中提问!

你想看这个教程的视频教程吗?
还有什么想了解更多吗?
需要我详细讲解一下吗?
如果可以,请告诉我!

真有趣!

👋打个招呼! Instagram | Twitter | LinkedIn | Medium | Twitch | YouTube

鏂囩珷鏉ユ簮锛�https://dev.to/lampewebdev/angular-pages-with-dynamic-layouts-hp
PREV
😊👉 公司文化比豪华的办公空间更重要
NEXT
21 个最佳 JavaScript 和 CSS 库,助您开发网站