Angular 2'nizi Açın: 1.5'ten Yükseltme
Yayınlanan: 2022-03-11Bir uygulamayı Angular 1.5'ten Angular 2'ye yükseltmek için adım adım bir kılavuz yazmak istedim, editörüm tarafından bir roman yerine bir makaleye ihtiyacı olduğu konusunda kibarca bilgilendirilmeden önce. Uzun uzun düşündükten sonra, Jason Aden'in Açısal 2'de Merhaba Dünyayı Geçmek başlıklı makalesinde ele alınan tüm noktalara ulaşarak, Angular 2'deki değişikliklerle ilgili geniş bir anketle başlamam gerektiğini kabul ettim. …Hata. Angular 2'nin yeni özelliklerine genel bir bakış için okumaya devam edin, ancak uygulamalı bir yaklaşım için tarayıcınızı tam burada tutun.
Bunun, demo uygulamamızı Angular 2'ye yükseltme sürecinin tamamını kapsayan bir seri olmasını istiyorum. Şimdilik, yine de, tek bir hizmetle başlayalım. Kodda dolambaçlı bir gezinti yapalım ve sorularınızı yanıtlayacağım, örneğin….
Açısal: Eski Yol
Benim gibiyseniz, Angular 2 hızlı başlangıç kılavuzu, TypeScript'e ilk baktığınız zaman olabilir. Kendi web sitesine göre çok hızlı bir şekilde TypeScript, "düz JavaScript'i derleyen JavaScript'in yazılı bir üst kümesidir". Aktarıcıyı (Babil veya Traceur'a benzer) kurarsınız ve güçlü yazmanın yanı sıra ES2015 ve ES2016 dil özelliklerini destekleyen büyülü bir dille sonuçlanırsınız.
Bu gizli düzenin hiçbirinin kesinlikle gerekli olmadığını bilmek size güven verici gelebilir. Düz eski JavaScript'te Angular 2 kodu yazmak çok zor değil, ancak buna değeceğini düşünmüyorum. Tanıdık bir bölgeyi tanımak güzel, ancak Angular 2 ile ilgili yeni ve heyecan verici şeylerin çoğu, yeni mimarisinden ziyade yeni düşünme biçimidir.
Şimdi Angular 1.5'ten 2.0.0-beta.17'ye yükselttiğim bu hizmete bakalım. Bu oldukça standart bir Angular 1.x hizmetidir ve yorumlarda belirtmeye çalıştığım birkaç ilginç özelliğe sahiptir. Standart oyuncak uygulamanızdan biraz daha karmaşıktır, ancak gerçekten yaptığı tek şey, Airbnb gibi kiralama sağlayıcılarının listelerini toplayan ücretsiz bir API olan Zilyo'yu sorgulamaktır. Üzgünüm, oldukça fazla kod var.
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);
Bu özel uygulamadaki kırışıklık, sonuçları bir harita üzerinde göstermesidir. Diğer hizmetler, sayfalandırma veya tembel kaydırma çubukları uygulayarak birden çok sonuç sayfasını işler, bu da her seferinde tek bir düzgün sonuç sayfası almalarına olanak tanır. Ancak, arama alanındaki tüm sonuçları göstermek istiyoruz ve tüm sayfalar yüklendikten sonra aniden görünmek yerine, sunucudan döndüklerinde hemen görünmelerini istiyoruz. Ek olarak, neler olduğu hakkında bir fikir sahibi olmaları için kullanıcıya ilerleme güncellemelerini görüntülemek istiyoruz.
Bunu Angular 1.5'te gerçekleştirmek için geri aramalara başvuruyoruz. $q.all
sarmalayıcısından görebileceğiniz gibi, onCompleted
geri aramasını tetikleyen sözler bizi orada yarı yolda bırakır, ancak işler yine de oldukça dağınık hale gelir.
Ardından, bizim için tüm sayfa isteklerini oluşturmak için lodash'ı getiririz ve her istek, kullanılabilir olur olmaz haritaya eklendiğinden emin olmak için onFetchPage
geri aramasını yürütmekten sorumludur. Ama bu karmaşıklaşıyor. Yorumlardan da anlaşılacağı üzere kendi mantığımda kayboldum ve neyin ne zaman hangi söze geri döndüğünü bir türlü çözemedim.
Kodun genel düzgünlüğü daha da fazla zarar görüyor (kesinlikle gerekli olandan çok daha fazla), çünkü bir kez kafam karıştığında, sadece oradan aşağıya doğru spiraller çiziyor. Benimle söyle, lütfen…
Angular 2: Yeni Bir Düşünme Yolu
Daha iyi bir yol var ve bunu size göstereceğim. ES6 (aka ES2015) kavramlarına çok fazla zaman harcamayacağım, çünkü bu şeyler hakkında bilgi edinmek için çok daha iyi yerler var ve bir başlangıç noktasına ihtiyacınız varsa, ES6-Features.org'da iyi bir genel bakış var tüm eğlenceli yeni özellikler. Bu güncellenmiş AngularJS 2 kodunu göz önünde bulundurun:
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); } }
Serin! Bu satır satır inceleyelim. TypeScript aktarıcısı, her şeyi vanilya JavaScript'e dönüştürdüğü için istediğimiz herhangi bir ES6 özelliğini kullanmamıza izin veriyor.
Başlangıçtaki import
ifadeleri, ihtiyacımız olan modülleri yüklemek için basitçe ES6 kullanıyor. Geliştirmemin çoğunu ES5'te (diğer bir deyişle normal JavaScript) yaptığım için, kullanmayı planladığım her nesneyi aniden listelemeye başlamanın biraz can sıkıcı olduğunu kabul etmeliyim.
Ancak TypeScript'in her şeyi JavaScript'e aktardığını ve modül yüklemeyi işlemek için gizlice SystemJS kullandığını unutmayın. Bağımlılıkların tümü eşzamansız olarak yükleniyor ve (iddiaya göre) kodunuzu içe aktarmadığınız sembolleri çıkaracak şekilde paketleyebiliyor. Ayrıca hepsi kulağa çok acı verici gelen "agresif küçültmeyi" destekliyor. Bu ithalat beyanları, tüm bu gürültüyle uğraşmaktan kaçınmak için ödenmesi gereken küçük bir bedeldir.

Her halükarda, Angular 2'nin kendisinden seçmeli özellikleri yüklemenin yanı sıra import {Observable} from 'rxjs/Observable';
. RxJS, Angular 2'nin altında yatan altyapının bir kısmını sağlayan, akıl almaz, çılgınca havalı bir reaktif programlama kitaplığıdır.
Şimdi @Injectable()
'a geliyoruz.
Dürüst olmak gerekirse bunun ne olduğundan hala tam olarak emin değilim, ancak bildirimsel programlamanın güzelliği, her zaman ayrıntıları anlamamıza gerek olmamasıdır. Onu izleyen sınıfa (veya başka bir nesneye) özellikler uygulayabilen süslü bir TypeScript yapısı olan dekoratör olarak adlandırılır. Bu durumda @Injectable()
hizmetimize bir bileşene nasıl enjekte edileceğini öğretir. En iyi gösterim doğrudan atın ağzından gelir, ancak oldukça uzundur, bu yüzden AppComponent'te nasıl göründüğüne dair kısa bir bakış:
@Component({ ... providers: [HTTP_PROVIDERS, ..., ZilyoService] })
Sıradaki sınıf tanımının kendisidir. Önünde export
ifadesi var, yani siz tahmin ettiniz, hizmetimizi başka bir dosyaya import
. Uygulamada, hizmetimizi yukarıdaki gibi AppComponent
bileşenimize aktaracağız.
Bundan hemen sonra, eylemde bazı gerçek bağımlılık enjeksiyonlarını görebileceğiniz yapıcı bulunur. Satır constructor(private http:Http) {}
, TypeScript'in sihirli bir şekilde Http hizmetinin bir örneği olarak tanıdığı http
adında özel bir örnek değişkeni ekler. Nokta TypeScript'e gidiyor!
Bundan sonra, gerçek et ve patateslere, get
işlevine ulaşmadan önce, sadece normal görünen bazı örnek değişkenleri ve bir yardımcı işlev işlevi var. Burada Http
başında görüyoruz. Angular 1'in vaat temelli yaklaşımına çok benziyor, ancak kaputun altında çok daha havalı. RxJS üzerine kurulu olmak, vaatlere göre birkaç büyük avantaj elde ettiğimiz anlamına gelir:
- Yanıtı artık umursamıyorsak,
Observable
iptal edebiliriz. Yazılı bir otomatik tamamlama alanı oluşturuyorsak ve "kedi" girdikten sonra artık "ca" sonuçlarını önemsemiyorsak bu durum söz konusu olabilir. -
Observable
birden fazla değer yayabilir ve abone, üretildikleri anda bunları tüketmek için tekrar tekrar çağrılır.
Birincisi birçok durumda harikadır, ancak yeni hizmetimizde odaklandığımız ikincisidir. get
işlevini satır satır inceleyelim:
return this.http.get(this._countUrl, { search: this.parameterize(params) })
Angular 1'de göreceğiniz söze dayalı HTTP çağrısına oldukça benziyor. Bu durumda, eşleşen tüm sonuçların sayısını almak için sorgu parametrelerini gönderiyoruz.
.map(this.extractData)
AJAX çağrısı geri döndüğünde, yanıtı akışa gönderir. Yöntem map
kavramsal olarak bir dizinin map
işlevine benzer, ancak aynı zamanda bir sözün o then
yöntemi gibi davranır, çünkü eşzamanlılık veya eşzamansızlıktan bağımsız olarak yukarı akışta olan her şeyin tamamlanmasını bekler. Bu durumda, yalnızca yanıt nesnesini kabul eder ve aşağı akışa geçmek için JSON verilerini açıklar. Şimdi elimizde:
.map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; })
Hala oraya kaydırmamız gereken garip bir geri aramamız var. Bakın, hepsi sihirli değil, ancak AJAX çağrısı geri döner dönmez, onCountResults
işleyebiliriz. Bu çok kötü değil. Bir sonraki satıra gelince:
.flatMap(sonuçlar => Observable.range(1, sonuçlar.totalPages))
Ah, hissedebiliyor musun? İzleyen kalabalığın üzerine ince bir sessizlik geldi ve büyük bir şeyin olmak üzere olduğunu söyleyebilirsiniz. Bu çizgi ne anlama geliyor? Sağ taraf o kadar da çılgın değil. Bir RxJS aralığı oluşturur, ki bu benim yüceltilmiş bir Observable
- sarılmış dizi olarak düşünüyorum. results.totalPages
5'e eşitse, Observable.of([1,2,3,4,5])
gibi bir şey elde edersiniz.
flatMap
, bekleyin, flatten
ve map
birleşimidir. Egghead.io'da konsepti açıklayan harika bir video var, ancak stratejim her Observable
bir dizi olarak düşünmek. Observable.range
kendi sarmalayıcısını oluşturarak bize 2 boyutlu dizi [[1,2,3,4,5]]
bırakıyor. flatMap
dış diziyi düzleştirir, bizi [1,2,3,4,5]
ile bırakır, ardından map
basitçe dizi üzerinde eşler, değerleri birer birer aşağı akışa geçirir. Dolayısıyla bu satır bir tamsayı ( totalPages
) kabul eder ve onu 1'den totalPages
kadar bir tamsayı akışına dönüştürür. Çok fazla görünmeyebilir, ancak ayarlamamız gereken tek şey bu.
Etkisini artırmak için bunu gerçekten tek satırda almak istedim, ama sanırım hepsini kazanamazsınız. Burada son satırda kurduğumuz tamsayıların akışına ne olduğunu görüyoruz. Bu adıma birer birer akarlar, ardından sorguya bir sayfa parametresi olarak eklenirler ve nihayet yepyeni bir AJAX isteğine paketlenirler ve bir sonuç sayfası getirmek için gönderilirler. İşte o kod:
.flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); })
totalPages
5 ise, 5 GET isteği oluşturur ve hepsini aynı anda göndeririz. flatMap
her yeni Observable
öğesine abone olur, bu nedenle istekler geri döndüğünde (herhangi bir sırayla) paketler açılır ve her yanıt (bir sonuç sayfası gibi) birer birer aşağı doğru itilir.
Tüm bu şeyin başka bir açıdan nasıl çalıştığına bakalım. Kaynak "sayma" isteğimizden, sonuç sayfalarının toplam sayısını buluyoruz. Her sayfa için yeni bir AJAX isteği oluştururuz ve ne zaman (veya hangi sırayla) dönerlerse dönsünler, hazır oldukları anda akışa gönderilirler. Bileşenimizin yapması gereken tek şey, get
yöntemimiz tarafından döndürülen Gözlenebilir'e abone olmaktır ve her sayfayı birbiri ardına tek bir akıştan alacaktır. Al bunu, vaatler.
Bundan sonrası biraz iklim karşıtı:
.map(this.extractData).catch(this.handleError);
Her yanıt nesnesi flatMap'ten geldiğinde, onun flatMap
, sayım isteğinden gelen yanıtla aynı şekilde çıkarılır. Sonuna kadar, akış tabanlı RxJS hata işlemenin nasıl çalıştığını göstermeye yardımcı olan catch
operatörü vardır. Observable
nesnenin eşzamansız hata işleme için de çalışması dışında, geleneksel dene/yakala paradigmasına oldukça benzer.
Bir hatayla karşılaşıldığında, bir hata işleyicisi ile karşılaşana kadar geçmiş operatörleri atlayarak aşağı doğru koşar. Bizim durumumuzda, handleError
yöntemi hatayı yeniden atar, bu da onu hizmet içinde engellememize ve aynı zamanda abonenin aşağı akışta daha da tetiklenen kendi onError
geri çağrısını sağlamasına izin verir. Hata işleme, zaten başardığımız tüm harika şeylere rağmen akışımızdan tam olarak yararlanmadığımızı gösteriyor. HTTP isteklerimizden sonra, bir hata döndürürse bireysel bir isteği yeniden deneyen bir retry
operatörü eklemek önemsizdir. Önleyici bir önlem olarak, aynı anda çok fazla istekle sunucuya istenmeyen posta göndermememiz için bir tür hız sınırlama ekleyerek range
oluşturucu ile istekler arasına bir operatör ekleyebiliriz.
Özet: Angular 2'yi Öğrenmek Sadece Yeni Bir Çerçeve Hakkında Değil
Angular 2'yi öğrenmek, tamamen yeni bir aileyle tanışmak gibidir ve bazı ilişkileri karmaşıktır. Umarım bu ilişkilerin bir nedenden dolayı geliştiğini göstermeyi başardım ve bu ekosistemde var olan dinamiklere saygı duyarak kazanılacak çok şey var. Umarım bu makaleyi de beğenmişsinizdir, çünkü yüzeyi zar zor çizdim ve bu konuda söylenecek daha çok şey var.