AppComponent 是系統內的 根組件,
這篇會講述登入初始流程。
開發過程中大概遇到幾種設定如下。
首先上個流程圖
那大概會需要以下幾樣服務:
在SharedMoudle
裡常用的共用模塊之一,
用途就是會跳個小框框提示目前狀況。
樣式請參照
day03 SharedModule(一)
在進入主要畫面之前,先做一層讀取畫面。
-src
|-app
|-app.component.css
|-app.component.html
|-app.component.ts
|-app.module.ts
|-spinner
|-spinner.component.css
|-spinner.component.html
|-spinner.component.ts
|-spinner.service.ts
--
spinner.service.ts
import { Injectable } from "@angular/core";
import { Subject } from "rxjs/internal/Subject";
export interface SpinnerState {
show: boolean;
}
@Injectable()
export class SpinnerService {
private spinnerSubject = new Subject<SpinnerState>();
spinnerState = this.spinnerSubject.asObservable();
constructor() {}
load() {
this.spinnerSubject.next(<SpinnerState>{ show: true });
}
hide() {
this.spinnerSubject.next(<SpinnerState>{ show: false });
}
}
--
spinner.component.ts
@Component({
selector: "app-spinner",
templateUrl: "./spinner.component.html",
styleUrls: ["./spinner.component.css"]
})
export class SpinnerComponent implements OnInit {
visible = false;
subscription: Subscription;
constructor(
private spinnerService: SpinnerService,
public dialog: MatDialog
) {
this.subscription = this.spinnerService.spinnerState
.subscribe((state: SpinnerState) => {
this.visible = state.show;
});
}
ngOnInit() {}
ngOnDestroy() {
if (!!this.subscription) {
this.subscription.unsubscribe();
}
}
}
SpinnerService
定義了一條管道 Subject,
跟它同名的SpinnerComponent
負責接收資料,
並且根據接受的值來判斷要顯示的 Spinner 畫面。
其他組件若要推送資料,則如下:
this.spinnerService.load();
this.spinnerService.hide();
Spinner 用法皆是參照 Angular 官網,
--
spinner.component.html
<div class="spinner flex" *ngIf="visible">
<div class="load"><div class="loader"></div></div>
</div>
樣式請參照下方#範例網。
--
app.service.ts
其實就是寫個登出 funcion,來做需要關掉的多項相關服務。
@Injectable()
export class AppService {
constructor(
private loginService: LoginService,
private userService: UserService
) {}
backLogin() {
this.userService.delUser();
this.loginService.logout();
// window.location.href = LoginUrl;
}
}
LoginService:
上一章節有提及,通常接收者為路由守衛,
如果有登入才能看到 Menu 跟首頁。
UserService:
上一章節有提及,紀錄登入者身份。
主要用途是會紀錄此登入者的一些喜好,例如使用的語言。
或是紀錄目前正在看的頁面,
通常 SPA 框架是重新整理後資料就會不見,
都要重新來過。
所以可以把正在看的頁面路徑做紀錄,後面會有詳細的介紹。
storage.service.ts
:import { Injectable } from "@angular/core";
import { Md5 } from "ts-md5/dist/md5";
import { LangKey, UserKey } from "../config/config";
@Injectable({
providedIn: "root"
})
export class StorageService {
defaultLangVal = "tw";
key = "";
constructor() {}
setKey(account: string) {
this.key = JSON.stringify(Md5.hashStr(account + UserKey));
this.getStorage();
}
setLangStorage(lang: string) {
sessionStorage.setItem(LangKey, lang);
}
getLangStorage() {
if (!sessionStorage.getItem(LangKey)) {
this.setLangStorage(this.defaultLangVal);
}
return sessionStorage.getItem(LangKey);
}
delStorage() {
sessionStorage.clear();
}
}
通常儲存 storage,會多一層加密當索引避免資安問題,
Demo 加密方式請參照
https://github.com/cotag/ts-md5
預設語系基本上會寫在這裡,當判斷 storage 已有存語系,
將會代替成目前使用語系,沒有的話將採用預設值。
最後AppComponent
將會照流程圖上,把各式流程串起來:
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
constructor(
private router: Router,
private service: AppService,
private matIconRegistry: MatIconRegistry,
private domSanitizer: DomSanitizer,
private spinnerService: SpinnerService,
private dataService: DataService,
private translate: TranslateService,
private storageService: StorageService,
private loginService: LoginService,
private dialog: MatDialog,
private userService: UserService
) {
ICONS.forEach(val => {
this.matIconRegistry.addSvgIcon(
val.tab,
this.domSanitizer.bypassSecurityTrustResourceUrl(val.path)
);
});
let arr = LANG.map(item => {
return item.short;
});
translate.addLangs(arr);
let lang = this.storageService.getLangStorage();
translate.setDefaultLang(lang);
translate.use(lang);
}
ngOnInit() {
if (!this.userService.getUser().token) {
this.router.events
.pipe(
filter(event => event instanceof RoutesRecognized),
take(1)
)
.subscribe((e: any) => {
if (!!e.state.root.queryParams["token"]) {
this.setToken(e.state.root.queryParams["token"]);
} else {
this.dialogOpen(1); //reconnect
}
});
}
}
setToken(token: string) {
if (!!token) {
this.userService.setToken(token);
this.spinnerService.load();
this.dataService.connect(token).subscribe((data: IData) => {
this.spinnerService.hide();
if (!!data.errorcode) {
this.dialogOpen(data.errorcode);
} else {
let user = data.res[0];
this.setLogin(user.account);
this.userService.setOne(user);
}
});
}
}
dialogOpen(errorcode) {
let dialogRef = this.dialog.open(DialogAlertComponent, {
width: "550px",
data: {
errorcode: errorcode
}
});
dialogRef.afterClosed().subscribe(() => {
this.service.backLogin();
});
}
setLogin(account: string) {
this.storageService.setKey(account);
this.loginService.login();
}
}
Demo 目前沒有做登入頁,所以直接假設拿到一組正確的 token。
ngOnInit()
判斷網址如果沒有 token 帶入,則會判斷連線失敗。
setToken()
則是拿 token 請求拿 user 身份訊息,
如果 user 帳號是停用的情況則會出現錯誤訊息。
https://stackblitz.com/edit/ngcms-appcomp