GraphQL használata egy Angular alkalmazásban

A modern webalkalmazások fejlesztése során az adatkommunikáció kulcsfontosságú. Ahogy az alkalmazások egyre komplexebbé válnak, úgy nő az igény a rugalmas, hatékony és karbantartható API-kra. A REST API-k hosszú ideig uralták ezt a területet, de a kihívások – mint például az adat túl- vagy alulekérdezése (over-fetching, under-fetching) és a számos végpont kezelése – új megoldások felé terelték a fejlesztőket. Itt jön képbe a GraphQL, egy lekérdezési nyelv az API-k számára, amely forradalmasítja az adatlekérdezést és -kezelést. Ebben a cikkben részletesen megvizsgáljuk, hogyan integrálhatjuk és használhatjuk a GraphQL-t Angular alapú alkalmazásainkban, kiemelve annak előnyeit és a legjobb gyakorlatokat.

Mi az a GraphQL és miért érdemes használni Angularral?

A GraphQL-t a Facebook fejlesztette ki 2012-ben (majd 2015-ben nyílt forráskódúvá tette), elsősorban a mobilalkalmazásaik növekvő adatigényeinek kiszolgálására. Lényegében egy specifikáció arról, hogyan kérhetünk le adatokat egy szervertől, és hogyan módosíthatjuk azokat. A legfontosabb különbség a REST-hez képest, hogy a GraphQL egyetlen végpontot használ, és a kliens pontosan megadhatja, milyen adatokat szeretne lekérdezni, és milyen formában. Ez kiküszöböli azokat a problémákat, amikor a REST API túl sok vagy túl kevés adatot küld vissza, ami optimalizáltabb hálózati forgalmat és gyorsabb alkalmazásokat eredményez.

Az Angular, mint egy robusztus és strukturált frontend keretrendszer, tökéletes partner a GraphQL számára. Az Angular komponensalapú architektúrája és az RxJS reaktív programozási mintái kiválóan illeszkednek a GraphQL adatfolyam-kezeléséhez. A GraphQL lehetővé teszi, hogy az Angular alkalmazások sokkal specifikusabb és hatékonyabb adatlekérdezéseket végezzenek, csökkentve a szerveroldali terhelést és javítva a felhasználói élményt.

A GraphQL fő előnyei Angular alkalmazásokban:

  • Precíz adatlekérdezés: A kliens pontosan azt kapja vissza, amire szüksége van, sem többet, sem kevesebbet. Ez csökkenti a hálózati forgalmat és gyorsítja az alkalmazást.
  • Kevesebb lekérdezés: Gyakran több REST végpontot kellene meghívni egy komplex nézet betöltéséhez, míg GraphQL-lel ez egyetlen lekérdezéssel megoldható.
  • Fejlesztői élmény: A GraphQL séma (schema) leírja az API összes képességét, így a frontend fejlesztők könnyedén felfedezhetik és használhatják azt, gyakran kódgenerálással is kiegészítve.
  • Típusbiztonság: A GraphQL séma erős típusbiztonságot nyújt, ami csökkenti a futásidejű hibákat és javítja a kód minőségét.
  • Valós idejű adatok: Az előfizetések (Subscriptions) segítségével könnyedén kezelhetők a valós idejű adatfrissítések, ami kritikus funkció számos modern alkalmazásban (pl. chatek, értesítések).
  • Gyorsabb iteráció: A séma evolúciója könnyebb, mint a REST API verziózása, mivel a kliens kódja csak a számára szükséges mezőket érinti.

A GraphQL beállítása Angular alkalmazásokban: Az Apollo Angular

Az Angular és a GraphQL összekapcsolásához a legnépszerűbb és legelterjedtebb klienskönyvtár az Apollo Angular. Ez az Apollo Client implementációja Angularra, amely kiválóan integrálódik az RxJS-sel és az Angular ökoszisztémájával. Kezelni tudja a lekérdezéseket (queries), mutációkat (mutations) és előfizetéseket (subscriptions), valamint fejlett cache mechanizmusokat kínál.

Telepítés és Alapkonfiguráció

Először is telepítenünk kell az Apollo Angular csomagokat az Angular alkalmazásunkba. Nyissunk meg egy terminált a projekt gyökérkönyvtárában, és futtassuk a következő parancsot:

ng add apollo-angular

Ez a parancs automatikusan telepíti a szükséges függőségeket (apollo-angular, @apollo/client, graphql, graphql-tag) és elvégzi az alapvető konfigurációt az app.module.ts fájlban. Ha kézzel szeretnénk telepíteni, a következő parancsokra van szükségünk:

npm install apollo-angular @apollo/client graphql-tag graphql

A konfigurációhoz az app.module.ts fájlban kell beállítanunk az ApolloClient példányt. Ez általában a GraphQL szerver URL-jének megadását jelenti, valamint a cache beállítását. Az ng add parancs által generált kód valami ilyesmi lesz:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { InMemoryCache } from '@apollo/client/core';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ApolloModule
  ],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: (httpLink: HttpLink) => {
        return {
          cache: new InMemoryCache(),
          link: httpLink.create({
            uri: 'http://localhost:4000/graphql', // Itt add meg a GraphQL szervered URL-jét
          }),
        };
      },
      deps: [HttpLink],
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Ez a konfiguráció beállít egy HttpLink-et a megadott GraphQL szerver URI-jára, és egy InMemoryCache-t, amely automatikusan tárolja és kezeli a lekérdezések eredményeit. Ez a cache kritikus fontosságú a teljesítmény és a felhasználói élmény szempontjából, mivel megakadályozza a felesleges hálózati kéréseket ugyanazokért az adatokért.

Adatok lekérdezése GraphQL-lel: Queries

A GraphQL-ben az adatlekérdezéseket „queries”-nek nevezzük. Ezek nagyon hasonlítanak a REST GET kéréseihez, de sokkal precízebbek. Az Apollo Angular segítségével egyszerűen végrehajthatunk lekérdezéseket. Először is definiálnunk kell a GraphQL lekérdezést a gql tag használatával, ami a graphql-tag könyvtárból érkezik.

import { Component, OnInit } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

interface Character {
  id: string;
  name: string;
  species: string;
}

interface AllCharacters {
  characters: Character[];
}

const GET_CHARACTERS = gql`
  query GetAllCharacters {
    characters {
      id
      name
      species
    }
  }
`;

@Component({
  selector: 'app-characters',
  template: `
    

Karakterek

Adatok betöltése...
Hiba történt: {{ error.message }}
  • {{ character.name }} ({{ character.species }})
`, }) export class CharactersComponent implements OnInit { characters: Character[] | undefined; loading: boolean = true; error: any; constructor(private apollo: Apollo) {} ngOnInit() { this.apollo .watchQuery({ query: GET_CHARACTERS, }) .valueChanges.subscribe(({ data, loading, error }) => { this.loading = loading; this.error = error; if (data) { this.characters = data.characters; } }); } }

Ebben a példában a GET_CHARACTERS lekérdezést használjuk karakterek listájának lekérdezésére. Az apollo.watchQuery metódus egy Observable-t ad vissza, amely frissül, amikor az adatok változnak (például a cache frissítése miatt). A valueChanges property egy olyan Observable-t ad, amely a lekérdezés eredményét tartalmazó objektumot bocsátja ki, benne az data, loading és error property-kkel. Ezeket felhasználva könnyedén kezelhetjük a betöltési állapotot és a hibákat a felhasználói felületen.

Adatok módosítása GraphQL-lel: Mutations

Az adatok módosítására, hozzáadására vagy törlésére a mutations-t használjuk a GraphQL-ben. Ezek a REST POST, PUT, PATCH, DELETE metódusainak felelnek meg. A mutációk is hasonlóan működnek a lekérdezésekhez, de a apollo.mutate metódust használjuk.

import { Component } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';

const ADD_CHARACTER = gql`
  mutation AddCharacter($name: String!, $species: String!) {
    addCharacter(name: $name, species: $species) {
      id
      name
      species
    }
  }
`;

@Component({
  selector: 'app-add-character',
  template: `
    

Karakter hozzáadása

{{ addedCharacter.name }} hozzáadva (ID: {{ addedCharacter.id }})

Hiba: {{ error.message }}

`, }) export class AddCharacterComponent { name: string = ''; species: string = ''; addedCharacter: any; error: any; constructor(private apollo: Apollo) {} addCharacter() { this.apollo .mutate({ mutation: ADD_CHARACTER, variables: { name: this.name, species: this.species, }, // Frissítjük a cache-t, hogy a GET_CHARACTERS lekérdezés is frissüljön update: (store, { data: { addCharacter } }) => { const data: any = store.readQuery({ query: GET_CHARACTERS }); if (data && data.characters) { store.writeQuery({ query: GET_CHARACTERS, data: { characters: [...data.characters, addCharacter] }, }); } }, }) .subscribe({ next: ({ data }) => { this.addedCharacter = data ? data.addCharacter : null; this.name = ''; this.species = ''; }, error: (error) => { this.error = error; }, }); } }

A mutációk esetében kulcsfontosságú a cache frissítése. Miután sikeresen hozzáadtunk, módosítottunk vagy töröltünk egy elemet, az Apollo cache-t is frissíteni kell, hogy a már létező lekérdezések, amelyek érintettek, is a legfrissebb adatokkal rendelkezzenek. Ezt az update funkcióval tehetjük meg, amely hozzáférést biztosít a cache-hez. Ebben a példában manuálisan hozzáadjuk az új karaktert a GET_CHARACTERS lekérdezés cache-elt adataihoz.

Valós idejű adatok kezelése: Subscriptions

A GraphQL Subscriptions lehetővé teszik a kliens számára, hogy valós idejű frissítéseket kapjon a szervertől, amikor bizonyos adatok változnak. Ez ideális chatek, értesítések, élő statisztikák vagy bármilyen valós idejű adatfolyam kezelésére. A subscriptions általában WebSocket kapcsolaton keresztül működnek.

WebSocket beállítása

Az Apollo Angular alapértelmezett konfigurációja csak HTTP linket tartalmaz. A subscriptions használatához telepítenünk kell a WebSocket linket és módosítanunk kell az app.module.ts konfigurációját:

npm install @apollo/client/link/ws subscriptions-transport-ws

Ezután módosítjuk a providers részt az app.module.ts-ben:

import { split } from '@apollo/client/core';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';

// ... (többi import)

@NgModule({
  // ...
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: (httpLink: HttpLink) => {
        const http = httpLink.create({
          uri: 'http://localhost:4000/graphql',
        });

        const ws = new WebSocketLink({
          uri: `ws://localhost:4000/graphql`, // WebSocket szerver URI-je
          options: {
            reconnect: true,
          },
        });

        // A split funkció lehetővé teszi, hogy eldöntsük,
        // melyik linket használjuk a kérés típusától függően.
        // Queries és Mutations mennek HTTP-n, Subscriptions WebSocket-en.
        const link = split(
          ({ query }) => {
            const definition = getMainDefinition(query);
            return (
              definition.kind === 'OperationDefinition' &&
              definition.operation === 'subscription'
            );
          },
          ws,
          http,
        );

        return {
          cache: new InMemoryCache(),
          link,
        };
      },
      deps: [HttpLink],
    },
  ],
  // ...
})
export class AppModule { }

Előfizetés használata

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';
import { Subscription } from 'rxjs';

interface Character {
  id: string;
  name: string;
}

interface CharacterAdded {
  characterAdded: Character;
}

const CHARACTER_ADDED_SUBSCRIPTION = gql`
  subscription OnCharacterAdded {
    characterAdded {
      id
      name
    }
  }
`;

@Component({
  selector: 'app-character-feed',
  template: `
    

Új karakterek

Új karakter érkezett: {{ newCharacter.name }} (ID: {{ newCharacter.id }})

Várakozás új karakterekre...

Hiba: {{ error.message }}

`, }) export class CharacterFeedComponent implements OnInit, OnDestroy { newCharacter: Character | undefined; error: any; private subscription: Subscription | undefined; constructor(private apollo: Apollo) {} ngOnInit() { this.subscription = this.apollo .subscribe({ query: CHARACTER_ADDED_SUBSCRIPTION, }) .subscribe({ next: ({ data }) => { if (data) { this.newCharacter = data.characterAdded; } }, error: (error) => { this.error = error; console.error('Subscription error', error); }, }); } ngOnDestroy() { this.subscription?.unsubscribe(); } }

Az apollo.subscribe metódus egy Observable-t ad vissza, akárcsak a watchQuery. Fontos, hogy az előfizetést az ngOnDestroy életciklus-hookban szüntessük meg, hogy elkerüljük a memóriaszivárgást és a felesleges hálózati forgalmat.

Haladó tippek és legjobb gyakorlatok

  • Hiba kezelés: Az Apollo Angular lehetővé teszi, hogy globálisan kezeljük a GraphQL hibákat az ErrorLink segítségével. Ez központosított hibaüzenetek megjelenítésére vagy logolására használható.
  • Kódgenerálás: A GraphQL Code Generator (graphql-code-generator.com) egy rendkívül hasznos eszköz, amely automatikusan TypeScript interfészeket és Apollo Service-eket generál a GraphQL sémából és a lekérdezésekből. Ez jelentősen javítja a típusbiztonságot és a fejlesztői élményt, csökkentve a manuális típusdefiníciók szükségességét.
  • Hitelesítés és jogosultság: Az Apollo Link Chain segítségével könnyedén hozzáadhatunk hitelesítési tokeneket a GraphQL kérésekhez (pl. Authorization header), vagy kezelhetjük a refresh token logikát.
  • Cache optimalizálás: Az InMemoryCache nagyon hatékony, de összetettebb esetekben szükség lehet egyedi cache stratégiákra. Például, ha paginationt használunk, a TypePolicies segítségével beállíthatjuk, hogyan fűzze össze a cache a különböző lapokat.
  • Szerveroldali renderelés (SSR): Angular Universal és GraphQL kombinációjával javíthatjuk az alkalmazás SEO-ját és a kezdeti betöltési sebességet. Az Apollo támogatja az SSR-t, lehetővé téve az adatok előzetes betöltését a szerveren.

Mikor érdemes GraphQL-t használni Angularral?

A GraphQL nem minden alkalmazáshoz a legjobb választás, de bizonyos esetekben jelentős előnyöket kínál:

  • Komplex adathálózat: Ha az alkalmazásnak sok különböző adatforrásból kell adatokat kombinálnia, és a kliensnek rugalmasan kell tudnia kiválasztani a szükséges mezőket.
  • Több platform: Ha ugyanazt az API-t kell használni webes, mobil és egyéb kliensek számára, ahol mindegyiknek eltérő adatigényei lehetnek.
  • Gyors iteráció és változó követelmények: A GraphQL séma könnyen bővíthető, és a kliensek továbbra is a régi lekérdezéseket használhatják, amíg a szükséges mezők elérhetők.
  • Valós idejű funkciók: Chatek, élő frissítések, értesítések egyszerűen megvalósíthatók subscriptions segítségével.

Egyszerű CRUD (Create, Read, Update, Delete) műveleteket végző, kis méretű alkalmazások esetében a REST API továbbra is megfelelő lehet, és kevesebb overhead-del járhat a beállítás és karbantartás. Fontos mérlegelni a projekt komplexitását és a csapat ismereteit a technológia kiválasztásakor.

Konklúzió

A GraphQL Angular alkalmazásokban való használata egy modern és hatékony megközelítés az adatkommunikációra. Az Apollo Angular kliens robusztus és jól integrált megoldást kínál a lekérdezések, mutációk és előfizetések kezelésére, miközben kihasználja az Angular reaktív képességeit. A precíz adatlekérdezés, a kevesebb hálózati forgalom, a kiváló fejlesztői élmény és a valós idejű adatok támogatása mind olyan előnyök, amelyek a GraphQL-t rendkívül vonzóvá teszik komplex és skálázható Angular alkalmazások építése során. Ha egy igazán dinamikus és adatintenzív webes megoldáson dolgozik, érdemes alaposan megfontolnia a GraphQL bevezetését, és megtapasztalnia az általa kínált szabadságot és hatékonyságot.

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük