iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
0
Modern Web

用Angular打造完整後台系列 第 12

day12 CoreComponent

簡述

在確認此使用者的相關資訊後會進入核心模塊,也是佈局畫面的底。
很多功能模塊會以延遲子路由的方式,掛在核心模塊裡。

畫面結構

coretheme

<!--core.component.html-->
<div id="side" [@slideInOut]="animationState">
  <app-menu></app-menu>
</div>
<div class="core-container">
  <header class="flex space-between"></header>
  <main class="flex straight-flex">
    <app-tab></app-tab>
    <router-outlet></router-outlet>
  </main>
  <footer class="flex"></footer>
</div>

流程

  • 在進入核心模塊之前,會先有路由守衛做判斷,
    若收到登入成功的訊息,會成功到CoreComponent
@Injectable()
export class GuardService implements CanActivate {
  constructor(
    private router: Router,
    private service: AppService,
    private loginService: LoginService
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    return this.loginService.loginState.pipe(
      map(e => {
        if (e) return true;
      }),
      catchError(e => {
        this.service.backLogin();
        return Observable.throw(e);
      })
    );
  }
}
  • 進入核心模塊後,會開始加載畫面上其他組件,
    Menutab 跟其他子模塊router-outlet
    不過這些後續章節再說吧。

功能

那除了上述其他加載的事情,
CoreComponent 本身要做的事情大概如下:

  • 切換語系
  • 設計RWD

檔案結構

-src
  |-app
    |-app.component.css
    |-app.component.html
    |-app.component.ts
    |-app.module.ts
    |-core
       |-menu
           ...
       |-animations.ts
       |-core-routing.module.ts
       |-core.component.css
       |-core.component.html
       |-core.component.ts
       |-core.module.ts
       |-core.service.ts
       |-guard.service.ts

實作

(一) 切換語系

在右上角有個切換語系,所以如果語系有更動,
全局都要更換翻譯。

changelang

core.service.ts

@Injectable()
export class CoreService {
  constructor(
    private service: AppService,
    private storageService: StorageService,
    public translate: TranslateService
  ) {}

  useLanguage(lang: string) {
    return this.translate.use(lang);
  }

  getNowLang() {
    return this.storageService.getLangStorage();
  }

  logout(): void {
    this.service.backLogin();
  }
}

--

core.component.ts

export class CoreComponent implements OnInit, OnDestroy {
  COPYRIGHT = COPYRIGHT;
  LANG = LANG;
  TOGGLELANG = TOGGLELANG;

  constructor(
    private service: CoreService,
    private userService: UserService,
    ...
  ) {}

...

  useLanguage($event) {
    if (!!$event) {
      this.service.useLanguage($event);
    }
  }

  getUserAccount(): string {
    if (
      !!this.userService.getUser() && 
      !!this.userService.getUser().account
    ) {
      return this.userService.getUser().account;
    }
  }
}

--

core.component.html

<div class="flex align-center text-red" *ngIf="TOGGLELANG">
  <div class="base-icons">
    <mat-icon svgIcon="global" (click)="select.open()"></mat-icon>
    <mat-select
      #select
      [value]="service.getNowLang()"
      (valueChange)="useLanguage($event)"
      color="red"
      style="width:100px;"
      class="b"
    >
      <mat-option *ngFor="let lang of LANG" 
      [value]="lang.short" class="b">
        {{ lang.long | translate }}
      </mat-option>
    </mat-select>
  </div>
</div>

(二) 設計RWD

通常如果版面有做RWD,則 Menu 就會有收放。

rwdmenu

流程:

變成平板尺寸的時候,就會出現漢堡按鈕,
點擊就會有動畫開菜單。

需要做到以下幾件事情:

  • 安裝hamburgers套件
  • 新增animations.ts(Menu收放特效)
  • 判斷螢幕尺寸

--

實作:

安裝hamburgers套件

npm install hamburgers -g

style.css

@import "~hamburgers/dist/hamburgers.min.css";

新增animations.ts

import { trigger, state, style, transition, animate, group } from "@angular/animations";

export const SlideInOutAnimation = [
  trigger("slideInOut", [
    state(
      "in",
      style({
        "max-height": "100%",
        "max-width": "100%",
        opacity: "1",
        visibility: "visible"
      })
    ),
    state(
      "out",
      style({
        "max-height": "0%",
        "max-width": "0%",
        opacity: "0",
        visibility: "hidden"
      })
    ),
    transition("in => out", [
      group([
        animate(
          "200ms ease-in-out",
          style({
            visibility: "hidden"
          })
        ),
        animate(
          "300ms ease-in-out",
          style({
            "max-height": "0%",
            "max-width": "0%"
          })
        )
      ])
    ]),
    transition("out => in", [
      group([
        animate(
          "1ms ease-in-out",
          style({
            visibility: "visible"
          })
        ),
        animate(
          "400ms ease-in-out",
          style({
            "max-height": "100%",
            "max-width": "100%"
          })
        ),
        animate(
          "500ms ease-in-out",
          style({
            opacity: "1"
          })
        )
      ])
    ])
  ])
];

並註冊在core.component.ts

@Component({
  selector: "app-core",
  templateUrl: "./core.component.html",
  styleUrls: ["./core.component.css"],
  animations: [SlideInOutAnimation]
})

判斷螢幕尺寸

export class CoreComponent implements OnInit, OnDestroy {
  ...
  subscription: Subscription;
  animationState = "in";
  isHamburger = false;
  isDevice = "";

  constructor(
    private breakpointObserver: BreakpointObserver,
    ...
  ) {}

  ngOnInit() {
    if (!!this.service.isHamburgerIn()) {
      this.subscription = this.service.isHamburgerIn()
      .subscribe(val => {
        if (this.isDevice==='mb') {
          this.isHamburger = val !== "";
          this.toggleMenu();
        }
      });
    }

    this.breakpointObserver.observe("(max-width: 1199px)")
    .subscribe(r => {
      this.isHamburger = r.matches;
      this.isDevice = r.matches ? "mb" : "pc";
      this.toggleMenu();
    });

    this.goHome();//預設頁面為首頁
  }

  ngOnDestroy() {
    if (!!this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  toggle() {
    this.isHamburger = !this.isHamburger;
    this.toggleMenu();
  }

  toggleMenu() {
    this.animationState = this.isHamburger ? "out" : "in";
  }
  ...
}
  • 首先 Materia 的breakpointObserver會偵測目前螢幕的尺寸,
    當小於等於1199px,則會觸發以下成立:
this.isHamburger = true;
this.isToggle = true;
  • isHamburger是指 是否出現漢堡選單按鈕。
    isDevice是指 目前螢幕尺寸是否為 mb 版。

  • 當螢幕為 mb 版時 isHamburger才會為true。
    則Menu會消失 animationState為'out'

  • 需要注意的是isHamburgerIn()
    為什麼這邊會出現Observable?
    原因是因為當Menu出現,點擊頁面項目後Menu就會關閉,
    並且要出現漢堡選單按鈕。

menunext

  • core.service.ts
@Injectable()
export class CoreService {
  private hamburgerSubject = new BehaviorSubject<string>("");

  constructor(
    ...
  ) {}

  nextHamburger(next: string) {
    this.hamburgerSubject.next(next);
  }

  isHamburgerIn(): Observable<string> {
    if (!!this.hamburgerSubject) {
      return this.hamburgerSubject.asObservable();
    }
    return null;
  }
...
}

--

Menu內的頁面項目被點擊則會觸發:
(扮演推送值)

//menu.component.ts
this.coreService.nextHamburger("next");

--

則CoreComponent訂閱:
(扮演接值)

//core.component.ts
ngOnInit() {
    if (!!this.service.isHamburgerIn()) {
      this.subscription = this.service.isHamburgerIn().subscribe(val => {
        if (this.isDevice==='mb') {
          this.isHamburger = val !== "";
          this.toggleMenu();
        }
      });
    }
    ...
}

...

toggleMenu() {
    this.animationState = this.isHamburger ? "out" : "in";
}

...


範例碼

https://stackblitz.com/edit/ngcms-corecomp

Start

一開始會跳出提示視窗顯示fail為正常,
請先從範例專案裡下載或是複製db.json到本地端,
並下指令:

json-server db.json

json-server開啟成功後請連結此網址:
https://ngcms-corecomp.stackblitz.io/cms?token=bc6e113d26ce620066237d5e43f14690


上一篇
day11 AppComponent(二) 登入初始流程
下一篇
day13 Menu
系列文
用Angular打造完整後台30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
archer
iT邦新手 3 級 ‧ 2020-02-09 14:26:50

您好:

照著您的範例實作,但您的範例中,好像少放入了model/tab.ts
所以有點卡住了,不知您是否可以把它們補全,或是另外把這檔案回給我也可以
import { ITabMain, ITabBase } from "../../model/tabs";
真的很不好意思,還要這樣煩您

謝謝您

Archer

archer iT邦新手 3 級 ‧ 2020-02-10 12:17:15 檢舉

謝謝您,還沒有看到一篇,所以不知道後續有說明,真的太感謝您了

0
archer
iT邦新手 3 級 ‧ 2020-02-18 11:42:26

您好,

看完了您的範例,真的是對我幫助很大,有很多的啟發,
尢其是 webService 及 dataService,
把資料的存取全部放入到這兩個泛型服務中,真的是太棒了,
對程式碼可以減少的非常多,
謝謝您的無私分享.

good luck

Archer

感謝你的回覆~
很開心有幫到你/images/emoticon/emoticon16.gif

我要留言

立即登入留言