iT邦幫忙

2019 iT 邦幫忙鐵人賽

2
Modern Web

Angular 深入淺出三十天系列 第 31

[Angular 深入淺出三十天] Day 30 - Angular 小學堂(四之四)

昨天的登入頁、購物車頁與結帳成功頁大家都有順利套完版嗎?!

如果沒有的話沒關係,看完今天的文章再繼續努力吧!!


結帳頁套版

今天一開始我們先來套結帳頁的版型,打開 assets/css/ 裡的 checkout.css 檔,一樣全部複製下來之後,貼到我們專案裡的 checkout.component.css 裡。

一樣要記得調整檔案路徑噢!!忘記怎麼修改的話可以看昨天的文章複習一下。

接著打開素材 pages/ 裡的 checkout-1.html,一樣只要複製中間的區塊就好:

Imgur

複製貼上到我們專案裡的 checkout.component.html 之後,就可以先來看一下畫面:

Imgur

耶!!套完了!!

還沒啦!!

這邊其實會稍微要大家跟著我處理一下,因為當初其實沒有想說要切路由,直接用狀態切換就好。

不過在寫文章的時候又想說,就多練習一些巢狀路由的設定也不錯,才把這邊切成其他路由。

所以接下來就要開始處理這部分了!!

首先先打開我們專案的 checkout.component.css 檔,然後開啟搜尋/取代的功能 (masOS 請按下 command + option + F , Windows 請按下 ctrl + H ,將整個檔案裡的 article 全部取代為 article /deep/

像這樣:

Imgur

最上面那兩個我移除是因為那兩個不需要改成 article /deep/

接著再到打開 checkout.component.html ,然後剪下這個區塊裡的程式碼:

Imgur

整個貼到 customer-info.component.html 裡,儲存後看一下畫面,應該就會跟剛剛一開始的時候長得一模一樣。

感謝邦友 jackson09 的留言,我才發現這段描述沒寫完全。^^"
重新調整如下:

整個貼到 customer-info.component.html 裡並儲存後,要在 checkout.component.html 剛剛剪下的那個區塊裡加上一個路由插座 <router-outlet></router-outlet> ,像是這個樣子:

<div class="container">

  <!-- 表單區域開始 -->
  <article class="font-light-green bg-dark-green">

    <!-- 在這裡加上路由插座 -->
    <router-outlet></router-outlet>

  </article>
  <!-- 表單區域結束 -->

  <!-- 側邊欄區域開始 -->
  <aside class="desktop">
    
    <!-- ... -->
    
  </aside>
  <!-- 側邊欄區域結束 -->

</div>

儲存後看一下畫面,應該就會跟跟剛剛一開始的時候長得一模一樣囉!

Angular 小知識:

在 Angular 裡,每個 component 的 css 是互不影響的,避免命名衝突以及讓程式碼更容易被維護。
不過如果像遇到這種一定要從父層設定樣式,且要能夠一併調整子層 Component 的樣式時,就需要加上 /deep/ 的特殊字來讓樣式能夠套用在子層上。

畫面沒問題之後,我們接下來要先改先打開 customer-info.component.ts ,然後 import 我們之前定義好的 'appPath' 進來,像這樣:

// Constant
import { appPath } from './app-path.const';

並在 CustomerInfoComponent 裡新增一個名為 path 的屬性,再將 appPath 指定給它,像這樣:

export class CustomerInfoComponent implements OnInit {

  /**
   * 給 Template 用的路由定義
   *
   * @memberof CustomerInfoComponent
   */
  path = appPath;

}

以上的動作在接下來的 PaymentInfoComponent 跟 ReceiptInfoComponent 都一樣,就不再贅述囉!

接著把 customer-info.component.html 裡的下一步這個按鈕的連結改為:

<a [routerLink]="['..', path.checkoutFlow.paymentInfo]">下一步</a>

咦?有發現這個連結長得有點奇怪嗎?

其實如果昨天有練習購物車頁的套版的話,應該會遇到路由設定的問題,因為在巢狀路由下的時候,連結是往子層下面找的。

我們用 Augury 幫我們畫的圖來解釋:

Imgur

是不是超清楚的?! Augury 超好用的!!

一開始我們的共用層連結是在 AppComponent 裡,所以在設定連結的時候只要像這樣即可:

<ul>
  <li><a [routerLink]="[path.home]">首頁</a></li>
  <li><a [routerLink]="[path.products]">甜點</a></li>
  <li><a [routerLink]="[path.login]">登入</a></li>
</ul>

因為路由機制會從 Appcomponent 往下找的關係,所以從上圖中的結構就能清楚地看到,路由機制可以找到相對應的路徑,沒有問題。

但如果像是現在我們要設定的連結是在 CustomerInfoComponent 裡的時候,如下圖紅框處:

Imgur

這時候如果我們連結設成這樣:

<a [routerLink]="path.checkoutFlow.paymentInfo">下一步</a>

可想而知,路由機制會往 CustomerInfoComponent 的下一層去找路由。

但有這個路由嗎?沒有。

所以這時候我們可以這樣設:

<a [routerLink]="['..', path.checkoutFlow.paymentInfo]">下一步</a>

'..' 有一種回上一層的感覺,然後再由上一層的位置往下找 path.checkoutFlow.paymentInfo 這個變數儲存的路徑,也就是 PaymentInfoComponent 所對應的路由。

設定好之後,應該就能夠正常導頁了:

Imgur

再來讓我們把 PaymentInfoComponent 跟 ReceiptInfoComponent 迅速地套版套一套,他們各自對應的素材檔案是 pages/ 底下的 checkout-2.htmlcheckout-3-1.htmlcheckout-3-2.html

PaymentInfoComponent 的套版非常簡單,只要將 checkout-2.html 裡的這塊程式碼:

Imgur

複製貼上到 payment-info.component.ts 裡,然後一樣修改一下連結:

<a [routerLink]="['..', path.checkoutFlow.receiptInfo]">下一步</a>

這樣就完工了,超簡單的吧?!

ReceiptInfoComponent 的套版其實也差不多,一樣將 checkout-3-1.html 裡的這塊程式碼:

Imgur

複製貼到上 receipt-info.component.ts 裡,然後修改一下連結:

<p class="full button"><a [routerLink]="['/', path.success]">確認結帳</a></p>

咦?!這次連結怎麼又不一樣了?!

這是因為之前用的 .. ,它最多只能用一次,也就是像上面這樣:

<a [routerLink]="['..', path.checkoutFlow.receiptInfo]">下一步</a>

但如果要用兩次以上像是這樣的話:

<a [routerLink]="['..', '..', path.success]">下一步</a>

路由機制讀取到的路徑就會變成 /checkout/../success ,然後就會被萬用路由轉回 /checkout/customer-info 了。

接下來打開素材 assets/css/ 底下的 checkout-3.css ,複製這邊的樣式設定到 receipt-info.component.css 裡,然後用上次提到的全部取代的功能將 .container article 全部取代為空字串。

像這樣:

Imgur

樣式設定完之後,這頁有個可以 swtich 的按鈕可以切換要顯示電子發票的表單區塊還是郵寄發票的表單區塊:

Imgur

這塊要怎麼處理咧?!

其實很簡單,利用我們學過的事件綁定再加上 *ngIf 來判斷要顯示的區塊就可以了!

首先我想先用 CLI 建個列舉來代表不同寄送方式:

ng generate enum checkout/receipt-info/send-type

然後打開 send-type.enum.ts , 輸入以下程式碼:

/**
 * 寄送發票的方式的列舉
 *
 * @export
 * @enum {number}
 */
export enum SendType {

  // 用 Email 寄送電子發票
  EMAIL,

  // 寄送實體發票到指定地址
  ADDRESS

}

接下來將這個列舉 import 到 receipt-info.component.ts 裡使用並將程式碼調整成這樣:

import { Component, OnInit } from '@angular/core';

// Constant
import { appPath } from 'src/app/app-path.const';

// Enum
import { SendType } from './send-type.enum';

@Component({
  selector: 'app-receipt-info',
  templateUrl: './receipt-info.component.html',
  styleUrls: ['./receipt-info.component.css']
})
export class ReceiptInfoComponent implements OnInit {

  /**
   * 給 Template 用的路由定義
   *
   * @memberof ReceiptInfoComponent
   */
  path = appPath;

  /**
   * 給 Template 用的寄送方式列舉
   *
   * @memberof ReceiptInfoComponent
   */
  sendType = SendType;

  /**
   * 當前的寄送類型,預設使用 Email
   *
   * @memberof ReceiptInfoComponent
   */
  selectedType = SendType.EMAIL;

  constructor() { }

  ngOnInit() {
  }

  /**
   * 切換寄送類型
   *
   * @param {number} type - 欲切換的寄送類型
   * @memberof ReceiptInfoComponent
   */
  switch(type: number): void {
    this.selectedType = type;
  }

  /**
   * 傳入的寄送類型是否為當前所選擇的寄送類型
   *
   * @param {number} type - 欲判斷的寄送類型
   * @returns {boolean}
   * @memberof ReceiptInfoComponent
   */
  didSelected(type: number): boolean {
    return this.selectedType === type;
  }

}

然後我們再把 receipt-info.component.ts 裡的這段 HTML :

<div class="segment font-light-green">
  <ul>
    <li class="selected"><a href="javascript:;">電子發票</a></li>
    <li><a href="checkout-3-2.html">郵寄發票</a></li>
  </ul>
</div>

改成:

<div class="segment font-light-green">
  <ul>

    <li [class.selected]="didSelected(sendType.EMAIL)">
      <a
        href="javascript:;"
        (click)="switch(sendType.EMAIL)"
      >
        電子發票
      </a>
    </li>

    <li [class.selected]="didSelected(sendType.ADDRESS)">
      <a
        href="javascript:;"
        (click)="switch(sendType.ADDRESS)"
      >
        郵寄發票
      </a>
    </li>

  </ul>
</div>

然後再把這段 HTML :

<form>

  <p class="full">
    <label for="email">電子郵件信箱</label>
    <input type="email" id="email" placeholder="example@email.com">
  </p>

  <p class="full">
    <label for="company-code">統一編號(選填)</label>
    <input type="text" id="company-code" placeholder="12345678">
  </p>

</form>

<ng-container></ng-container> 將兩個 <p></p> 像這樣包起來:

<form>

  <!-- 電子發票所需之表單區域開始 -->
  <ng-container>

    <p class="full">
      <label for="email">電子郵件信箱</label>
      <input type="email" id="email" placeholder="example@email.com">
    </p>

    <p class="full">
      <label for="company-code">統一編號(選填)</label>
      <input type="text" id="company-code" placeholder="12345678">
    </p>

  </ng-container>
  <!-- 電子發票所需之表單區域結束 -->
  
</form>

接著再新增一個 <ng-template></ng-template> ,然後把素材 pages/ 裡的 checkout-3-2.html 打開,把以下這段的 HTML 複製下來:

Imgur

貼到剛剛新增的 <ng-template></ng-template> 裡,像這樣:

<form>

  <!-- 電子發票所需之表單區域開始 -->
  <ng-container>
    <!-- ... -->
  </ng-container>
  <!-- 電子發票所需之表單區域結束 -->

  <!-- 郵寄發票所需之表單區域開始 -->
  <ng-template>

      <p class="address full">

        <label for="address">地址</label>

        <select name="city" id="city">
          <option value="高雄市">高雄市</option>  
        </select>

        <select class="float-right" name="county" id="county">
            <option value="新興區">新興區</option>  
        </select>

        <input type="text" id="address" placeholder="幸福路 520 號">

        <span class="same-checkbox">
          <input type="checkbox" name="same-address" id="same-address">
          <label for="same-address">同運送地址</label>
        </span>

      </p>

      <p class="full">
        <label for="company-code">統一編號(選填)</label>
        <input type="text" id="company-code" placeholder="12345678">
      </p>

  </ng-template>
  <!-- 郵寄發票所需之表單區域結束 -->
  
</form>

最後再把 *ngIf 的判斷加入:

<form>

  <!-- 電子發票所需之表單區域開始 -->
  <ng-container *ngIf="didSelected(sendType.EMAIL); else addressArea">
    <!-- ... -->
  </ng-container>
  <!-- 電子發票所需之表單區域結束 -->

  <!-- 郵寄發票所需之表單區域開始 -->
  <ng-template #addressArea>
    <!-- ... -->
  </ng-template>
  <!-- 郵寄發票所需之表單區域結束 -->
  
</form>

完成了!來看看效果吧:

Imgur

好的,如此一來我們就完成了結帳頁的套版了!!

至於甜點那頁的就留給你們練習囉?!

範例程式碼的部份我會上傳到 Github 上讓大家參考,如果有任何的問題也歡迎在文章底下留言或是傳訊給我。

目前的系列賽文章我預計會再發兩篇文章,一篇是關於如果已經跟著這個系列的文章練習過也研讀過一遍的話,接下來的學習方向以及學習資源分享;另外一篇則是完賽心得。

敬請期待!!


上一篇
[Angular 深入淺出三十天] Day 29 - Angular 小學堂(四之三)
下一篇
[Angular 深入淺出三十天] Day 31 - 三十天之後
系列文
Angular 深入淺出三十天33
0
開心學編程
iT邦新手 5 級 ‧ 2018-11-23 15:38:48

有個問題額,用ng build --prod打包的話,會報錯
Invalid configuration of route '': routes must have either a path or a matcher specified

看更多先前的回應...收起先前的回應...
Leo iT邦新手 4 級‧ 2018-11-23 18:35:36 檢舉

非常感謝你發現問題!!

麻煩你稍等我一下,我處理完再回報狀況讓你知道!

Leo iT邦新手 4 級‧ 2018-11-23 20:07:01 檢舉

開心學編程,我這邊試過是沒有問題的耶~~~@@

如下圖所示:

Imgur

會不會是你那邊有調整過程式碼?

你再試試看~ 如果有什麼問題再 PO 上來我們一起討論囉!

catstar iT邦新手 5 級‧ 2018-12-06 10:55:07 檢舉

同一樓問題,編譯時不會有問題
是必須用瀏覽器去檢視console才會出現
使用ng build 不會出錯
使用ng build --prod console報錯
部屬至IIS,ROUTE中的USEHASE已設為TRUE

Leo iT邦新手 4 級‧ 2018-12-13 09:37:52 檢舉

Hi Catstar,

抱歉這麼晚才發現你的留言 >_<"

所以你的問題是已經解決了嗎?還是還存在呢?

如果還存在,方便截圖讓我看一下完整的錯誤訊息嗎?

0
jackson09
iT邦新手 5 級 ‧ 2018-12-13 09:31:58

Leo大大 有問題詢問~

整個貼到 customer-info.component.html 裡,儲存後看一下畫面,應該就會跟剛剛一開始的時候長得一模一樣。<

剪下貼到customer-info.component.html,進行這一步後我個人資料清單不見了 T_T
https://ithelp.ithome.com.tw/upload/images/20181213/20113704fHgBAvf6XN.png
我的checkout.component.html、coustomer-info.component.html、checkout.component.css如下:
https://ithelp.ithome.com.tw/upload/images/20181213/20113704foSHDg6y4l.png

看更多先前的回應...收起先前的回應...
Leo iT邦新手 4 級‧ 2018-12-13 10:02:50 檢舉

Hi Jackson,

非常感謝你的留言!該段落是我沒有描述清楚。

我重新調整了一下描述,請你再看一次並練習看看。

如果還有問題請你務必讓我知道,感激不盡!

/images/emoticon/emoticon41.gif

感謝您的更新 已解決上述問題
但下一步我又卡關了 需要求救(sorry)

畫面沒問題之後,我們先把 customer-info.component.html 裡的下一步這個按鈕的連結改為:
<a [routerLink]="['..', path.checkoutFlow.paymentInfo]">下一步

把原本的a href改成a [routerLink]後它無法點擊連結
https://ithelp.ithome.com.tw/upload/images/20181213/20113704MVV7lbwECM.png
https://ithelp.ithome.com.tw/upload/images/20181213/20113704mEKMwe7PPY.png

點擊"下一步"後顯示error
https://ithelp.ithome.com.tw/upload/images/20181213/20113704cF15UjzGI0.png

Leo iT邦新手 4 級‧ 2018-12-13 11:32:49 檢舉

Hi Jackson,

抱歉,這邊又是我漏了敘述。

我重新調整過敘述了!麻煩你再看一次並練習看看。

如果還有問題請你務必讓我知道,感激不盡!

/images/emoticon/emoticon41.gif

感謝Leo大大的耐心回應s 我完成了!接下來要自己捉摸甜點頁/images/emoticon/emoticon13.gif

另外發現一些引用路徑s有誤

複製貼上到 payment-info.component.ts 裡,然後一樣修改一下連結:
複製貼到上 receipt-info.component.ts 裡,然後修改一下連結:
然後我們再把 receipt-info.component.ts 裡的這段 HTML :

這三句應該都是引用html檔而非ts檔 再次感謝你的文章/images/emoticon/emoticon41.gif

Leo iT邦新手 4 級‧ 2018-12-13 15:33:07 檢舉

Hi Jackson,

很高興這個系列文跟我都有幫上你的忙 ^^

加油!!/images/emoticon/emoticon12.gif

0
a405066
iT邦新手 5 級 ‧ 2019-09-04 16:24:05

HI 大大你好!

套板完成確認畫面跟route正常之後
也把甜點頁做出來了

但我想說試著做做看如果甜點名稱不要寫死
直接是一個ts檔
像這樣
https://ithelp.ithome.com.tw/upload/images/20190904/20120596gxqGaRsuWG.png

但想請問
我實際把甜點頁做出來之後(旁邊的甜點類別也可以做filter了!)
https://ithelp.ithome.com.tw/upload/images/20190904/20120596OVKecddxBG.png

可是因為我甜點項目很多,所以導致卷軸要卷很下面
可是我看到你最下面有一個這樣的東西,可以切換到下一頁,雖然是寫死的
https://ithelp.ithome.com.tw/upload/images/20190904/201205961jRG696ZAs.png

我想問問這個可以怎麼實作?
我有想到在上面綁click事件,是否還有更簡便的方法呢?

Leo iT邦新手 4 級‧ 2019-09-04 16:30:20 檢舉

Hi a405066,

恭喜你完成套版!!

沒錯,你的方向都很正確!

分頁按鈕是我我也會綁 click ,然後用頁碼去算當前要顯示資料中的幾筆到幾筆(例如第一頁顯示第 0 筆到 第 9 筆,共十筆)。

加油加油!!有問題儘管發問! :)

0
a405066
iT邦新手 5 級 ‧ 2019-09-04 18:11:54

HI 大大你好

謝謝你的回覆!!

我想再問個問題QQ 很抱歉 第一次學前端問題有點多QQ

我目前在實作點甜點按鈕下的加入購物車,然後點購物車可以看到剛剛點過的清單
https://ithelp.ithome.com.tw/upload/images/20190904/20120596woMkQfNEB2.png

但現在有個問題
巧克力熔岩我點了兩次,所以購物車產生了兩筆,可是我點其中一筆的'+'時,會跟著連動,兩筆的數量會變成一樣的,能幫我看看是為什麼嗎QQ

我在計算數量的地方,已經有傳入index了,應該不會這樣才對QQ
下面是相關的程式碼...

在計算數量的地方,我是直接把清單上記錄的數量改掉,改掉後再修改該筆紀錄的總金額,讓前端看到的畫面可以連動
https://ithelp.ithome.com.tw/upload/images/20190904/20120596aRkQBixIAV.png

component的地方則是直接傳入index
https://ithelp.ithome.com.tw/upload/images/20190904/20120596jJq2Uf7i5T.png

為什麼會導致他的連動呢QQ

Leo iT邦新手 4 級‧ 2019-09-04 18:21:11 檢舉

Hi a405066,

簡單來說,之所以會連動就是因為他們其實都參考到同一個變數位置的關係。

因為你目前較不熟悉 JavaScript 特性,你可以閱讀一下有關於 JavaScript「淺拷貝」與「深拷貝」的相關資訊。

0
a405066
iT邦新手 5 級 ‧ 2019-09-12 15:40:51

HI 大大你好

我又來問問題了QQ

想請問 header和footer 只能放在appcomponent才能作用嘛?
因一樣的段落我放在appcomponet能顯示,但我移進別的component就被吃掉了QQ

Leo iT邦新手 4 級‧ 2019-09-12 16:31:20 檢舉

Hi a405066,

當然可以移呀,但是你要把相關宣告也都移走才會有作用噢!

我要留言

立即登入留言