Angular 利用HttpClient取回後端資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {HttpClient, HttpHeaders } from '@angular/common/http';

@Component({
selector: 'app-test-list',
imports: [NgFor],
templateUrl: './test-list.component.html',
styleUrl: './test-list.component.scss'
})

export class TestListComponent implements OnInit {
getLists() {
const apiUrl ="https://xxxx"
this.http.get<addListsType[]>(`${apiUrl}/api/toDoLists`, this.httpOptions).pipe(catchError(this.handleError))
.subscribe((lists )=> {
consle.log(lists)
this.todoDataList = lists;
})
}
}

上範例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
import {HttpClient, HttpHeaders } from '@angular/common/http';

@Component({
selector: 'app-test-list',
imports: [NgFor],
templateUrl: './test-list.component.html',
styleUrl: './test-list.component.scss'
})

export class TestListComponent implements OnInit {
constructor( private http: HttpClient ) { }
private readonly httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
private handleError(error: any) {
console.error('API 錯誤:', error);
return throwError(() => new Error(error.message || '錯誤已消失'));
}
todoDataList: addListsType[] = [];
tableHeader: string[] = ['項目','名稱','建立日期','更新日期','操作'];
getLists() {
const apiUrl ="https://xxxx"
this.http.get<addListsType[]>(`${apiUrl}/api/toDoLists`, this.httpOptions).pipe(catchError(this.handleError))
.subscribe((lists )=> {
let arrLists:any[] = [];
lists.map( (item: any,index:number) => {
let query: any = {
_id: item._id,
title: item.title,
Editing: false, // 編輯
Status: false, //選取狀態
CanEdit: true, //可以編輯
buildDate: item.buildDate,
updataDate: item.buildDate,
}
// 新增的欄位push 到陣列
arrLists.push(query);

//排序
arrLists.sort((a, b) => {
return b.buildDate - a.buildDate
})
})
//渲染的陣列等於新增後後的陣列
this.todoDataList = arrLists;
})
}
}

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<table class="table table-striped">
<thead >
<tr >
<th *ngFor="let item of tableHeader| slice: 0 : 5 ;let index = index " scope="col">{{item}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of todoDataList;let index = index">
<td>{{index +1}}</td>
<td>{{item.title | uppercase }} </td>
<td>{{item.buildDate | date:'yyyy-MM-dd HH:mm' }} </td>
<td>{{item.updataDate | TaiwanYear }} </td>

<td>
<button type="button" class="btn btn-secondary">Cancel</button>
<button type="button" class="btn btn-success ml-2">Submit</button></td>
</tr>
</tbody>
</table>


參考

Angular HttpClient 封裝

HttpClient 是 Angular 對 XMLHttpRequest 和 Fetch 的封裝
理解 HttpClient

provideHttpClient 源碼在 provider.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
export function provideHttpClient(
...features: HttpFeature<HttpFeatureKind>[]
): EnvironmentProviders {
if (ngDevMode) {
const featureKinds = new Set(features.map((f) => f.ɵkind));
if (
featureKinds.has(HttpFeatureKind.NoXsrfProtection) &&
featureKinds.has(HttpFeatureKind.CustomXsrfConfiguration)
) {
throw new Error(
ngDevMode
? `Configuration error: found both withXsrfConfiguration() and withNoXsrfProtection() in the same call to provideHttpClient(), which is a contradiction.`
: '',
);
}
}

const providers: Provider[] = [
// 1. HttpClient 是最常用的Service Providers
HttpClient,
HttpXhrBackend,
HttpInterceptorHandler,
{provide: HttpHandler, useExisting: HttpInterceptorHandler},
// 2. default Angular 內部使用XMLHttpRequest發送請求
{provide: HttpBackend, useExisting: HttpXhrBackend},
{
provide: HTTP_INTERCEPTOR_FNS,
useValue: xsrfInterceptorFn,
multi: true,
},
{provide: XSRF_ENABLED, useValue: true},
{provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor},
];

for (const feature of features) {
providers.push(...feature.ɵproviders);
}
// 製作環境提供者
return makeEnvironmentProviders(providers);
}
  • HttpClient 是最常用的Service Providers
  • Angular 默認內部使用XMLHttpRequest發送請求

全局載入

  1. 添加 HttpClient 相關的 providers
    app.config.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
    import { provideRouter } from '@angular/router';
    + import {provideHttpClient} from '@angular/common/http';
    import { routes } from './app.routes';
    import { provideClientHydration, withEventReplay } from '@angular/platform-browser';

    export const appConfig: ApplicationConfig = {
    providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    // 添加 HttpClient 相關的 providers
    + provideHttpClient(),
    provideRouter(routes),
    provideClientHydration(withEventReplay())
    ]
    };
    Fetch 不支持上傳進度,這個是 Fetch 目前最大的缺陷,這也是為什么 Angular 任然以 XMLHttpRequest 作为默認。

src/app 新增 api 資料夾 => 新增 web-api.service.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
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class WebApiService {
private readonly http = inject(HttpClient);

private readonly httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

get(url: string): Observable<any> {
return this.http.get(url, this.httpOptions)
.pipe(catchError(this.handleError));
}

post(url: string, model: any): Observable<any> {
return this.http.post(url, model, this.httpOptions)
.pipe(catchError(this.handleError));
}

put(url: string, model: any): Observable<any> {
return this.http.put(url, model, this.httpOptions)
.pipe(catchError(this.handleError));
}

delete(url: string): Observable<any> {
return this.http.delete(url, this.httpOptions)
.pipe(catchError(this.handleError));
}

private handleError(error: any) {
console.error('API 錯誤:', error);
return throwError(() => new Error(error.message || '錯誤已消失'));
}
}

src/app 新增 api 資料夾=> 新增http-provider.service.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
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { WebApiService } from './web-api.service';
import { environment } from '../../environments/environment';
import dayjs from 'dayjs'
@Injectable({ providedIn: 'root' })
export class HttpProviderService {
private readonly apiUrl = environment.apiUrl;
private readonly webApiService = inject(WebApiService);

getAllLists(): Observable<any> {
return this.webApiService.get(`${this.apiUrl}/api/toDoLists`);
}
// 編輯
deleteTodoById(_id: string): Observable<any> {
return this.webApiService.delete(`${this.apiUrl}/api/toDo/${encodeURIComponent(_id)}`);
}

getTodoById(id: string): Observable<any> {
return this.webApiService.get(`${this.apiUrl}/api/toDo/${encodeURIComponent(id)}`);
}
// 新增
addList(newTask: string): Observable<any> {
let query:any = {
title: newTask,
buildDate: Date.now(),
updataDate: Date.now(),
}
return this.webApiService.post(`${this.apiUrl}/api/toDo`,query);
}

// 編輯
updateTodo(item: any): Observable<any> {
console.log('updateTodo', item)
let query:any = {
title: item.title,
buildDate: dayjs(item.buildDate).valueOf(),//改為時間搓
updataDate: Date.now(),
}
return this.webApiService.put(`${this.apiUrl}/api/toDo/${encodeURIComponent(item._id)}`, query);
}
}

參考資料

Vite Angular

Vue 安裝
Node v20.17.0
Angular CLI 19.2.5.

1
npm create vite@latest

到專案內

1
cd 專案

Port 4200 is already in use.
Would you like to use a different port? (Y/n)
連接埠 4200 已被使用。
您想使用其他連接埠嗎? (是/否)
Would you like to use a different port? Yes
Component HMR has been enabled.
If you encounter application reload issues, you can manually reload the page to bypass HMR and/or disable this feature with the --no-hmr command line option.
Please consider reporting any issues you encounter here: https://github.com/angular/angular-cli/issues

Browser bundles
Initial chunk files | Names | Raw size
polyfills.js | polyfills | 90.20 kB |
main.js | main | 23.25 kB |
styles.css | styles | 96 bytes |

                 | Initial total    | 113.55 kB

Server bundles
Initial chunk files | Names | Raw size
polyfills.server.mjs | polyfills.server | 570.97 kB |
chunk-VIHJC4M4.mjs | - | 23.69 kB |
server.mjs | server | 1.39 kB |
main.server.mjs | main.server | 800 bytes |

Application bundle generation complete. [2.570 seconds]

Watch mode enabled. Watching for file changes…
NOTE: Raw file sizes do not reflect development server per-request transformations.

參考資料

Angular 19安裝

Angular 分頁套件

Angular 分頁套件

ngx-pagination

1
npm install ngx-pagination --save

ChangeDetectionStrategy 變更偵測策略

Typescript
元件內的變更偵測策略,打造高效能元件
使用 OnPush

https://ithelp.ithome.com.tw/articles/10209130
ChangeDetectionStrategy 變更偵測策略
  • 引入ngx-pagination
  • 裝飾器引入ChangeDetectionStrategy.OnPush
  • 父對子傳值 數據接收
  • 定義分頁:每頁幾筆,當前頁面,全部筆數,選擇變換每頁顯示筆數
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
import {ChangeDetectionStrategy,Component,Input,OnInit,Output,EventEmitter,} from '@angular/core';
import { addListsType } from '../../Types/toDo'
import { NgFor, NgIf, NgClass, CommonModule } from '@angular/common';
+ import {NgxPaginationModule} from 'ngx-pagination';
import { FormsModule } from '@angular/forms';

@Component({
selector: 'app-to-do-list',
imports: [NgxPaginationModule,NgFor,NgIf,NgClass,FormsModule,CommonModule ],
templateUrl: './to-do-list.component.html',
styleUrl: './to-do-list.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush
})

+ export class BasicExampleComponent implements OnInit {
+ @Input() sendTodoList?:addListsType[] |any ;
@Input() sendItemId?: string;
@Input() sendErrorItemMessage?: string;
@Input() sendEditItem?: Observable<addListsType[]>;

@Output() newItem = new EventEmitter<addListsType>();
@Output() newId = new EventEmitter<string>();
@Output() newEvent = new EventEmitter<any>();
@Output() newUpdateItem = new EventEmitter<addListsType>();

openEditItem(item:any) {
this.newItem.emit(item);
}
editItem(item: any) {
this.newUpdateItem.emit(item);
}
openDeleteAlert(id:string) {
this.newId.emit(id)
}
deleteItem(id:string) {
this.newId.emit(id)
}
blurEvent(event:any) {
this.newEvent.emit(event)
}
// 每頁幾筆
+ currentPage: number | undefined = 1;
// 當前頁面
+ itemsPerPage: number = 10;
// 全部筆數
+ totalItems: number | any

+ pagePerOptions = [
+ { value: 5, label: '5筆' },
+ { value: 10, label: '10筆' },
+ { value: 15, label: '15筆' },
+ { value: 20, label: '20筆' },
+ { value: 30, label: '30筆' },
+ { value: 40, label: '40筆' },
+ { value: 50, label: '50筆' },
+ ]
// 選擇變換每頁顯示筆數
+ changeItemsPerPage(item: any) {
+ this.itemsPerPage = item.value;
+ }
// 設備變換時改變每頁顯示
deviceWidth:number =screen.width;
deviceDisplay() {
if (typeof Screen !== undefined) {
switch (this.deviceWidth) {
case 1920:
this.itemsPerPage=20;
break;
case 1560:
this.itemsPerPage=15;
break;
case 1440:
this.itemsPerPage=10;
break;
}
}
}
ngOnInit(): void {
this.deviceDisplay()
}
}

html

  • *ngFor="let item of sendTodoList | paginate: { itemsPerPage: itemsPerPage, currentPage: currentPage} "
  • (pageChange)="currentPage = $event"
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
<div class="lists">
<ul *ngIf="sendTodoList">
<li
*ngFor="let item of sendTodoList |
paginate: { itemsPerPage: itemsPerPage, currentPage: currentPage} ">
<div class="subject">
<label [for]="item._id">
<span class="edit_area"
[ngClass]="sendItemId === item._id ? 'noEditing' : ''"
>
{{item.title}}
</span>
<div class="edit_input_area"
[ngClass]="sendItemId === item._id? 'editing' : ''"
>
<input
type="text"
(blur)="blurEvent($event)"
[(ngModel)]="item.title"
[value]="item.title"
>
<div class="errorMessage">
{{sendErrorItemMessage}}</div>
</div>
</label>
</div>
<div class="buildDate">
{{item.buildDate}}
</div>
<div class="updataDate">
{{item.updataDate}}
</div>
<div class="btns_group">
<button
(click)="openEditItem(item)"
[ngClass]="sendItemId === item._id ? 'noEditing' : ''"
class="btns edit" >
<span>編輯</span><i class="fa-solid fa-pencil"></i>
</button>
<button (click)="editItem(item)"
[ngClass]="sendItemId === item._id? 'editing' : ''"
class="btns add"><span>儲存</span><i class="fa-solid fa-plus"></i></button>
<button
(click)="openDeleteAlert(item._id)"
class="btns delete"><span>刪除</span> <i class="fa-solid fa-xmark"></i></button>
</div>
</li>
</ul>
</div>
<!--分頁細節-->
<div class="pagination_area">
<pagination-controls class="my-pagination" (pageChange)="currentPage = $event"></pagination-controls>
<div class="pagination_detail">
<div class="total_length">
共{{sendTodoList?.length}}筆,</div>
<div class="currentPage"> 當前第{{currentPage}}頁,</div>
<div class="itemsPerPage">每頁{{itemsPerPage}}筆</div>
</div>
<div class="btn-group">
<a class="btn" data-toggle="dropdown" aria-expanded="false">
筆數
</a>
<div class="dropdown-menu">
<a class="dropdown-item"
*ngFor="let item of pagePerOptions"
(click)="changeItemsPerPage(item)"
>{{item.label}}
</a>
</div>
</div>
</div>
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
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
.btns{
display: flex;
height: 30px;
width:70px;
justify-content: center;
align-items: center;
}
.add{
background-color: green;
border: 1px solid green;
color:white;
&:hover,&:focus{
background-color: #0f760f;
border: 1px solid #0f760f;
}
}
.edit{
background-color: #ff9800;
border: 1px solid #ff9800;
color:white;
&:hover,&:focus{
background-color: #bc750c;
border: 1px solid #bc750c;
}
}
.delete{
background-color: red;
border: 1px solid red;
color:white;
&:hover,&:focus{
background-color: rgb(182, 9, 9);
border: 1px solid rgb(182, 9, 9);
}
}
nav{
display: block;
unicode-bidi: isolate;
}
.lists{
display: flex;
justify-content: center;
width: 60%;
max-width: 60%;
margin: auto;
ul{
list-style: none;
padding-inline-start: 0px;
width: 100%;
max-width: 100%;
min-height: 800px;
li{
border-bottom:1px solid #ddd;
display: flex;
list-style: none;
.subject{
align-items: center;
display: flex;
padding: 2px auto;
padding-left: 15px;
width: 50%;
label{
display: flex;
justify-content: flex-start;
width: 95%;
margin-bottom: 0;
span.edit_area{
display:flex;
&.noEditing{
display: none;
}
}
.edit_input_area{
align-items: center;
display: none;
flex-direction: column;
justify-content: flex-start;
width: 100%;
input{
display: flex;
border: 1px dashed #263238;
}
&.editing{
display: flex;
}
}
}

}
.buildDate,.updataDate{
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
font-size: 14px;
padding-left: 6px;
}
.btns_group{
display: flex;
}
a,button{
margin: 5px;
cursor: pointer;
white-space: nowrap;
&:disabled {
cursor: not-allowed;
}
i{
margin-left: 5px;
}
&.edit{
display: flex;
&.noEditing{
display: none;
}
}
&.add{
display: none;
&.editing{
display: flex;
}
&:disabled{
cursor: not-allowed;
}
}
}
}
}
}


input{
display: block;
width: 100%;
height: calc(1.2em + .75rem + 2px);
padding: 0.075rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
margin-left: -.75rem;
&:hover{
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
.errorMessage{
color:red;
font-size: 12px;
align-self: flex-start;
margin-left: -15px;
}



.editing{
display: flex;
}
.noEditing{
display: none;
}
@media screen and (width<=1560px) {
.lists{
ul{
min-height: 480px;
}
}
}
@media screen and (width<=1440px) {
.lists{
ul{
min-height: 480px;
}
}
}
@media screen and (width<=820px){
.add_form,
.lists{
width: 80%;
max-width: 80%;
}
}
//
@media screen and (width<=768px){
.lists {
width: 85%;
max-width: 85%;
ul {
margin-left: 10px;
width: 90%;
li {
margin-left: -15px;
.subject {
width: 45%;
}
}
}
}
}
@media screen and (width<=667px){
.lists {
width: 85%;
max-width: 85%;
ul {
margin-left: 10px;
width: 90%;
li {
margin-left: -15px;
flex-wrap: wrap;
div{
padding: 10px;
}
.subject {
min-width: 100%;
background-color: #f4f3f3;
label{
width: 100%;
height: auto;
.edit_input_area{
&.editing{
flex-direction: none;
padding: 0px;
height: 55px;
}
.errorMessage{
padding: 0;
}
}
}
}
.buildDate,.updataDate{
min-width: 45%;
margin: auto;
}
.btns_group{
margin: auto;
}
}
}
}
}
@media screen and (width<=600px){
.lists ul li a {
span{
display: none;
}
i{
font-size: 1.4rem;
}
&.delete{
i{
font-size: 1.7rem;
}
}
}
}
//iphone5/SE
@media screen and (width<=568px){
.lists {
ul {
li {
.subject {
width: 100%;
max-width: 100%;
white-space:nowrap;

}
.buildDate{
flex-direction: column;
}
}
}
}
}
//iphone 14 pro
@media screen and (width<=430px){
.lists ul li {
margin-left: -15px;
.subject label .edit_area{
font-size: 1.1rem;
}
}
}
@media screen and (width<=414px){
.lists ul li {
margin-left: -15px;
.subject label .edit_area{
font-size: 1.1rem;
}
a{
&.btns{
width: 50px;
}
i{
font-size: 1.4rem;
}
&.delete{
i{
font-size: 1.7rem;
}
}
}
}
}
.pagination_area{
display: flex;
justify-content: center;
.my-pagination ::ng-deep {
align-items: center;
.ngx-pagination {
display: flex;
justify-content: center;
li{
&:first-child{
margin-right: 22px;
}
&.current {
background: transparent;
border-radius: 50%;
width: 32px;
height: 32px;
border: 2px solid #ffd207;
color:#333;
}
&.pagination-previous{
position: relative;
width: 25px;
&.disabled{
color: #ddd;
position: relative;
&:before{
font-size: 42px;
position: absolute;
top: -17px;
left: 0px;
}
span{
display: none;
}
}
a{
position: relative;
font-size: 0px;
&:before{
color: #333;
font-size: 35px;
position: absolute;
top: -11px;
left: -3px;
}
&:hover{
background: transparent;
border:0px solid transparent;
}
}
&:hover{
border-style: transparent;
}
}
a{
&:hover{
font-weight: bolder;
background: transparent;
border-color: #ffd207;
}
}
&.pagination-next {
width: 45px;
&:hover{
border-color: transparent;
}
&::after{
color: #333;
font-size: 35px;
}
&.disabled{
color: #ddd;
position: relative;
&:before{
font-size: 35px;

}
&::after{
color: #ddd;
font-size: 35px;
position: absolute;
top: -11.5px;
left: 17px
}
span{
display: none;
}
}
a{
color: transparent;
font-size: 0px;
position: relative;
&:hover{
font-weight: bolder;
background: transparent;
border-bottom: 1px solid red;
}
&:after{
margin-left: 0rem;
color: #333;
font-size: 35px;
position: absolute;
top: -14px;
left: 20px;
}
&:hover{
background: transparent;
border:0px solid transparent;
}
}
}
}
}
}
.pagination_detail{
display: flex;
margin-top: 4px;
}
.btn-group{
margin-left: 10px;
.btn{
height: 32px;
border: 2px solid #ffd207;
padding: 2px 5px;
&:hover,&:focus{
background-color: #ffd207;
color:#333;
}
}
.dropdown-menu{
min-width: 5rem;
}
}
}

@media screen and (width<=480px){
.pagination_area {
margin-left: -15px;
.pagination_detail{
font-size: 1.1rem;
.currentPage{
display: none;
}
}
}
}
@media screen and (width<=428px){
.pagination_area {
.pagination_detail{
display: none;
}
}
}
@media screen and (width<=360px){
.pagination_area .my-pagination .ngx-pagination {
li{
&:first-child {
margin-right: 5px;
}
&.pagination-previous a{
&:before {
left: 5px;
}
}
&.current{
height: 28px;
width: 28px;
}
}
}

}

例如

新增刪除編輯列表分頁功能

參考資料

Angular 實戰Navbar

功能與邏輯

獲取Nav數據 => 初始化宣染active 在Home => 點擊Nav Item 獲取頁面名稱===pageTitle 新增active樣式
父對子傳值
子元件引入Input
  • @InPut() => import {Input} 引入Input
  • @InPut() 傳值參數:屬性;
  • implements OnInit =>import {OnInit}引入Input OnInit
  • 使用到a標籤時引入RouterLink
  • 使用到動態Class時引入NgClass
從父元件引入子元件
  • 從父元件引入子元件 如果是父對子傳值 [傳值參數]= 父元件內的傳值欄位參數
1
[傳值參數]= 父元件內的傳值欄位參數
子對父傳值
子元件引入Output,EventEmitter
  • Output () 傳值參數名稱 =new EventEmitter<屬性>() ;
  • 函式傳值 this.傳值參數名稱.emit(item)
  • Html =>(傳值參數名稱)="函式($event)"

以下完整程式碼

父元件

從app.component.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
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+import { NavbarComponent } from './components/navbar/navbar.component';
import { CreateNavComponent} from './components/create-nav/create-nav.component';
import { environment } from '../environment/environment'
import { navListType,navListActiveType } from './Types/nav';

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

export class AppComponent {
title = 'vite_project';
navLists: navListType[] = [
{ title: 'Home', path: '/' },
{ title: 'Users', path: 'users' },
{title:'Login',path:'login'}
]
isActive: navListActiveType["isActive"] = true;
pageTitle: navListActiveType["pageTitle"] = "Home";
actionList(title: string) {
this.pageTitle = title;
}
bgStyle:string ='red'
ngOnInit() {
console.log(environment.production ? 'Production' : '開發中')
}
}

app.component.html檔案內引入子元件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<app-navbar
[sendNavLists]="navLists"
[sendPageTitle]="pageTitle"
[sendIsActive]="isActive"
(newPageTitle)="actionList($event)"
></app-navbar>
<app-create-nav
[sendBgStyle]="bgStyle"
[sendCreateLists]="navLists"
[sendPageTitle]="pageTitle"
[sendIsActive]="isActive"
(newPageTitle)="actionList($event)"
></app-create-nav>

<main class="main">
</main>
<router-outlet />

子元件

navbar.component.ts

  • @InPut() => import {Input} 引入Input
  • @InPut() 傳值參數:屬性;
  • implements OnInit =>import {OnInit}引入Input OnInit
  • 使用到a標籤時引入RouterLink
  • 使用到動態Class時引入NgClass
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 { NgFor,NgClass } from '@angular/common';
+ import { Component,OnInit ,Input,Output, EventEmitter} from '@angular/core';
import { RouterLink } from '@angular/router'; // a標籤 [RouterLink]
import { navListType } from '../../Types/nav'; // Type屬性
//@Component=>裝飾器
@Component({
selector: 'app-navbar',
+ imports: [RouterLink,NgFor,NgClass],
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss'
})
// 實現初始化 implements OnInit
+ export class NavbarComponent implements OnInit {
//父對子傳值
+ @Input() sendNavLists!: navListType[];
+ @Input() sendPageTitle!: string;
+ @Input() sendIsActive!: boolean;
// 子對父傳值
+ @Output() newPageTitle = new EventEmitter<string>();
//點擊頁面按鈕函式
sendActionList(path: string) {
this.newPageTitle.emit(path);
}
ngOnInit(): void {}
}
navbar.component.html
  • *ngFor="let item of sendNavLists ; let i = index"
  • [ngClass]="sendPageTitle === item.title ? 'activeNav' : ''"
  • [routerLink]="item.path"
  • (click)="sendActionList(item.title)"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<nav class="nav">
<ul>
<li
+ *ngFor="let item of sendNavLists ; let i = index"
+ [ngClass]="sendPageTitle === item.title ? 'activeNav' : ''"
>
<a
+ [routerLink]="item.path"

+ (click)="sendActionList(item.title)"
+ class="cursor-pointer"
>{{item.title}}</a>
</li>

</ul>
</nav>

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
ul{
display: flex;
li{
list-style: none;
display: flex;
margin: auto 10px;
}
}
.nav {
background-color: #ffd207;
box-shadow: 2px 2px 2px 1px #0003;
display: flex;
height: 70px;
justify-content: center;
list-style-type: none;
margin: 0px auto auto auto;
padding-inline-start: 0px;
max-width: 100%;
width: 100%;

li {
display: flex;
list-style-type: none;
margin: auto 10px;
&.activeNav {
border-bottom: 1px solid #333;
}
a {
cursor: pointer;
color: #333;
font-size: 1.4rem;
text-decoration: none;
}
}
}

.activeNav {
border-bottom: 1px solid #333;
}

github

Angular 實戰Todo

手把手使用Angular,目前版本號是Angular Cli 19.2.0,
Node 安裝 v20.17.0,npm 10.8.2

基礎使用:

  • Router路由
  • 環境變數
  • 使用Angular Material來幫我們快速建立各種元件
  • Angular Binding繫結:
    • 內嵌繫結:使用雙括號將變數宣染
    • 屬性繫結[ ]繫結, [(ngModel)]表單綁定 :
      • styleex.[ngStyle]="{'font-size': 26 + counter + 'px'}";[ngStyle]="{'font-size': 26 + counter + 'px'}";
      • class ex.[ngClass]="{'Hidden':isShowLists}" => [ngClass]="{‘樣式’:動態屬性布林值}"; => 引入 import { NgFor, NgIf,NgClass} from '@angular/common';
      • [id]="item._id"
      • 圖片雙向綁定[src]="item.imgPath"
      • 表單雙向綁定 [(ngModel)]="item.Status" => 引入 import { FormsModule } from '@angular/forms'
    • 事件繫結
      • 點擊事件 (click)="sendEditItem(item)"
      • checkbox表單事件 (change)="isAllSelected()"
      • 鍵盤事件(keyup.enter)="sendAddItem(newTask)"
      • 鍵盤事件(blur)
      • 表單事件(input)
    • 渲染列表 *ngFor => import { NgFor} from '@angular/common';
    • 渲染判斷 *NgIf => import { NgIf} from '@angular/common';
  • 父對子傳遞
  • 子對父傳值

Router路由

src/app/app.routes.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
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home/home.component';
import { UsersComponent } from './users/users.component';
import { UserComponent } from './user/user.component';
import { LoginComponent } from './login/login.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: "login", component: LoginComponent},
{ path: '**', component: PageNotFoundComponent },
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [RouterModule]
})
//https://angular.tw/start/start-routing
export class AppRoutingModule { }

環境變數

環境變數

使用Angular Material來幫我們快速建立各種元件

產生一個新的組件

1
ng g c 頁面或是功能

父元件

### app.component.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
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { environment } from '../environments/environment';
import { NavbarComponent } from "./components/navbar/navbar.component";
import { navListType } from "./Type/nav";
@Component({
selector: 'app-root',
imports: [
RouterOutlet,
FormsModule,
NavbarComponent,
],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = "我的網站";
navLists: navListType[] = [
{ title: 'Home' ,path:'/'},
{ title: 'Users', path: 'user' },
{title:'Login',path:'login'}
]
pageTitle: string = 'Home';
isActive: boolean = false;
actionList(path: string) {
this.pageTitle = path;
}
ngOnInit(): void {
console.log(environment.production ? 'Production' : '開發中')
}
}

子對父傳遞

父對子傳遞

app.component.html

router-outlet 如同Vue 的router-view
1
2
3
4
5
6
7
8
9
10
<app-navbar
[sendNavLists]="navLists"
[sendPageTitle]="pageTitle"
[sendIsActive]="isActive"
(newPageTitle)="actionList($event)"
></app-navbar>

<main class="main">
<router-outlet />
</main>

父元件

父對子傳值

home.component.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
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { environment } from '../../environments/environment';
import { addListsType } from '../Type/toDo';
import { ToDoHeaderComponent } from '../components/to-do-header/to-do-header.component';
import { ToDOComponent } from '../components/to-do/to-do.component';
@Component({
selector: 'app-home',
imports: [FormsModule,ToDoHeaderComponent,ToDOComponent],
templateUrl: './home.component.html',
styleUrl: './home.component.scss'
})
export class HomeComponent {
newTask: string = '';
header:any= {
'Content-Type': 'application/json',
}
//新增
addItem(newTask: string) {
if (newTask.length>0) {
const vm = this;
const api = `${environment.apiUrl}/api/toDo`;
let query: any = {
title: newTask,
buildDate: Date.now(),
updataDate: Date.now(),
}
console.log('query', query)
fetch(api, { method: 'POST', headers: vm.header, body: JSON.stringify(query) })
.then((res) => res.json())
.then((data) => {
//unshift(data) 新增到第一個
//push(data)新㽪到最後一個
vm.addLists.unshift(data)
})
} else {
alert('沒有填寫新增項目')
}
};

//獲取
addLists: addListsType[] = [];
getAddLists() {
const vm = this;
const api = `${environment.apiUrl}/api/toDoLists`;
fetch(api, { method: 'GET' })
.then((res) => {return res.json();})
.then((res) => {
res.map(function (item: addListsType) {
let query: any = {
_id: item._id,
title: item.title,
Editing: false, // 編輯
Status: false, //選取狀態
CanEdit:true, //可以編輯
buildDate: item.buildDate,
updataDate: item.updataDate,
}
return vm.addLists.push(query);
/*Sort()排列*/
vm.addLists.sort(function (a, b) {
return b.buildDate - a.buildDate
})
return vm.addLists;
})
})
.catch((error) => {
console.log(`Error: ${error}`);
})
}
//刪除
deleteItem(item: any) {
const vm = this;
const api = `${environment.apiUrl}/api/toDo/${item._id}`
fetch(api, {method: 'DELETE',headers: this.header})
.then((res) => res.json())
.then((data) => {
const index = vm.addLists.findIndex(task => task._id === item._id);
vm.addLists.splice(index, 1)
});
}
//編輯
editItem(item: any) {
console.log('editItem', item)
const vm = this;
const api = `${environment.apiUrl}/api/toDo/${item._id}`;
let query: any = {
title: item.title,
buildDate: item.buildDate,
updataDate: Date.now(),
}
fetch(api, {
method: 'PUT',
headers: vm.header,
body: JSON.stringify(query)
})
.then((res) => res.json())
.then((data) => {
if (!item.Editing) {
alert("編輯成功")
}
});
}

//全選
selectAll() {

}
ngOnInit(): void {
this.getAddLists();
}
}

home.component.html

1
2
3
4
5
6
7
8
9
10
<app-to-do-header
(sendNewTask)="addItem($event)"
>
</app-to-do-header>
<app-to-do
[sendAddLists]="addLists"
(sendChangeDeleteItem)="deleteItem($event)"
(sendChangeEditItem)="editItem($event)"
></app-to-do>

子元件

這是NavBar

components/to-do-header

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, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { NgFor } from '@angular/common';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { navListType } from '../../Type/nav';
@Component({
selector: 'app-navbar',
imports: [
NgFor,
CommonModule,
RouterLink
],
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss',

})

export class NavbarComponent implements OnInit{
@Input() sendNavLists!: navListType[];
@Input() sendPageTitle!: string;
@Input() sendIsActive!: boolean;

@Output() newPageTitle = new EventEmitter<string>();
//點擊頁面按鈕函式
sendActionList(path: string) {
this.newPageTitle.emit(path);
}
ngOnInit(): void {

}
}
1
2
3
4
5
6
7
8
 <ul  class="nav">
<li *ngFor="let item of sendNavLists ; let i = index"
[ngClass]="sendPageTitle === item.title ? 'activeNav' : ''">
<a [routerLink]="item.path"
(click)="sendActionList(item.title)"
class="cursor-pointer">{{item.title}}</a>
</li>
</ul>

這是新增表單區域

子對父傳遞

父對子傳遞

components/to-do-header ### to-do-header.component.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
import { Component,Input, OnInit,Output, EventEmitter } from '@angular/core';
import { FormsModule } from '@angular/forms'
@Component({
selector: 'app-to-do-header',
standalone: true,
imports: [ FormsModule ],
templateUrl: './to-do-header.component.html',
styleUrl: './to-do-header.component.scss'
})
export class ToDoHeaderComponent implements OnInit {
@Output() sendNewTask = new EventEmitter<string>();
newTask: string = "";
header:any= {
'Content-Type': 'application/json',
}
//新增
sendAddItem(newTask: string) {
const message = '沒有填寫新增項目';
newTask.length>0?this.sendNewTask.emit(newTask):this.messageAlert(message )
}
messageAlert(message:string) {
alert(message)
}
ngOnInit(): void {
}
}
### to-do-header.component.html
1
2
3
4
5
6
7
8
9
10
<div class="todo_lists_header">
<input type="text"
[(ngModel)]="newTask"
[value]="newTask"
[placeholder]="newTask"
(keyup.enter)="sendAddItem(newTask)" >
<a class="button add_button"
(click)="sendAddItem(newTask)">新增</a>
</div>

## 這是ToDo列表 ### to-do.component.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
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { FormsModule } from '@angular/forms'
import { NgFor, NgIf,NgClass} from '@angular/common';
import { addListsType } from '../../Type/toDo';
@Component({
selector: 'app-to-do',
imports: [NgFor,NgIf,NgClass,FormsModule],
templateUrl: './to-do.component.html',
styleUrl: './to-do.component.scss'
})

export class ToDOComponent implements OnInit{
@Input() sendAddLists!: addListsType[];

@Output() sendChangeDeleteItem = new EventEmitter<any>();
@Output() sendChangeEditItem = new EventEmitter<Object>()

sendDeleteItem(item: any) {
this.sendChangeDeleteItem.emit(item)
}
sendEditItem(item: any) {
item.Editing = !item.Editing;
this.sendChangeEditItem.emit(item)
};

masterSelected:boolean= false;
constructor(){}

sendAllChecked() {
for (var i = 0; i < this.sendAddLists.length; i++) {
this.sendAddLists[i].Status = this.masterSelected;
}
}
isAllSelected() {
this.masterSelected = this.sendAddLists.every(function(item:any) {
return item.Status == true;
})
}
isShowLists: boolean = false;
sendDeleteAll() {
this.isShowLists = true;
// this.sendAddLists =[]
}
ngOnInit(): void { }
}

to-do.component.html

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
<div class="todo_lists">
<div class="subject"><b>待辦事項</b></div>
<div class="flex-start">
<label class="all" for="all">全部
<input
type="checkbox"
name="all"
id="all"
[(ngModel)]="masterSelected"
(change)="sendAllChecked()"
>
<span class="round-mark"></span>
</label>
<i *ngIf="masterSelected"
(click)="sendDeleteAll()"
class="fa-solid fa-trash"></i>
</div>
<ul class="todo-list"
[ngClass]="{'Hidden':isShowLists}"
>
<!--*ngFor 陣列-->
<li
*ngFor="let item of sendAddLists ; let i = index">
<label class="checkBox"
[for]="item._id">
<span class="edit_area"
[ngClass]="{'noEditing':item.Editing}"
>
{{item.title}}
</span>
<!-- 選取狀態-->
<input
type="checkbox"
(change)="isAllSelected()"
[id]="item._id"
[(ngModel)]="item.Status"
>
<span class="round-mark"></span>
<div class="edit_area">
<input
type="text"
[(ngModel)]="item.title"
[ngClass]="{'editing':item.Editing}"
[value]="item.title">
</div>
</label>
<!--操作-->
<i (click)="sendEditItem(item)" class="fa-solid fa-pencil"></i>
<i (click)="sendDeleteItem(item)" class="fa-solid fa-xmark" ></i>
</li>
</ul>
</div>

Attribute, class, and style bindings and Two-way binding
參考資料

Vite Vue Line第三方登入

Line
網站上設定使用Line第三方登入的功能,需先到Line進行資料的設定與驗證,並取得「Channel ID」與「Channel secret」後,交由工程師做最後的串接才可使用於網頁中。 申請Line第三方登入之前,必須有一個Line的個人or官方帳號,若沒有則需先申請一組

導向授權頁面的query(Page Mode)
以下為導向各家授權頁面的Base URL

Google:https://accounts.google.com/o/oauth2/v2/auth

Facebook:https://www.facebook.com/v7.0/dialog/oauth

Line:https://access.line.me/oauth2/v2.1/authorize

Vite Vue 第三方登入

Vue Google 第三方登入

功能說明:第三方登入,按下Google登入按鈕,選擇Google登入的帳號
使用第三方Google登入 需要有一組 Google OAuth 使用的 Client ID, 你可以到 Google Console 新增一個「OAuth 2.0 用戶端 ID」
這邊提醒,在建立 OAuth Client ID 時,已授權的 JavaScript 來源,記得填寫上您的正式環境或開發環境的 Domain,且建議使用 HTTPS。
### 申請 Google 認證模式OAuth Google Console 畫面
1. 建立一個新專案
2. 啟用API服務
3. 建立憑證 => OAuth用戶端ID
4. OAuth用戶端ID
選取網頁應用程式
填寫名稱
已授權重先導向 =>
https://developers.google.com/oauthplayground

取得Google client_id 用戶端編號

安裝 vue3-google-login

官網參考資料
安裝指令

1
npm install vue3-google-login

到 進入點 main.ts引入vue3-google-login

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
+ import vue3GoogleLogin from 'vue3-google-login'
+ const clientId= `${import.meta.env.VITE_GOOGLE_CLIENT_ID}`;
import App from './App.vue'
+ const GoogleLoginOptions = { clientId: clientId}

const app = createApp(App);
+app.use(vue3GoogleLogin,GoogleLoginOptions);
app.mount('#app')

頁面的使用

  • 引入 vue3-google-login 的 googleAuthCodeLogin, googleSdkLoaded
  • clientId 就是Google OAuth的 Client ID
  • ** googleSdkLoaded 內的 google.accounts.oauth2.initCodeClient 是 client_id: clientId.value,
  • scope: 'email profile openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
功能說明:第三方登入,按下Google登入按鈕,選擇Google登入的帳號
1. 按下登入按鈕
2. 選擇Google登入的帳號
3. 如果如圖就是已經成功串接到 Google OAuth,按下繼續
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
<template>
<i @click="login" class="fa-brands fa-google-plus" :title="Google 登入"></i>
<i class="fa-brands fa-facebook"></i>
</template>

<route lang="yaml">
meta:
layout: frontLayout
</route>

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
mport { googleAuthCodeLogin, googleSdkLoaded } from "vue3-google-login"

const clientId = ref<string>(`${import.meta.env.VITE_GOOGLE_CLIENT_ID}`)

const login = () => {
googleAuthCodeLogin().then((res) => {
console.log("Handle the res", res)
})
}
const loginS = () => {
googleSdkLoaded((google) => {
google.accounts.oauth2.initCodeClient({
client_id: clientId.value,
scope: 'email profile openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
callback: (res) => {
console.log("Handle the res", res)
}
}).requestCode()
})
}
參考資料

Vite FaceBook 第三方登入

FaceBook Sdk 申請

一. 到facebook開發人員網站,點選我的應用程式
二. 點選建立應用程式
三. 點選建立應用程式
四. 點選使用Fb驗證用戶索取資料,按下確定按鈕
五. 如圖先選『我還不想連結商家資產管理組合』,讓繼續按鈕可以選取
六. 選取『前往主控台』
出現跳出框
七. 『自訂使用案例』=> Facebook 權限
八. 『自訂使用案例』 => Facebook 設定
有效得重新導向
九. 『自訂使用案例』 => Facebook 『快速入門』
1.告訴我們您的網站 2.設定 Facebook JavaScript SDK
Facebook JavaScript SDK 沒有任何需要下載或安裝的獨立檔案,您只需要將一小段一般的 JavaScript 置入 HTML 中,就會以非同步的方式將 SDK 載入頁面中。非同步載入是指不會阻擋頁面中其他元素的載入。
3.檢查登入狀態
載入您的網頁時,第一個步驟是確認用戶是否已經使用「Facebook 登入」來登入您的應用程式。呼叫 FB.getLoginStatus 來啟動這個程序。這個函式會觸發對 Facebook 的呼叫來取得登入狀態,接著呼叫您的回呼函式來傳回結果。
以下擷取自上述程式碼範例,為在頁面載入時用來檢查用戶登入狀態所執行的部分程式碼
九. 下圖Id 就是我要們獲取的重要參數
//

安裝

1
npm install --save @healerlab/vue3-facebook-login

Vue3 Facebook Login

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
<script setup lang="ts">
import { HFaceBookLogin } from '@healerlab/vue3-facebook-login';
const fbAppID = ref<string>(`${import.meta.env.VITE_FB_APP_ID}`)
const onSuccess = (response) => {
// get your auth token and info
// 取得你的身分驗證令牌和訊息
}

const onFailure = () => {
// logic if auth failed
// 身份驗證失敗時的邏輯
}
</script>

<style scoped lang="scss">
.fb-button {
display: inline-block;
margin: 10px 0 10 0;
color: white;
background-color: #1967d2;
border-radius: 8px;
padding: 16px;
cursor: pointer;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<HFaceBookLogin
v-slot="fbLogin"
:app-id="fbAppID"
@onSuccess="onSuccess"
@onFailure="onFailure"
scope="email,public_profile"
fields="id,name,email,first_name,last_name,birthday">
<i @click="fbLogin.initFBLogin" class="fa-brands fa-facebook"></i>
</HFaceBookLogin>
</template>