{"id":13786061,"url":"https://github.com/arjunyel/angular-spacex-graphql-codegen","last_synced_at":"2025-04-09T13:08:38.863Z","repository":{"id":38461731,"uuid":"194707330","full_name":"arjunyel/angular-spacex-graphql-codegen","owner":"arjunyel","description":null,"archived":false,"fork":false,"pushed_at":"2023-03-03T10:03:34.000Z","size":529,"stargazers_count":272,"open_issues_count":19,"forks_count":41,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-01T20:52:13.340Z","etag":null,"topics":["angular","apollo-angular","graphql","graphql-code-generator","spacex"],"latest_commit_sha":null,"homepage":"https://youtu.be/7wzR4Ig5pTI","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arjunyel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-01T16:23:49.000Z","updated_at":"2025-04-01T12:25:38.000Z","dependencies_parsed_at":"2024-08-03T19:05:43.053Z","dependency_job_id":"8f7af45f-3506-4bce-8e1d-85c34c5fec22","html_url":"https://github.com/arjunyel/angular-spacex-graphql-codegen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunyel%2Fangular-spacex-graphql-codegen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunyel%2Fangular-spacex-graphql-codegen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunyel%2Fangular-spacex-graphql-codegen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunyel%2Fangular-spacex-graphql-codegen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arjunyel","download_url":"https://codeload.github.com/arjunyel/angular-spacex-graphql-codegen/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248045233,"owners_count":21038553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","apollo-angular","graphql","graphql-code-generator","spacex"],"created_at":"2024-08-03T19:01:08.876Z","updated_at":"2025-04-09T13:08:38.835Z","avatar_url":"https://github.com/arjunyel.png","language":"TypeScript","readme":"# Angular Spacex Graphql Codegen\n\n## Mission\n\nOur goal is to make an Angular app with a list of the past SpaceX launches along with an associated details page. Data is provided via the [SpaceX GraphQL API](https://medium.com/open-graphql/launching-spacex-graphql-api-b3d7029086e0) and Angular services are generated via [GraphQL Code Generator](https://graphql-code-generator.com/). We use [Apollo Angular](https://www.apollographql.com/docs/angular/) to access data from the frontend. The API is free so please be nice and don't abuse it.\n\n## End Result\n\n![List view](list-example.jpg)\n\n![Details view](details-example.jpg)\n\n## Steps\n\n1. Generate a new angular application with routing\n\n   ```bash\n   ng new angular-spacex-graphql-codegen --routing=true --style=css\n   ```\n\n   Make sure to delete the default template in `src/app/app.component.html`\n\n1. Install the [Apollo VS Code plugin](https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo) and in the root of the project add `apollo.config.js`\n\n   ```javascript\n   module.exports = {\n     client: {\n       service: {\n         name: 'angular-spacex-graphql-codegen',\n         url: 'https://api.spacex.land/graphql/'\n       }\n     }\n   };\n   ```\n\n   This points the extension at the SpaceX GraphQL API so we get autocomplete, type information, and other cool features in GraphQL files. You may need to restart VS Code.\n\n1. Generate our two components:\n\n   ```bash\n   ng g component launch-list --changeDetection=OnPush\n   ```\n\n   ```bash\n   ng g component launch-details --changeDetection=OnPush\n   ```\n\n   Because our generated services use observables we choose OnPush change detection for the best performance.\n\n1. In `src/app/app-routing.module.ts` we setup the routing:\n\n   ```typescript\n   import { LaunchListComponent } from './launch-list/launch-list.component';\n   import { LaunchDetailsComponent } from './launch-details/launch-details.component';\n\n   const routes: Routes = [\n     {\n       path: '',\n       component: LaunchListComponent\n     },\n     {\n       path: ':id',\n       component: LaunchDetailsComponent\n     }\n   ];\n   ```\n\n1. Each component will have its own data requirments so we co-locate our graphql query files next to them\n\n   ```graphql\n   # src/app/launch-list/launch-list.graphql\n\n   query pastLaunchesList($limit: Int!) {\n     launchesPast(limit: $limit) {\n       id\n       mission_name\n       links {\n         flickr_images\n         mission_patch_small\n       }\n       rocket {\n         rocket_name\n       }\n       launch_date_utc\n     }\n   }\n   ```\n\n   ```graphql\n   # src/app/launch-details/launch-details.graphql\n\n   query launchDetails($id: ID!) {\n     launch(id: $id) {\n       id\n       mission_name\n       details\n       links {\n         flickr_images\n         mission_patch\n       }\n     }\n   }\n   ```\n\n   Note the first line: `query launchDetails($id: ID!)` When we generate the Angular service the query name is turned into PascalCase and GQL is appended to the end, so the service name for the launch details would be LaunchDetailsGQL. Also in the first line we define any variables we'll need to pass into the query. Please note it's import to include id in the query return so apollo can cache the data.\n\n1. We add [Apollo Angular](https://www.apollographql.com/docs/angular/) to our app with `ng add apollo-angular`. In `src/app/graphql.module.ts` we set our API url `const uri = 'https://api.spacex.land/graphql/';`.\n\n1. Install Graphql Code Generator and the needed plugins `npm i --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-apollo-angular @graphql-codegen/typescript-operations`\n\n1. In the root of the project create a `codegen.yml` file:\n\n   ```yml\n   # Where to get schema data\n   schema:\n     - https://api.spacex.land/graphql/\n   # The client side queries to turn into services\n   documents:\n     - src/**/*.graphql\n   # Where to output the services and the list of plugins\n   generates:\n     ./src/app/services/spacexGraphql.service.ts:\n       plugins:\n         - typescript\n         - typescript-operations\n         - typescript-apollo-angular\n   ```\n\n1. In package.json add a script `\"codegen\": \"gql-gen\"` then `npm run codegen` to generate the Angular Services.\n\n1. To make it look nice we add Angular Material `ng add @angular/material` then in the `app.module.ts` we import the card module and add to the imports array: `import { MatCardModule } from '@angular/material/card';`\n\n1. Lets start with the list of past launches displayed on the screen:\n\n   ```typescript\n   import { map } from 'rxjs/operators';\n   import { PastLaunchesListGQL } from '../services/spacexGraphql.service';\n\n   export class LaunchListComponent {\n     constructor(private readonly pastLaunchesService: PastLaunchesListGQL) {}\n\n     // Please be careful to not fetch too much, but this amount lets us see lazy loading imgs in action\n     pastLaunches$ = this.pastLaunchesService\n       .fetch({ limit: 30 })\n       // Here we extract our query data, we can also get info like errors or loading state from res\n       .pipe(map((res) =\u003e res.data.launchesPast));\n   }\n   ```\n\n   ```html\n   \u003cng-container *ngIf=\"pastLaunches$ | async as pastLaunches\"\u003e\n     \u003cmain\u003e\n       \u003csection class=\"container\"\u003e\n         \u003cmat-card\n           *ngFor=\"let launch of pastLaunches\"\n           [routerLink]=\"['/', launch.id]\"\n         \u003e\n           \u003cmat-card-header\u003e\n             \u003cimg\n               height=\"50\"\n               width=\"50\"\n               mat-card-avatar\n               loading=\"lazy\"\n               [src]=\"launch.links.mission_patch_small\"\n               alt=\"Mission patch of {{ launch.mission_name }}\"\n             /\u003e\n             \u003cmat-card-title\u003e{{ launch.mission_name }}\u003c/mat-card-title\u003e\n             \u003cmat-card-subtitle\n               \u003e{{ launch.rocket.rocket_name }}\u003c/mat-card-subtitle\n             \u003e\n           \u003c/mat-card-header\u003e\n           \u003cimg\n             height=\"300\"\n             width=\"300\"\n             mat-card-image\n             loading=\"lazy\"\n             [src]=\"launch.links.flickr_images[0]\"\n             alt=\"Photo of {{ launch.mission_name }}\"\n           /\u003e\n         \u003c/mat-card\u003e\n       \u003c/section\u003e\n     \u003c/main\u003e\n   \u003c/ng-container\u003e\n   ```\n\n   Notice the cool addition of [lazy loading images](https://web.dev/native-lazy-loading), if you emulate a mobile device in Chrome and fetch enough launches you should see the images lazy load while you scroll!\n\n   To make it look nice we add CSS Grid\n\n   ```css\n   .container {\n     padding-top: 20px;\n     display: grid;\n     grid-gap: 30px;\n     grid-template-columns: repeat(auto-fill, 350px);\n     justify-content: center;\n   }\n\n   .mat-card {\n     cursor: pointer;\n   }\n   ```\n\n1. Next we make the details page for a launch, we get the id from the route params and pass that to our service\n\n   ```typescript\n   import { ActivatedRoute } from '@angular/router';\n   import { map, switchMap } from 'rxjs/operators';\n   import { LaunchDetailsGQL } from '../services/spacexGraphql.service';\n\n   export class LaunchDetailsComponent {\n     constructor(\n       private readonly route: ActivatedRoute,\n       private readonly launchDetailsService: LaunchDetailsGQL\n     ) {}\n\n     launchDetails$ = this.route.paramMap.pipe(\n       map((params) =\u003e params.get('id') as string),\n       switchMap((id) =\u003e this.launchDetailsService.fetch({ id })),\n       map((res) =\u003e res.data.launch)\n     );\n   }\n   ```\n\n   The HTML looks very similar to the list of launches\n\n   ```html\n   \u003cng-container *ngIf=\"launchDetails$ | async as launchDetails\"\u003e\n     \u003csection style=\"padding-top: 20px;\"\u003e\n       \u003cmat-card style=\"width: 400px; margin: auto;\"\u003e\n         \u003cmat-card-header\u003e\n           \u003cmat-card-title\u003e{{ launchDetails.mission_name }}\u003c/mat-card-title\u003e\n         \u003c/mat-card-header\u003e\n         \u003cimg\n           height=\"256\"\n           width=\"256\"\n           mat-card-image\n           [src]=\"launchDetails.links.mission_patch\"\n           alt=\"Mission patch of {{ launchDetails.mission_name }}\"\n         /\u003e\n         \u003cmat-card-content\u003e\n           \u003cp\u003e{{ launchDetails.details }}\u003c/p\u003e\n         \u003c/mat-card-content\u003e\n       \u003c/mat-card\u003e\n     \u003c/section\u003e\n     \u003csection class=\"photo-grid\"\u003e\n       \u003cimg\n         *ngFor=\"let pic of launchDetails.links.flickr_images\"\n         [src]=\"pic\"\n         alt=\"Picture of {{ launchDetails.mission_name }}\"\n         height=\"300\"\n         width=\"300\"\n         loading=\"lazy\"\n       /\u003e\n     \u003c/section\u003e\n   \u003c/ng-container\u003e\n   ```\n\n   Finally we add CSS Grid for the pictures\n\n   ```css\n   .photo-grid {\n     padding-top: 30px;\n     display: grid;\n     grid-gap: 10px;\n     grid-template-columns: repeat(auto-fill, 300px);\n     justify-content: center;\n   }\n   ```\n\n1. `npm start`, navigate to `http://localhost:4200/`, and it should work!\n\n### Extra Credit: Relative launch times\n\nThanks to the new builtin [relative time formating](https://v8.dev/features/intl-relativetimeformat) in V8, we can add `launched x days ago`\n\n1. Generate the pipe: `ng g pipe relative-time --module=app.module --flat=false`\n\n1. The pipe takes in the UTC time and returns a formatted string\n\n   ```typescript\n   import { Pipe, PipeTransform } from '@angular/core';\n\n   const milliSecondsInDay = 1000 * 3600 * 24;\n\n   // Cast as any because typescript typing haven't updated yet\n   const rtf = new (Intl as any).RelativeTimeFormat('en');\n\n   @Pipe({\n     name: 'relativeTime'\n   })\n   export class RelativeTimePipe implements PipeTransform {\n     transform(utcTime: string): string {\n       const diffInMillicseconds =\n         new Date(utcTime).getTime() - new Date().getTime();\n       const diffInDays = Math.round(diffInMillicseconds / milliSecondsInDay);\n       return rtf.format(diffInDays, 'day');\n     }\n   }\n   ```\n\n1. Add the pipe to our launch card in src/app/launch-list/launch-list.component.html\n\n   ```html\n   \u003cmat-card-subtitle\n     \u003e{{ launch.rocket.rocket_name }} - launched {{ launch.launch_date_utc |\n     relativeTime }}\u003c/mat-card-subtitle\n   \u003e\n   ```\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farjunyel%2Fangular-spacex-graphql-codegen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farjunyel%2Fangular-spacex-graphql-codegen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farjunyel%2Fangular-spacex-graphql-codegen/lists"}