Egy modern webalkalmazás egyik legfontosabb komponense a felhasználói keresőmező, különösen akkor, ha valós idejű adatokkal – például időjárási információkkal – dolgozunk. A keresési funkcionalitás fejlesztése során alapvető cél, hogy minden implementált lépés egyben kiadható verzióként is működjön, így az alkalmazás iteratív módon fejlődhet. Ennek érdekében a történet technikai feladatokra való bontása nemcsak szervezési szempontból indokolt, hanem elősegíti a moduláris, tesztelhető és újrahasználható kód létrehozását is.

Elsőként egy Angular-vezérelt űrlapelemet hozunk létre, amely képes kezelni a felhasználói bevitelt. Bár első pillantásra indokolatlannak tűnhet a formális Angular formok használata egyetlen inputmező esetén, a reaktív megközelítés lehetőséget nyújt validációk, hibakezelések és a komponens viselkedésének finomhangolására. A FormControl osztály az input mezőhöz kötve reagál az eseményekre, lehetőséget adva adatellenőrzésre vagy automatikus válaszüzenetek megjelenítésére.

Az Angular két űrlaptípust támogat: a template-alapú űrlapokat és a reaktív űrlapokat. Előbbiek szorosabb kapcsolatban állnak az AngularJS megközelítésével, ahol a logika nagy része a HTML-sablonban helyezkedik el – azonban ez gyorsan átláthatatlanná és nehezen karbantarthatóvá válhat. Ezzel szemben a reaktív űrlapok a komponens osztályában, TypeScript-kóddal vezéreltek, így a logika könnyebben tesztelhető, újrahasználható és strukturáltabb.

Egy új, önálló citySearch komponens bevezetése elengedhetetlen a komponens architektúra szétválasztásához. Ez biztosítja, hogy a keresési logika független legyen az alkalmazás többi részétől. A FormsModule és ReactiveFormsModule modulokat importálni kell a formák használatához, míg az Angular Material segítségével – MatFormFieldModule és MatInputModule – esztétikus és akadálymentes inputmezőt biztosíthatunk. A keresőmező HTML-sablonját egyszerűen definiáljuk, ahol a FormControl változó köti össze a felhasználói bevitelt a komponens logikájával.

A reaktív űrlapok három szintű kontrollt biztosítanak: a FormControl az alapegység, egy inputmezőhöz rendelve; a FormArray ismétlődő mezők tömbje; míg a FormGroup lehetővé teszi ezek logikai összefogását. Későbbi bővítések során a FormBuilder objektum leegyszerűsíti ezek kezelését, különösen komplex űrlapoknál.

A keresőkomponens integrációja után a fő alkalmazá

Hogyan optimalizáljuk az Angular alkalmazások teljesítményét?

Az Angular-ban gyakran találkozunk teljesítményproblémákkal, ha a változókat vagy a tulajdonságokat olyan módon kezeljük, amely folyamatosan újraszámoltatja azokat. Ilyen például a get age() tulajdonság, amelyet a következőképpen implementálunk:

typescript
get age() { return this.now.getFullYear() - this.dateOfBirth.getFullYear(); }

Ez a kód ugyan megoldja az életkor számítását, de nem a legjobb teljesítményt nyújtja. A probléma abból adódik, hogy minden egyes változtatáskor, amit az Angular változás-ellenőrzési algoritmus végrehajt, újra és újra meghívódik a getFullYear() függvény a this.now és this.dateOfBirth objektumok esetében. Mivel az Angular alapértelmezett működése szerint a változások ellenőrzése akár 60-szor is megtörténhet másodpercenként, a teljesítmény romlása elkerülhetetlen.

Ez a problémás kód különösen akkor válhat észrevehetővé, ha a age tulajdonságot sablonban használjuk, hiszen akkor minden frissítéskor újraszámolódik, ami a DOM újrarendereléséhez vezethet, túlzott számítási költséget generálva.

Egy lehetséges megoldás, hogy egy tiszta egyedi pipe-ot (csövet) készítünk, amely biztosítja, hogy az Angular csak akkor ellenőrizze az életkort, ha a függő változók, például a születési dátum megváltoznak. Az Angular hivatalos dokumentációjában bővebb információ található a tiszta csövekről, és azok szerepéről a változás-ellenőrzés optimalizálásában. Ezt az alábbi linken találhatjuk: https://angular.dev/guide/pipes/change-detection.

Egy másik lehetőség a számított jelek használata. A számított jelek olyan olvasható jelek, amelyek más jelekből származtatják az értékeiket. Az alábbi példában a dateOfBirth és az age változókat számított jelekként definiáljuk:

typescript
now = new Date(); dateOfBirth = signal(this.formGroup.get('dateOfBirth')?.value || this.now); age = computed(() => this.now.getFullYear() - this.dateOfBirth().getFullYear());

Ebben a kódban a dateOfBirth egy jelként van definiálva, míg az age egy számított jel, amely automatikusan frissül, ha a dateOfBirth változik. Ily módon az Angular változás-ellenőrzési algoritmusának nem kell aggódnia a túlzott számítási költség miatt, hiszen a age csak akkor számítódik újra, ha valóban szükséges. Ez a megoldás egyszerű és hatékony, de van egy apró bökkenő. Mivel jelenleg nem léteznek olyan komponensek, amelyek képesek kezelni a jeleket, és nem biztosítják a szükséges FormGroup támogatást, a dateOfBirth vagy az age nem használható közvetlenül reakciós formákban.

Ez a helyzet jól illusztrálja, hogy milyen jelentős változást hoznak a jelek az Angular alkalmazásokban. További részletek a számított jelekről az Angular hivatalos dokumentációjában találhatók: https://angular.dev/guide/signals#computed-signals.

A születési dátumok kezelésére a dátumok érvényesítése is szükséges lehet. Ha egy olyan dátumot szeretnénk kezelni, amely az utolsó száz évben van, az alábbi kód segítségével beállíthatjuk a minDate értéket:

typescript
minDate = new Date(this.now.getFullYear() - 100, this.now.getMonth(), this.now.getDate());

A sablonban az életkor számítása az alábbiak szerint történik:

html
Date of Birth @if (formGroup.get('dateOfBirth')?.value) { {{ age }} year(s) old }

A dátumválasztó (DatePicker) használata során a felhasználó csak olyan dátumokat választhat, amelyek az utolsó 100 évre vonatkoznak. A választott dátum alapján az életkor automatikusan kiszámítódik, és a felhasználó számára megjelenik.

A típus- és címek kezelésének egy másik gyakori problémája a címek automatikus kiegészítése, vagy más néven a typeahead támogatás. Az alábbi példában az államok listájának automatikus szűrésére egy aszinkron adatfolyammal dolgozunk:

typescript
const state = this.formGroup.get('address.state');
if (state != null) { this.states$ = state.valueChanges.pipe( startWith(''), map((value) => USStateFilter(value)) ); }

A sablonban az async pipe segítségével a szűrt államokat jelenítjük meg a felhasználónak:

html
State
@for (state of states$ | async; track state) { {{ state.name }} }

Ez lehetővé teszi, hogy a felhasználó gyorsan és egyszerűen kiválaszthassa az államot egy automatikusan szűrt listából.

A dinamikus űrlapok kezelésére egy másik példa a telefon számok bevitele. A phones mező egy tömb, amely lehetővé teszi, hogy több telefonszámot adjunk hozzá. A következő kód segítségével FormArray-t használunk:

typescript
phones: this.formBuilder.array(this.buildPhoneArray(user?.phones || [])),

A FormArray dinamikus formák kezelésére alkalmas, és a felhasználók bármennyi telefonszámot hozzáadhatnak. A különböző telefonok beépítése érdekében több segédfüggvény is rendelkezésre áll, amelyek egyszerűsítik a tömb kezelését és a szükséges formák létrehozását. Az alábbi kód részlet mutatja, hogyan adhatunk hozzá új telefonszámot:

typescript
addPhone() {
this.phonesArray.push(this.buildPhoneFormControl(this.formGroup.get('phones').value.length + 1));
}

Ez az eljárás lehetővé teszi, hogy dinamikusan bővítsük a telefonszámok listáját a felhasználói igényeknek megfelelően, miközben biztosítja, hogy minden egyes mező érvényesítése helyesen történik.

Fontos figyelembe venni, hogy a megfelelő formák és a változás-ellenőrzés optimalizálása elengedhetetlen az Angular alkalmazások teljesítményének javítása érdekében. A felesleges újraszámítások elkerülése és a jelek, csövek (pipes), valamint a dinamikus formák megfelelő alkalmazása kulcsfontosságú a zökkenőmentes és hatékony felhasználói élmény biztosításában.