{"id":18256914,"url":"https://github.com/mediacomem/comem-travel-log-ionic-setup","last_synced_at":"2026-03-19T03:33:06.004Z","repository":{"id":42223062,"uuid":"161184580","full_name":"MediaComem/comem-travel-log-ionic-setup","owner":"MediaComem","description":"COMEM+ Travel Log Ionic application setup","archived":false,"fork":false,"pushed_at":"2024-01-16T15:32:58.000Z","size":518,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-08-10T15:45:10.171Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MediaComem.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2018-12-10T14:08:03.000Z","updated_at":"2021-12-13T08:20:00.000Z","dependencies_parsed_at":"2025-02-14T17:49:24.545Z","dependency_job_id":null,"html_url":"https://github.com/MediaComem/comem-travel-log-ionic-setup","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/MediaComem/comem-travel-log-ionic-setup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fcomem-travel-log-ionic-setup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fcomem-travel-log-ionic-setup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fcomem-travel-log-ionic-setup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fcomem-travel-log-ionic-setup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MediaComem","download_url":"https://codeload.github.com/MediaComem/comem-travel-log-ionic-setup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Fcomem-travel-log-ionic-setup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29331600,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T06:13:03.264Z","status":"ssl_error","status_checked_at":"2026-02-11T06:12:55.843Z","response_time":97,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-11-05T10:24:07.590Z","updated_at":"2026-02-11T10:02:24.487Z","avatar_url":"https://github.com/MediaComem.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# COMEM+ Travel Log Ionic Setup\r\n\r\n\u003ca name=\"top\"\u003e\u003c/a\u003e\r\n\r\nThis repository contains instructions to build a skeleton application that can serve as a starting point to develop the Travel Log mobile application.\r\nThe completed skeleton app is available [here](https://github.com/Tazaf/comem-travel-log-ionic-starter).\r\n\r\nThis tutorial is used in the [COMEM+](http://www.heig-vd.ch/comem) [Mobile Applications course](https://github.com/MediaComem/comem-appmob) taught at [HEIG-VD](http://www.heig-vd.ch).\r\n\r\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\r\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\r\n\r\n- [Prerequisites](#prerequisites)\r\n- [Features](#features)\r\n- [Design the user interface](#design-the-user-interface)\r\n- [Set up the application](#set-up-the-application)\r\n  - [Create a blank Ionic app](#create-a-blank-ionic-app)\r\n  - [Serve the application locally](#serve-the-application-locally)\r\n- [Set up the navigation structure](#set-up-the-navigation-structure)\r\n  - [Create the pages](#create-the-pages)\r\n  - [Update the app to use the pages](#update-the-app-to-use-the-pages)\r\n  - [Default tab](#default-tab)\r\n- [Set up security](#set-up-security)\r\n  - [Check the documentation of the API's authentication resource](#check-the-documentation-of-the-apis-authentication-resource)\r\n  - [Create model classes](#create-model-classes)\r\n  - [Create an authentication service](#create-an-authentication-service)\r\n  - [Create the login screen](#create-the-login-screen)\r\n  - [Use the authentication service to protect access to the layout page](#use-the-authentication-service-to-protect-access-to-the-layout-page)\r\n  - [Storing the authentication credentials](#storing-the-authentication-credentials)\r\n  - [Log out](#log-out)\r\n  - [Configuring an HTTP interceptor](#configuring-an-http-interceptor)\r\n- [Multi-environment \u0026 sensitive configuration](#multi-environment--sensitive-configuration)\r\n  - [Environment files](#environment-files)\r\n  - [Create the actual configuration file](#create-the-actual-configuration-file)\r\n  - [Add the environment files to your `.gitignore` file](#add-the-environment-files-to-your-gitignore-file)\r\n  - [When are the environment files used](#when-are-the-environment-files-used)\r\n  - [Feed the configuration to Angular](#feed-the-configuration-to-angular)\r\n\r\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\r\n\r\n## Prerequisites\r\n\r\nThese instructions assume that you are using the Travel Log API based on one of the suggestions of the previous course [Web-Oriented Architecture](https://github.com/MediaComem/comem-archioweb),\r\nand that you are familiar with the [documentation of the reference API](https://comem-travel-log-api.onrender.com/).\r\n\r\nYou will need to have [Node.js](https://nodejs.org) installed.\r\nThe latest LTS (Long Term Support) version is recommended (^20.0.0 at the time of writing).\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n## Features\r\n\r\nThis guide describes a proposed list of features and an example user interface based on those features.\r\n**This is only a suggestion** ; you can support other features and should definitely make a different user interface (the proposal below is not necessarily the best approach)\r\n\r\nThe proposed app should allow users to do the following:\r\n\r\n- Create new trips \u0026 places.\r\n- See visited places on an interactive map.\r\n- Browse the list of trips.\r\n\r\nThe following sections describe a proposed UI mockup of the app and steps to set up a skeleton implementation.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n## Design the user interface\r\n\r\nBefore diving into the code, you should **always take a moment to design the user interface** of your app.\r\nThis doesn't have to be a final design, but it should at least be a sketch of what you want.\r\nThis helps you think in terms of the whole app and of the relationships between screens.\r\n\r\n![UI Design](setup/ui-structure.png)\r\n\r\nAs you can see, we propose to use a tab view with 3 screens, and an additional 4th screen accessible from the trip list:\r\n\r\n- The create trip tab.\r\n- The places map tab.\r\n- The trip list tab.\r\n  - A trip details page.\r\n\r\nNow that we (somewhat) know what we want, we can start setting up the app!\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n## Set up the application\r\n\r\n### Create a blank Ionic app\r\n\r\nMake sure you have the [Ionic CLI][ionic-cli] installed, and that your computer is correctly configured to deploy on a mobile device :\r\n\r\n```bash\r\n$\u003e ionic -v\r\n7.1.5\r\n```\r\n\r\n\u003e If you have an error when running the above command, this probably means that you need to install the [Ionic CLI][ionic-cli]. To do so, execute:\r\n\u003e\r\n\u003e ```bash\r\n\u003e $\u003e npm install -g ionic@latest\r\n\u003e ```\r\n\r\nGo in the directory where you want your app source code to be located, then start generating your Ionic app with the following command:\r\n\r\n\u003e No need to create a dedicated directory for your app ; it will be created for you by the CLI tool\r\n\r\n```bash\r\n$\u003e ionic start\r\n```\r\n\r\nThe command will ask you if you want to use the app creation wizard. Answering \"yes\" will open your browser on a user-friendly configuration page (note that you'll need a free Ionic account to finish the wizard). This is **entirely optional** and you can define this configuration using the CLI creation wizard also.\r\n\r\n\u003e Since there is no way of generating an app with the **Blank** starter from this wizard, its recommended to answer \"No\" and keep using the CLI instead.\r\n\r\nYou'll be asked to give a name to your new application. You are free to name it as you wish. For the rest of this starter though, we'll call it `travel-log`.\r\n\r\nWhatever you choose to do, be sure to:\r\n\r\n- Select **Angular** as your app's underlying framework\r\n- Select the **Standalone** approach for Angular\r\n- Select the **Blank** starter template\r\n\r\n  \u003e If you are using the browser creation wizard, select whichever template seems to fit best your app's mockups.\r\n\r\n  \u003e Even though it is not recommended to start your project using a template other than the **Blank** one, you are more than advised to generate test apps using those other starters to look at how they are developed and use what you need in your own app.\r\n\r\nThen wait for the install to proceed... ⏳\r\n\r\nGo into the app directory. The `ionic start` command should already have initialized a Git repository and made the first commit, so let's check this out:\r\n\r\n```bash\r\n$\u003e cd travel-log\r\n$\u003e git log\r\ncommit XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (HEAD -\u003e master)\r\nAuthor: John Doe \u003cjohn.doe@example.com\u003e\r\nDate:   Mon Nov 4 14:25:29 2019 +0100\r\n\r\n    Initial commit\r\n```\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Serve the application locally\r\n\r\nTo make sure everything was set up correctly, use the following command from the root of your project directory to serve the application locally in your browser:\r\n\r\n```bash\r\n$\u003e ionic serve\r\n```\r\n\r\nYou should see something like this in your browser (or a dark version, depending on your OS settings):\r\n\r\n![Serving the blank app](setup/blank-app-serve.png)\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n## Set up the navigation structure\r\n\r\nAs defined in our UI design, we want the following 4 screens:\r\n\r\n- The trip creation tab.\r\n- The places map tab.\r\n- The trip list tab.\r\n  - The trip details screen.\r\n\r\nLet's start by setting up the 3 tabs.\r\nWe will use Ionic's [Tabs][ionic-tabs] component.\r\n\r\n\u003e If your app **does not** use tabs, you should use whatever's appropriate instead. Refer to the Ionic documentation if need be.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Create the pages\r\n\r\nEach page will be an [Angular component][angular-component].\r\nIonic has a `generate` command that can automatically set up the files we need to create each page's component.\r\n\r\nWe will create a `Layout` page that will contain the layout for the tabs (that is where the tabs bar and the slot for each tab will be displayed) ; each tab page should therefor be a subpage of `Layout`.\r\n\r\nFirst let's **delete the `src/app/home` folder entirely**. Then update the `app.routes.ts` file to empty the routes array:\r\n\r\n```ts\r\nconst routes: Routes = [\r\n  // Nothing here !\r\n];\r\n```\r\n\r\nThen, use the `ionic generate` command to create the `Layout` page.\r\n\r\n```bash\r\n$\u003e ionic generate page Layout\r\n```\r\n\r\nUpdate the `app.route.ts` file to add the routes leading to this new `Layout` page:\r\n\r\n```ts\r\nimport { Routes } from \"@angular/router\";\r\n\r\nexport const routes: Routes = [\r\n  {\r\n    path: \"\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/layout.page\").then((m) =\u003e m.LayoutPage),\r\n  },\r\n];\r\n```\r\n\r\nNow, we can easily create and plug the subpages:\r\n\r\n\u003e Use the up arrow key to retrieve the last executed command. This will prevent you typing the line three times\r\n\r\n```bash\r\n$\u003e ionic generate page layout/CreateTrip\r\n$\u003e ionic generate page layout/PlacesMap\r\n$\u003e ionic generate page layout/TripList\r\n```\r\n\r\n\u003e Notice how each page name is **preceeded by `layout/`**. That tells the CLI where to put the page's files.\r\n\r\nThis will generate the following files:\r\n\r\n\u003e For the CreateTrip page\r\n\r\n```\r\nsrc/app/layout/create-trip/create-trip.page.scss\r\nsrc/app/layout/create-trip/create-trip.page.html\r\nsrc/app/layout/create-trip/create-trip.page.spec.ts\r\nsrc/app/layout/create-trip/create-trip.page.ts\r\n```\r\n\r\n\u003e For the PlacesMap page\r\n\r\n```\r\nsrc/app/layout/places-map/places-map.page.scss\r\nsrc/app/layout/places-map/places-map.page.html\r\nsrc/app/layout/places-map/places-map.page.spec.ts\r\nsrc/app/layout/places-map/places-map.page.ts\r\n```\r\n\r\n\u003e For the TripList page\r\n\r\n```\r\nsrc/app/layout/trip-list/trip-list.page.scss\r\nsrc/app/layout/trip-list/trip-list.page.html\r\nsrc/app/layout/trip-list/trip-list.page.spec.ts\r\nsrc/app/layout/trip-list/trip-list.page.ts\r\n```\r\n\r\nFor each page, we have:\r\n\r\n- An HTML template (`*.page.html`).\r\n- A [Sass/SCSS][sass] stylesheet (`*.page.scss`).\r\n- An Angular component (`*.page.ts`).\r\n- A test suite with a default test (`*.page.spec.ts`).\r\n\r\nNow update the HTML template for each page and add some content at the end of the `\u003cion-content\u003e` tag.\r\nFor example, in `src/app/layout/create-trip/create-trip.page.html`:\r\n\r\n```html\r\n\u003cion-content [fullscreen]=\"true\"\u003e\r\n  \u003c!-- Leave the previous content here --\u003e\r\n  \u003c!-- Add the div below. The .ion-padding class adds some space around the content --\u003e\r\n  \u003cdiv class=\"ion-padding\"\u003eLet's create some trips\u003c/div\u003e\r\n\u003c/ion-content\u003e\r\n```\r\n\r\n### Update the app to use the pages\r\n\r\nNow that the pages are ready, we need to display them.\r\n\r\nFor each of your new pages, Ionic added a route leading to them in `app.routes.ts`. This should look like this (minus the comments):\r\n\r\n```ts\r\nimport { Routes } from \"@angular/router\";\r\n\r\nexport const routes: Routes = [\r\n  {\r\n    // Default route\r\n    path: \"\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/layout.page\").then((m) =\u003e m.LayoutPage),\r\n  },\r\n  {\r\n    // Route that loads the CreateTripPage component\r\n    path: \"create-trip\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/create-trip/create-trip.page\").then(\r\n        (m) =\u003e m.CreateTripPage\r\n      ),\r\n  },\r\n  {\r\n    // Route that loads the PlacesMapPage component's\r\n    path: \"places-map\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/places-map/places-map.page\").then(\r\n        (m) =\u003e m.PlacesMapPage\r\n      ),\r\n  },\r\n  {\r\n    // Route that loads the TripListPage component\r\n    path: \"trip-list\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/trip-list/trip-list.page\").then((m) =\u003e m.TripListPage),\r\n  },\r\n];\r\n```\r\n\r\nFor the tabs to propertly works, you need to **change this structure** so that each tab page route is **a child** of the `LayoutPage` route.\r\n\r\n\u003e This is because each tab page should be rendered **inside** the `LayoutPage` instead of completely replacing it.\r\n\r\nAdd a `children` property to the default route with an empty array, then move the three tab page's route in this empty array:\r\n\r\n```ts\r\nconst routes: Routes = [\r\n  {\r\n    // Default route\r\n    path: \"\",\r\n    component: LayoutPage,\r\n    children: [\r\n      {\r\n        // Route that loads the CreateTripPage component\r\n        path: \"create-trip\",\r\n        loadComponent: () =\u003e\r\n          import(\"./layout/create-trip/create-trip.page\").then(\r\n            (m) =\u003e m.CreateTripPage\r\n          ),\r\n      },\r\n      {\r\n        // Route that loads the PlacesMapPage component's\r\n        path: \"places-map\",\r\n        loadComponent: () =\u003e\r\n          import(\"./layout/places-map/places-map.page\").then(\r\n            (m) =\u003e m.PlacesMapPage\r\n          ),\r\n      },\r\n      {\r\n        // Route that loads the TripListPage component\r\n        path: \"trip-list\",\r\n        loadComponent: () =\u003e\r\n          import(\"./layout/trip-list/trip-list.page\").then((m) =\u003e m.TripListPage),\r\n      },\r\n    ],\r\n  },\r\n];\r\n```\r\n\r\nNow, **update the layout page's component** (`src/app/layout/layout.page.ts`) to include the list of tabs we want:\r\n\r\n\u003e You can also delete the empty `ngOnInit()` method and remove the `OnInit` interface implementation from the class definition.\r\n\r\n```ts\r\nimport { CommonModule } from \"@angular/common\";\r\nimport { Component } from \"@angular/core\";\r\nimport { FormsModule } from \"@angular/forms\";\r\nimport { IonicModule } from \"@ionic/angular\";\r\nimport { add, map, list } from \"ionicons/icons\";\r\n\r\n// Custom type that represent a tab data.\r\ndeclare type PageTab = {\r\n  title: string; // The title of the tab in the tab bar\r\n  icon: string; // The icon of the tab in the tab bar\r\n  path: string; // The route's path of the tab to display\r\n};\r\n\r\n@Component({\r\n  selector: \"app-layout\",\r\n  templateUrl: \"./layout.page.html\",\r\n  styleUrls: [\"./layout.page.scss\"],\r\n  standalone: true,\r\n  imports: [IonicModule, CommonModule, FormsModule],\r\n})\r\nexport class LayoutPage {\r\n  tabs: PageTab[];\r\n\r\n  constructor() {\r\n    this.tabs = [\r\n      { title: \"New Trip\", icon: add, path: \"create-trip\" },\r\n      { title: \"Places Map\", icon: map, path: \"places-map\" },\r\n      { title: \"Trip List\", icon: list, path: \"trip-list\" },\r\n    ];\r\n  }\r\n}\r\n```\r\n\r\n\u003e Be sure that the value of each `PageTab`'s `path` property matches the corresponding route in the `layout-routing.module.ts` file.\r\n\r\nThird, we will **replace the ENTIRE content of the layout page's template** (`src/app/layout/layout.page.html`) with a template that uses Ionic's [Tabs component][ionic-tabs].\r\n\r\nAngular's `ngFor` directive allows us to iterate over the `tabs` array we declared in the layout page's component,\r\nand create an `\u003cion-tab-button\u003e` element for each of them, instead of defining them by hand:\r\n\r\n```html\r\n\u003cion-tabs\u003e\r\n  \u003cion-tab-bar slot=\"bottom\"\u003e\r\n    \u003cion-tab-button [tab]=\"tab.path\" *ngFor=\"let tab of tabs\"\u003e\r\n      \u003cion-icon [icon]=\"tab.icon\"\u003e\u003c/ion-icon\u003e\r\n      \u003cion-label\u003e{{ tab.title }}\u003c/ion-label\u003e\r\n    \u003c/ion-tab-button\u003e\r\n  \u003c/ion-tab-bar\u003e\r\n\u003c/ion-tabs\u003e\r\n```\r\n\r\nYou should now be able to navigate between the 3 tabs!\r\n\r\n![Serving the blank app](setup/tabs-setup.png)\r\n\r\n### Default tab\r\n\r\nIf you want your user to be redirected to a specific tab when first loading your app, you can do so by updating the `routes` array in the `app.routes.ts` file. For example, to define the `trip-list` tab as the default one:\r\n\r\n```ts\r\nimport { Routes } from \"@angular/router\";\r\n\r\nexport const routes: Routes = [\r\n  {\r\n    path: \"\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/layout.page\").then((m) =\u003e m.LayoutPage),\r\n    children: [\r\n      // Previous routes\r\n      {\r\n        path: \"\",\r\n        redirectTo: \"trip-list\", // Or whatever tab should be the default one.\r\n        pathMatch: \"full\",\r\n      },\r\n    ],\r\n  },\r\n];\r\n```\r\n\r\n## Set up security\r\n\r\nTo use the app, a user should identify themselves.\r\n\r\nYou will add a login screen that the user **must go through** before accessing the other screens.\r\n\r\nAuthentication will be performed by the [Travel Log API][travel-log-api] (or your own API if you don't use Travel Log)\r\n\r\nThe API requires a \"Bearer token\" be sent to identify the user when making requests on some resources (e.g. when creating trips).\r\nThis token must be sent in the `Authorization` header for all requests requiring identification.\r\n\r\nOnce login/logout is implemented, you will also set up an **HTTP interceptor** to automatically add this header to every request.\r\n\r\n### Check the documentation of the API's authentication resource\r\n\r\nThe Travel Log API provides an [`/auth` resource](https://demo-travel-log-api.onrender.com/#api-Authentication-CreateAuthenticationToken)\r\non which you can make a POST request to authenticate.\r\n\r\nYou need to make a call that looks like this:\r\n\r\n```json\r\nPOST /api/auth HTTP/1.1\r\nContent-Type: application/json\r\n\r\n{\r\n  \"username\": \"jdoe\",\r\n  \"password\": \"test\"\r\n}\r\n```\r\n\r\nThe response will contain the token we need for authentication,\r\nas well as a representation of the authenticated user:\r\n\r\n```json\r\nHTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\r\n  \"token\": \"eyJhbGciOiJIU.eyJpc3MiOiI1OGM1YjUwZTA0Nm.gik21xyT4_NzsduWMLVp8\",\r\n  \"user\": {\r\n    \"createdAt\": \"2018-12-09T11:58:18.265Z\",\r\n    \"href\": \"/api/users/d68cf4e9-1349-4d45-b356-c1294e49ef23\",\r\n    \"id\": \"d68cf4e9-1349-4d45-b356-c1294e49ef23\",\r\n    \"name\": \"jdoe\",\r\n    \"tripsCount\": 2,\r\n    \"updatedAt\": \"2018-12-09T11:58:18.265Z\"\r\n  }\r\n}\r\n```\r\n\r\nYou will need to perform this request and retrieve that information when the user logs in.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Create model classes\r\n\r\nLet's create a few classes to use as models when communicating with the API.\r\nThat way we will benefit from TypeScript's typing when accessing model properties.\r\n\r\n\u003e **If you are NOT using the Travel Log API, you will need to adapt those models to what your API returns**.\r\n\r\nCreate a `src/app/security/user.model.ts` file which exports a model representing a user of the API:\r\n\r\n```ts\r\nexport type User = {\r\n  id: string;\r\n  href: string;\r\n  name: string;\r\n  tripsCount: number;\r\n  createdAt: string;\r\n  updatedAt: string;\r\n};\r\n```\r\n\r\nCreate a `src/app/security/auth-request.model.ts` file which exports a model representing a request to the authentication resource:\r\n\r\n```ts\r\nexport type AuthRequest = {\r\n  username: string;\r\n  password: string;\r\n};\r\n```\r\n\r\nCreate a `src/app/security/auth-response.model.ts` file which exports a model representing a successful response from the authentication resource:\r\n\r\n```ts\r\nimport { User } from \"./user.model\";\r\n\r\nexport type AuthResponse = {\r\n  token: string;\r\n  user: User;\r\n};\r\n```\r\n\r\n### Create an authentication service\r\n\r\nSince the new service we'll create will make Http requests, we **need** to provide Angular's `HttpClient` own service to our entire app. Do so in the `src/main.ts` file:\r\n\r\n```ts\r\n// Other imports should not be touched\r\nimport { enableProdMode, importProvidersFrom } from \"@angular/core\";\r\nimport { provideHttpClient } from \"@angular/common/http\";\r\n\r\nif (environment.production) {\r\n  enableProdMode();\r\n}\r\n\r\nbootstrapApplication(AppComponent, {\r\n  providers: [\r\n    // Previous providers should not be touched...\r\n    provideHttpClient(), // \u003c-- Add this line\r\n  ],\r\n});\r\n```\r\n\r\nNow, let's generate a reusable, injectable service to manage authentication:\r\n\r\n```bash\r\n$\u003e ionic generate service security/Auth\r\n```\r\n\r\nYou can replace the content of the generated `src/app/security/auth.service.ts` file with the following code:\r\n\r\n```ts\r\nimport { Injectable } from \"@angular/core\";\r\nimport { Observable, ReplaySubject, filter, map } from \"rxjs\";\r\nimport { AuthResponse } from \"./auth-response.model\";\r\nimport { HttpClient } from \"@angular/common/http\";\r\nimport { User } from \"./user.model\";\r\nimport { AuthRequest } from \"./auth-request.model\";\r\n\r\n/***********************************************************/\r\n/*********!!! REPLACE BELOW WITH YOUR API URL !!! **********/\r\n/***********************************************************/\r\nconst API_URL = \"\u003cREPLACE_ME\u003e\";\r\n\r\n/**\r\n * Authentication service for login/logout.\r\n */\r\n@Injectable({ providedIn: \"root\" })\r\nexport class AuthService {\r\n  #auth$: ReplaySubject\u003cAuthResponse | undefined\u003e;\r\n\r\n  constructor(private http: HttpClient) {\r\n    this.#auth$ = new ReplaySubject(1);\r\n    // Emit an undefined value on startup for now\r\n    this.#auth$.next(undefined);\r\n  }\r\n\r\n  /**\r\n   * @returns An `Observable` that will emit a `boolean` value\r\n   * indicating whether the current user is authenticated.\r\n   * This `Observable` will never complete and must be unsubscribed for when not needed.\r\n   */\r\n  isAuthenticated$(): Observable\u003cboolean\u003e {\r\n    return this.#auth$.pipe(map((auth) =\u003e Boolean(auth)));\r\n  }\r\n\r\n  /**\r\n   * @returns An `Observable` that will emit the currently authenticated `User` object only if there\r\n   * currently is an authenticated user.\r\n   */\r\n  getUser$(): Observable\u003cUser | undefined\u003e {\r\n    return this.#auth$.pipe(map((auth) =\u003e auth?.user));\r\n  }\r\n\r\n  /**\r\n   * @returns An `Observable` that will emit the currently authenticated user's `token`, only if there\r\n   * currently is an authenticated user.\r\n   */\r\n  getToken$(): Observable\u003cstring | undefined\u003e {\r\n    return this.#auth$.pipe(map((auth) =\u003e auth?.token));\r\n  }\r\n\r\n  /**\r\n   * Sends an authentication request to the backend API in order to log in a user with the\r\n   * provided `authRequest` object.\r\n   *\r\n   * @param authRequest An object containing the authentication request params\r\n   * @returns An `Observable` that will emit the logged in `User` object on success.\r\n   */\r\n  logIn$(authRequest: AuthRequest): Observable\u003cUser\u003e {\r\n    const authUrl = `${API_URL}/auth`;\r\n    return this.http.post\u003cAuthResponse\u003e(authUrl, authRequest).pipe(\r\n      map((auth) =\u003e {\r\n        this.#auth$.next(auth);\r\n        console.log(`User ${auth.user.name} logged in`);\r\n        return auth.user;\r\n      })\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Logs out the current user.\r\n   */\r\n  logOut(): void {\r\n    this.#auth$.next(undefined);\r\n    console.log(\"User logged out\");\r\n  }\r\n}\r\n```\r\n\r\n\u003e You need to set the value of the constant `API_URL` to your dedicated API URL. In this example, `API_URL` will have a value of \"https://demo-travel-log-api.onrender.com/api\"\r\n\r\n### Create the login screen\r\n\r\nGenerate a login page component with:\r\n\r\n```bash\r\n$\u003e ionic generate page auth/Login\r\n```\r\n\r\n\u003e This will add a `login` route at the end of the `app.routes.ts` array.\r\n\r\nAdd the following HTML form **at the end of the `\u003cion-content\u003e` tag** of `src/app/security/login/login.page.html`:\r\n\r\n\u003e This assume that you have read [the slide doc explaining how to use forms with Angular and Ionic components][forms-with-angular-and-ionic]. If you **DID NOT**, now would be an appropriate time...\r\n\r\n```html\r\n\u003cdiv class=\"ion-padding\"\u003e\r\n  \u003cform #loginForm=\"ngForm\" (submit)=\"onSubmit(loginForm)\"\u003e\r\n    \u003c!-- Username input --\u003e\r\n    \u003cion-input\r\n      label=\"Username\"\r\n      labelPlacement=\"floating\"\r\n      inputmode=\"text\"\r\n      #username=\"ngModel\"\r\n      required=\"true\"\r\n      name=\"username\"\r\n      [(ngModel)]=\"authRequest.username\"\r\n    \u003e\u003c/ion-input\u003e\r\n    \u003c!-- Error message displayed if the username is invalid --\u003e\r\n    \u003cion-text color=\"danger\" *ngIf=\"username.invalid \u0026\u0026 username.touched\"\r\n      \u003eUsername is required.\u003c/ion-text\r\n    \u003e\r\n\r\n    \u003c!-- Password input --\u003e\r\n    \u003cion-input\r\n      label=\"Password\"\r\n      labelPlacement=\"floating\"\r\n      inputmode=\"text\"\r\n      #password=\"ngModel\"\r\n      required=\"true\"\r\n      type=\"password\"\r\n      name=\"password\"\r\n      [(ngModel)]=\"authRequest.password\"\r\n    \u003e\u003c/ion-input\u003e\r\n\r\n    \u003c!-- Error message displayed if the password is invalid --\u003e\r\n    \u003cion-text color=\"danger\" *ngIf=\"password.invalid \u0026\u0026 password.touched\"\r\n      \u003ePassword is required.\u003c/ion-text\r\n    \u003e\r\n\r\n    \u003c!-- Submit button --\u003e\r\n    \u003cion-button type=\"submit\" expand=\"block\" [disabled]=\"loginForm.invalid\"\r\n      \u003eLog in\u003c/ion-button\r\n    \u003e\r\n\r\n    \u003c!-- Error message displayed if the login failed --\u003e\r\n    \u003cion-text color=\"danger\" *ngIf=\"loginError\"\r\n      \u003eUsername or password is invalid.\u003c/ion-text\r\n    \u003e\r\n  \u003c/form\u003e\r\n\u003c/div\u003e\r\n```\r\n\r\nUpdate `src/app/security/login/login.page.ts` as follows:\r\n\r\n```ts\r\nimport { CommonModule } from \"@angular/common\";\r\nimport { Component } from \"@angular/core\";\r\nimport { FormsModule, NgForm } from \"@angular/forms\";\r\nimport { Router } from \"@angular/router\";\r\nimport { IonicModule } from \"@ionic/angular\";\r\nimport { AuthRequest } from \"../auth-request.model\";\r\nimport { AuthService } from \"../auth.service\";\r\n\r\n@Component({\r\n  selector: \"app-login\",\r\n  templateUrl: \"./login.page.html\",\r\n  styleUrls: [\"./login.page.scss\"],\r\n  standalone: true,\r\n  imports: [IonicModule, CommonModule, FormsModule],\r\n})\r\nexport class LoginPage {\r\n  /**\r\n   * This authentication request object will be updated when the user\r\n   * edits the login form. It will then be sent to the API.\r\n   *\r\n   * NOTE: The \"Partial\u003cAuthRequest\u003e\" type here has the same properties as \"AuthRequest\",\r\n   * but they are all optional.\r\n   */\r\n  authRequest: Partial\u003cAuthRequest\u003e = {};\r\n\r\n  /**\r\n   * If true, it means that the authentication API has return a failed response\r\n   * (probably because the name or password is incorrect).\r\n   */\r\n  loginError = false;\r\n\r\n  constructor(private auth: AuthService, private router: Router) {\r\n    this.authRequest = {};\r\n  }\r\n\r\n  /**\r\n   * Called when the login form is submitted.\r\n   */\r\n  onSubmit(form: NgForm) {\r\n    // Do not do anything if the form is invalid.\r\n    if (form.invalid) {\r\n      return;\r\n    }\r\n\r\n    // Hide any previous login error.\r\n    this.loginError = false;\r\n\r\n    // Perform the authentication request to the API.\r\n    // NOTE: Since our form is valid, it means that \"this.authRequest\" is actually\r\n    // a perfectly valid \"AuthRequest\" object, and that's what we are telling TypeScript\r\n    // here with \"as AuthRequest\".\r\n    this.auth.logIn$(this.authRequest as AuthRequest).subscribe({\r\n      next: () =\u003e this.router.navigateByUrl(\"/\"),\r\n      error: (err) =\u003e {\r\n        this.loginError = true;\r\n        console.warn(`Authentication failed: ${err.message}`);\r\n      },\r\n    });\r\n  }\r\n}\r\n```\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Use the authentication service to protect access to the layout page\r\n\r\nNow that we have a service to manage authentication and a working page for users to log in, we need to make sure that unauthenticated user can not access restricted pages and are instead redirected to the login page.\r\n\r\nWe will use an [Angular Guard][angular-guard] to do this.\r\n\r\nCreate a new file at `src/app/security/only-authenticated.guard.ts` with the following content:\r\n\r\n```ts\r\nimport { inject } from \"@angular/core\";\r\nimport { CanActivateFn, Router } from \"@angular/router\";\r\nimport { AuthService } from \"./auth.service\";\r\nimport { map } from \"rxjs\";\r\n\r\nexport const onlyAuthenticated: CanActivateFn = () =\u003e {\r\n  const authService = inject(AuthService);\r\n  const router = inject(Router);\r\n\r\n  return authService\r\n    .isAuthenticated$()\r\n    .pipe(\r\n      map((isAuthenticated) =\u003e\r\n        isAuthenticated ? true : router.parseUrl(\"/login\")\r\n      )\r\n    );\r\n};\r\n```\r\n\r\nTo use this guard, open the `src/app/app.routes.ts` file and add a new `canActivate` property to the first `''` route:\r\n\r\n```ts\r\n// Previous imports remain untouched\r\nimport { onlyAuthenticated } from \"./security/only-authenticated.guard\";\r\n\r\nexport const routes: Routes = [\r\n  {\r\n    path: \"\",\r\n    loadComponent: () =\u003e\r\n      import(\"./layout/layout.page\").then((m) =\u003e m.LayoutPage),\r\n    canActivate: [onlyAuthenticated], // \u003c-- Add this line\r\n    children: [\r\n      // Children routes remain untouched\r\n    ],\r\n  },\r\n  {\r\n    path: \"login\",\r\n    loadComponent: () =\u003e\r\n      import(\"./security/login/login.page\").then((m) =\u003e m.LoginPage),\r\n  },\r\n];\r\n```\r\n\r\nThe login screen is ready!\r\nIf you reload your app, you should see that you are automatically redirected to the login page.\r\n\r\nYou can now try logging in, provided that there is an existing user in your API dataset.\r\n\r\n\u003e If it's not the case, you can create one with Postman by sending a request to the endpoint of your API that allows registering new users\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Storing the authentication credentials\r\n\r\nNow you can log in, but there's a little problem.\r\n\r\nEvery time the app is reloaded, you lose all data so you have to log back in.\r\nThis is particularly annoying for local development since the browser is automatically refreshed every time you change the code.\r\n\r\nYou need to use more persistent storage for the security credentials, that is the authentication token.\r\nIonic provides a [storage module][storage] which will automatically select an appropriate storage method for your platform.\r\nIt will use [SQLite][sqlite] on phones when available; for web platforms it will use [IndexedDB][indexed-db], [WebSQL][websql] or [Local Storage][local-storage].\r\n\r\nTo use the Ionic storage module, you must first install it:\r\n\r\n```bash\r\n$\u003e npm i @ionic/storage-angular\r\n```\r\n\r\nThen add it to the `importProvidersFrom()` function call in `src/main.ts`:\r\n\r\n```ts\r\n// Other imports remain untouched\r\nimport { IonicStorageModule } from \"@ionic/storage-angular\";\r\n\r\nif (environment.production) {\r\n  enableProdMode();\r\n}\r\n\r\nbootstrapApplication(AppComponent, {\r\n  providers: [\r\n    // Other providers remain untouched\r\n    importProvidersFrom(IonicStorageModule.forRoot()),\r\n  ],\r\n});\r\n```\r\n\r\nInitialize the storage in `AppComponent` in `src/app/app.component.ts`:\r\n\r\n```ts\r\n// Other imports...\r\nimport { Storage } from \"@ionic/storage-angular\";\r\n\r\nexport class AppComponent {\r\n  constructor(storage: Storage) {\r\n    storage.create();\r\n  }\r\n}\r\n```\r\n\r\nNow you can import the `Storage` service in `AuthService` in `src/app/security/auth.service.ts`:\r\n\r\n```ts\r\n// Other imports remain untouched\r\nimport { /* Other imports */, from } from \"rxjs\";\r\nimport { Storage } from \"@ionic/storage-angular\";\r\n\r\n/**\r\n * Authentication service for login/logout.\r\n */\r\n@Injectable({ providedIn: \"root\" })\r\nexport class AuthService {\r\n  // Untouched code\r\n\r\n  constructor(private http: HttpClient, private readonly storage: Storage) {\r\n    // Untouched code\r\n  }\r\n\r\n  // Untouched code\r\n\r\n  /**\r\n   * Persists the provided `AuthResponse` to the storage.\r\n   *\r\n   * @param auth The AuthResponse to persist\r\n   * @returns An `Observable` that will emit when the authentication is persisted\r\n   */\r\n  #saveAuth$(auth: AuthResponse): Observable\u003cvoid\u003e {\r\n    return from(this.storage.set(\"auth\", auth));\r\n  }\r\n}\r\n\r\n// Untouched code\r\n```\r\n\r\nThe storage module returns Promises, but we'll be plugging this new function into `logIn$()` which uses Observables,\r\nso we convert the Promise to an Observable before returning it, with the `from` function.\r\n\r\n\u003e The `from` method can be imported from `rxjs`\r\n\r\nYou can now update the `logIn$()` method to persist the API's authentication response with the new `#saveAuth$()` method.\r\nTo do that, use RxJS's [`delayWhen`][rxjs-delay-when] operator, which allows us to delay an Observable stream (in this case, the one that indicates our user is authenticated) until another Observable emits\r\n(in this case, the one that saves the authentication response).\r\n\r\n\u003e This way, we only emit the `auth.user` when we are **sure** that the `auth` object has been saved in the storage.\r\n\r\n```ts\r\nlogIn$(authRequest: AuthRequest): Observable\u003cUser\u003e {\r\n\r\n  const authUrl = `${API_URL}/auth`;\r\n  return this.http.post\u003cAuthResponse\u003e(authUrl, authRequest).pipe(\r\n    // Delay the observable stream while persisting the authentication response.\r\n    delayWhen((auth) =\u003e this.#saveAuth$(auth)),\r\n    map(auth =\u003e {\r\n      this.#auth$.next(auth);\r\n      console.log(`User ${auth.user.name} logged in`);\r\n      return auth.user;\r\n    })\r\n  );\r\n}\r\n```\r\n\r\n\u003e the `delayWhen` function can be imported from `rxjs/operators`.\r\n\r\nWhen testing in the browser, you should already see the object being stored in IndexedDB (the default storage if using Chrome).\r\n\r\nYou must now load it when the app starts.\r\nYou can do that in the constructor of `AuthService` by replacing the line `this.#auth$.next();`:\r\n\r\n```ts\r\nconstructor(private http: HttpClient, private storage: Storage) {\r\n  this.#auth$ = new ReplaySubject(1);\r\n  this.storage.get('auth').then((auth) =\u003e {\r\n    // Emit the loaded value into the observable stream.\r\n    this.#auth$.next(auth);\r\n  });\r\n}\r\n```\r\n\r\n\u003e Since the storage provider's `get` method returns a promise, you can only use the result in a `.then` asynchronous callback:\r\n\r\nYour app should now remember user credentials even when you reload it!\r\n\r\nFinally, also update the `AuthService`'s `logOut()` method to remove the stored authentication from storage when a user logs out:\r\n\r\n```ts\r\nlogOut() {\r\n  this.#auth$.next(undefined);\r\n  // Remove the stored authentication from storage when logging out.\r\n  this.storage.remove('auth');\r\n  console.log('User logged out');\r\n}\r\n```\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Log out\r\n\r\nYou should also add a UI component to allow the user to log out.\r\n**As an example**, we will display a logout button in the title bar of the trip creation screen.\r\n\r\n\u003e This is **only for example**. In your real project, you might want to put this logout button in a more appropriate location... Just sayin'\r\n\r\nAdd an `\u003cion-buttons\u003e` tag with a logout button in `src/app/layout/create-trip/create-trip.page.html`:\r\n\r\n```html\r\n\u003cion-header\u003e\r\n  \u003cion-toolbar\u003e\r\n    \u003cion-title\u003eCreateTrip\u003c/ion-title\u003e\r\n\r\n    \u003c!-- Logout button --\u003e\r\n    \u003cion-buttons slot=\"end\"\u003e\r\n      \u003cion-button (click)=\"logOut()\"\u003e\r\n        \u003cion-icon [icon]=\"logOutIcon\"\u003e\u003c/ion-icon\u003e\r\n      \u003c/ion-button\u003e\r\n    \u003c/ion-buttons\u003e\r\n  \u003c/ion-toolbar\u003e\r\n\u003c/ion-header\u003e\r\n```\r\n\r\nLet's assume that when logging out, we want the user redirected to the login page. To do that, you will need to:\r\n\r\n- Inject the Angular Router, which will allow you to navigate to a defined route ;\r\n- Inject the `AuthService`, so that we can use use its `logOut()` method,\r\n- Add a `logOut()` method in the `CreateTripPage` component, since it's what we call in its HTML template above.\r\n\r\nWe'll also need to import the logo what we use in the button.\r\n\r\nAfter doing all that, your `CreateTripPage` component should look something like this:\r\n\r\n```ts\r\n// Other imports...\r\nimport { Router } from \"@angular/router\";\r\nimport { AuthService } from \"src/app/security/auth.service\";\r\nimport { logOut as logOutIcon } from \"ionicons/icons\";\r\n\r\n@Component({\r\n  /* ... */\r\n})\r\nexport class CreateTripPage implements OnInit {\r\n  readonly logOutIcon = logOutIcon;\r\n\r\n  constructor(\r\n    // Inject the authentication provider.\r\n    private auth: AuthService,\r\n    // Inject the router\r\n    private router: Router\r\n  ) {}\r\n\r\n  ngOnInit() {}\r\n\r\n  // Add a method to log out.\r\n  logOut() {\r\n    console.log(\"logging out...\");\r\n    this.auth.logOut();\r\n    this.router.navigateByUrl(\"/login\");\r\n  }\r\n}\r\n```\r\n\r\nYou should now see the logout button in the navigation bar after logging in.\r\n\r\n\u003e If you need to include this button in other places of your UI, it could be a good idea to create a dedicated component for this, which will contains the logout logic. This will prevent you from copy pasting the same logic every time you need it.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Configuring an HTTP interceptor\r\n\r\nNow that you have login and logout functionality, and an authentication service that stores an authentication token, you can authenticate for other API calls.\r\n\r\nLooking at the API documentation, at some point you will need to [create a trip](https://comem-travel-log-api.onrender.com/#api-Trips-CreateTrip).\r\nThe documentation states that you must send a bearer token in the `Authorization` header, like this:\r\n\r\n```\r\nPOST /api/trips HTTP/1.1\r\nAuthorization: Bearer 0a98wumv\r\nContent-Type: application/json\r\n\r\n{\"some\":\"json\"}\r\n```\r\n\r\nWith Angular, you would have to make this call like this every time:\r\n\r\n\u003e **NOTE** that this is an example. You **should not** do this in your application's code.\r\n\r\n```js\r\nthis.http.post(\"http://example.com/path\", body, {\r\n  headers: {\r\n    Authorization: `Bearer ${token}`,\r\n  },\r\n});\r\n```\r\n\r\nBut it's a bit annoying to have to manually specify this header **for every request**.\r\nAfter all, we know that we need it for most calls.\r\n\r\n[HttpInterceptor][http-interceptor]s are Angular services that can be registered with the HTTP client to automatically intercept requests and change them or their responses according to your needs.\r\n\r\nThis solves our problem: we want to register an interceptor that will automatically add the `Authorization` header to all requests **if the user is logged in**.\r\n\r\nTo demonstrate that it works, start by adding a call to list trips in the `TripListPage` component in `src/app/layout/trip-list/trip-list.page.ts`:\r\n\r\n```ts\r\n// Other imports remain untouched\r\nimport { IonicModule, ViewWillEnter } from \"@ionic/angular\";\r\nimport { HttpClient } from \"@angular/common/http\";\r\n\r\n@Component({\r\n  /* ... */\r\n})\r\nexport class TripListPage implements ViewWillEnter {\r\n  constructor(private readonly http: HttpClient) {}\r\n\r\n  ionViewWillEnter(): void {\r\n    // Make an HTTP request to retrieve the trips.\r\n    const url = \"https://demo-travel-log-api.onrender.com/api/trips\";\r\n    this.http.get(url).subscribe((trips) =\u003e {\r\n      console.log(`Trips loaded`, trips);\r\n    });\r\n  }\r\n}\r\n```\r\n\r\n\u003e **⚠** Doing an HTTP request **inside a component's code** is **NOT** a best practice. Components should not be responsible of retrieving the data, they should only be responsible of asking another service for it and providing it to their template.\r\n\r\n\u003e In your application, you should define dedicated services that will handle calling your API.\r\n\r\nIf you display the trip list tab and check XHR Network requests in your browser's developer tools,\r\nyou will see that there is no `Authorization` header sent even when the user is logged in.\r\n\r\nLet's create a new file at `src/app/security/auth.interceptor.ts`, with the following content:\r\n\r\n\u003e Read the code to try and understand what's going on.\r\n\r\n```ts\r\nimport { HttpInterceptorFn } from \"@angular/common/http\";\r\nimport { inject } from \"@angular/core\";\r\nimport { first, switchMap } from \"rxjs\";\r\nimport { AuthService } from \"./auth.service\";\r\n\r\nexport const authInterceptor: HttpInterceptorFn = (req, next) =\u003e {\r\n  // Get the instance of the AuthService\r\n  const auth = inject(AuthService);\r\n\r\n  // Get the bearer token (if any).\r\n  return auth.getToken$().pipe(\r\n    // first() will re-emit the first emitted value of the source Observable\r\n    // (here, getToken$()), then complete. An Obseravble returned by an Interceptor\r\n    // MUST complete at some point, otherwise the intercepted request will be forever hanging\r\n    first(),\r\n    switchMap((token) =\u003e {\r\n      // Add it to the request if it doesn't already have an Authorization header.\r\n      if (token \u0026\u0026 !req.headers.has(\"Authorization\")) {\r\n        req = req.clone({\r\n          headers: req.headers.set(\"Authorization\", `Bearer ${token}`),\r\n        });\r\n      }\r\n      return next(req);\r\n    })\r\n  );\r\n};\r\n```\r\n\r\nNow you need to register this interceptor in your application configuration. In `src/main.ts`, add:\r\n\r\n```ts\r\n// Other imports remain untouched\r\nimport { /* ... */, withInterceptors } from '@angular/common/http';\r\nimport { authInterceptor } from './app/security/auth.interceptor';\r\n\r\nif (environment.production) {\r\n  enableProdMode();\r\n}\r\n\r\nbootstrapApplication(AppComponent, {\r\n  providers: [\r\n    // Other providers remain untouched\r\n    provideHttpClient(withInterceptors([ authInterceptor ])),\r\n    // Other providers remain untouched\r\n  ],\r\n});\r\n```\r\n\r\nNow all your API calls will have the `Authorization` header when the user is logged in.\r\n\r\n\u003e You can verify this by looking at the XHR Network requests in the trip list tab and search for the `Authorization` header in the request's headers\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n## Multi-environment \u0026 sensitive configuration\r\n\r\nSometimes you might have to store values that should not be committed to version control:\r\n\r\n- **Environment-specific** values that may change depending on where you deploy your app\r\n- **Sensitive information** like access tokens or passwords.\r\n\r\nFor example, in our earlier HTTP calls, the URL was **hardcoded**:\r\n\r\n```ts\r\nconst url = \"https://demo-travel-log-api.onrender.com/api/trips\";\r\nthis.http.get(url).subscribe((trips) =\u003e {\r\n  // ...\r\n});\r\n```\r\n\r\nThis is not optimal considering the multi-environment problem (and the fact that we copy-pasted it several time)\r\nIf you wanted to change environments, you would have to manually change the URL every time.\r\n\r\nLet's find a way to centralize this configuration.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Environment files\r\n\r\nThere is already a mechanism in place to handle those environment-specific values with Angular.\r\n\r\nIn `src/environments`, you should find two files: `environment.ts` and `environment.prod.ts`.\r\n\r\nThe purpose of those file is to hold the configuration values of a specific environment, so that you could easily swap one config with another to deploy your app in different environment (\"development\", \"test\", \"staging\", \"production\", etc).\r\n\r\nThe first file, `environment.ts` is the default file and the one that should hold the configuration for your development environment. It should **not be committed** as your development config might be different than the one of your fellows developers.\r\n\r\nThe other one, `environment.prod.ts`, is the file that will contain production specific values. **It should not be commited** at all (especially when using a public repository), since it could contain **VERY** sensitive information.\r\n\r\nAlas, both those files have already been commited when the project was set up... It's not a huge problem as both those files don't currently contain anything sensitive.\r\n\r\nYou need to tell git to untrack them, though, so **delete both of them** from your filesystem (we'll recreate them later), then **commit those deletions** in git:\r\n\r\n```bash\r\n$\u003e rm src/environments/*\r\n$\u003e git add src/environments/*\r\n$\u003e git commit -m \"Remove environment files from git\"\r\n```\r\n\r\nNow, create a **placeholder file** whose purpose is to describe what are the environment data used in the app, so that each developer can create its own `environment.ts` on their local copy of the project.\r\nCreate the `environment.sample.ts` file in `src/environments`, with this exact content (comment included):\r\n\r\n```ts\r\n// Copy this file to environment.ts and replace the values with your configuration\r\nexport const environment = {\r\n  production: false,\r\n  apiUrl: \"https://example.com/api\",\r\n};\r\n```\r\n\r\n\u003e The `environment.sample.ts` file is a **placeholder** and should **NOT** contain the actual configuration.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Create the actual configuration file\r\n\r\nWith this placeholder file, you can now (re)create the actual `src/environments/environment.ts` configuration file (by copying and renaming the `environment.sample.ts` file), this time with your actual configuration values, at least for development:\r\n\r\n```ts\r\nexport const environment = {\r\n  production: false,\r\n  apiUrl: \"https://demo-travel-log-api.onrender.com/api\",\r\n};\r\n```\r\n\r\nWhile we're at it, let's also (re)create the `environment.prod.ts` file, used for production builds, with this content:\r\n\r\n```ts\r\nexport const environment = {\r\n  production: true,\r\n  // That's the same api Url in our case, but in real project, it would certainly be different (you don't want to develop using the same instance as the production application...)\r\n  apiUrl: \"https://demo-travel-log-api.onrender.com/api\",\r\n};\r\n```\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Add the environment files to your `.gitignore` file\r\n\r\nOf course, you **don't want to commit neither `environment.ts` nor `environment.prod.ts`**, but you do want to commit `environment.sample.ts`\r\nso that anyone who clones your project can see what configuration options are required.\r\nTo do so, add these lines at the bottom of your `.gitignore` file:\r\n\r\n```\r\n# Environment files\r\nsrc/environments/*\r\n!src/environments/environment.sample.ts\r\n```\r\n\r\nThe first line tells git not to track any file in the `src/environments` folder... except for the specific `environment.sample.ts` file (this is the second line).\r\n\r\nYou now have your uncommitted environment files!\r\n\r\n### When are the environment files used\r\n\r\nThe `environment.ts` file is loaded when you execute the `ionic serve` command.\r\n\r\n\u003e If you have any `ionic serve` command running, kill it and start it again to apply the changes.\r\n\r\nWhen executing the `ionic serve` command with the `--prod` flag, like so...\r\n\r\n```bash\r\n$\u003e ionic serve --prod\r\n```\r\n\r\n...Ionic tells Angular to replaces the content of the `environment.ts` file by the content from `environment.prod.ts`, in the build (**it does not replace the content of the actual file, thankfully**).\r\n\r\nThis way, whatever the environment your app is running on, `environment.ts` is **always the file holding the adequate configuration!**\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n### Feed the configuration to Angular\r\n\r\nNow that you have your configuration file, you want to use its values in your code.\r\n\r\nSince it's a TypeScript file like any other, you simply have to import and use it.\r\n\r\n\u003e **Remember that you should only import `environment.ts` in your code**, not the `environment.prod.ts` or any other variant, as it's content will change depending on the environment.\r\n\r\n```ts\r\n// Other imports...\r\n// TODO: import the environment config.\r\nimport { environment } from \"src/environments/environment\";\r\n\r\n// ...\r\nexport class TripListPage implements ViewWillEnter {\r\n  // ...\r\n  ionViewWillEnter(): void {\r\n    const url = `${environment.apiUrl}/trips`;\r\n    this.http.get(url).subscribe((trips) =\u003e {\r\n      console.log(`Trips loaded`, trips);\r\n    });\r\n  }\r\n  // ...\r\n}\r\n```\r\n\r\nDo not forget to also update the authentication service in `src/app/security/auth.service.ts`, which also has a hardcoded URL:\r\n\r\n```ts\r\n// Other imports...\r\n// TODO: import the environment config.\r\nimport { environment } from \"src/environments/environment\";\r\n\r\n// TODO: Remove the API_URL constant\r\n\r\n// ...\r\nexport class AuthService {\r\n  // ...\r\n  logIn(authRequest: AuthRequest): Observable\u003cUser\u003e {\r\n    // TODO: replace the hardcoded API URL by the one from the environment config.\r\n    const authUrl = `${environment.apiUrl}/auth`;\r\n    // ...\r\n  }\r\n  // ...\r\n}\r\n```\r\n\r\n\u003e You can then safely delete the line that defines the `API_URL` constant.\r\n\r\n\u003ca href=\"#top\"\u003e↑ Back to top\u003c/a\u003e\r\n\r\n[angular-component]: https://angular.dev/guide/components\r\n[angular-guard]: https://angular.dev/guide/routing/common-router-tasks#preventing-unauthorized-access\r\n[cordova]: https://cordova.apache.org/\r\n[forms-with-angular-and-ionic]: https://mediacomem.github.io/comem-devmobil/latest/subjects/angular-forms-ionic/#1\r\n[ionic-cli]: https://ionicframework.com/docs/cli\r\n[ionic-tabs]: https://ionicframework.com/docs/api/tabs\r\n[lazy-loading]: https://angular.io/guide/lazy-loading-ngmodules\r\n[rxjs-delay-when]: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-delayWhen\r\n[sass]: http://sass-lang.com\r\n[travel-log-api]: https://demo-travel-log-api.onrender.com/\r\n[storage]: https://github.com/ionic-team/ionic-storage\r\n[sqlite]: https://sqlite.org\r\n[indexed-db]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API\r\n[websql]: https://www.w3.org/TR/webdatabase/\r\n[local-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage\r\n[http-interceptor]: https://angular.dev/guide/http/interceptors\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmediacomem%2Fcomem-travel-log-ionic-setup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmediacomem%2Fcomem-travel-log-ionic-setup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmediacomem%2Fcomem-travel-log-ionic-setup/lists"}