รับ Angular 2 ของคุณ: อัปเกรดจาก 1.5

เผยแพร่แล้ว: 2022-03-11

ฉันเริ่มต้นจากต้องการเขียนคำแนะนำทีละขั้นตอนสำหรับการอัปเกรดแอปจาก Angular 1.5 เป็น Angular 2 ก่อนที่ฉันจะได้รับแจ้งจากบรรณาธิการอย่างสุภาพว่าเธอต้องการบทความมากกว่านวนิยาย หลังจากไตร่ตรองอย่างถี่ถ้วนแล้ว ฉันยอมรับว่าฉันต้องเริ่มต้นด้วยการสำรวจกว้างๆ เกี่ยวกับการเปลี่ยนแปลงใน Angular 2 โดยทำทุกประเด็นที่กล่าวถึงในบทความ Getting Past Hello World ของ Jason Aden ในบทความ Angular 2 …อ๊ะ. ไปข้างหน้าและอ่านเพื่อดูภาพรวมของคุณลักษณะใหม่ของ Angular 2 แต่สำหรับแนวทางปฏิบัติ โปรดใช้เบราว์เซอร์ของคุณที่นี่

ฉันต้องการให้สิ่งนี้กลายเป็นซีรีส์ที่รวมกระบวนการทั้งหมดของการอัปเกรดแอปสาธิตของเราเป็น Angular 2 ในที่สุด เรามาเริ่มต้นด้วยบริการเดียว มาสำรวจโค้ดกันแบบคดเคี้ยวกัน แล้วฉันจะตอบคำถามที่คุณอาจมี เช่น….

'โอ้ ไม่ ทำไมทุกอย่างถึงแตกต่างกัน'

เชิงมุม: ทางเก่า

หากคุณเป็นเหมือนฉัน คู่มือเริ่มต้นอย่างรวดเร็วของ Angular 2 อาจเป็นครั้งแรกที่คุณเคยดู TypeScript ตามเว็บไซต์ของตัวเองอย่างรวดเร็วจริง ๆ TypeScript คือ "ชุด superset ของ JavaScript ที่คอมไพล์เป็น JavaScript ธรรมดา" คุณติดตั้งทรานสปิลเลอร์ (คล้ายกับ Babel หรือ Traceur) และจบลงด้วยภาษาอัศจรรย์ที่รองรับคุณสมบัติภาษา ES2015 และ ES2016 รวมถึงการพิมพ์ที่รัดกุม

คุณอาจรู้สึกอุ่นใจที่รู้ว่าไม่มีการตั้งค่าที่ลึกลับนี้ไม่จำเป็นอย่างยิ่ง การเขียนโค้ด Angular 2 ใน JavaScript แบบธรรมดานั้นไม่ยากอย่างยิ่ง แม้ว่าฉันจะไม่คิดว่ามันคุ้มค่าที่จะทำเช่นนั้น เป็นเรื่องดีที่จะจดจำอาณาเขตที่คุ้นเคย แต่สิ่งใหม่และน่าตื่นเต้นมากมายเกี่ยวกับ Angular 2 คือวิธีคิดแบบใหม่แทนที่จะเป็นสถาปัตยกรรมใหม่

โพสต์นี้จะกล่าวถึงการอัปเกรดบริการเป็น Angular 2 จาก 1.5

มีอะไรใหม่และน่าตื่นเต้นเกี่ยวกับ Angular 2 คือวิธีคิดแบบใหม่แทนที่จะเป็นสถาปัตยกรรมใหม่
ทวีต

มาดูบริการนี้ที่ฉันอัปเกรดจาก Angular 1.5 เป็น 2.0.0-beta.17 กัน เป็นบริการที่เป็นมาตรฐานของ Angular 1.x โดยมีคุณลักษณะที่น่าสนใจเพียงไม่กี่อย่างที่ฉันพยายามจะสังเกตในความคิดเห็น มันซับซ้อนกว่าแอปพลิเคชันของเล่นมาตรฐานของคุณเล็กน้อย แต่สิ่งที่ทำจริง ๆ คือการค้นหา Zilyo ซึ่งเป็น API ที่ให้บริการฟรีซึ่งรวบรวมรายชื่อจากผู้ให้บริการเช่าเช่น Airbnb ขออภัย รหัสค่อนข้างเยอะ

zilyo.service.js (1.5.5)

 'use strict'; function zilyoService($http, $filter, $q) { // it's a singleton, so set up some instance and static variables in the same place var baseUrl = "https://zilyo.p.mashape.com/search"; var countUrl = "https://zilyo.p.mashape.com/count"; var state = { callbacks: {}, params: {} }; // interesting function - send the parameters to the server and ask // how many pages of results there will be, then process them in handleCount function get(params, callbacks) { // set up the state object if (params) { state.params = params; } if (callbacks) { state.callbacks = callbacks; } // get a count of the number of pages of search results return $http.get(countUrl + "?" + parameterize(state.params)) .then(extractData, handleError) .then(handleCount); } // make the factory return { get : get }; // boring function - takes an object of URL query params and stringifies them function parameterize(params) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } // interesting function - takes the results of the "count" AJAX call and // spins off a call for each results page - notice the unpleasant imperativeness function handleCount(response) { var pages = response.data.result.totalPages; if (typeof state.callbacks.onCountResults === "function") { state.callbacks.onCountResults(response.data); } // request each page var requests = _.times(pages, function (i) { var params = Object.assign({}, { page : i + 1 }, state.params); return fetch(baseUrl, params); }); // and wrap all requests in a promise return $q.all(requests).then(function (response) { if (typeof state.callbacks.onCompleted === "function") { state.callbacks.onCompleted(response); } return response; }); } // interesting function - fetch an individual page of results // notice how a special callback is required because the $q.all wrapper // will only return once ALL pages have been fetched function fetch(url, params) { return $http.get(url + "?" + parameterize(params)).then(function(response) { if (typeof state.callbacks.onFetchPage == "function") { // emit each page as it arrives state.callbacks.onFetchPage(response.data); } return response.data; // took me 15 minutes to realize I needed this }, (response) => console.log(response)); } // boring function - takes the result object and makes sure it's defined function extractData(res) { return res || { }; } // boring function - log errors, provide teaser for greater ambitions function handleError (error) { // In a real world app, we might send the error to remote logging infrastructure var errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return errMsg; } } // register the service angular.module('angularZilyoApp').factory('zilyoService', zilyoService);

ริ้วรอยในแอปนี้คือแสดงผลลัพธ์บนแผนที่ บริการอื่น ๆ จัดการผลลัพธ์หลายหน้าโดยใช้การแบ่งหน้าหรือตัวเลื่อนแบบสันหลังยาว ซึ่งช่วยให้พวกเขาสามารถดึงหน้าผลลัพธ์ที่เรียบร้อยได้ครั้งละหนึ่งหน้า อย่างไรก็ตาม เราต้องการแสดงผลลัพธ์ทั้งหมดภายในพื้นที่การค้นหา และเราต้องการให้ผลลัพธ์ปรากฏทันทีที่กลับมาจากเซิร์ฟเวอร์ แทนที่จะปรากฏขึ้นทันทีเมื่อโหลดหน้าเว็บทั้งหมดแล้ว นอกจากนี้ เราต้องการแสดงการอัปเดตความคืบหน้าให้กับผู้ใช้ เพื่อให้พวกเขาได้ทราบว่าเกิดอะไรขึ้น

ที่เกี่ยวข้อง: คู่มือสำคัญสำหรับการสัมภาษณ์ AngularJS

เพื่อให้บรรลุสิ่งนี้ใน Angular 1.5 เราจึงใช้การเรียกกลับ สัญญาจะพาเราไปที่นั่น อย่างที่คุณเห็นจาก $q.all wrapper ที่เรียก onCompleted callback แต่สิ่งต่าง ๆ ยังคงค่อนข้างยุ่งเหยิง

จากนั้นเรานำ lodash เข้ามาเพื่อสร้างคำขอหน้าทั้งหมดสำหรับเรา และแต่ละคำขอมีหน้าที่รับผิดชอบในการดำเนินการเรียกกลับ onFetchPage เพื่อให้แน่ใจว่าถูกเพิ่มลงในแผนที่ทันทีที่พร้อมใช้งาน แต่มันซับซ้อน อย่างที่คุณเห็นจากความคิดเห็น ฉันหลงทางในตรรกะของตัวเอง และไม่สามารถจัดการกับสิ่งที่ได้รับกลับมาซึ่งสัญญาได้เมื่อไร

ความเรียบร้อยโดยรวมของโค้ดจะยิ่งแย่ลงไปอีก (มากเกินความจำเป็นอย่างยิ่ง) เพราะเมื่อฉันสับสน มันก็จะวนลงมาจากที่นั่นเท่านั้น พูดกับฉันได้โปรด...

'ต้องมีวิธีที่ดีกว่า'

เชิงมุม 2: วิธีคิดแบบใหม่

มีวิธีที่ดีกว่านี้ และฉันจะแสดงให้คุณเห็น ฉันจะไม่ใช้เวลามากเกินไปกับแนวคิด ES6 (aka ES2015) เพราะมีที่ที่ดีกว่ามากในการเรียนรู้เกี่ยวกับสิ่งนั้น และถ้าคุณต้องการจุดกระโดด ES6-Features.org มีภาพรวมที่ดี ของคุณสมบัติใหม่ที่สนุกสนานทั้งหมด พิจารณาโค้ด AngularJS 2 ที่อัปเดตนี้:

zilyo.service.ts (2.0.0-beta.17)

 import {Injectable} from 'angular2/core'; import {Http, Response, Headers, RequestOptions} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; @Injectable() export class ZilyoService { constructor(private http: Http) {} private _searchUrl = "https://zilyo.p.mashape.com/search"; private _countUrl = "https://zilyo.p.mashape.com/count"; private parameterize(params: {}) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } get(params: {}, onCountResults) { return this.http.get(this._countUrl, { search: this.parameterize(params) }) .map(this.extractData) .map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; }) .flatMap(results => Observable.range(1, results.totalPages)) .flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); }) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); } let body = res.json(); return body.result || { }; } private handleError (error: any) { // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return Observable.throw(errMsg); } }

เย็น! มาเดินผ่านแถวนี้กัน อีกครั้ง Transpiler TypeScript ช่วยให้เราใช้คุณลักษณะ ES6 ที่เราต้องการเพราะแปลงทุกอย่างเป็น JavaScript วานิลลา

คำสั่ง import ในตอนเริ่มต้นนั้นใช้ ES6 เพื่อโหลดในโมดูลที่เราต้องการ เนื่องจากฉันพัฒนาส่วนใหญ่ใน ES5 (หรือที่เรียกว่า JavaScript ปกติ) ฉันต้องยอมรับว่ามันค่อนข้างน่ารำคาญที่ต้องเริ่มแสดงรายการทุกอ็อบเจ็กต์ที่ฉันวางแผนจะใช้

อย่างไรก็ตาม โปรดทราบว่า TypeScript กำลัง transpiling ทุกอย่างลงไปที่ JavaScript และแอบใช้ SystemJS เพื่อจัดการการโหลดโมดูล การขึ้นต่อกันทั้งหมดถูกโหลดแบบอะซิงโครนัส และมัน (ถูกกล่าวหา) สามารถรวมรหัสของคุณในลักษณะที่แยกสัญลักษณ์ที่คุณไม่ได้นำเข้าออก นอกจากนี้ยังสนับสนุน "การลดขนาดเชิงรุก" ซึ่งฟังดูเจ็บปวดมาก คำชี้แจงการนำเข้าเหล่านี้เป็นราคาเพียงเล็กน้อยที่ต้องจ่ายเพื่อหลีกเลี่ยงการรับมือกับเสียงรบกวนทั้งหมด

นำเข้าคำสั่งใน Angular ทำเบื้องหลังมากมาย

ใบแจ้งยอดการนำเข้าเป็นราคาเพียงเล็กน้อยสำหรับสิ่งที่เกิดขึ้นเบื้องหลัง

อย่างไรก็ตาม นอกเหนือจากการโหลดในคุณสมบัติที่เลือกจาก Angular 2 เองแล้ว โปรดสังเกตเป็นพิเศษเกี่ยวกับ import {Observable} from 'rxjs/Observable'; . RxJS เป็นไลบรารีการเขียนโปรแกรมแบบรีแอกทีฟสุดเจ๋งที่มีโครงสร้างพื้นฐานบางอย่างที่เป็นพื้นฐานของ Angular 2 เราจะได้ยินจากมันในภายหลังอย่างแน่นอน

ตอนนี้เรามาที่ @Injectable()

ฉันยังไม่แน่ใจนักว่าความจริงแล้วมันคืออะไร แต่ข้อดีของการเขียนโปรแกรมเชิงเปิดเผยก็คือเราไม่จำเป็นต้องเข้าใจรายละเอียดเสมอไป เรียกว่ามัณฑนากรซึ่งเป็นโครงสร้าง TypeScript แฟนซีที่สามารถใช้คุณสมบัติกับคลาส (หรือวัตถุอื่น ๆ ) ที่ตามมา ในกรณีนี้ @Injectable() จะสอนบริการของเราถึงวิธีการฉีดเข้าไปในส่วนประกอบ การสาธิตที่ดีที่สุดมาจากปากม้าโดยตรง แต่มันค่อนข้างยาว ดังนั้นนี่คือตัวอย่างคร่าวๆ ของรูปลักษณ์ใน AppComponent ของเรา:

 @Component({ ... providers: [HTTP_PROVIDERS, ..., ZilyoService] })

ถัดมาคือนิยามของคลาสเอง มีคำสั่ง export ก่อนหน้า ซึ่งหมายความว่า คุณเดาได้ว่า เราสามารถ import บริการของเราไปยังไฟล์อื่นได้ ในทางปฏิบัติ เราจะนำเข้าบริการของเราไปยังองค์ประกอบ AppComponent ดังที่กล่าวไว้ข้างต้น

@Injectable() สอนบริการของเราถึงวิธีการฉีดเข้าไปในส่วนประกอบ

@Injectable() สอนบริการของเราถึงวิธีการฉีดเข้าไปในส่วนประกอบ

หลังจากนั้นคือคอนสตรัคเตอร์ ซึ่งคุณสามารถเห็นการฉีดพึ่งพาการทำงานจริง ตัว constructor(private http:Http) {} เพิ่มตัวแปรอินสแตนซ์ส่วนตัวชื่อ http ที่ TypeScript รับรู้อย่างน่าอัศจรรย์ว่าเป็นอินสแตนซ์ของบริการ Http ชี้ไปที่ TypeScript!

หลังจากนั้น มันเป็นเพียงตัวแปรอินสแตนซ์ที่ดูปกติและฟังก์ชันยูทิลิตี้ ก่อนที่เราจะไปถึงเนื้อและมันฝรั่งจริง ฟังก์ชัน get ที่นี่เราเห็น Http ในการดำเนินการ ดูเหมือนแนวทางตามสัญญาของ Angular 1 มาก แต่ภายใต้ประทุนนั้นเย็นกว่า การสร้างขึ้นบน RxJS หมายความว่าเราได้รับข้อได้เปรียบเหนือสัญญาสองประการ:

  • เราสามารถยกเลิก Observable หากเราไม่สนใจคำตอบอีกต่อไป อาจเป็นกรณีนี้หากเรากำลังสร้างช่องเติมข้อความอัตโนมัติสำหรับหัวพิมพ์ และไม่สนใจผลลัพธ์ของ "ca" อีกต่อไปเมื่อป้อน "cat" แล้ว
  • Observable สามารถปล่อยค่าได้หลายค่าและสมาชิกจะถูกเรียกซ้ำแล้วซ้ำอีกเพื่อใช้ค่าเหล่านี้ในขณะที่ผลิต

อันแรกนั้นยอดเยี่ยมในหลาย ๆ สถานการณ์ แต่อย่างที่สองที่เรามุ่งเน้นในบริการใหม่ของเรานั้น มาดูฟังก์ชัน get ทีละบรรทัดกัน:

 return this.http.get(this._countUrl, { search: this.parameterize(params) })

มันดูค่อนข้างคล้ายกับการเรียก HTTP ตามสัญญาที่คุณจะเห็นใน Angular 1 ในกรณีนี้ เรากำลังส่งพารามิเตอร์การสืบค้นเพื่อรับจำนวนผลลัพธ์ที่ตรงกันทั้งหมด

 .map(this.extractData)

เมื่อการโทร AJAX กลับมา จะส่งการตอบกลับไปยังสตรีม map เมธอดมีแนวความคิดคล้ายกับฟังก์ชัน map ของอาร์เรย์ แต่ then มีลักษณะเหมือนเมธอดของสัญญา เพราะมันรอให้ทุกอย่างที่เกิดขึ้นต้นน้ำเสร็จสมบูรณ์ โดยไม่คำนึงถึงความซิงโครไนซ์หรืออะซิงโครไนซ์ ในกรณีนี้ มันเพียงยอมรับวัตถุการตอบสนองและแซวข้อมูล JSON เพื่อส่งผ่านดาวน์สตรีม ตอนนี้เรามี:

 .map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; })

เรายังมีการติดต่อกลับที่น่าอึดอัดใจที่เราจำเป็นต้องเข้าไปข้างใน ดูสิ มันไม่ใช่เวทมนตร์ทั้งหมด แต่เราสามารถประมวล onCountResults ทันทีที่การโทร AJAX กลับมา ทั้งหมดนี้โดยไม่ต้องออกจากสตรีมของเรา ที่ไม่เลวร้ายเกินไป สำหรับบรรทัดถัดไป:

.flatMap(ผลลัพธ์ => Observable.range(1, results.totalPages))

เอ่อ คุณรู้สึกได้ไหม ฝูงชนที่มองดูอยู่นั้นเกิดความเงียบงันเล็กน้อย และคุณสามารถบอกได้ว่ามีบางสิ่งที่สำคัญกำลังจะเกิดขึ้น บรรทัดนี้หมายความว่าอย่างไร? ส่วนทางขวาไม่ได้บ้าขนาดนั้น มันสร้างช่วง RxJS ซึ่งฉันคิดว่าเป็นอาร์เรย์ Observable รับการยกย่อง หาก results.totalPages เท่ากับ 5 คุณจะลงเอยด้วย Observable.of([1,2,3,4,5])

flatMap คือ รอสักครู่ เป็นการผสมผสานระหว่าง flatten และ map มีวิดีโอที่ยอดเยี่ยมที่อธิบายแนวคิดที่ Egghead.io แต่กลยุทธ์ของฉันคือการคิดว่า Observable ทุกตัวเป็นอาร์เรย์ Observable.range สร้าง wrapper ของตัวเอง ปล่อยให้เรามีอาร์เรย์ 2 มิติ [[1,2,3,4,5]] flatMap ทำให้อาร์เรย์ภายนอกราบเรียบโดยปล่อยให้เรามี [1,2,3,4,5] จากนั้น map เพียงแค่แมปเหนืออาร์เรย์โดยส่งค่าดาวน์สตรีมทีละรายการ ดังนั้นบรรทัดนี้จึงยอมรับจำนวนเต็ม ( totalPages ) และแปลงเป็นสตรีมของจำนวนเต็มจาก 1 เป็น totalPages อาจดูเหมือนไม่มาก แต่นั่นคือทั้งหมดที่เราต้องตั้งค่า

ศักดิ์ศรี

ฉันต้องการแสดงสิ่งนี้ในบรรทัดเดียวเพื่อเพิ่มผลกระทบ แต่ฉันเดาว่าคุณไม่สามารถชนะได้ทั้งหมด ที่นี่เราจะเห็นว่าเกิดอะไรขึ้นกับสตรีมของจำนวนเต็มที่เราตั้งค่าไว้ในบรรทัดสุดท้าย พวกเขาไหลเข้าสู่ขั้นตอนนี้ทีละตัว จากนั้นจะถูกเพิ่มไปยังการสืบค้นเป็นพารามิเตอร์ของหน้า ก่อนที่จะถูกจัดแพ็คเกจเป็นคำขอ AJAX ใหม่ล่าสุดและส่งไปดึงหน้าผลลัพธ์ นี่คือรหัสนั้น:

 .flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); })

หาก totalPages เป็น 5 เราจะสร้างคำขอ GET 5 รายการและส่งคำขอทั้งหมดพร้อมกัน flatMap สมัครรับข้อมูล Observable ใหม่แต่ละรายการ ดังนั้นเมื่อคำขอกลับมา (ในลำดับใดๆ ก็ตาม) คำขอเหล่านั้นจะถูกเปิดออกและการตอบสนองแต่ละรายการ (เช่น หน้าผลลัพธ์) จะถูกผลักไปที่ดาวน์สตรีมทีละรายการ

ลองดูว่าสิ่งทั้งหมดนี้ทำงานอย่างไรจากอีกมุมหนึ่ง จากคำขอ "การนับ" เริ่มต้นของเรา เราจะพบจำนวนหน้าของผลลัพธ์ทั้งหมด เราสร้างคำขอ AJAX ใหม่สำหรับแต่ละหน้า และไม่ว่าจะกลับมาเมื่อใด (หรือในลำดับใด) คำขอเหล่านั้นก็จะถูกผลักเข้าไปในสตรีมทันทีที่พร้อม ทั้งหมดที่คอมโพเนนต์ของเราต้องทำคือสมัครรับข้อมูล Observable ที่ส่งคืนโดยเมธอด get ของเรา และจะได้รับแต่ละหน้าทีละหน้า ทั้งหมดมาจากสตรีมเดียว รับที่สัญญา

การตอบสนองแต่ละครั้งจะถูกผลักไปที่ปลายน้ำทีละครั้ง

องค์ประกอบจะได้รับแต่ละหน้า ทีละหน้า ทั้งหมดจากสตรีมเดียว

ทั้งหมดนั้นค่อนข้างต่อต้านจุดสุดยอดหลังจากนั้น:

 .map(this.extractData).catch(this.handleError);

เมื่อแต่ละอ็อบเจ็กต์ตอบกลับมาจาก flatMap JSON จะถูกดึงออกมาในลักษณะเดียวกับการตอบกลับจากคำขอนับ ติดอยู่ที่ส่วนท้ายมีโอเปอเรเตอร์ catch ซึ่งช่วยแสดงให้เห็นว่าการจัดการข้อผิดพลาด RxJS แบบสตรีมทำงานอย่างไร มันค่อนข้างคล้ายกับกระบวนทัศน์ try/catch แบบดั้งเดิม ยกเว้นว่าวัตถุ Observable นั้นใช้งานได้สำหรับการจัดการข้อผิดพลาดแบบอะซิงโครนัสเช่นกัน

เมื่อใดก็ตามที่พบข้อผิดพลาด จะแข่งดาวน์สตรีม โดยข้ามโอเปอเรเตอร์ที่ผ่านไปมาจนกว่าจะพบตัวจัดการข้อผิดพลาด ในกรณีของเรา วิธี handleError จะส่งข้อผิดพลาดอีกครั้ง ทำให้เราสามารถสกัดกั้นภายในบริการได้ แต่ยังช่วยให้สมาชิกสามารถเรียกกลับ onError ของตัวเองซึ่งจะเริ่มทำงานที่ปลายน้ำยิ่งขึ้นไปอีก การจัดการข้อผิดพลาดแสดงให้เราเห็นว่าเราไม่ได้ใช้ประโยชน์จากสตรีมของเราอย่างเต็มที่ แม้จะมีสิ่งดีๆ ที่เราได้ทำไปแล้วก็ตาม การเพิ่มโอเปอเรเตอร์ retry หลังจากคำขอ HTTP ของเรานั้นไม่ใช่เรื่องยาก ซึ่งจะลองส่งคำขอทีละรายการหากมีข้อผิดพลาดกลับมา เพื่อเป็นมาตรการป้องกัน เรายังสามารถเพิ่มโอเปอเรเตอร์ระหว่างตัวสร้าง range และคำขอ โดยเพิ่มรูปแบบการจำกัดอัตราบางรูปแบบ เพื่อที่เราจะไม่ส่งสแปมเซิร์ฟเวอร์ด้วยคำขอจำนวนมากเกินไปในคราวเดียว

ที่เกี่ยวข้อง: จ้าง 3% อันดับแรกของนักพัฒนา AngularJS อิสระ

สรุป: การเรียนรู้ Angular 2 ไม่ใช่แค่เกี่ยวกับเฟรมเวิร์กใหม่เท่านั้น

การเรียนรู้ Angular 2 เป็นเหมือนการพบปะครอบครัวใหม่ทั้งหมด และความสัมพันธ์บางอย่างของพวกเขาก็ซับซ้อน หวังว่าฉันจะสามารถแสดงให้เห็นว่าความสัมพันธ์เหล่านี้มีวิวัฒนาการด้วยเหตุผล และมีอะไรอีกมากที่จะได้รับจากการเคารพการเปลี่ยนแปลงที่มีอยู่ในระบบนิเวศนี้ หวังว่าคุณจะชอบบทความนี้เช่นกัน เพราะฉันเพิ่งจะขีดข่วนพื้นผิว และมีอะไรอีก มาก ที่จะพูดในเรื่องนี้

ที่เกี่ยวข้อง: Perks ทั้งหมดไม่ยุ่งยาก: บทช่วยสอน Angular 9