มีอะไรใหม่ใน Angular 17
10/11/2566
ยุคเรเนซองส์ของ Angular มาถึงแล้ว! พบกับ Angular เวอร์ชั่นใหม่ล่าสุด ที่นอกจากจะมาพร้อมโลโก้ใหม่ไฉไลกว่าเดิมแล้ว ยังมาพร้อมฟีเจอร์คับคั่งที่เหมือนยกเครื่อง Angular ใหม่ให้แจ่มกว่าเดิมอีกด้วย
สารบัญ
- ลุคใหม่ไฉไลกว่าเดิม
- Doc ใหม่น่าใช้กว่าเดิม
- Built-in control flow
- Deferrable view
- ปรับปรุง hybrid rendering
- Vite และ esbuild
- Dependency injection ใน DevTools
- Standalone API ตั้งแต่ต้น
- ขั้นต่อไปของ reactivity
- ทดลองซัพพอร์ต View Transitions API
- Input value transforms
- Style และ styleUrls เป็น string
- ไฮไลต์อัพเดทจาก Community
- อัพเดทอื่นๆ
- สรุป
ลุคใหม่ไฉไลกว่าเดิม
ในช่วงปีที่ผ่านมานี้ Angular ได้ออกฟีเจอร์ใหม่ๆมามากมายไม่ว่าจะเป็น Angular Signal, SSR Hydration, Standalone Component, Directive Composition และอื่นๆอีกนับไม่ถ้วน แต่ว่าโลโก้ Angular นั้นยังไม่แตกต่างจากยุค AngularJS ซักเท่าไหร่ เพื่อเป็นการสื่อถึงความเปลี่ยนแปลงของ Angular และนำไปสู่ความทันสมัยในอนาคต ทางทีมจึงได้เปลี่ยนดีไซน์ของโลโก้ Angular ใหม่มาเป็นโลโก้แบบด้านล่างครับ
Doc ใหม่น่าใช้กว่าเดิม
นอกจากโลโก้ใหม่แล้ว ทางทีม Angular ยังได้ทำเว็บ document ขึ้นมาใหม่ซึ่งก็คือ angular.dev โดยเว็บใหม่นี้จะประกอบไปด้วยโครงสร้างใหม่ ไกด์ใหม่ คอนเทนต์ใหม่ และยังเป็นแพลตฟอร์มที่เป็น interactive ทำให้เราสามารถเรียนรู้ Angular ผ่านบราวเซอร์ได้เลย โดยทั้งหมดนี้สร้างมาจากขุมพลังของ WebContainers ครับ
โดยทางทีมวางแผนไว้ว่า angular.dev จะเป็น default เว็บของ Angular เริ่มตั้งแต่เวอร์ชั่น 18 เป็นต้นไปครับ
Built-in control flow
หลังจากที่ออก RFC มาเพื่อขอความคิดเห็นเกี่ยวกับ block template syntax แบบใหม่ ทางทีมได้เก็บ feedback และนำไปทบทวนและในที่สุดก็ออกมาเป็น control flow syntax ใหม่ที่ใช้ใน template ของ Angular ครับ โดย syntax ใหม่นี้มีข้อดีคือ
- มีความใกล้เคียงกับ syntax ใน JavaScript ทำให้เราไม่ต้องไปดู doc เวลาจะใช้
- Type checking ทำได้ดีขึ้น
- เป็น concept ที่เกิดตอน build ทำให้ลดงานที่ต้องทำในตอน runtime และยังช่วยลดขนาดของ bundle ได้มากถึง 30KB นอกจากนี้ยังช่วยเพิ่มคะแนนของ Core Web Vital ได้อีกด้วย
- สามารถใช้ใน Angular Template ได้เลยไม่ต้อง import ใดๆเพิ่ม
- performance ดีขึ้นอย่างเห็นได้ชัด
Conditional statements
ลองเปรียบเทียบกันระหว่าง syntax แบบเก่ากับแบบใหม่ครับ
<!-- แบบเก่า -->
<div *ngIf="loggedIn; else anonymousUser">
The user is logged in
</div>
<ng-template #anonymousUser>
The user is not logged in
</ng-template>
<!-- แบบใหม่ -->
@if (loggedIn) {
The user is logged in
} @else {
The user is not logged in
}
จะเห็นได้ว่าแบบใหม่เข้าใจง่ายขึ้น และเคส else
ก็เขียนได้ง่ายกว่ากว่าเดิม นอกจากนี้ยังสามารถใส่เคส else if
ได้อีกด้วย
ลองมาดูเคส switch
จะยิ่งเห็นว่าใช้งานง่ายกว่าเดิมครับ
<!-- แบบเก่า -->
<div [ngSwitch]="accessLevel">
<admin-dashboard *ngSwitchCase="admin"/>
<moderator-dashboard *ngSwitchCase="moderator"/>
<user-dashboard *ngSwitchDefault/>
</div>
<!-- แบบใหม่ -->
@switch (accessLevel) {
@case ('admin') { <admin-dashboard/> }
@case ('moderator') { <moderator-dashboard/> }
@default { <user-dashboard/> }
}
ด้วย syntax ใหม่จะทำเรื่อง type-narrowing ในแต่ละ case ได้ดีขึ้นซึ่งถ้าเป็น ngSwitch
จะทำไม่ได้ครับ
Built-in for loop
สำหรับ for loop syntax ใหม่จะหน้าตาแบบนี้ครับ
@for (user of users; track user.id) {
{{ user.name }}
} @empty {
Empty list of users
}
ใน @for
จะจำเป็นต้องใส่ track
โดยจะตามด้วย expression ซึ่งจะช่วยเรื่อง performance ได้ดีขึ้น อีกทั้งถ้าใน collection นั้นมีสมาชิกเป็น 0 เรายังสามารถใช้ @empty
เพื่อโชว์กรณีไม่มีสมาชิกใน collection ได้อีกด้วย
ด้านล่างจะเป็นตารางเปรียบเทียบประสิทธิภาพ @for
แบบ syntax ใหม่กับ ngFor
แบบเก่าครับ โดยประสิทธิภาพจะดีกว่าเดิมมากถึง 90% เลยครับ
control flow syntax ใหม่ออกมาเป็น developer preview แล้วใน Angular 17 นี้โดยเราสามารถทำการ migrate จาก syntax แบบเก่าโดยใช้คำสั่งด้านล่างนี้ครับ
ng generate @angular/core:control-flow
Deferrable view
ด้วย block syntax แบบใหม่ นอกจากเรื่องของ control flow แล้ว ทางทีมยังออก deferrable view เพื่อใช้ในการทำ lazy loading ซึ่งนอกจากจะทำให้ประสิทธิภาพของแอพดีขึ้นแล้ว ยังทำให้ developer experience ดีขึ้นอีกด้วยครับ
สมมุติว่าเรามี blog แล้วอยากจะ lazy load ส่วนที่เป็น user comment ถ้าก่อนหน้านี้เราจะต้องใช้ ViewContainerRef
และต้องคอยจัดการ cleanup, error หรือ show placeholder ด้วยตัวเองทั้งหมด แต่ด้วยการมาของ Deferrable view จะช่วยทำให้เราทำเรื่องที่ว่ามาทั้งหมดได้ง่ายขึ้นเยอะเลย เพียงแค่เขียนแบบนี้
@defer {
<comment-list />
}
โดยส่วนนี้ Angular จะทำตอน compile time ไม่ว่าจะเป็นการหา component directive หรือ pipe ที่อยู่ใน @defer
block แล้วทำการสร้าง dynamic import และจัดการขั้นตอนการ loading และการเปลี่ยนระหว่าง state ให้เราทั้งหมด
ถ้าเราจะทำการ lazy load component ตอนที่ยังไม่โชว์ใน viewport เราอาจจะใช้ IntersectionObserver
API ช่วยแต่ก็อาจจะทำได้ยาก แต่ด้วย @defer
เราสามาราถทำได้ง่ายๆแบบนี้
@defer (on viewport) {
<comment-list />
} @placeholder {
<!-- A placeholder content to show until the comments load -->
<img src="comments-placeholder.png">
}
จากตัวอย่างข้างบน Angular จะทำการ render placeholder ก่อนและรอจนกว่า viewport จะโชว์ เมื่อโชว์แล้วจึงจะทำการโหลด <comment-list/>
และพอโหลดเสร็จก็จะแทนที่ placeholder ด้วย <comment-list/>
ครับ
นอกจากนี้เรายังสามารถ handler loading state และ error state ได้ด้วยแบบนี้
@defer (on viewport) {
<comment-list/>
} @loading {
Loading…
} @error {
Loading failed :(
} @placeholder {
<img src="comments-placeholder.png">
}
Deferrable view ยัง built-in มาด้วย trigger หลายตัวดังนี้ครับ
on idle
จะทำการ lazy load เมื่อ browser ไม่ได้ทำงานหนักอะไรon immediate
ทำการ lazy load แบบอัตโนมัติโดยไม่ block browseron timer(<time>)
ทำการดีเลย์ด้วยเวลาที่กำหนดon viewport
และon viewport(<ref>)
สามารถกำหนด ref สำหรับ anchor element เมื่อ anchor element โชว์ก็จะทำการ lazy load componenton interaction
และon interaction(<ref>)
ทำการ lazy load เมื่อ user มี interaction กับ element ที่กำหนดon hover
และon hover(<ref>)
lazy load เมื่อ user hover element ที่กำหนดwhen <expr>
เป็น custom trigger ทำให้เราสามารถ lazy load ตามเงื่อนไขที่กำหนดได้
Deferrable view ยังสามารถกำหนดให้ทำการ prefetch dependency ก่อนจะทำการ render component ได้ด้วยครับ โดยเราสามารถเพิ่ม prefetch
ลงใน statement ของ @defer
แบบนี้
@defer (on viewport; prefetch on idle) {
<comment-list />
}
ปรับปรุง hybrid rendering
เมื่อเราใช้คำสั่ง ng new
จะมี option ให้เราสามารถเพิ่ม hybrid rendering (SSR/SSG) ลงไปในแอพได้เลยแบบในรูปด้านล่างนี้ครับ
หรือเราจะเพิ่ม SSR ด้วยตัวเองโดยใช้คำสั่งนี้ครับ
ng new my-app --ssr
Hydration ออกจาก developer preview
หลังจากที่ออกฟีเจอร์ hydration มาเป็น developer preview ใน Angular 16 มาในเวอร์ชั่นนี้ตัว Hydration ออกจาก developer preview และจะถูกเปิดเป็น default เมื่อสร้างแอพใหม่ที่เป็น Server Side Rendering ครับ
package @angular/ssr ใหม่
ทางทีม Angular ได้ย้าย Angular Universal repo ไปที่ Angular CLI repo เพื่อทำให้ Server side rendering integrate กับ Angular CLI ได้ดีขึ้น
เราสามารถเพิ่ม hybrid rendering ลงไปในแอพที่มีอยู่แล้วได้ด้วยคำสั่ง
ng add @angular/ssr
โดยคำสั่งข้างบนจะทำการสร้าง server entry point, เพิ่มความสามารถในการ build แบบ SSR และ SSG และเปิด hydration เป็น default ให้เราเลยครับ ซึ่งตัว @angular/ssr
เทียบเท่ากับ @nguniversal/express-engine
ถ้าแอพไหนยังใช้ตัว express engine อยู่ พอใช้คำสั่งด้านบนก็จะทำการ migrate เป็น @angular/ssr
ให้เราเลยครับ
Deploy app ด้วย SSR
ถ้าเราใช้ Firebase ในการ deploy ด้วยฟีเจอร์ใหม่ของ Firebase จะทำให้เราแทบไม่ต้อง config ก็สามารถ deploy ได้ง่ายๆแบบนี้ครับ
firebase experiments:enable webframeworks
firebase init hosting
firebase deploy
ส่วนถ้าใครใช้ monorepo ที่มีความซับซ้อนหรืออยากใช้ tool แบบ native ก็สามารถเพิ่ม AngularFire และ deploy ได้ด้วยคำสั่งนี้ครับ
ng add @angular/fire
ng deploy
สำหรับการ deploy ไปยัง edge worker ทางทีมได้เพิ่มการซัพพอร์ต ECMAScript module สำหรับ Angular SSR และเพิ่ม fetch
backend สำหรับ HttpClient
และยังร่วมมือกับทีม CloudFlare เพื่อเพิ่มขั้นตอน ในการช่วย deploy Angular แอพด้วยครับ
Lifecycle hook ใหม่
เพื่อปรับปรุง performance ของ Angular SSR และ SSG ทางทีมจึงต้องการหลีกเลี่ยงการทำ DOM emulation และ DOM manipulation โดยตรง ดังนั้นจึงจำเป็นต้องเพิ่ม lifecycle เพื่อใช้ในการ interact กับ element หรือเพื่อเริ่มการทำงานของ third-party library หรือวัดขนาด element เป็นต้น โดย lifecycle ใหม่จะมีดังนี้ครับ
afterRender
ใช้ register callback ที่ถูกเรียกทุกครั้งที่แอพ render เสร็จafterNextRender
ใช้ register callback ที่ถูกเรียกครั้งต่อไปที่แอพ render เสร็จ
เฉพาะ browser เท่านั้นที่จะเป็นคนเรียก lifecycle hook ทั้งสองอันนี้ทำให้เราสามารถใส่ custom DOM logic ใน component ได้อย่างปลอดภัยครับ ยกตัวอย่างถ้าเราอยากเริ่มต้นการทำงานของ chart library เราสามารถใช้ afterNextRender
ได้แบบนี้
@Component({
selector: 'my-chart-cmp',
template: `<div #chart>{{ ... }}</div>`,
})
export class MyChartCmp {
@ViewChild('chart') chartRef: ElementRef
chart: MyChart | null
constructor() {
afterNextRender(
() => {
this.chart = new MyChart(this.chartRef.nativeElement)
},
{ phase: AfterRenderPhase.Write }
)
}
}
แต่ละ lifecycle hooks สามารถกำหนด option phase
(read, write) เพื่อที่ Angular จะใช้ลด layout trash และเพิ่ม performance ได้ครับ
Vite และ esbuild
ใน Angular 16 ทางทีมได้เปิดให้ใช้ Vite และ esbuild เป็น develop preview ให้ได้ลองใช้ทั้งสองตัวนี้ในการ build และพบว่ามีความเร็วในการ build ดีขึ้นถึง 67% ใน Angular 17 ทางทีมเลยเลยเปิดให้ใช้ Vite และ esbuild เป็น default ในการ build Angular แอพครับ
นอกจากนี้ถ้าเราใช้ hybrid rendering จะพบว่าความเร็วในการ build ดีขึ้นถึง 87% และความเร็วตอน edit แล้ว refresh เวลาใช้ ng serve
เพิ่มขึ้น 80%
Dependency injection ใน DevTools
ด้วย debugging API ใหม่ ทางทีมได้สร้าง UI เพื่อใช้ทำการ inspect Angular แอพซึ่งจะทำให้เราสามารถ
- ดู dependency ของ component ใน component inspector
- ดู injector tree และ dependency resolution path
- ดู provider ที่ถูกประกาศในแต่ละ injector
สามารถดูเกี่ยวกับ DevTool เพิ่มเติมได้ที่นี่ ครับ
Standalone API ตั้งแต่ต้น
หลังจากที่เก็บ feedback เกี่ยวกับ standalone component มาประมาณปีครึ่ง ทางทีม Angular ก็ได้มั่นใจและทำการเปิดให้ใช้ Standalone API ตั้งแต่ต้นใน Angular 17 นี้ครับ โดยคำสั่ง ng generate
จะทำการสร้าง standalone component, directive และ pipe ให้เราเลยครับ
ส่วน NgModules ก็ยังอยู่ครับไม่ได้ไปไหน แต่ทางทีมก็แนะนำให้เราเปลี่ยนไปใช้ standalone API แทน ซึ่งก็จะมี schematic ที่ใช้ migrate ให้เป็น standalone API ตามคำสั่งด้านล่างนี้ครับ
ng generate @angular/core:standalone
ขั้นต่อไปของ reactivity
หนึ่งในสิ่งที่เป็นการเปลี่ยนแปลงสำคัญของ Angular framework ก็คือการเปลี่ยนไปใช้ signal-based reactivity system ครับ ซึ่งทางทีมก็ทำงานหนักเพื่อที่จะให้ยังเป็น backward compatible และทำงานร่วมกับ Zone.js ได้
โดยใน Angular 17 นี้ตัว Angular Signal ก็ได้ออกจาก developer preview แล้ว ยกเว้นแต่ฟังก์ชั่น effect
ที่ยังคงอยู่ใน developer preview ครับ
หลังจากนี้ทางทีมก็จะทำต่อในเรื่อง signal-based input, view query และอื่นๆอีกมากมายที่จะมาพร้อมกับ Angular 18 อย่างแน่นอน
ทดลองซัพพอร์ต View Transitions API
View Transitions API สามารถทำให้เราทำเรื่อง Transition ใน DOM ได้ง่ายขึ้น โดยใน Angular 17 ก็จะมีฟีเจอร์ที่ซัพพอร์ต View Transition API โดยตรง ซึ่งจะอยู่ใน Angular router โดยจะมีชื่อว่า withViewTransitions
ครับ เราสามารถใช้ฟีเจอร์นี้ได้แบบนี้
bootstrapApplication(App, {
providers: [provideRouter(routes, withViewTransitions())],
})
withViewTransitions
รับ config option object ที่มี property onViewTransitionCreated
ที่เป็น callback เพื่อใช้ควบคุม
- ว่าจะ skip บาง animation
- เพิ่ม class เพื่อ custom animation และเอา class เหล่านี้ออกเมื่อ animation เสร็จ
Input value transforms
สมมุติว่าเรามี component ที่รับ input เป็น boolean แบบนี้
@Component({
standalone: true,
selector: 'my-expander',
template: `…`,
})
export class Expander {
@Input() expanded: boolean = false
}
และอยากจะส่งค่าไปใน component แบบนี้
<my-expander expanded />
เราจะเจอ error ประมาณว่า string is not assignable to boolean ซึ่งตัว Input value transform ก็จะมาช่วยเราในจุดนี้ครับ โดยเราสามารถแก้เคสด้านบนได้แบบนี้
@Component({
standalone: true,
selector: 'my-expander',
template: `…`,
})
export class Expander {
@Input({ transform: booleanAttribute }) expanded: boolean = false
}
Style และ styleUrls เป็น string
ใน Angular component ตรงส่วนของ styles และ styleUrls ปกติรับแต่ array แต่ใน Angular 17 สามารถใส่เป็น string ได้เลยแบบนี้ครับ
@Component({
styles: `
...
`
})
// หรือแบบนี้
@Component({
styleUrl: 'styles.css'
})
ซึ่งเราก็ยังสามารถใส่เป็น array ได้อยู่เหมือนเดิมครับ แต่กรณีที่มีแค่ไฟล์เดียวแบบใหม่จะสะดวกกว่า
ไฮไลต์อัพเดทจาก Community
HttpClient
สามารถใช้ fetch ใน backend ได้แล้ว- custom
HttpTransferCache
- ซัพพอร์ต
nameChunks
ใน application builder
อัพเดทอื่นๆ
- เพิ่ม preconnect ใน image directive อัติโนมัติ
- Defer loading animations module
สรุป
ใน Angular 17 นี้ ทางทีม Angular ก็ได้เพิ่มฟีเจอร์ใหม่ๆ มากมายไม่ว่าจะเป็น control flow syntax แบบใหม่, deferrable view, ปรับปรุง hybrid rendering และอื่นๆอีกมากมาย เพื่อให้เข้ากับ Angular ในยุคเรเนซองส์ ทางทีมจึงมีการเปลี่ยนโลโก้ใหม่ให้ดูทันสมัยยิ่งขึ้น และยังมีการสร้างเว็บ document ใหม่ เพื่อรองรับการเปลี่ยนแปลงที่มี และอัพเดทใหม่ๆที่กำลังจะมาถึงด้วยครับ