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>

樣式請參照下方#範例網。

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;

(三) 其他 Service

LoginService:
上一章節有提及,通常接收者為路由守衛,
如果有登入才能看到 Menu 跟首頁。

UserService:
上一章節有提及,紀錄登入者身份。

(四) StorageService

主要用途是會紀錄此登入者的一些喜好,例如使用的語言。

或是紀錄目前正在看的頁面,
通常 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 帳號是停用的情況則會出現錯誤訊息。