สปากลายเป็นอินเทรนด์เป็นสิ่งที่ตรงกันข้ามกับเว็บไซต์ที่แสดงผลฝั่งเซิร์ฟเวอร์ มีข้อดีและข้อเสียเช่นเคย แต่สปาสามารถสร้างประสบการณ์ผู้ใช้ที่ราบรื่นซึ่งยากที่จะทำซ้ำโดยไม่ต้องใช้วิธีการดังกล่าว นี่เป็นส่วนหนึ่งของการลงทุนในเว็บแอปพลิเคชันในดินแดนที่ถูกครอบครองโดยแอปพลิเคชันเดสก์ท็อป โดยทั่วไปแล้วเว็บแอปพลิเคชันจะถูกวิพากษ์วิจารณ์ว่าซบเซาเมื่อเปรียบเทียบกับแอพเดสก์ท็อป แต่ความก้าวหน้าที่สำคัญในเทคโนโลยีเว็บ (โดยเฉพาะอย่างยิ่ง NodeJS และเครื่องยนต์ V8 ของ Google รวมถึงกรอบการทำงานเช่น Angular และ ReactJs) ปัญหาน้อยกว่าที่เคยเป็น
ฟังก์ชั่น SPAS โดยการโหลดหน้า HTML เดียวบนไคลเอนต์จากนั้นอัปเดตเนื้อหา (และ DOM) แบบไดนามิก - ตรงข้ามกับการดึงหน้า HTML ที่อัปเดตจากเซิร์ฟเวอร์เมื่อใดก็ตามที่ผู้ใช้คลิกที่ใดก็ได้
NodeJS
และ npm
โดยทำตามคำแนะนำที่เหมาะสมที่นี่TypeScript
จากที่นี่npm install -g @angular/cli
ng new my-project
cd my-project && ng serve
localhost:4200
ในเว็บเบราว์เซอร์ของคุณ หากคุณเปิด my-project/src/app/app.component.ts
คุณควรได้รับการต้อนรับด้วยสิ่งต่อไปนี้:
/** app.component.ts */
import { Component } from '@angular/core' ; // #1
@ Component ( { // #2
selector : 'app-root' , // #3
templateUrl : './app.component.html' , // #4
styleUrls : [ './app.component.css' ] // #5
} )
export class AppComponent {
title = 'app' ; // #6
}
มาทำลายสิ่งนี้กันหน่อย:
Component
จากโมดูล @angular
ที่อยู่ในโฟลเดอร์ node_modules
@Component
Decorator ทำเครื่องหมายคลาสเป็นองค์ประกอบเชิงมุม มัณฑนากรถูกพารามิเตอร์ด้วยวัตถุตัวเลือกซึ่งเราจะใช้พารามิเตอร์เพียงไม่กี่ตัวเท่านั้นselector
จะกำหนดสิ่งที่แท็ก HTML สำหรับส่วนประกอบนั้น-ตัวอย่างเช่นแท่นนี้จะถูกฉีดลงใน HTML โดยใช้ <app-root> … </app-root>
templateUrl
ใช้อาร์กิวเมนต์สตริงที่ชี้ไปที่ไฟล์ HTML ที่ทำหน้าที่เป็นส่วนมุมมองของส่วนประกอบ นอกจากนี้คุณยังสามารถใช้พารามิเตอร์ template
เพื่อเขียน HTML โดยตรงเมื่อเทียบกับการชี้ไปที่ไฟล์ โดยทั่วไปไม่แนะนำโดยทั่วไปเว้นแต่ส่วนมุมมองเป็นเพียงเส้นหรือสองบรรทัดstyleUrls
ใช้รายการสตริงซึ่งแต่ละสตริงเป็นเส้นทางไปยังไฟล์ CSStitle
สามารถอ้างอิงได้จาก app.component.html
นำทางไปยัง app.component.html
นี่คือจุดเข้าสู่แอปพลิเคชันของเราและ HTML ระดับสูงสุดที่คุณควรแก้ไข ทุกอย่างจะแสดงผลภายในบริบทนี้
ตอนนี้เราจะลบเนื้อหาของไฟล์นี้และแทนที่ด้วยสิ่งต่อไปนี้:
< div class =" container " >
< h1 > Live football scores </ h1 >
< div class =" score-card-list " >
< div class =" score-card " >
< span > Team 1 </ span >
< span > 0 : 0 </ span >
< span > Team 2 </ span >
</ div >
</ div >
</ div >
พลิกกลับไปที่เว็บเบราว์เซอร์ของคุณและคุณควรเห็นว่าหน้าอัปเดตโดยอัตโนมัติ ยอดเยี่ยม.
สายตาคุณอาจจะผิดหวังในขั้นตอนนี้ แต่เป็นงานที่กำลังดำเนินอยู่
วัสดุเชิงมุมเป็นห้องสมุดที่ดูแลโดยทีมงานจาก Google เพื่อให้ส่วนประกอบการออกแบบวัสดุที่ใช้งานง่ายสำหรับแอปพลิเคชันเชิงมุม สไตล์ของส่วนประกอบเหล่านี้สามารถปรับแต่งเนื้อหาของหัวใจของคุณได้ แต่ก็เป็นวิธีที่ง่ายในการอัพเกรดรูปลักษณ์และความรู้สึกของแอพต้นแบบที่มีความพยายามน้อยที่สุด
มาติดตั้งไลบรารีที่ต้องการด้วย npm install --save @angular/material @angular/cdk @angular/animations
สิ่งนี้จะดาวน์โหลดไฟล์ที่จำเป็นลงในโฟลเดอร์ node_modules
ของคุณและอัปเดตไฟล์ packages.json
อย่างเหมาะสม
คุณจะต้องบอก Angular เพื่อโหลดส่วนประกอบที่เกี่ยวข้อง คุณจะต้องทำ googling เกี่ยวกับวิธีการทำงานนี้ (นี่คือเทปเทป 101 ประเภทของการสอน) แต่โดยทั่วไปคุณเพียงแค่ต้องแก้ไขไฟล์ app.modules.ts
เพื่อรวมโมดูลเหล่านั้นที่คุณต้องการ
ก่อนอื่นเพิ่มภาพเคลื่อนไหวเช่น So:
import { BrowserAnimationsModule } from '@angular/platform-browser/animations' ;
@ NgModule ( {
...
imports : [ BrowserAnimationsModule ] ,
...
} )
จากนั้นเพิ่มการนำเข้าโมดูลที่เราต้องการ:
import { MatButtonModule , MatCardModule } from '@angular/material' ;
@ NgModule ( {
...
imports : [ MatButtonModule , MatCardModule ] ,
...
} )
โปรดทราบว่าคุณจะต้องนำเข้าส่วนประกอบใดก็ตามที่คุณใช้ในแอพของคุณ เหตุผลที่ไม่รวมห้องสมุดทั้งหมดคือการช่วยให้ webpack
ทำการเขย่าต้นไม้ซึ่งโดยทั่วไปแล้วจะนำรหัสที่ไม่ได้ใช้ออกมาเมื่อรวมรหัสทั้งหมดของเราลงในไฟล์ขนาดเล็ก .js
เมื่อเสร็จแล้วเราจะสามารถเข้าถึงส่วนประกอบที่กำหนดไว้ล่วงหน้าจำนวนมากและคุณสามารถนำเข้าได้ตามที่ต้องการ นอกจากนี้ยังเป็นที่น่าสังเกตว่าธีม prebuilt นั้นใช้งานง่าย
ในเชิงมุมส่วนประกอบเป็นโมดูลาร์ แต่ละองค์ประกอบมีไฟล์ CSS ของตัวเองซึ่งใช้กับมุมมองเฉพาะนั้นเท่านั้น ข้อยกเว้นอย่างหนึ่งคือไฟล์ styles.css
ที่พบในไดเรกทอรี src
ซึ่งใช้ทั่วโลก
สไตล์ไม่ได้เป็นจุดสนใจของบทช่วยสอนนี้ดังนั้นเราจะพยายามหลีกเลี่ยงมันมากที่สุดเท่าที่จะทำได้ในอนาคต เพื่อให้รู้สึกดีขึ้น เล็กน้อย เกี่ยวกับสิ่งที่เรากำลังทำอยู่นี่คือ CSS ง่าย ๆ ในการคัดลอกและวางลงในไฟล์ styles.css
(แนวปฏิบัติที่ดีกว่าจะเพิ่มบิตที่เกี่ยวข้องในไฟล์ CSS ของส่วนประกอบที่เหมาะสมสำหรับการอ้างอิงในอนาคต):
// Outer wrapper div
. container {
text-align : center;
}
// Wrapper for the score cards
. score-card-list {
display : flex;
flex-direction : column;
align-items : center;
}
// Each score card
. score-card {
width : 550 px ;
display : flex !important ;
}
// Home team score
. home {
flex : 1 ;
text-align : right;
}
// Away team score
. away {
flex : 1 ;
text-align : left;
}
// Score section of score card
. score {
width : 100 px ;
}
อัปเดต HTML ของคุณเพื่อรวมคลาส:
...
< mat-card class =" score-card " >
< span class =" home " > Team 1 </ span >
< span class =" score " > 0 : 0 </ span >
< span class =" away " > Team 2 </ span >
</ mat-card >
...
หากคุณสับสนเกี่ยวกับ flex
ฉัน ขอ แนะนำให้ตรวจสอบ Flexbox Froggy
ข้อดีอย่างหนึ่งของการใช้เฟรมเวิร์กเช่น Angular คือเราสามารถใช้ตรรกะการเขียนโปรแกรมบางอย่างได้โดยตรงใน HTML ในกรณีของเราสำหรับลูปจะทำให้ชีวิตง่ายขึ้นมาก
ลองทำสิ่งต่อไปนี้:
...
< mat-card class =" score-card " *ngFor =" let team of ['Arsenal', 'Liverpool', 'Tottenham']; let i=index " >
< span class =" home " > {{ team }} </ span >
< span class =" score " > 0 : 0 </ span >
< span class =" away " > Team {{ i+1 }} </ span >
</ mat-card >
...
สิ่งนี้แนะนำแนวคิดใหม่ที่หลากหลายและควรสร้างสิ่งต่อไปนี้:
แนวคิดที่ควรทราบ:
{{ 'Some text = ' + a_number }}
บริการถูกใช้เพื่อการเข้าถึงข้อมูลที่เป็นนามธรรมห่างจากส่วนประกอบ สิ่งนี้จะช่วยให้ส่วนประกอบยังคงผอมและมุ่งเน้นไปที่การสนับสนุนมุมมอง การทดสอบหน่วยและการบำรุงรักษารหัสนั้นง่ายขึ้นโดยการรักษาความรับผิดชอบการแยกนี้
ในการสร้างบริการโดยใช้ CLI คุณสามารถใช้ ng generate service football-data
(หรือ ng gs football-data
สำหรับคนที่สั้นตรงเวลา) สิ่งนี้จะสร้างไฟล์ใหม่สองไฟล์ในไดเรกทอรี project-dir/src/app
: football-data.service.ts
และ football-data.service.spec.ts
สิ่งนี้เป็นไปตามการประชุมเชิงมุมโดยที่ไฟล์ทดสอบจะได้รับส่วนขยาย .spec.ts
แม้ว่าการทดสอบจะมีประโยชน์และสำคัญ แต่มันก็อยู่นอกขอบเขตของการสอนนี้ทันทีเพื่อให้คุณสามารถเพิกเฉยได้ในขณะนี้ (ขออภัย @cornel)
เพิ่มบรรทัดต่อไปนี้ใน football-data.service.ts
:
import { Injectable } from '@angular/core' ;
import { HttpHeaders } from '@angular/common/http' ;
@ Injectable ( ) // Designate this class as an injectable service
export class FootballDataService {
// Token for accessing data on football-data.org (see: https://www.football-data.org/client/register)
HEADERS = new HttpHeaders ( { 'X-Auth-Token' : 'dc25ff8a05123411sadgvde5bb16lklnmc7' } ) ;
// Convenience constant
COMPETITION_URL = 'http://api.football-data.org/v1/competitions/' ;
// ID for the Premier League
PL_ID = 445 ;
constructor ( ) { }
}
บริการมีคำอธิบายประกอบด้วย @Injectable
ซึ่งบอกคอมไพเลอร์ว่าอินสแตนซ์ของบริการสามารถฉีดเข้าไปในส่วนประกอบได้ คุณต้องระบุผู้ให้บริการสำหรับแต่ละบริการ ผู้ให้บริการจัดเตรียมซิงเกิลอย่างมีประสิทธิภาพในระดับที่ระบุ
ตัวอย่างเช่นคุณสามารถเพิ่มบริการของคุณไปยังอาร์เรย์ของผู้ให้บริการใน app.module.ts
ซึ่งจะส่งผลให้อินสแตนซ์ซิงเกิลของบริการที่ถูกเปิดเผยตลอดทั้งแอพ มิฉะนั้นคุณสามารถเพิ่มลงในรายชื่อผู้ให้บริการของส่วนประกอบซึ่งจะส่งผลให้อินสแตนซ์ของซิงเกิลตันพร้อมใช้งานกับส่วนประกอบนั้นและส่วนประกอบใด ๆ ภายในบริบท
คำเตือน: มันง่ายต่อการสร้างการพึ่งพาวงกลมระหว่างบริการเมื่อแอปของคุณเติบโตขึ้น ระวังสิ่งนี้ ถ้า (เมื่อ) คุณเคยได้รับข้อความแสดงข้อผิดพลาดที่ดูเหมือนว่า Cannot resolve all parameters for MyDataService(?)
มันอาจเกี่ยวข้องกับปัญหาการพึ่งพาแบบวงกลม
เราจะใช้ประโยชน์จาก API ที่ยอดเยี่ยมได้อย่างอิสระที่ football-data.org คุณจะต้องได้รับคีย์ API ของคุณเอง แต่มันง่ายที่จะทำ - ทำตามคำแนะนำในเว็บไซต์ มีเอกสารเชิงลึกมากขึ้น แต่ 99% ของสิ่งที่คุณต้องการสามารถเห็นได้ในตัวอย่างเล็ก ๆ ที่ระบุไว้ที่นี่
สำหรับตัวอย่างนี้สิ่งที่เราต้องการทำจริงๆคือดึงข้อมูลเกมสำหรับเกมทั้งหมดในรอบปัจจุบัน (AKA "Game Week" หรือ "Match Day") /v1/competitions/{id}/fixtures
endpoint ส่งคืนข้อมูลนี้ แต่สำหรับรอบที่ผ่านมาทั้งหมดในฤดูกาลปัจจุบัน เพื่อให้ได้ข้อมูลสำหรับรอบเดียวเราจำเป็นต้องตั้งค่าพารามิเตอร์ matchday
เช่น /v1/competitions/{id}/fixtures?matchday=14
เพื่อให้ได้วันจับคู่ปัจจุบันเราสามารถขอตารางลีกได้เนื่องจากมันกลับมาสำหรับวันจับคู่ปัจจุบันโดยค่าเริ่มต้น
ก่อนอื่นเราจำเป็นต้องฉีดบริการ HttpClient
ลงใน FootballDataService
ของเราเพื่อใช้ประโยชน์จากฟังก์ชั่น HTTP ของ Angular:
import { HttpClient , HttpHeaders } from '@angular/common/http' ;
...
constructor ( private http : HttpClient ) { }
. . .
สำคัญ : การเพิ่มตัวแปรส่วนตัวลงในตัวสร้างของบริการเชิงมุมหรือส่วนประกอบพร้อมกับการประกาศประเภท typescript เฉพาะนั้นเป็นข้อมูลที่เพียงพอสำหรับเวทมนตร์ดำของ Angular ในการทำงาน ตอนนี้คอมไพเลอร์จะฉีดอินสแตนซ์ที่เหมาะสมลงในบริการนี้ดังนั้นคุณจึงสามารถเข้าถึงได้
มาเพิ่มฟังก์ชั่นเพื่อดึงตารางลีก (และวันจับคู่ปัจจุบัน) จากเซิร์ฟเวอร์:
...
getTable ( ) {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/leagueTable' ,
{ headers : this . HEADERS } ) ;
}
...
TypeScript จะช่วยคุณได้ แต่วิธีการใช้วิธีการสำหรับวิธี http.get
ของ Angular ของ Angular มีลักษณะดังนี้:
/**
* Construct a GET request which interprets the body as JSON and returns it.
*
* @return an `Observable` of the body as an `Object`.
*/
get ( url : string , options ?: {
headers ?: HttpHeaders | {
[ header : string ] : string | string [ ] ;
} ;
observe?: 'body' ;
params?: HttpParams | {
[ param : string ] : string | string [ ] ;
} ;
reportProgress?: boolean ;
responseType?: 'json' ;
withCredentials?: boolean ;
} ) : Observable < Object > ;
เครื่องหมายคำถามแสดงให้เห็นว่า headers
พารามิเตอร์ observe
, params
, reportProgress
ความก้าวหน้า, responseType
และ withCredentials
ล้วนเป็นตัวเลือกเป็นส่วนหนึ่งของวัตถุเสริมสำหรับ options
เราจะผ่านค่าสำหรับ url
และ options.headers
เท่านั้น
คุณอาจสงสัยว่าฟังก์ชั่น getTable()
ที่เราเพิ่งสร้างผลตอบแทน มันส่งคืนสตรีม Observable
Observable
นั้นเป็นหลักอย่างที่ Luuk Gruijs กล่าวไว้ "คอลเลกชันขี้เกียจที่มีค่าหลายค่าเมื่อเวลาผ่านไป" ซึ่งฟังดูบ้า แต่จริงๆแล้วค่อนข้างตรงไปตรงมา
ในระยะสั้นสตรีม Observable
นั้นถือว่าเป็น "เย็น" จนกว่าจะได้รับการสมัครเป็นสมาชิก - เช่นตัวแปรจะโหลดอย่างเกียจคร้านเมื่อใช้เอาต์พุต เมื่อสตรีมเป็น "ร้อน" มันจะอัปเดตสมาชิกทั้งหมดเมื่อใดก็ตามที่ค่าเปลี่ยน มันเป็นทางเลือกในการใช้ Promise
สำหรับการจัดการสถานการณ์ async ใน JavaScript
ในกรณีนี้คำขอ GET
จะถูกไล่ออกเมื่อตัวแปร Observable
จะถูกสมัครรับเนื่องจากอินเตอร์เฟส REST จะส่งคืนค่าเดียวต่อการโทรเท่านั้น
เราสามารถใช้พลังของการพิมพ์แบบคงที่ใน typeScript เพื่อให้ชัดเจนยิ่งขึ้น:
...
import { Observable } from 'rxjs/Observable' ;
...
getTable ( ) : Observable < any > {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/leagueTable' ,
{ headers : this . HEADERS } ) ;
}
...
ในความเป็นจริงเนื่องจากเอกสารประกอบฟุตบอล DATA.org บอกเราอย่างชัดเจนว่าจะคาดหวังอะไรจากการโทรที่เหลือเราจึงสามารถก้าวไปอีกขั้นหนึ่งและสร้างแบบจำลองวัตถุได้เช่นกันใน src/app/models/leagueTable.ts
:
import { Team } from './team' ;
export class LeagueTable {
leagueCaption : string ;
matchday : number ;
standing : Team [ ] ;
}
และใน src/app/models/team.ts
:
export class Team {
teamName : string ;
crestURI : string ;
position : number ;
points : number ;
playedGames : number ;
home : {
goals : number ;
goalsAgainst : number ;
wins : number ;
draws : number ;
losses : number ;
} ;
away : {
goals : number ;
goalsAgainst : number ;
wins : number ;
draws : number ;
losses : number ;
} ;
draws : number ;
goalDifference : number ;
goals : number ;
goalsAgainst : number ;
losses : number ;
wins : number ;
}
ซึ่งช่วยให้เราสามารถอัปเดต football-data.service.ts
เป็น:
import 'rxjs/add/operator/map' ;
import { LeagueTable } from './models/leagueTable' ;
...
getTable ( ) : Observable < LeagueTable > {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/leagueTable' ,
{ headers : this . HEADERS } )
. map ( res => res as LeagueTable ) ;
}
...
สิ่งนี้จะช่วยให้เรารักษาความมีสติของเราโดยการลดแบบจำลองทางจิตที่เราต้องการเพื่อให้ทันสมัยในขณะที่ทำงานกับวัตถุที่ซับซ้อนเนื่องจาก IDE สามารถแนะนำเราได้
หมายเหตุด้านข้าง: คำหลัก as
เพียงแค่บอก TypeScript เพื่อไว้วางใจเราเกี่ยวกับประเภทของวัตถุแทนที่จะพยายามหามันผ่านการตรวจสอบบางประเภท อันตราย แต่มีประโยชน์เช่นสิ่งที่น่าสนใจที่สุด
โอเคนำทางกลับไปที่ src/app/app.component.ts
และเพิ่มบรรทัดต่อไปนี้เพื่อฉีด FootballDataService
ลงในส่วนประกอบ:
import { FootballDataService } from './football-data.service' ;
...
export class AppComponent {
title = 'app' ;
constructor ( private footballData : FootballDataService ) { }
}
ตอนนี้เราจะเพิ่มวิธี ngOnInit
ลงในส่วนประกอบ นี่คือตะขอวงจรชีวิตมาตรฐานที่จัดทำโดย Angular ที่ยิงหลังจากคุณสมบัติทั้งหมดของข้อมูลของส่วนประกอบได้รับการเริ่มต้น โดยทั่วไปแล้วมันจะยิงในการเริ่มต้นของวัตถุ แต่ช้ากว่าวิธี constructor
เล็กน้อยซึ่งยิงก่อนที่จะลงทะเบียนอินพุตและเอาต์พุตทั้งหมดไปยังส่วนประกอบแล้ว
กฎทั่วไปของหัวแม่มือคือการวางรหัสที่คุณต้องการเรียกใช้ในการเริ่มต้นในวิธี ngOnInit
พิเศษนี้มากกว่าตัวสร้าง เพิ่มลงในส่วนประกอบเช่นนั้น:
import { Component , OnInit } from '@angular/core' ;
...
export class AppComponent implements OnInit {
...
constructor ( private footballData : FootballDataService ) { }
ngOnInit ( ) {
// Code you want to invoke on initialisation goes here
}
...
ในกรณีของเราเราต้องการโหลดตารางลีกเพื่อให้เราสามารถเพิ่มบางอย่างเช่นนี้:
...
ngOnInit ( ) {
// Load league table from REST service
this . footballData . getTable ( )
. subscribe (
data => console . log ( data ) ,
error => console . log ( error )
) ;
}
...
หากคุณเปิดคอนโซลในเว็บเบราว์เซอร์ของคุณคุณควรเห็นอะไรแบบนี้:
ยอดเยี่ยม. ตอนนี้เรามีข้อมูลเจ๋ง ๆ มากมายที่เราสามารถทำได้ในอนาคต (ไม่น้อยไปกว่าการเชื่อมโยงโดยตรงไปยัง Club Crest Images) แต่ตอนนี้เราสนใจในการแข่งขันในปัจจุบันจริงๆ
ตอนนี้ขอเพิ่มฟังก์ชั่นอื่นในบริการข้อมูลของเราเพื่อรับข้อมูลสำหรับรอบปัจจุบันของการแข่งขัน:
...
import { GameWeek } from './models/gameWeek' ;
...
getFixtures ( matchDay : number ) : Observable < GameWeek > {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/fixtures?matchday=' + matchDay , { headers : this . HEADERS } )
. map ( res => res as GameWeek ) ;
}
...
ด้วย GameWeek
และ Fixture
ที่กำหนดดังนี้:
// src/app/models/gameWeek.ts
import { Fixture } from './fixture'
export class GameWeek {
count : number ;
fixtures : Fixture [ ] ;
}
// src/app/models/fixture.ts
export class Fixture {
awayTeamName : string ;
date : string ;
homeTeamName : string ;
matchday : number ;
result : {
goalsAwayTeam : number ;
goalsHomeTeam : number ;
halfTime : {
goalsAwayTeam : number ;
goalsHomeTeam : number ;
}
} ;
status : 'SCHEDULED' | 'TIMED' | 'POSTPONED' | 'IN_PLAY' | 'CANCELED' | 'FINISHED' ;
_links : {
awayTeam : { href : string ; } ;
competition : { href : string ; } ;
homeTeam : { href : string ; } ;
self : { href : string ; } ;
} ;
}
ด้วยความรู้ที่ได้รับการจับคู่ใหม่ของเราเราสามารถขอข้อมูลเซิร์ฟเวอร์ที่เหลือเกี่ยวกับรอบปัจจุบันของการแข่งขัน อย่างไรก็ตามเราต้องรอการโทรครั้งแรกเพื่อให้เสร็จก่อนก่อนทำครั้งที่สอง การปรับโครงสร้างบางอย่างหมายความว่าเราสามารถทำได้ในการโทรกลับได้อย่างง่ายดาย:
import { Component , OnInit } from '@angular/core' ;
import { FootballDataService } from './football-data.service' ;
import { LeagueTable } from './models/leagueTable' ;
@ Component ( {
selector : 'app-root' ,
templateUrl : './app.component.html' ,
styleUrls : [ './app.component.css' ] ,
providers : [ FootballDataService ]
} )
export class AppComponent implements OnInit {
title = 'app' ;
table : LeagueTable ;
gameweek : GameWeek ;
constructor ( private footballData : FootballDataService ) { }
ngOnInit ( ) {
this . getTable ( ) ;
}
getTable ( ) {
this . footballData . getTable ( )
. subscribe (
data => {
this . table = data ; // Note that we store the data locally
this . getFixtures ( data . matchday ) ; // Call this function only after receiving data from the server
} ,
error => console . log ( error )
) ;
}
getFixtures ( matchDay : number ) {
this . footballData . getFixtures ( matchDay )
. subscribe (
data => this . gameweek = data , // Again, save locally
error => console . log ( error )
) ;
}
}
ตอนนี้เรากำลังไปที่ไหนสักแห่ง!
เนื่องจากเราได้ตั้งค่าข้อมูลเป็นตัวแปรสมาชิกบนองค์ประกอบ typeScript ของเราจึงสามารถเข้าถึงได้โดยตรงโดย HTML ที่เกี่ยวข้อง ในความเป็นจริงหากคุณใช้รหัส Visual Studio ตัวแก้ไขโอเพ่นซอร์สของ Microsoft คุณสามารถเพิ่มปลั๊กอินบริการภาษาเชิงมุมเพื่อให้ รหัส JavaScript เสร็จสมบูรณ์ใน HTML ! อัศจรรย์. และมันได้รับการดูแลโดยทีมเชิงมุม
มาแทนที่ข้อมูลจำลองจากก่อน:
< div class =" container " >
< h1 > Live football scores </ h1 >
< div class =" score-card-list " >
< mat-card class =" score-card " *ngFor =" let fixture of gameweek.fixtures " >
< span class =" home " > {{ fixture.homeTeamName }} </ span >
< span class =" score " > {{ fixture.result.goalsHomeTeam }} : {{ fixture.result.goalsAwayTeam }} </ span >
< span class =" away " > {{ fixture.awayTeamName }} </ span >
</ mat-card >
</ div >
</ div >
สังเกต gameweek?.fixtures
ไวยากรณ์: The ?
สัญลักษณ์ทำหน้าที่เป็นมือสั้นสำหรับ if (gameweek != null) { return gameweek.fixtures } else { return null }
และมันมีประโยชน์อย่างไม่น่าเชื่อเมื่อเข้าถึงตัวแปรที่จะได้รับจากการโทรแบบอะซิงโครนัส
et voila!
ส่วนต่อไปนี้ไม่จำเป็นอย่างเคร่งครัด แต่มันแสดงให้เห็นถึงวิธีการทำสิ่งต่าง ๆ ที่สำคัญและจะช่วยให้รหัสของเราเป็นแบบแยกส่วนและสามารถควบคุมได้หากเราตัดสินใจที่จะนำไปข้างหน้า (ดูที่ NativeScript เป็นวิธีการสร้าง แอพมือถือดั้งเดิมที่มีเชิงมุมและตัวพิมพ์)
เรากำลังจะทำบัตรคะแนนการติดตั้งเป็นส่วนประกอบของตัวเอง เริ่มต้นด้วยความช่วยเหลือจาก CLI: ng generate component score-card
(หรือ ng gc score-card
) สิ่งนี้จะสร้างไฟล์ .ts
, .html
และ .css
ใน src/app/score-card
เปิดการ์ด score-card.component.ts
ที่จะได้รับการต้อนรับจากมัณฑนากรที่คุ้นเคย:
...
@ Component ( {
selector : 'app-score-card' , // Note this!
templateUrl : './score-card.component.html' ,
styleUrls : [ './score-card.component.css' ]
} )
...
หมายเหตุฟิลด์ selector
-มันบอกเราถึงวิธีการเข้าถึงส่วนประกอบ (ในกรณีนี้โดยใช้แท็ก <app-score-card></app-score-card>
)
refactor รหัสของเราโดยการย้ายเนื้อจาก app.component.html
เป็น score-card.component.html
:
<!-- app.component.html -->
< div class =" container " >
< h1 > Live football scores </ h1 >
< div class =" score-card-list " >
< app-score-card *ngFor =" let fixture of gameweek?.fixtures " [fixture] =" fixture " > </ app-score-card >
</ div >
</ div >
<!-- score-card.component.html -->
< mat-card class =" score-card " >
< span class =" home " > {{ fixture.homeTeamName }} </ span >
< span class =" score " >
{{ fixture.result.goalsHomeTeam }} : {{ fixture.result.goalsAwayTeam }}
</ span >
< span class =" away " > {{ fixture.awayTeamName }} </ span >
</ mat-card >
หมายเหตุบิต [fixture]="fixture"
ภายในแท็ก <app-score-card>
> นี่คือวิธีที่เราผ่านข้อมูลระหว่างส่วนประกอบ
ในไวยากรณ์เชิงมุม [...]
แสดงถึงอินพุต (…)
แสดงถึงเอาต์พุตและ [(…)]
หมายถึงการผูกสองทาง [(…)]
เรียกอีกอย่างว่า "Banana Box Syntax" และคุณจะพบมันบ่อยครั้งในรูปแบบของ [(ngModel)]="someVariable"
นี่หมายถึงการเชื่อมโยงสองทางระหว่างค่าของตัวแปรและค่าของวัตถุ DOM นี่เป็นส่วน สำคัญ ของการใช้ Angular
ตัวอย่างเช่นเราสามารถแมปค่าของแท็ก input
โดยตรงกับตัวแปรที่แสดงบนหน้าจอและ DOM จะได้รับการอัปเดตโดยอัตโนมัติเมื่อใดก็ตามที่ค่าขององค์ประกอบ input
เปลี่ยนไป:
< p >
What is your name?
< input type =" text " [(ngModel)] =" name " />
</ p >
< p >
Your name: {{ name }}
</ p >
คุณสามารถตรวจสอบตัวอย่าง Plunker ได้ที่นี่
กลับไปที่ฟุตบอล: เพื่อที่จะได้รับค่าอินพุตลงในส่วนประกอบเราต้องอัปเดต score-card.component.ts
ดังนี้:
import { Component , Input } from '@angular/core' ;
import { Fixture } from '../models/fixture' ;
@ Component ( {
selector : 'app-score-card' ,
templateUrl : './score-card.component.html' ,
styleUrls : [ './score-card.component.css' ]
} )
export class ScoreCardComponent {
@ Input ( ) fixture : Fixture ; // Note the decorator
constructor ( ) { }
}
มีสองวิธีที่ชัดเจนในการส่งข้อมูลระหว่างส่วนประกอบ: การใช้ @Input
/ @Output
และการใช้บริการ
@Input()
วิธีแรกมีประโยชน์สำหรับการส่งข้อมูลระหว่างผู้ปกครองและเด็ก ๆ แต่อาจน่าเบื่อหากข้อมูลจำเป็นต้องเดินทางผ่านเลเยอร์ซ้อนกัน ข้อมูลสามารถส่งผ่านไปยังส่วนประกอบเด็กโดยใช้ @Input
Decorator ตัวแปรอินพุตนี้จะถูกส่งผ่านโดยการอ้างอิงหากเป็นวัตถุหรือส่งผ่านตามค่าหากเป็นแบบดั้งเดิม
// someComponent.ts
// ...
import { Input } from '@angular/core'
// ...
export class SomeComponent {
// @Input() variables get set after the `constructor(...)`
// method, but before `ngOnInit()` fires
@ Input ( ) aNumber : number ; // This gets set via parent HTML
// ...
}
<!-- someComponentParent.html -->
< h1 >
I am a parent where SomeComponent gets rendered
</ h1 >
<!-- Pass a value to the aNumber variable -->
< some-component [aNumber] =" 48 " > </ some-component >
@Output()
ข้อมูลยังสามารถปล่อยออกมาจากเด็กไปยังส่วนประกอบหลักโดยใช้มัณฑนากร @Output()
นี่ไม่ใช่การเชื่อมโยงทิศทาง แต่เป็นตัวปล่อยเหตุการณ์ที่ยิงในเวลาที่กำหนดไว้ล่วงหน้า กรณีการใช้งานทั่วไปจะแจ้งให้ผู้ปกครองทราบเมื่อมีการเปลี่ยนแปลงค่าในเด็ก
// someComponent.ts
// ...
import { Input , Output , EventEmitter } from '@angular/core'
// ...
export class SomeComponent {
@ Input ( ) aNumber : number ;
// Emits an event (of type `number`) to the parent
@ Output ( ) numberChanged : EventEmitter < number > = new EventEmitter < number > ( ) ;
// ...
// Event emitters need to be triggered manually
// Any object can be emitted
emitValueChanged ( ) : void {
this . numberChanged . emit ( this . aNumber ) ;
}
}
<!-- someComponentParent.html -->
< h1 >
I am a parent where SomeComponent gets rendered
</ h1 >
<!-- Pass a value to the aNumber variable -->
< some-component [aNumber] =" 48 " (valueChanged) =" aNumberChanged($event) " > </ some-component >
// someComponentParent.ts
export class SomeParentComponent {
// ...
aNumberChanged ( updatedNumber : number ) : void {
console . log ( `aNumber changed to ${ updatedNumber } ` ) ;
}
// ...
}
ใช้บริการ
มีหลายวิธีในการใช้บริการเพื่อส่งผ่านข้อมูลระหว่างส่วนประกอบ รายละเอียดอยู่นอกเหนือขอบเขตของบทช่วยสอนนี้ แต่มันก็คุ้มค่าที่จะสังเกตเห็นการมีอยู่ของเทคนิคดังกล่าวเพราะอาจจำเป็นต้องใช้ในแอปพลิเคชันที่ค่อนข้างซับซ้อน
รูปแบบทั่วไปคือการกำหนดสตรีม Observable
ในบริการซึ่งส่วนประกอบใดที่สามารถผลักดันข้อความหรือสมัครสมาชิกเพื่อแจ้งข้อความใหม่ นี่คือรถบัสกิจกรรมได้อย่างมีประสิทธิภาพ
ในกรณีทั่วไปต้องพิมพ์ข้อความเพื่อให้ผู้ฟังสามารถมองเห็นสิ่งที่ใช้ได้ เป็นตัวอย่างที่ค่อนข้างไร้สาระบริการสามารถปล่อย PersonNameChangedEvent
ซึ่ง PersonComponent
สามารถตอบสนองได้ในขณะที่ LandingPageComponent
อาจเลือกที่จะเพิกเฉยต่อเหตุการณ์นี้โดยสิ้นเชิง
Angular ให้กรอบการทำงานของเสาหินและมีความเห็นสูงภายในเพื่อสร้างแอปพลิเคชัน สิ่งนี้ให้ข้อได้เปรียบของโครงสร้าง - ธรรมชาติที่มีความเห็นของเฟรมเวิร์กช่วยให้ผู้ใช้สามารถออกแบบแอพที่ปรับขนาดได้จากการเดินทางและความซับซ้อนจำนวนมากถูกซ่อนอยู่จากนักพัฒนา ในทางกลับกันการใช้ Angular (และ typescript สำหรับเรื่องนั้น) แนะนำรหัสหม้อไอน้ำจำนวนมากที่อาจทำให้คุณช้าลงหากคุณกำลังสร้างแอปพลิเคชันขนาดเล็กดังนั้นมันจึงคุ้มค่าที่จะพิจารณาว่าแอปกำลังจะไปที่ไหนก่อนที่จะเข้าสู่ Angular
อย่างไรก็ตาม CLI เชิงมุมมาไกลและเนื่องจากการยกของหนักเท่าไหร่ฉันอาจจะใช้ Angular สำหรับเกือบทุกโครงการในอนาคตอันใกล้นี้