Angular @input 與 @output

將父元件資料傳到子元件

在子元件引入@angular/core =>Input
@input變數的窗口

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
import { Component,Input } from '@angular/core';
import { CommonModule,Location } from '@angular/common';
import { RouterLink } from '@angular/router';

interface navListType{
title: string,
path: string,
}
@Component({
selector: 'app-navbar', //這是元件的名稱
standalone: true,
imports: [
RouterLink,CommonModule,
],
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss'
})
export class NavbarComponent {
@Input() navListsCurrent = '';
@Input() npageTitleCurrent = '';
@Input() isActiveCurrent = '';
//點擊頁面按鈕函式
actionList(path: string) {
this.npageTitleCurrent = path;
}
}

父元件
.ts

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
48
49
50
import { Component, OnInit } from '@angular/core';
import { RouterOutlet ,RouterLink} from '@angular/router';
import {MatButtonModule} from '@angular/material/button';
import { ButtonModule } from 'primeng/button';
import { ToDoComponent } from './components/to-do/to-do.component';
import { NavbarComponent } from './components/navbar/navbar.component';

interface navListType{
title: string,
path: string,
}
@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
MatButtonModule,
ButtonModule,
ToDoComponent,
NavbarComponent,
RouterLink,
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit{
normalValue='noValue'
constructorValue="noValue"
ngOnInitValue = "noValue"

navLists: navListType[] = [
{ title: 'Home' ,path:'index'},
{title:'FetchData',path:'fetch-data'}
]
//頁面名稱
pageTitle: string = 'Home';
isActive: boolean = false;
constructor() {
console.log('constructor啟動了')
this.constructorValue='從constructor取值'
this.normalValue='從constructor取值'
}
ngOnInit(): void {
console.log('ngOnInit啟動了')
this.ngOnInitValue='從ngOnInit取值'
this.normalValue='從ngOnInit取值'
}
title = 'tode_project';
}

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<main class="main">
<app-navbar
[navListsCurrent]="navLists"
[npageTitleCurrent]="pageTitle"
[isActiveCurrent]="pageTitle"
></app-navbar>

</main>
<div>
<div>normalValue的值:{{normalValue}}</div>
<div> constructor的值:{{constructorValue}}</div>
<div>ngOnInit的值:{{ngOnInitValue}}</div>
</div>
<router-outlet />

@input 父向子傳遞資料


@input 父向子傳遞資料 官網

Angular 中的 constructor與ngOnInit 有什麼差別

Angular中的元件(component)啟動的時候,分別會觸發兩個事件

  • constructor:是javascript中class產生出來時候會執行的建構式
  • ngOnInit:則是Angular眾多生命週期中的其中之一,會在元件(component)啟動的時候觸發,

元件啟動順序:constructor > ngOnInit

src/app/app.component.ts
引入 OnInit
constructor()與ngOnInit()console.log(出現順序)

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
import { Component, OnInit } from '@angular/core';
import { RouterOutlet ,RouterLink} from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
RouterLink
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})


export class AppComponent implements OnInit{
constructor() {
console.log('constructor啟動了')
}
ngOnInit(): void {
console.log('ngOnInit啟動了')
}
title = 'tode_project';
}

測試結果順序結果:
constructor啟動了
ngOnInit啟動了
元件啟動順序:constructor > ngOnInit

定義變數值,變數顯示在頁面上順序:

normalValue的值:從ngOnInit取值 >
constructor的值:從constructor取值 >
ngOnInit的值:從ngOnInit取值

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
import { Component, OnInit } from '@angular/core';
import { RouterOutlet ,RouterLink} from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
RouterLink
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})


export class AppComponent implements OnInit{
normalValue='noValue'
constructorValue="noValue"
ngOnInitValue="noValue"
constructor() {
console.log('constructor啟動了')
this.constructorValue='從constructor取值'
this.normalValue='從constructor取值'
}
ngOnInit(): void {
console.log('ngOnInit啟動了');
this.ngOnInitValue='從ngOnInit取值'
this.normalValue='從ngOnInit取值'
}
title = 'tode_project';
}

測試渲染結果順序結果:

normalValue的值:從ngOnInit取值
constructor的值:從constructor取值
ngOnInit的值:從ngOnInit取值
測試渲染結果 結果:
不論在constructor或在ngOnInit賦值都是沒有問題的。
如果在constructor和ngOnInit連續賦值的話,就會依照小結一的結論照順序賦值,最後得到ngOnInit給予的值。

@Input的裝飾器,可以讓你從外部輸入到資料到元間裡面

Angular Fetch CRUD

Html

綁定表單 [(ngModel)]與 使用

[ngClass]=”{‘disabled’:item.isEdit}”

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<ng-container class="container">
<div class="title">
<h1 >{{Title}}</h1>
<img [src]="imageLink" [alt]="imageAlt" [title]="imageAlt">
</div>
<div class="forms">
<div class="input-group">
<input type="text" class="form-control"
[(ngModel)]="userForm.name" placeholder="姓名"
/>
<input type="text" class="form-control" placeholder="電子郵件"
[(ngModel)]="userForm.email"
/>
<input type="password" class="form-control" placeholder="密碼"
[(ngModel)]="userForm.password"
/>
<input type="text" class="form-control" placeholder="城市"
[(ngModel)]="userForm.address"
/>
<input type="text" class="form-control" placeholder="鄉鎮"
[(ngModel)]="userForm.country"
/>
</div>
<button class="btn btn-primary" (click)="addItem()">Add</button>
</div>
<div class="container">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">項目</th>
<th scope="col">姓名</th>
<th scope="col">電子郵件</th>
<th scope="col">城市</th>
<th scope="col">區域</th>

</tr>
</thead>
<tbody>

<!-- @for (post of posts; track post.id) {-->
<tr *ngFor="let item of posts ; let index = index" >
<td>
<p > {{item.user_id}}</p>
</td>
<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.name"/>
<p *ngIf="!item.isEdit"> {{item.name}}</p>
</td>
<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.email"/>
<p *ngIf="!item.isEdit"> {{item.email}}</p>
</td>
<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.country"/>
<p *ngIf="!item.isEdit"> {{item.country}}</p>
</td>

<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.address"/>
<p *ngIf="!item.isEdit"> {{item.address}}</p>
</td>

<td>
<button class="btn btn-warning" (click)="editPost(item.user_id,index)" >Edit</button>
<button class="btn btn-danger ml-2" (click)="remotePost(item.user_id,index)" [ngClass]="{'disabled':item.isEdit}">Delete</button></td>
</tr>
<!-- }-->
</tbody>
</table>
</div>
</ng-container>

.ts

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { Component, inject, OnInit } from '@angular/core';
// Angular 17以後必須引入才能在Html 使用*ngFor
import {CommonModule} from '@angular/common';
import { FormsModule } from '@angular/forms'
interface listType{
id: string | never,
subject: string,
editTask: string,
isShow:boolean
}
interface userFormType{
user_id?: number |null | never |any,
name?: string|any|null ,
email?: string|any|null ,
password?: string|any|null ,
address?: string|any|null ,
country?: string|any|null ,
}
@Component({
selector: 'app-fetch-data',
standalone: true,
imports: [
FormsModule,
CommonModule,// Angular 17以後必須引入
],
templateUrl: './fetch-data.component.html',
styleUrl: './fetch-data.component.scss'
})
export class FetchDataComponent implements OnInit {
Title: string = 'To Do Lists';
imageAlt:string= '圖片';
imageLink: string = "https://cdn-icons-png.flaticon.com/512/4697/4697260.png";
userForm: userFormType = {
user_id: null,
name: "",
email: "",
password: "",
address: "",
country: "",
}

lists: listType[] = [];
newTask: string = "";//表單綁定
isAvalible: boolean = false;
posts: any = [];
isEdit: boolean = false;
url: string = 'https://api-node-mysql-project.vercel.app/users';
header:any= {
'Content-Type': 'application/json',
}
// 新增
addItem() {
const api="https://api-node-mysql-project.vercel.app/users"
const vm = this;
let query = {
name: vm.userForm.name,
email: vm.userForm.email,
password: vm.userForm.password,
address: vm.userForm.address,
country: vm.userForm.country
// id:Date.now().toString(),
}
fetch(api, { method: 'POST',headers:vm.header , body: JSON.stringify(query)})
.then((res) => res.json())
.then((data) => {
vm.isAvalible = true;
console.log('新增的參數', query, data)
vm.posts.push(query);
this.userForm.name = ""
this.userForm.address = ""
this.userForm.password = ""
this.userForm.country=""
});
vm.isAvalible = true;
}

//獲取
fetchGetValue() {
const vm = this;
const api="https://api-node-mysql-project.vercel.app/users"
fetch(api,{method: 'GET' })
.then((res) => res.json())
.then((data) => {
data.map((item: any, index: any) => {
let query = {
user_id: item.user_id,
name: item.name,
email: item.email,
password: item.password,
address: item.address,
country: item.country,
isEdit: false,
}
return vm.posts.push(query)
});

console.log( 'vm.posts', vm.posts )
});
}

// 編輯
editPost(user_id: any,index:any) {
const vm = this;
vm.posts[index].isEdit = true;
const api = `https://api-node-mysql-project.vercel.app/users/${user_id}`
let query = {
name: vm.posts[index].name,
email: vm.posts[index].email,
password: vm.posts[index].password,
address: vm.posts[index].address,
country: vm.posts[index].country,
// id:Date.now().toString(),
}
fetch(api, {
method: 'PUT',
headers: vm.header,
body: JSON.stringify(query)
})
.then((res) => res.json())
.then((data) => {
console.log('changedRows', data.result.changedRows,'屬性',typeof data.result.changedRows);
if (data.result.changedRows===1) {
alert('編輯成功')
vm.posts[index].isEdit = false;
}
});
}
// 刪除
remotePost(user_id: any,index:any) {
console.log(user_id);
const api = `https://api-node-mysql-project.vercel.app/users/${user_id}`
fetch(api, {method: 'DELETE',headers: this.header})
.then((res) => res.json())
.then((data) => {
console.log(data);
if (data.result.insertId === 0) {
this.posts.splice(index, 1);
alert(user_id+'刪除成功')
}
});
}
ngOnInit(): void {
this.fetchGetValue()
}
}

fetch Get

1
2
3
4
5
6
7
8
9
10
fetch(api)
.then((res) => {
return res.json();
})
.then( (res) => {
console.log(res);
})
.catch((error) => {
console.log(`Error: ${error}`);
})

fetch Post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 header:any= {
'Content-Type': 'application/json',
}
fetch(api, {
method: 'POST',
headers:{'Content-Type': 'application/json'} ,
body: JSON.stringify(query)})
.then((res) => res.json())
.then((data) => {
let query={
title:'這是新增'
}
this.posts.push(query);
});

Angular CRUD

Angular 雙向綁定 [(ngModel)]

綁定表單 [(ngModel)]

[(ngModel)]必須引入FormsModule

[ngClass]=”{‘disabled’:item.isEdit}”

[ngClass]必須引入NgClass
Html
1
2
3
4
<input *ngIf="item.isEdit" [(ngModel)]="item.name"/>
<p *ngIf="!item.isEdit"> {{item.name}}</p>

<button class="btn btn-danger ml-2" (click)="remotePost(item.user_id,index)" [ngClass]="{'disabled':item.isEdit}">Delete</button>

Ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component, OnInit } from '@angular/core';
+ import { NgIf } from '@angular/common';
+ import { FormsModule } from '@angular/forms';

@Component({
selector: 'app-login',
standalone:true,
+ imports: [ FormsModule ,NgIf],
templateUrl: './login.component.html',
styleUrl: './login.component.scss'
})

export class LoginComponent implements OnInit {

ngOnInit(): void {}
}

Angular CRUD

Angular Error

錯誤訊息:[ERROR] NG8001: ‘app-to-do’ is not a known element

在app資料夾內app.component.html 新增

1
2
3
4
<main class="main">
<app-to-do></app-to-do>
</main>
<router-outlet />
錯誤訊息:[ERROR] NG8001: 'app-to-do' is not a known element
處理方式:imports引入 ToDoComponent
在app資料夾內引入app.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+ import { ToDoComponent } from './components/to-do/to-do.component';

@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
+ ToDoComponent
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'tode_project';
}

Error: getaddrinfo ENOTFOUND localhost

處理方式

Mac 系統前往

1
/private/etc/
將hosts檔案移動到桌面修改
1
27.0.0.1 localhost

錯誤訊息 Can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’

無法綁定到“ngModel”,因為它不是“input”的已知屬性

處理方式
引入 FormsModule
1
2
3
4
5
6
7
8
9
10
11
import { FormsModule } from '@angular/forms';

[...]

@NgModule({
imports: [
[...]
FormsModule
],
[...]
})

錯誤訊息 The pipe ‘async’ could not be found

處理方式
引入 CommonModule
1
import { CommonModule } from '@angular/common';

錯誤訊息 打包時 [WARNING] Module ‘dayjs’ used by ‘src/app/home/home.component.ts’ is not ESM

處理方式
angular.json 檔案內 allowedCommonJsDependencies(允許 CommonJs 依賴項):["dayjs"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 {
"projects":{
"vite_project":{
"architect":{
"build":{
"options":{
+ "allowedCommonJsDependencies": [
+ "dayjs"
+ ],
}
}
}
}
}
}

錯誤訊息 Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.

Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
More info and automated migrator: https://sass-lang.com/d/import
The plugin “angular-sass” was triggered by this import

處理方式

src/style.scss引入方式@import修改為@use

1
@use 'bootstrap/scss/bootstrap';

錯誤訊息 打包時 Did not expect successive traversals.

▲ [WARNING] 9 rules skipped due to selector errors:
.table>>> -> Did not expect successive traversals.
.table-sm>>> -> Did not expect successive traversals.
.table-bordered>>* -> Did not expect successive traversals.
.table-bordered>>> -> Did not expect successive traversals.
.table-borderless>>> -> Did not expect successive traversals.
.form-floating>label -> Did not expect successive traversals.
.form-floating>
label -> Did not expect successive traversals.
.btn-group>+.btn -> Did not expect successive traversals.
.btn-group>+.btn -> Did not expect successive traversals.

處理方式
angular.json 檔案內
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"projects":{
"vite_project":{
"architect":{
"build":{
+ "options":{
+ "optimization": {
+ "scripts": true,
+ "styles": {
+ "minify": true,
+ "inlineCritical": false
+ },
+ "fonts": true
+ }
}
}
}
}
}
}

錯誤訊息 打包時 bundle exceeded maximun. Budget 500kb was not met by ….

捆綁包超出最大值。預算 500 kb 未達
如圖

處理方式
angular.json 檔案內 budgets捆綁initial最初 與 anyComponentStyle任何組件樣式 =>maximumWarning 最大警告 =>maximumError最大誤差
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
{
"projects":{
"vite_project":{
"architect":{
"build":{
"configurations": {
"production": {
"budgets":[
{
"type": "initial",
+ "maximumWarning": "50MB",
+ "maximumError": "10MB"
},
{
"type": "anyComponentStyle",
+ "maximumWarning": "50MB",
+ "maximumError": "10MB"
}
]
}
}
}
}
}
}
}

錯誤訊息:ERROR NullInjectorError: R3InjectorError(Environment Injector)[_ProductsService -> _ApiService -> _HttpClient -> _HttpClient]:

處理方式如下
src/app/app.config.ts 引入 provideHttpClient
1
2
3
4
5
6
7
8
9
10
11
12
13
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
+ import {provideHttpClient} from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
+ provideHttpClient(),
provideRouter(routes)
]
};

✘ [ERROR] The ‘index/product/:id’ route uses

✘ [ERROR] The ‘index/product/:id’ route uses prerendering and includes parameters, but ‘getPrerenderParams’ is missing. Please define ‘getPrerenderParams’ function for this route in your server routing configuration or specify a different ‘renderMode’.

處理方式
1
2
3
4
5
6
7
8
9
import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
{
path: '**',
+ renderMode: RenderMode.Server
}
];

Angular Router路由的使用

設定路由

一前置作業新增頁面

新增Home與fetchData 頁面
新增Home頁面
1
2
3
// 新增元件指令
ng g c 頁面或是功能
ng g c Home
產生了home 資料夾內有以下檔案
1
2
3
4
home.component.html
home.component.scss
home.component.spec.ts
home.component.ts
新增user 頁面
1
ng g c User
產生了ng g c User 資料夾內有以下檔案
1
2
3
4
user.component.html
user.component.scss
user.component.spec.ts
user.component.ts
新增PageNotFound 頁面
1
ng g c PageNotFound
產生了page-not-found 資料夾內有以下檔案
1
2
3
4
page-not-found.component.html
page-not-found.component.scss
page-not-found.component.spec.ts
page-not-found.component.ts
二 在 app.routes.ts檔案設定
  • 引入Home,Users,User,PageNotFound元件,解構寫法必須加名稱Component 大駝峰式寫法
  • export const routes: Routes = [path: '路徑', component: 頁面元件]
  • 首頁 { path: "", redirectTo:'home',pathMatch:'full' },
  • 404頁 { path: '**', component: PageNotFoundComponent }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Routes } from '@angular/router';
//引入,解構寫法必須加名稱Component 大駝峰式寫法
import { HomeComponent } from './home/home.component';
import { UsersComponent } from './users/users.component';
import { UserComponent } from './user/user.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

export const routes: Routes = [
{ path: "", redirectTo:'home',pathMatch:'full' },
{ path: "home", component:HomeComponent },
{ path: "users", component: UsersComponent},
{ path: "user/:userId", component: UserComponent},
{ path: '**', component: PageNotFoundComponent }, // 404頁面
];

配置應用程式 在app.config.ts檔案中新增以下設定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

+ import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
provideClientHydration(),
provideAnimationsAsync()
]
};

this.router.navigate([‘users’]); 切換至頁面

  • 引入Router
  • constructor( private router: Router, ) { ... }
  • this.router.navigate([path網址])
1
2
3
4
5
6
7
8
9
10
11
12
import {  Router } from '@angular/router';

constructor(
private router: Router,
) {
...
}

viewProduct( item: any): void {
console.log(item)
this.router.navigate(["/index/product/"+item._id]);
}
### 元件的引入(Navba主選單的切換) 到components新增
1
2
// ng g c 頁面或是功能
ng g c Navbar
產生了navbar 資料夾內有以下檔案
1
2
3
4
navbar.component.html
navbar.component.scss
navbar.component.spec.ts
navbar.component.ts
利用 routerLink 指令實作頁面切換 navbar.component.html檔案內新增
  • routerLink是routes內的path
  • routerActive="active"
1
2
3
4
5
6
7
8
9
10
11
<nav class="nav">
<ul>
<li>
<a routerLink="/" routerActive="active" class="cursor-pointer">Home</a>
</li>
<li>
<a routerLink="fetch-data" routerActive="active" class="cursor-pointer">FetchData</a>
</li>
</ul>
</nav>

navbar.component.ts
引入RouterLink
import { RouterLink} from '@angular/router';
@Component({
imports: [
RouterLink
],
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Component } from '@angular/core';
+ import { RouterLink } from '@angular/router';
@Component({
selector: 'app-navbar',
standalone: true,
imports: [
+ RouterLink
],
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss'
})
export class NavbarComponent {

}

在首頁載入

在 app.component.ts
引入NavbarComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+ import { NavbarComponent } from './components/navbar/navbar.component';
import { environment } from '../environments/environment';

@Component({
selector: 'app-root',
imports: [
RouterOutlet,
+ NavbarComponent,
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = environment.production ? 'Production' : '開發中';
}

嵌套路由

1
<router-outlet></router-outlet>

在 app.component.html 會使用 , 元件來定義頁面所需要顯示的位置

1
2
3
4
5
<app-navbar></app-navbar>
<main class="main">
<router-outlet />
</main>

router 案例github

404 頁面

如果要顯示自訂 404 頁面,請設定通配符路由,並將元件屬性設定為PageNotFoundComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Routes } from '@angular/router';
//引入,解構寫法必須加名稱Component 大駝峰式寫法
import { HomeComponent } from './home/home.component';
import { UsersComponent } from './users/users.component';
import { UserComponent } from './user/user.component';
+ import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

export const routes: Routes = [
{ path: "", component: HomeComponent },
{ path: "users", component: UsersComponent},
{ path: "user/:userId", component: UserComponent},
+ { path: '**', component: PageNotFoundComponent }, // 404頁面
];

router 初次設定

動態 Router

以UserComponent(user資料夾user.component.ts) 為例
  • 載入@angular/core 的inject 載入@angular/router 的 ActivatedRoute,
  • private activeRouter = inject(ActivatedRoute);
  • this.activeRouter.snapshot.paramMap.get('userId')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+  import { Component, OnInit, inject } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { ActivatedRoute } from '@angular/router';

@Component({
selector: 'app-user',
imports: [
+ CommonModule,// Angular 17以後必須引入
],
templateUrl: './user.component.html',
styleUrl: './user.component.scss'
})
+ export class UserComponent implements OnInit{
+ private activeRouter = inject(ActivatedRoute);
+ ngOnInit(): void {
+ this.getDetailID();
+ }

+ getDetailID(): void {
+ const userId = this.activeRouter.snapshot.paramMap.get('userId');
+ console.log('userId', userId);
+ }
}

模組化且延遲載入,提升更好的使用者體驗

Angular 19 App with Lazy Loading
隨著專案的擴大,路由設定會越來越大,元件也會越來越多

  • loadComponent:() => import('檔案位置').then((m)=> m.HomeComponent)
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

import { Routes } from '@angular/router';

import { UserComponent } from './user/user.component';

export const routes: Routes = [
{ path: "", redirectTo: 'home', pathMatch: 'full' },
{
path: "home",
loadComponent:() => import('./home/home.component').then((m)=> m.HomeComponent)
},
{
path: "users",
loadComponent:() =>import('./users/users.component').then((m)=>m.UsersComponent)
},
{
path: "user/:userId",
component: UserComponent
},
{
path: "login",
loadComponent:() =>import('./login/login.component').then((m)=>m.LoginComponent)
},
{
path: "test_login",
loadComponent:()=>import('./test-login/test-login.component').then((m)=>m.TestLoginComponent)
},
{
path: "admin",
loadComponent:() =>import('./admin/index-admin/index-admin.component').then((m)=>m.IndexAdminComponent) ,
children: [
{
path: "admin_home",
loadComponent:()=>import('./admin/look/look.component').then((m)=>m.LookComponent)
},
{ path: '', redirectTo: 'admin_home', pathMatch: 'full' },
]
},
{
// 404頁面
path: '**',
loadComponent: () => import('./page-not-found/page-not-found.component').then((m) => m.PageNotFoundComponent)
},
];

Angular 列表渲染與判斷

渲染列表

*ngFor渲染列表 let item of todoDataList;let index = index,index 為索引
1
2
3
4
5
6
7
8
<ul *ngFor="let item of todoDataList;let index = index">
<li >
{{index +1}}
</li>
<li>
{{item.title}}
</li>
</ul>

判斷是否顯示與渲染列表

to-do.component.html

@if(isAvalible){}@else{}:判斷是否顯示
@for(i of tasks; track $index){}渲染列表
按鈕上(click)="addTasks()" 函式的綁定
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
<div class="lists">
@if(isAvalible){
<table class="table table-striped">
<thead>
<tr>
<th scope="col">項目</th>
<th scope="col">事項</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
@for (item of lists; track $index) {
<tr>
<td>
{{item.id}}
</td>
<td>
@if(item.isShow){
<input type="text" [(ngModel)]="item.editTask"/>
}@else{
{{item.subject}}
}
</td>
<td>
<button class="btn btn-warning" (click)="editTasks($index,item.editTask)">Edit</button>
<button class="btn btn-danger ml-2" (click)=" deleteTasks($index)">Delete</button></td>
</tr>
}

</tbody>
</table>
}
</div>

@if{}@else{}

在to-do.component.ts引入FormsModule
  • 引入FormsModuleimport { FormsModule } from '@angular/forms'
    因為ngModel是隸屬於FormsModule模組下的套件,所以要import FormsModule進來
  • importsFormsModule
  • export class ToDoComponent 內的資料定義與函式
    lists: listType[] = [];
    newTask: string = "";//表單綁定
    isAvalible : boolean = false;
    addTasks()函式綁定在Button
    addTasks()函式的邏輯判斷
事件繫結
input事件:輸入框偵測到任何輸入都會觸發 同時要帶入$event 事件參數,$event 是詳細描述此次事件之各種數值的物件 有幾種Evnet方式可以玩看看
  • (input): 偵測任何輸入時觸發
  • (keyup): 離開鍵盤按鍵時觸發
  • (blur): 焦點(當前鼠標或鍵盤聚焦之處)離開輸入框時觸發
  • (change): 焦點離開輸入框、並且值被改變時才觸發
參考資料
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
48
49
import { Component } from '@angular/core';
+ import { FormsModule } from '@angular/forms'
@Component({
selector: 'app-to-do',
standalone: true,
imports: [
+ FormsModule
],
templateUrl: './to-do.component.html',
styleUrl: './to-do.component.scss'
})

export class ToDoComponent {
Title: string = 'To Do Lists';
imageLink: string = "https://cdn-icons-png.flaticon.com/512/4697/4697260.png";
+ tasks: string[] = [];
+ newTask: string = "";//表單綁定
isAvalible : boolean = false;

//新增
+ addTasks() {
const vm = this;
//新增的參數
let query = {
id:Date.now().toString(),
subject: vm.newTask.trim(),
editTask: vm.newTask.trim(),
isShow:false,
}
console.log('新增的參數',query)
vm.lists.push(query);
vm.isAvalible = true;
}
//刪除
deleteTasks(index: number) {
const vm = this;
vm.lists.splice(index,1);
vm.isAvalible = vm.lists.length > 0;
}
//編輯
editTasks(index: number, editTask: string): string | void {
const vm = this;
vm.lists[index].isShow = true;
console.log(editTask)
vm.lists[index].subject = editTask;
console.log(vm.lists);
vm.newTask ="";
}
}

安裝primeng

1
npm install --save primeng 

全局引入 MatButtonModule與ButtonModule

在app.component.ts
  • 引入MatButtonModule
  • 引入ButtonModule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+ import {MatButtonModule} from '@angular/material/button';
+ import { ButtonModule } from 'primeng/button';
import { ToDoComponent } from './components/to-do/to-do.component';

@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
+ MatButtonModule,
+ ButtonModule,
ToDoComponent,
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'tode_project';
}

啟動
1
ng serve
點擊函式觀看 表單與按鈕綁定函式的執行 更正函式

渲染列表 *ngFor

  • 必須引入import { FormsModule } from '@angular/forms'
  • imports: [ CommonModule,// Angular 17以後必須引入 ],
  • *ngFor="let item of posts ; let index = index"
.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
//必須引入
import { FormsModule } from '@angular/forms'
@Component({
selector: 'app-fetch-data',
standalone: true,
imports: [
FormsModule,
CommonModule,// Angular 17以後必須引入
],
templateUrl: './fetch-data.component.html',
styleUrl: './fetch-data.component.scss'
})

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
<ng-container class="container">
<div class="container">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">項目</th>
<th scope="col">姓名</th>
<th scope="col">電子郵件</th>
<th scope="col">城市</th>
<th scope="col">區域</th>

</tr>
</thead>
<tbody>

<!-- @for (post of posts; track post.id) {-->
<tr *ngFor="let item of posts ; let index = index" >
<td>
<p > {{item.user_id}}</p>
</td>
<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.name"/>
<p *ngIf="!item.isEdit"> {{item.name}}</p>
</td>
<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.email"/>
<p *ngIf="!item.isEdit"> {{item.email}}</p>
</td>
<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.country"/>
<p *ngIf="!item.isEdit"> {{item.country}}</p>
</td>

<td>
<input *ngIf="item.isEdit" [(ngModel)]="item.address"/>
<p *ngIf="!item.isEdit"> {{item.address}}</p>
</td>

<td>
<button class="btn btn-warning" (click)="editPost(item.user_id,index)" >Edit</button>
<button class="btn btn-danger ml-2" (click)="remotePost(item.user_id,index)" [ngClass]="{'disabled':item.isEdit}">Delete</button></td>
</tr>
<!-- }-->
</tbody>
</table>
</div>
</ng-container>

判斷是否顯示 *ngIf

#dataEmpty

雙向綁定表單 [(ngModel)]

[ngClass]=”{‘disabled’:item.isEdit}”

1
2
3
4
<input *ngIf="item.isEdit" [(ngModel)]="item.name"/>
<p *ngIf="!item.isEdit"> {{item.name}}</p>

<button class="btn btn-danger ml-2" (click)="remotePost(item.user_id,index)" [ngClass]="{'disabled':item.isEdit}">Delete</button>

Angular CRUD

Angular 表單與按鈕的綁定

表單與按鈕的綁定

to-do.component.html

表單上[(ngModel)]="newTask
按鈕上(click)="addTasks()" 函式的綁定
1
2
3
4
5
6
7
8
<div class="forms">
<div class="input-group">
<input type="text" class="form-control"
[(ngModel)]="newTask"
/>
<button class="btn btn-primary" (click)="addTasks()">Add</button>
</div>
</div>
在to-do.component.ts 如果使用表單綁定 [(ngModel)] 要引入FormsModule
  • 引入FormsModuleimport { FormsModule } from '@angular/forms'
  • importsFormsModule
  • export class ToDoComponent 內的資料定義與函式
    tasks : string[] =[];
    newTask: string = "";//表單綁定
    isAvalible : boolean = false;
    addTasks()函式綁定在Button
    addTasks()函式的邏輯判斷
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
import { Component } from '@angular/core';
+ import { FormsModule } from '@angular/forms'
@Component({
selector: 'app-to-do',
standalone: true,
imports: [
+ FormsModule
],
templateUrl: './to-do.component.html',
styleUrl: './to-do.component.scss'
})

export class ToDoComponent {
Title: string = 'To Do Lists';
imageLink: string = "https://cdn-icons-png.flaticon.com/512/4697/4697260.png";
+ tasks: string[] = [];
+ newTask: string = "";//表單綁定
isAvalible : boolean = false;
+ addTasks() {
+ const vm = this;
//如果表單左右有空格就清除,將表單數據塞入陣列中,並且將 表單清空
if (vm.newTask.trim() != null) {
vm.tasks.push(vm.newTask)
vm.newTask="";
vm.isAvalible = true;
}
console.log(vm.tasks)
}
}

安裝primeng

PrimeNG 是一套豐富的 Angular 開源 UI 元件。造訪PrimeNG 網站以取得互動式簡報、綜合文件和其他資源。

1
npm install --save primeng 

全局引入 MatButtonModule與ButtonModule

在app.component.ts
  • 引入MatButtonModule
  • 引入ButtonModule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+ import {MatButtonModule} from '@angular/material/button';
+ import { ButtonModule } from 'primeng/button';
import { ToDoComponent } from './components/to-do/to-do.component';

@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
+ MatButtonModule,
+ ButtonModule,
ToDoComponent,
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'tode_project';
}

啟動
1
ng serve
點擊函式觀看 表單與按鈕綁定函式的執行 更正函式

Angular 資料與圖片的渲染

app/components/to-do/to-do.component.ts
export class ToDoComponent 內新增

Title: string = 'To Do Lists';
imageLink:string= "https://cdn-icons-png.flaticon.com/512/4697/4697260.png";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Component } from '@angular/core';


@Component({
selector: 'app-to-do',
standalone: true,
imports: [],
templateUrl: './to-do.component.html',
styleUrl: './to-do.component.scss'
})
export class ToDoComponent {
Title: string = 'To Do Lists';
imageLink:string= "https://cdn-icons-png.flaticon.com/512/4697/4697260.png";
}
to-do.component.html
資料使用花刮號渲染
圖面使用[src]="定義的圖片位址"
1
2
3
4
5
<div class="title">
<h1 >{{Title}}</h1>
<img [src]="imageLink" alt="">
</div>