Angular Tutorial

第五課 Angular基礎之四

Service與Routing

Outline

  • 複習: 內插 屬性繫結 事件繫結
  • Services: 資料服務 功能服務
  • Routing: 頁面瀏覽

複習: 內插 屬性繫結 事件繫結

期中考檢討

屬性繫結與內插純量變數(1/5)

元件版型 (template)

<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
  <div class="navbar-brand">
    <img [src]="logo" />
    {{ title }}
  </div>
  <!--略 -->
</nav>
import { Component } from '@angular/core';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent {
  title = '教士會館';
  logo = '/assets/images/house_icon_white.png';

  constructor() { }

}

元件 (component)

 屬性繫結: 讀取TS檔定義的屬性

 內插: 讀取TS檔定義的屬性

 

 

屬性繫結使用時機?
[引數]="屬性名稱"

屬性繫結與內插物件(2/5)

元件版型 (template)

<div class="col-md-5">
  <agm-map [latitude]="location.lat" [longitude]="location.lng" [zoom]="15">
    <agm-marker [latitude]="lat2" [longitude]="lng2"></agm-marker>
  </agm-map>
</div>
import { Component } from '@angular/core';

@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  styleUrls: ['./contact.component.css']
})
export class ContactComponent {
  title = 'My first AGM project';  // 純量: 字串
  lat2 = 25.174926;                 // 純量: 浮點數
  lng2 = 121.4336303;
  location = {                     // 物件: 兩個欄位
    lat: 25.174926,
    lng: 121.4336303
  };
}

元件 (component)

屬性繫結: 純量屬性

屬性繫結: 物件.欄位名稱

屬性繫結與內插陣列(3/5)

元件版型 (template)

<ul class="navbar-nav ml-auto">
  <li class="nav-item mr-5 display-5" *ngFor="let m of menu">
    <a href="#" class="nav-link">{{ m }}</a>
  </li>
</ul>
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'OnePageSite';    // 純量: 字串
  menu = [                  // 字串陣列
    '回到首頁',
    '鄰近景點', 
    '豐富菜色',
    '營業項目',
    '聯絡我們', 
  ];
}

元件 (component)

內插: 使用迴圈變數

迴圈語法

陣列名稱

❶​ 自訂迴圈變數

 

屬性繫結與內插物件陣列(4/5)

元件版型 (template)

<ul class="navbar-nav ml-auto">
  <li class="nav-item mr-5 display-5" *ngFor="let m of myMenu">
    <a href="{{ m.url }}" class="nav-link">{{ m.title }}</a>
  </li>
</ul>
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'OnePageSite';    // 純量: 字串
  myMenu = [                // 物件陣列: 兩個欄位
    { title: '回到首頁', url: '#'},
    { title: '鄰近景點', url: '#hero-section'},
    { title: '豐富菜色', url: '#feature-section'},
    { title: '營業項目', url: '#product-section'},
    { title: '聯絡我們', url: '#contact-section'}
  ];
}

元件 (component)

內插: 使用迴圈變數

迴圈語法

陣列名稱

❶​ 自訂迴圈變數

<!-- 內插 或 屬性繫結? -->
  <a href="{{ m.url }}"> 內插
  <a [href]="m.url"> 屬性繫結

 

屬性繫結與內插資料流向(5/5)

title = 'OnePageSite';
url = 'http://au.edu.tw/au4163';
price = 300;

內插

屬性繫結

<div class="container">
    <h1>{{ title }}</h1>
</div>
<div class="jumbotron">
    <img [src]="url" />
</div>

事件繫結

雙向繫結

事件繫結click事件(1/3)

元件版型 (template)

<div class="m-5">
  <button class="btn btn-lg" [class]="btnColor" (click)="clickme()">
    點選送出 
  </button>
</div>
import { Component } from '@angular/core';

// ...[略]...
export class AppComponent {
  btnColor = 'btn-success'; // BS4: 綠色按鈕

  clickme() {    // <-- 事件處理器
    if (this.btnColor === 'btn-success') {
      this.btnColor = 'btn-primary';   // BS4: 藍色按鈕
    } else {
      this.btnColor = 'btn-success';
    }
  }
}

元件 (component)

事件繫結

指定處理器

❶​ 綁定click事件

屬性繫結

按鈕顏色參考資料: Bootstrap 4官網 Buttons

 撰寫處理器

事件繫結click事件(2/3)

元件版型 (template)

<div class="m-5">時段選擇:
  <select (change)="changeOption($event)">
    <option *ngFor="let day of weekdays">{{ day }}</option>
  </select>
</div>
<div>你選擇的項目是: {{ choice }}</div>
import { Component } from '@angular/core';

// ...[略]...
export class AppComponent {
  weekdays = ['星期二', '星期三', '星期四', '星期五'];
  choice = this.weekdays[0];

  changeOption(e) { // 事件處理器
    console.log('已更換選項', e);
    this.choice = e.target.value;    
  }
}

元件 (component)

事件繫結

指定處理器

❶​ 綁定change事件

按鈕顏色參考資料: Bootstrap 4官網 Buttons

撰寫處理器

 系統變數$event為參數

屬性繫結與內插資料流向(3/3)

changeOption(e) { // 事件處理器
  console.log('已更換選項', e);
  this.choice = e.target.value;
}

內插

屬性繫結

<div class="m-5">時段選擇:
  <select (change)="changeOption($event)">
    <option">時段A</option>
    <option">時段B</option>
  </select>
</div>

事件繫結

雙向繫結

資料傳送: 需倚賴設定參數

Services

資料服務 功能服務

資料服務資料存取物件設計模式

元件

TS檔

HTML檔

屬性繫結

事件繫結

資料存取物件

如何設計為佳?

資料庫

資料模型TS

資料介面TS

資料服務

資料庫

實作

TS檔

使用

schema

對應

存取

元件

TS檔

HTML檔

屬性繫結

事件繫結

注入

呼叫服務

資料存取設計模式

Angular App架構

事件

屬性

app.component.html

app.component.ts

資料服務

功能服務

ng g service 服務名稱

Angular App架構Services

注入器

資料服務

功能服務

export class HeroService {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}

hero.service.ts

app.component.ts

import { HeroService } from './hero.service';
constructor(public hs: HeroServicve){
    this.hs.log('log服務');
}

建立與使用Services

資料模型TS

資料介面TS

資料服務

資料庫

實作

TS檔

使用

schema

對應

存取

元件

TS檔

HTML檔

屬性繫結

事件繫結

注入

呼叫服務

Ⓐ 新增_model資料夾
新增「資料模型.ts檔」

Ⓑ 在_model資料夾中
新增「資料界面.ts檔」

Ⓒ 建立服務

ng g service 服務名稱

實作資料存取

Ⓓ 使用服務

建立Service 新增資料模型

export class MenuItem {
    title: string; // 共兩個欄位
    url: string;

    constructor(t, u) { // 建立物件
        this.title = t;
        this.url = u;
    }
    // 另外可提供getTitle(), setTitle()等methods
}

資料模型:選單項目

Ⓐ資料模型

 資料模型檔:新增_model資料夾,新增menuitem.ts

❶ 為class檔,提供欄位定義

提供建立物件功能

提供個別欄位存取功能

建立Service 建立資料介面

import { MenuItem } from './menuitem';

export interface Menu {
    items: MenuItem[];   // 選單項目陣列

    getMenu(): MenuItem[]; // 回傳選單
}

資料介面:選單

 在_model資料夾,繼續新增menu.ts

❶ 引入選單項目if necessary

❷ 設定介面所需欄位此處只需一欄

❸ 設定介面應提供功能此處只有一個功能

Ⓑ 資料界面

建立Service 建立服務

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class MenuService {

  constructor() { }

}

Angular服務檔:預設內容

  新增服務

❶ Injectable: 可注射(注入其他元件)

Ⓒ 建立服務

ng g service _services/menu

❷ 'root': 代表所有元件都可用不需做其他設定

建立Service 建立服務

import { Injectable } from '@angular/core';
import { Menu } from '../_model/menu';

@Injectable({
  providedIn: 'root'
})
export class MenuService implements Menu {
  // 需定義介面設定的欄位

  constructor() {
  }
  
  // 需實作介面定義的所有函式
}

  實作介面

❶ 引入資料介面檔

Ⓒ 建立服務

❷ implements: 實作介面檔

❺ 需建立資料(從資料來源讀取)

建立Service 建立服務

import { MenuItem } from './../_model/menuitem';
import { Injectable } from '@angular/core';
import { Menu } from '../_model/menu';

@Injectable({
  providedIn: 'root'
})
export class MenuService implements Menu {
  items: MenuItem[];

  constructor() {
    this.items = [
      new MenuItem('回到首頁', '#'),
      new MenuItem('鄰近景點', '#hero-section'),
      new MenuItem('豐富菜色', '#feature-section'),
      new MenuItem('營業項目', '#product-section'),
      new MenuItem('聯絡我們', '#contact-section'),
    ];
  }

  getMenu(): MenuItem[] {
    return this.items;
  }
}

  實作介面

加入介面定義的欄位

Ⓒ 建立服務

因欄位需要,故引入

❺ 建立資料(資料來源: 直接設定)

 實作介面定義的所有函式

建立Service 使用服務

import { MenuService } from './../_services/menu.service';
import { Component, OnInit } from '@angular/core';
import { MenuItem } from '../_model/menuitem';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
  title = '教士會館';
  newMenu: MenuItem[];

  logo = '/assets/images/house_icon_white.png';

  constructor(public ms: MenuService) { }

  ngOnInit() {
    this.newMenu = this.ms.getMenu();
  }

}

 使用服務

 注射服務: 在建構子中定義引數

引入

 使用服務提供的函式

Ⓓ 使用服務

public 引數名稱: 服務名稱

元件 (component)

建立Service 使用服務

<!-- 省略 -->
<ul class="navbar-nav ml-auto">
    <li class="nav-item mr-5" *ngFor="let menuitem of newMenu">
      <a href="{{ menuitem.url }}" class="nav-link">
        {{menuitem.title}}
      </a>
    </li>
</ul>
<!-- 省略 -->

 使用服務

屬性繫結

Ⓓ 使用服務

版型 (component)

減少constructor複雜度

  • Angular元件生命週期
  • 使用ngOnInit()

元件生命週期

建立元件、指令

綁定的資料變數內容有所變動

<input [(ng-model)]="name">

元件初始化,僅執行一次!

清除元件,歸還記憶體空間

元件生命週期ngOnInit

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

...
export class NavbarComponent implements OnInit{
  ...
  constructor(private data: DataService) {}

  ngOnInit(): void {
    this.data.getSiteSettings().then(value=> this.settings = value);
  }
}

 1. import OnInit

 2. 無回傳值 (void)

練習

為下列Class建立 DAO

//....
export class FeatureComponent implements OnInit {
  food: Food[] = [
    {name:"素猴菇三杯雞(蛋奶素)", price: 300, img:"/assets/images/mushroom.jpg"},
    {name:"養生藥膳花雕雞",price: 300, img:"/assets/images/soup.jpg"},
    {name:"北海道鮭魚定食",price: 340, img:"/assets/images/fish.jpg"},
  ];
  ...
}

Router

  • 單頁面應用程式SPA

單頁面應用程式single page applications

單頁面

about.component.ts

+

about.component.html

contact.component.ts

+

contact.component.html

使用Router

範例:兩個頁面

  1. 首頁
  2. 關於

新建第二個頁面

ng g component intro
ng g component home

使用Router

  • 新建元件: 二個頁面 (home頁面 與 intro頁面)
  • 新增AppRoutingModule
    • 設定路徑(routes)
  • 使用Routing相關指令
    • <router-outlet>
    • routerLink指令

使用Router新建元件(1/3)

ng g component intro
import { Component, OnInit } from '@angular/core';

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

  constructor() { }

  ngOnInit() {
  }

}
<nav aria-label="breadcrumb" role="navigation">
  <div class="container">
    <ol class="breadcrumb bg-light">
      <li class="breadcrumb-item"><a href="#">首頁</a></li>
      <li class="breadcrumb-item active" aria-current="page">關於我們</li>
    </ol>
  </div>
</nav>
<div class="container">
  <div class="card text-center my-5">
    <div class="card-header">
      <h1 class="display-4">真理大學教士會館</h1>
    </div>
    <img class="card-img-top" src="/assets/images/house_outline.jpg">
    <div class="card-body">
      <p style="font-size: 1.25rem">教士會館前身為宣教士宿舍建於1875年,與馬偕故居同時建造,由馬偕博士親自設計監造,以作為宣教士住宿之用,因外型白色又稱「馬偕的小白宮」。原與馬偕故居為同型建築,二次大戰後租給德記洋行時加蓋了二樓,並在後院蓋了一座翹脊古厝。 馬偕時代住過這裏的有華雅各、閏虔益、黎約翰、吳威廉等助理牧師。日治時代偉彼得牧師住得最久,因此有「偉牧師館」之名。後來被淡江中學租為學寮稱為「玄武寮」。1965年以後為真理大學教士會館。
      </p>
    </div>
  </div>
</div>

注意此元件的

  • selector: 'app-intro'
  • 元件類別名稱:IntroComponent

intro.component.html

新的Bootstrap元件

Breadcrumb

breadcrumb

intro.component.ts

使用Router新建元件(2/3)

.jumbotron{
  position: relative;
  background: #000 url("/assets/images/house_outline.jpg") center center;
  width: 100%;
  height: 800px;
  background-size: cover;
  overflow: hidden;
}
<nav aria-label="breadcrumb" role="navigation">
  <div class="container">
    <ol class="breadcrumb bg-light">
      <li class="breadcrumb-item"><a href="#">首頁</a></li>
      <li class="breadcrumb-item active" aria-current="page">
            關於我們
      </li>
    </ol>
  </div>
</nav>

Breadcrumb元件

intro.component.css

使用Router新建元件(3/3)

ng g component home
import { Component, OnInit } from '@angular/core';

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

  constructor() { }

  ngOnInit() {
  }

}
<app-navbar></app-navbar>
<app-jumbo></app-jumbo>
<app-hero></app-hero>
<app-feature></app-feature>
<app-product></app-product>
<app-contact></app-contact>

將原來 AppComponent的內容改放到HomeComponent

home.component.html

AppComponent版型也貼到HomeComponent版型

home.component.ts

使用Router AppRoutingModule(1/5)

ng new新建專案時,第一個問題回答Y,自動產生 app-routing.module.ts

Router模組:AppRoutingModule

使用Router AppRoutingModule(2/5)

...
import { HomeComponent } from './app.component';
import { IntroComponent } from './intro/intro.component';

const routes: Routes = [
  {path: '', component: AppComponent},
  {path: 'introduction', component: IntroComponent}
];

摘錄app-routing.module.ts

路徑陣列

每一對路徑至少包含兩個屬性:

    (1) path: url相對路徑 (例:http://localhost:4200/introduction)

    (2) component: 要執行哪一個元件

使用Router使用<router-outlet>(3/5)

...
<router-outlet></router-outlet>
...

定義路徑

app.component.html

以http://localhost:4200/introduction 為例:

       IntroComponent的HTML內容會顯示在

      <router-outlet></router-outlet>之間

...
const routes: Routes = [
  {path: '', component: AppComponent},
  {path: 'introduction', component: IntroComponent}
];

摘錄app-routing.module.ts

使用Router使用<router-outlet>(4/5)

<app-navbar></app-navbar>

  <router-outlet></router-outlet>

  <app-contact></app-contact>
  <footer class="p-3 footer bg-dark text-muted">
    <div class="container">
      <p class="text-center">
        Copyright © 2017 教士會館. 版權所有
      </p>
    </div>
  </footer>

<router-outlet> 該放在哪裡?

app.component.html

app.component是個好地方

但並非一定要放在appComponent內

使用Router設定超連結: routerLink(5/5)

    ...
    menu: [
      { title: "教士會館", route: "/" },
      { title: "會館訊息", route: "/" },
      { title: "特色餐點", route: "/" },
      { title: "交通資訊", route: "/" },
      { title: "關於我們", route: "/introduction" }
    ],
    ...
<ul class="navbar-nav ml-auto">
    <li class="nav-item mr-5 display-5 dropdown" *ngFor="let menuitem of menu">
      <a [routerLink]="menuitem.route" class="text-white nav-link">
        {{ menuitem.title }}
      </a>
    </li>
</ul>

.ts檔

.html檔

綜合練習

  • 單頁: 視情況由多種元件組成
  • Router: 增加更多頁面

非同步執行:Promise

  • 什麼是Promise?

什麼是Promise?

Consumer

不需停下等待

app端

提出要求

承諾

提供服務

服務端

什麼是Promise?

Consumer

不需停下等待

app端

提出要求

承諾

提供服務

服務端

提供失敗

繼續執行

???

練習

  • 使用 ng g service建立服務
  • 使用Promise處理資料服務

Angular Tutorial

By Leuo-Hong Wang

Angular Tutorial

第五課:Angular基礎之四 Services與Routing

  • 1,412