github Source code
註: 若只要取得本篇筆記所介紹的內容可使用以下指令: “git clone --branch v2.0 https://github.com/calvinegs/Angular_Material_Admin_Template.git”
在本篇筆記中將繼續使用 Angular Material 中的其他 components 來加強 Admin Template。在此文中除了會使用 SnackBar component 來實做一個 Notifier ,同時也把之前介紹過的 Loading Spnner 功能也一併加入。
除此之外也會建置一個 Login Page,身份驗證是一個應用程式中不可缺少的功能,有了這個登入畫面,將會被應用到另一篇 angular Interceptor - JWT 的應用中(待續)。
有關 Admin Template 第一個版本的介紹請見 Loding Spinner
加強 Admin Template 功能#
預計實現的功能:
使用 Angular Material SnackBar 為 Admin Template 加入 Notifier 功能。#
使用以下步驟:
- 取得 Admin Template 第一個版本程式碼
- 實現基本版本 Notifier
- 實現客制化 Notifier 功能
- 使用 material 實現一個 Login Page
取得 Admin Template 第一個版本程式碼#
首先要為 Admin Template 第一個版本加入 Notifier 功能,可以使用 `git clone`指令取得特定版程式碼。以下指令是取得 Tag為 ver-1.0 版本的程式碼: “git clone --branch v1.1 https://github.com/calvinegs/Angular_Material_Admin_Template.git”
。使用這個版本的程式碼繼續在上面加入本篇筆記的相關功能。
實現基本版本 Notifier#
將使用 angular material snackBar component 來實現 Notifier 功能。
匯入 snackBar module#
使用 material snackBar component前要先匯入 snackBarModule 到 share-material-module.ts 中,並把它也 export 出去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NavigationComponent } from '../navigation/navigation.component';
import { LayoutModule } from '@angular/cdk/layout';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'
@NgModule({
declarations: [
NavigationComponent
],
imports: [
CommonModule,
FormsModule,
LayoutModule,
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatSlideToggleModule,
MatSnackBarModule,
RouterModule
],
exports: [
NavigationComponent
]
})
export class ShareMaterialModule { }
|
建立 NotifierService#
接著建立 notifier.service.ts
$ npx ng g s services/notifier
NotifierService 程式碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// notifier.service.ts
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
@Injectable({
providedIn: 'root'
})
export class NotifierService {
constructor( private snackBar: MatSnackBar) { }
showNotification(displayMessage: string, buttonText: string) {
this.snackBar.open(displayMessage, buttonText, {
horizontalPosition: 'center', // snack Bar 顯示的水平位置
verticalPosition: 'bottom', // snack Bar 顯示的垂直位置
panelClass: 'error' // 自定顏色
})
}
}
|
客制化 css#
在 NotifierService 中使用了一些客制化的 css,把 css 程式碼加入到 style.scss
.error.mat-mdc-snack-bar-container{
--mdc-snackbar-container-color: red; // 背景
--mdc-snackbar-supporting-text-color: white; // 文字顏色
--mat-mdc-snack-bar-button-color: yellow; // 按鈕文字顏
}
使用 NotifierService 來顯示 Notifier#
在 flexBox component 中加入一個功能按鈕,並在按下按鈕後呼叫 NotefierService 中的 showNotification() 方法來顯示 Notifier 在螢幕下方。
首先在 flexBox component template 中加入以下程式碼。
<!-- flexbox.component.html -->
<p>flexbox works!</p>
<button mat-raised-button color="primary" (click)='showError()'>Show Error</button>
在上述程式中使用了 mat-raised-button directive 所以要在 share module 中匯入 MatButtonModule,並且也要 export 出來。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// share-material.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NavigationComponent } from '../navigation/navigation.component';
import { LayoutModule } from '@angular/cdk/layout';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'
@NgModule({
declarations: [
NavigationComponent
],
imports: [
CommonModule,
FormsModule,
LayoutModule,
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatSlideToggleModule,
MatSnackBarModule,
RouterModule
],
exports: [
NavigationComponent,
MatButtonModule,
]
})
export class ShareMaterialModule { }
|
flexBox component class 的程式碼如下:
- 先在 constructor 中注入 Notifier Service
- 再建立一個 showError() method,並在這個方法內呼叫 Notifier Service 中的 showNotification() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// flexbox.component.ts
import { Component } from '@angular/core';
import { NotifierService } from 'src/app/services/notifier.service';
@Component({
selector: 'app-flexbox',
templateUrl: './flexbox.component.html',
styleUrls: ['./flexbox.component.scss']
})
export class FlexboxComponent {
constructor(private notifierService: NotifierService) {}
showError() {
this.notifierService.showNotification('資料讀取失敗!','關閉');
}
}
|
執行程式後結果#
優化 NotifierService#
優化的功能包含了:
- 支援不同類型的 Notifier (Success/Error)
- Notifier 預設五秒鐘會自動消失
修改 NotifierService 中 showNotification() method:
1
2
3
4
5
6
7
8
9
|
// notifier.service.ts
showNotification(displayMessage: string, buttonText: string, messageType: 'error' | 'success') {
this.snackBar.open(displayMessage, buttonText, {
duration: 5000,
horizontalPosition: 'center',
verticalPosition: 'bottom',
panelClass: messageType
})
}
|
在 styles.scss 中再加入額外的 succs style 設定
.success.mat-mdc-snack-bar-container{
--mdc-snackbar-container-color: gray;
--mdc-snackbar-supporting-text-color: yellow;
--mat-mdc-snack-bar-button-color: white;
}
修改 flexbox.component.html:
1
2
3
4
5
6
|
<p>flexbox works!</p>
<div class="button-container">
<button class="btn" mat-raised-button color="primary" (click)="showSnackBar('success')">success</button>
<button class="btn" mat-raised-button color="accent" (click)="showSnackBar('error')">error</button>
</div>
|
修改 flexbox.component.scss:
.container{
display: flex;
}
.btn{
margin: 5px;
}
修改 flexbox.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import { Component } from '@angular/core';
import { NotifierService } from 'src/app/services/notifier.service';
@Component({
selector: 'app-flexbox',
templateUrl: './flexbox.component.html',
styleUrls: ['./flexbox.component.scss']
})
export class FlexboxComponent {
constructor(private notifierService: NotifierService) {}
showSnackBar(typeNotifier:'error'|'success') {
if (typeNotifier === 'success')
this.notifierService.showNotification('資料已順利讀取完成!','OK', typeNotifier);
else
this.notifierService.showNotification('資料讀取失敗!','關閉', typeNotifier);
}
}
|
優化後的結果#
優化後同一個 method showSnackBar() 已可以支援不同類型的 notifier ,並顯示出不同的 style:
實現客制化 Notifier 功能#
如果你的 notifier 需要更豐富的 UI,MatSnackBar 也支援可自定使用者介紹的方式來顯示,下面來看看如何做到客制化 UI。
新建立一個 component#
使用 ng cli 在 components 目錄下建立一個新元件
$ npx ng g c components/notifier
NotifierServer 中新的 Method#
在新元件中將會使用到 NotifierServer 中新的 method,如下:
// notifier.service.ts
// ...
showCustomNotification(displayMessage: string, buttonText: string, messageType: 'error' | 'success') {
this.snackBar.openFromComponent(NotifierComponent, {
data: {
message: displayMessage,
buttonText: buttonText,
messageType: messageType
},
duration: 5000,
horizontalPosition: 'center',
verticalPosition: 'bottom',
panelClass: [messageType]
})
};
新 component 的程式碼#
Notifier Component 程式內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// notifier.component.ts
import { Component, Inject } from '@angular/core';
import { MatSnackBarRef, MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar';
@Component({
selector: 'app-notifier',
templateUrl: './notifier.component.html',
styleUrls: ['./notifier.component.scss']
})
export class NotifierComponent {
constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any,
public snackBarRef: MatSnackBarRef<NotifierComponent>) {
}
|
Notifier Component template 內容:
1
2
3
4
5
6
7
8
9
10
11
12
|
<!-- notifier.component.heml -->
<div>
{{data.messageType}}
</div>
<div class="content-style">
{{data.message}}
</div>
<div class="button-style">
<button mat-flat-button [ngClass]=data.messageType (click)="snackBarRef.dismiss()">
{{data.buttonText}}
</button>
</div>
|
Notifier Component style 內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/* notifier.component.scss */
.success {
background-color: gray;
}
.error {
background-color: aqua;
}
.content-style{
border: 1px solid white;
border-radius: 4px;
padding: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
.button-style{
text-align: center;
}
|
flexBox Component 程式內容:
1
2
3
4
5
6
7
|
// flexbox.component.ts
showCustomSnackBar(typeNotifier: 'error'|'success') {
if (typeNotifier === 'success')
this.notifierService.showCustomNotification('資料已順利讀取完成!','OK', typeNotifier);
else
this.notifierService.showCustomNotification('資料讀取失敗!','關閉', typeNotifier);
}
|
flexBox Component template 內程式內容:
1
2
3
4
5
6
7
|
<!-- flexbox.component.html -->
<div class="button-container">
<button class="btn" mat-raised-button color="primary" (click)="getData('success')">success</button>
<button class="btn" mat-raised-button color="accent" (click)="getData('error')">error</button>
<button class="btn" mat-raised-button color="primary" (click)="showCustomSnackBar('success')">Custom SnackBar</button>
<button class="btn" mat-raised-button color="accent" (click)="showCustomSnackBar('error')">Custom SnackBar</button>
</div>
|
測試 客制化 Notifier 功能#
以上是如何使用 snackBar 來實現 Notifier 功能。接下來繼續來完善我們的 Admin Template
為 Admin Template 加入 Loading spinner#
建立一個 Loading Service#
使用 Cli 建立一個 service
$ npx ng g s services/loading
service 程式碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// loading.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, delay, Observable, of, switchMap } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoadingService {
private _loading$ = new BehaviorSubject<boolean>(false);
loading$: Observable<boolean> = this._loading$.pipe(
switchMap(isLoading => {
if (!isLoading) {
return of(false);
}
return of(true).pipe(delay(1000));
})
)
constructor() { }
show() {
this._loading$.next(true);
}
hide() {
this._loading$.next(false);
}
}
|
建立一個 interceptor#
透過 Angular Cli 來建立一個 Service,這個 service 的功能是在發出每 http request 時會先顯示 loading spinner,並在 request 完成後關閉 loading spinner。
透過Angular cli 建立 interceptor
$ npx ng g interceptor interceptors/loadingSpin
interceptor 中的程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// loading-spin.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { finalize, Observable } from 'rxjs';
import { LoadingService } from '../services/loading.service';
@Injectable()
export class LoadingSpinInterceptor implements HttpInterceptor {
constructor(private loadingServie : LoadingService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
this.loadingServie.show();
return next.handle(request).pipe(
finalize(()=> {
this.loadingServie.hide();
})
);
}
}
|
完成的 interceptor 必須在 app.module.ts 中先註冊
1
2
3
4
5
6
7
8
|
// app.module.ts
// ...
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: LoadingSpinInterceptor,
multi: true
}],
bootstrap: [AppComponent]
|
在 Admin Template 加入 Loading spinner#
把 Material Spinner ui component 加入到 NavigationComponent 中,以下程式碼放在程式最前頭:
1
2
3
4
5
|
<!-- navigation.component.html -->
<ng-container *ngIf="loading$ | async">
<div class="overlay"></div>
<mat-spinner></mat-spinner>
</ng-container>
|
在 navigation.component.scss 加入以下 css 用來顯示 spinner,並鎖定畫面。
mat-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 5;
}
.overlay {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
z-index: 2;
backdrop-filter: blur(2px);
}
並在 component class 中加入 loading$ 變數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import { Component } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { LoadingService } from '../services/loading.service';
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss']
})
export class NavigationComponent {
isDarkTheme = false;
loading$ = this._loadingService.loading$;
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches),
shareReplay()
);
constructor(private breakpointObserver: BreakpointObserver,
private _loadingService: LoadingService) {}
ngOnInit() {
this.isDarkTheme = localStorage.getItem('theme') === "Dark" ? true : false;
}
storeThemeSelection() {
localStorage.setItem('theme', this.isDarkTheme ? "Dark" : "Light");
}
}
|
在 ShareMaterialModule 中要匯入/出 Progress Spinner Module#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NavigationComponent } from '../navigation/navigation.component';
import { LayoutModule } from '@angular/cdk/layout';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'
@NgModule({
declarations: [
NavigationComponent
],
imports: [
CommonModule,
FormsModule,
LayoutModule,
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatSlideToggleModule,
MatSnackBarModule,
MatProgressSpinnerModule,
RouterModule
],
exports: [
NavigationComponent,
MatButtonModule,
MatProgressSpinnerModule
]
})
export class ShareMaterialModule { }
|
建立一個 發送 http request 來取得遠端資料的 Service#
使用 cli 建立一個具有發送 http request 的 service
程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class GetDataService {
constructor(private http: HttpClient) { }
getData() {
return this.http.get('https://jsonplaceholder.typicode.com/posts');
}
}
|
建立一個測試 Spinner 功能的元件#
使 cli 來建立一個可測試 Spinner 的元件:
$ npx ng g c components/getData
<!-- getData.Component.html -->
<p>get-data works!</p>
<button mat-raised-button color="primary" (click)="getData()">取得資料</button>
// getData.Component.html
import { Component } from '@angular/core';
import { GetDataService } from 'src/app/services/get-data.service';
@Component({
selector: 'app-get-data',
templateUrl: './get-data.component.html',
styleUrls: ['./get-data.component.scss']
})
export class GetDataComponent {
data: unknown;
constructor(private _getData: GetDataService) {}
getData() {
this._getData.getData().subscribe( data => {
this.data = data;
})
}
}
為 GetDataComponent 建立對應的 route
// app.module.ts
const routes: Routes = [
{ path: 'flexbox', component: FlexboxComponent },
{ path: 'getdata', component: GetDataComponent }
];
測試 Spinner 功能#
為測試 Spinner 功能,我們要讓網路連線速度下降。可以透過使用 chrome 開發者工具中的 network 功能中的:Disable cash & throttling 來進行模擬。
執行程式開啟瀏覽器後先按 F12 來開啟開發者工具,切換到 Network 功能區,先勾選“Disable cache"選項
接著下拉 Throttling 選項,點選最下面的 “Add”
再點選 “Add custom profile"
建立一個 20Mbps 的連線設定檔
測試結果:
最後我們還可以在 LoadingSpinInterceptor 中加入 Notifier 來顯示資料已讀取完成的訊息。
1
2
3
4
5
6
7
8
9
10
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
this.loadingServie.show();
return next.handle(request).pipe(
finalize(()=> {
this.loadingServie.hide();
this.notifier.showCustomNotification('資料讀成完成!', 'ok', 'success')
})
);
}
|
使用 material 實現一個 Login page#
現在我們 Admin Template 所具備的功能已越來越完整,在本篇筆記最後,要再使用 Material 相關 UI 來建立一個系統登入的功能畫面。
使用 angular cli 來新增個 login component 的框架
$ npx ng g c components/login
在 ShareMaterialModule 匯入相關的 Material Module#
先在 ShareMaterialModule 匯入相關的 Material Module: MatIconModule、MatCardModule、MatInputModule等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
// share-material.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NavigationComponent } from '../navigation/navigation.component';
import { LayoutModule } from '@angular/cdk/layout';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
@NgModule({
declarations: [
NavigationComponent
],
imports: [
CommonModule,
FormsModule,
LayoutModule,
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatSlideToggleModule,
MatSnackBarModule,
MatProgressSpinnerModule,
RouterModule
],
exports: [
NavigationComponent,
MatButtonModule,
MatProgressSpinnerModule,
MatIconModule,
MatCardModule,
MatInputModule
]
})
export class ShareMaterialModule { }
|
Component Class 中的程式碼#
LoginComponent Class 程式碼中使用到 Reactive Forms,記得要在 app.module.ts 中匯入 ReactiveFormsModule。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// login.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent {
hide: boolean = false;
constructor(private fb: FormBuilder) {
}
ngOnInit() {
}
loginForm: FormGroup = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]]
})
onLogin() {
if (!this.loginForm.valid) {
return;
}
console.log(this.loginForm.value);
}
}
|
login Template#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
<!-- login.components.html -->
<mat-card>
<mat-card-content>
<div class="header">
<P>Sign Into Your Account </P>
</div>
<form (ngSubmit)="onLogin()" name="loginForm" [formGroup]="loginForm">
<div class="emailInput">
<mat-form-field class="full-width" appearance="outline">
<mat-label>Email</mat-label>
<input
formControlName="email"
matInput
placeholder="Enter email address" required
/>
<mat-error *ngIf="!loginForm.controls['email'].valid">
Email is required
</mat-error>
</mat-form-field>
</div>
<div>
<span>
<a class="text-link" class="aLink" routerLink="/auth/forgot-password">Forgot Password?</a>
</span>
<mat-form-field class="full-width" appearance="outline">
<mat-label>Password</mat-label>
<input formControlName="password" matInput [type]=" hide ? 'password' : 'text'" required />
<button mat-icon-button matSuffix (click)="hide = !hide" [attr.aria-label]="'Hide Password'"
[attr.aria-pressed]="hide">
<mat-icon>
{{hide ? 'visibility_off' : 'visibility'}}
</mat-icon>
</button>
<mat-error *ngIf="!loginForm.controls['password'].valid">
Password is required
</mat-error>
</mat-form-field>
</div>
<button mat-flat-button color="primary">Login</button>
</form>
<div class="button-row">
<p>Create New Account</p>
</div>
</mat-card-content>
</mat-card>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
/* login.components.scss */
mat-card {
max-width: 600px;
margin: 2em auto;
text-align: center;
max-height: 600px;
}
.header {
text-align: center;
}
.full-width {
width: 80%;
}
.button-row {
padding-top: 5px;
}
.button-row a {
margin-right: 8px;
text-align: center;
}
.forget-password {
padding-left: 0px;
}
.emailInput {
padding-top: 10px;
}
.contentBody {
padding: 60px 1rem;
background: #1b6ca8;
display: block;
}
.aLink {
float: right;
padding-right: 60px;
text-decoration: none;
}
|
在 AppModule 中匯入使用到的 Module#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// app.module.ts
// ...
import { ReactiveFormsModule } from '@angular/forms';
// ...
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
ShareMaterialModule,
HttpClientModule,
ReactiveFormsModule
],
// ...
|
在 AppRoutingModule 中加入 Route 的設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FlexboxComponent } from './components/flexbox/flexbox.component';
import { GetDataComponent } from './components/get-data/get-data.component';
import { LoginComponent } from './components/login/login.component';
const routes: Routes = [
{ path: 'flexbox', component: FlexboxComponent },
{ path: 'getdata', component: GetDataComponent },
{ path: 'login', component: LoginComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
|
直接在瀏覽器 URL 輸入: http://localhost:4200/login,會見到如下圖
雖然 Login Form 已可以順利的呈現,但可以看出來有一個問題:它嵌套在 Navigator 中,感覺是不是有點怪。接下來我們就來調整一下,讓 Login Form “獨立”出來,不要嵌套在 Navigator 中。
首先先調整一下 Route 的設定:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FlexboxComponent } from './components/flexbox/flexbox.component';
import { GetDataComponent } from './components/get-data/get-data.component';
import { LoginComponent } from './components/login/login.component';
import { NavigationComponent } from './navigation/navigation.component';
const routes: Routes = [
{
path: '',
component: NavigationComponent,
children: [
{ path: '', redirectTo: 'flexbox', pathMatch: 'full'},
{ path: 'flexbox', component: FlexboxComponent },
{ path: 'getdata', component: GetDataComponent },
]
},
{
path: 'login',
component: LoginComponent,
// canActivate: [NonAuthGuard]
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
再來修改 app.component.html
<router-outlet></router-outlet>
結果:
完工!!