You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: ARCHITECTURE.md
+15-3Lines changed: 15 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -233,6 +233,10 @@ We'll refer to these in the rest of the document.
233
233
234
234
## [`app`] Feature folder structure
235
235
236
+
|**:brain: Design decision**|
237
+
| :-- |
238
+
| We split sections of our app — e.g. the website — into _feature folders_, placed within the `app/src/app` folder, with shared features and utilities (accessible by any other feature) placed within the `app/src/app/shared` folder.<br><br>You are welcome to deviate from this though, where it makes sense for you. |
239
+
236
240
|**:white_check_mark: Pattern**|
237
241
| :-- |
238
242
| We highly recommend separating the code within the top-level feature folders into the following subfolders: **`data`**, **`feature`**, **`ui`** and **`util`**. And trying to keep these at one hierarchical level. We've found that this is a great starting folder structure (and general architecture) which helps you quickly find stuff, whilst spending minimal time on figuring out what goes where. |
@@ -257,17 +261,25 @@ Instead, we do make use of [build-time prerendering](https://angular.dev/guide/p
257
261
258
262
And then everything else in the app is fully dynamic (i.e. rendered on the client) — a special empty "loader" HTML file (prerendered from the [`LoaderShellComponent`](./app/src/app/loader-shell.component.ts)) is served for all these routes, and we've configured Firebase Hosting and the PWA set-up to serve this loader file for these routes (more details below).
259
263
264
+
> [!NOTE]
265
+
>
266
+
> In this tech stack, we haven't included a CMS or dynamic page generation system (e.g. from Markdown files). Instead, we use static prerendered pages — via regular Angular components and routes — for the website content (and any other static pages).
267
+
>
268
+
> This is a simple and effective way to add static content, but will probably not scale up to larger uses cases (like a blog with hundreds of posts, or a full-on marketing website). At that point, you'd want to consider hosting your website as a separate static site (which has added benefits like: you can deploy it faster and more frequently).
269
+
260
270
## [`app`] The prerendering set-up
261
271
262
272
For the build-time prerendering of pages, we:
263
273
264
274
- Configure the `prerender` option in `angular.json` to prerender all paths defined in the [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file.
265
275
- We also set `"discoverRoutes": false` so only the routes we explicitly specify are prerendered.
276
+
- Specify all static paths we want prerendered, in the `prerendered-routes.txt` file.
277
+
- Out of the box, we have the website home page (`/`) and the about page (`/about`).
266
278
- Specify the `/loader` path in the `prerendered-routes.txt` file, so that the loader shell is prerendered too.
267
279
- The `/loader` route serves an empty shell of the app (using the [`LoaderShellComponent`](./app/src/app/loader-shell.component.ts)), which then loads the full app on the client-side. This route is defined in the [`app.routes.ts`](./app/src/app/app.routes.ts) file.
268
280
- This is used as the default HTML file to serve for all fully dynamic parts of the app.
269
281
270
-
So, when we run the production build (`pnpm build`) Angular will output static HTML files for the prerendered routes and an HTML file for the loader shell (as well as the usual JavaScript, CSS, etc. assets)
282
+
So, when we run the production build (`pnpm build`) Angular will output static HTML files for the prerendered routes (including an HTML file for the loader shell) together with the usual JavaScript, CSS, etc. assets.
271
283
272
284
> [!NOTE]
273
285
>
@@ -312,9 +324,9 @@ Whilst on the topic of Firebase Hosting, we also set up some caching headers in
312
324
| :-- |
313
325
| We use Angular's PWA capabilities, mainly the [service worker support](https://angular.dev/ecosystem/service-workers), and provide a basic PWA set-up out of the box, with a manifest, caching, icons and a simple in-app update notification.<br><br>Once the app is loaded on a user's device (via the web browser, or from the home screen / app launcher) any new updates are downloaded behind the scenes and the user is informed when there's an update. |
314
326
315
-
The core of a PWA config is the [`manifest.webmanifest`](./app/src/manifest.webmanifest) file, which defines the app's name, icons, colors, etc. This is used by the browser to provide a more "app-like" experience when the user adds the app to their home screen / app launcher (depending on device capabilities). **You'll need to customize this file to specify your app's name and branding.**
327
+
The core of a PWA config is the [`manifest.webmanifest`](./app/src/manifest.webmanifest) file, which defines the app's name, icons, colors, etc. This is used by the browser to provide a more "app-like" experience when the user adds the app to their home screen / app launcher (depending on device capabilities). This file follows the regular [PWA manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) spec. **You'll need to customize this file to specify your app's name and branding.**
316
328
317
-
For the Angular service worker, the [`app/ngsw-config.json`](./app/ngsw-config.json) file is the main configuration, determining how to cache assets, handle updates, etc.
329
+
For the Angular service worker, the [`app/ngsw-config.json`](./app/ngsw-config.json) file is the main configuration, determining how to cache assets, handle updates, etc ([docs](https://angular.io/guide/service-worker-config)).
318
330
319
331
The [`app/src/app/app.component.ts`](./app/src/app/app.component.ts) file contains the logic for the in-app update notification, which checks for updates to the app and prompts the user to reload when a new version is available.
@@ -161,7 +161,7 @@ What you need locally before you start:
161
161
1. In the "Your apps" section click the icon representing web app ("</>") and give the app a name, like "PWA". You can skip the Firebase Hosting setup for now.
162
162
- After this app entry is created you'll be shown some config. Ignore this for now as you'll copy the config at the end of this Firebase project set up process (see below).
163
163
1. To use Firebase Functions you need to be on [a paid plan](https://firebase.google.com/pricing) (as Functions are not supported on the "no-cost" tier). Use the "Upgrade" button at the bottom of the left-hand sidebar (this will need a billing account to be set up).
164
-
- Note: the base template is already set up to use and deploy functions but does not come with any actual functions out of the box, so it can be used on [the free tier](https://firebase.google.com/pricing) if needed, but only after some modifications — see the relevant "how-to" below for removing services like functions etc.
164
+
- Note: the base template is already set up to use and deploy functions but does not come with any actual functions out of the box, so it can be used on [the free tier](https://firebase.google.com/pricing) if needed, but only after some modifications — see the relevant "how-to" guide below for removing services like functions etc.
165
165
- You'll be able to set a budget alert as part of the upgrade process.
166
166
- IMPORTANT: Firebase won't actually stop charging you if you go over the budget amount, so keep a very close eye on usage and make sure you understand your usage patterns (and the costs associated with them).
167
167
1. Click "Authentication" in the left-hand menu (under "Build") and enable the "Email/Password" sign-in method together with the "Email link (passwordless sign-in)" option too (on the same page).
@@ -308,39 +308,146 @@ Feel free to deviate from these as you wish. The base template is designed to be
308
308
309
309
## How-to guides
310
310
311
-
Here we document some quick how-to guides to help you get started with the template.
311
+
Here we document some how-to guides to help you get started with this base template.
312
312
313
313
> [!NOTE]
314
314
>
315
-
> It's recommended to read the [architecture and design decisions](ARCHITECTURE.md) doc before diving into these guides.
315
+
> It's recommended to familiarize yourself with the [architecture and design decisions](ARCHITECTURE.md) doc before diving into these guides.
316
+
317
+
### Adding a new static prerendered page
318
+
319
+
Say you want to add a new static prerendered page to the website part of your app, e.g. a "Contact us" page (served at `/contact`). Here's how you can do it:
320
+
321
+
1. Generate a new component in the [`app/src/app/website`](./app/src/app/website) folder:
322
+
- In the `app` folder, run: `pnpm ng generate component website/feature/contact-page`
323
+
1. Add the new component to the website routes in the [`app/src/app/website/website.routes.ts`](./app/src/app/website/website.routes.ts) file.
324
+
- Follow the example of the `'about'` route. So, add: `{ path: 'contact', component: ContactPageComponent },` in the `children` array.
325
+
1. Add an entry in the [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file.
326
+
- Add a new line with `/contact` on it.
327
+
1. (Optionally) Add a nav entry in the [`app/src/app/website/website-shell.component.ts`](./app/src/app/website/website-shell.component.ts) file.
328
+
1. Check everything works locally by running the dev servers and inspecting the app locally.
329
+
1. Run `pnpm build` in the top-level `app` folder to test the production build.
330
+
- Within the top-level `app` folder, you can inspect the `dist/app/browser` folder to check the prerendered HTML files.
331
+
1. Add an entry in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting.rewrites` key) to serve the relevant prerendered HTML file for this new path.
332
+
- Follow the example for the `/about` path.
333
+
- Important: make sure this new entry comes _before_ the `**` catch-all entry.
334
+
1. Add an exclusion for this new path in the [`app/ngsw-config.json`](./app/ngsw-config.json) file (under the `navigationUrls` key).
335
+
- So, add: `"!/contact"` to the end of the array.
336
+
1. Add content to this new page as you see fit.
337
+
1. Fix the tests and make sure everything works as expected.
338
+
1. Commit your changes and push to GitHub, opening a PR so the CI pipeline can run.
339
+
1. When the CI is green, deploy the app to your live Firebase project.
340
+
- By running the `./deploy` script in the root folder, locally.
316
341
317
-
### Adding a new static / prerendered page
342
+
> [!TIP]
343
+
>
344
+
> Technically, you can make any route in the app a prerendered page — it doesn't have to be part of the 'website' feature folder. But it's a good idea to keep all the prerendered pages in one place for consistency.
318
345
319
-
TODO
346
+
### Adding a fully dynamic and lazily-loaded feature to your app
320
347
321
-
### Adding your dynamic app bits
348
+
You'll likely have a main feature section of your app which will be the dynamic user-specific part of the app (together with other dynamic sections like admin, account, etc.)
322
349
323
-
TODO
350
+
> [!TIP]
351
+
>
352
+
> By "fully dynamic" we mean that all the logic, UI, etc. runs client-side (in the user's browser). The server (a static host in our case) just serves an empty shell to load the app. This is essentially a single-page app (SPA) architecture.
353
+
354
+
Say your main feature is a "dashboard", here's how you can add this to the app (as a fully dynamic and lazily-loaded feature):
355
+
356
+
1. Create a new feature folder: `app/src/app/dashboard`.
357
+
- Within this folder you'll want to organize things within `data`, `feature`, `ui` and `util` subfolders (see the [architecture](./ARCHITECTURE.md) doc for details).
358
+
1. Create a routes file: `app/src/app/dashboard/dashboard.routes.ts`.
359
+
- This file will define the routes for the feature as you would in any Angular app.
360
+
1. Register the parent path as a route in the `app/src/app/app.routes.ts` file.
361
+
- You can follow the example of the `website` routes, setting your `path` to `/dashboard` (or whatever you prefer).
362
+
- Note the use of `import('./dashboard/dashboard.routes')` here — this tells Angular to perform the lazy loading of the feature based on its routes.
363
+
1. You'll likely want a shell component for the feature, which all child routes will be rendered within.
364
+
- Run `pnpm ng generate component dashboard/dashboard-shell` to generate this.
365
+
- And register this as the component for the parent route in the `dashboard.routes.ts` file, with all child routes defined within.
366
+
- This component will be the parent component for the feature, and will likely contain the main layout (with shared nav, etc.) It _must_ have a `<router-outlet />` in it to render the child routes.
367
+
1. You'll likely want to lock down everything within this feature to authenticated users only.
368
+
- You can use the `authGuard` provided by the base template, by adding `canMatch: [authGuard('authed')]` to the parent route (the one where you specified the dashboard shell component).
369
+
- You could instead choose to only secure particular child routes, by adding the `canMatch` property to those routes instead, leaving others open.
370
+
1. You'll likely want to add a nav entry for this feature on your website pages, so users can navigate to it.
371
+
- Add this in [`app/src/app/website/website-shell.component.ts`](../app/src/app/website/website-shell.component.ts).
372
+
1. Your feature is ready for development.
324
373
325
374
### Configuring the PWA
326
375
327
-
TODO
376
+
The base template comes with a basic PWA setup, including a manifest file, service worker, etc. Here's how you can configure this for your app:
377
+
378
+
- Configure the app name, colors, icons, scope, start URL, etc. in: [`app/src/manifest.webmanifest`](./app/src/manifest.webmanifest).
379
+
- This follows the regular [PWA manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) spec.
380
+
- Also update the `theme-color` meta tag in the [`index.html`](./app/src/index.html) file to match.
381
+
- Update the icons in the [`app/src/assets/icons`](./app/src/assets/icons) folder.
382
+
- Configure the service worker in: [`app/ngsw-config.json`](./app/ngsw-config.json).
383
+
- This is the Angular service worker config file ([docs](https://angular.io/guide/service-worker-config)).
384
+
- Note that we provide a sensible set-up that works well with the combination of static prerendered pages and dynamic features in the app, so you may not need to change this (and should be careful when doing so).
385
+
- Configure the in-app update notification in: [`app/src/app/app.component.ts`](./app/src/app/app.component.ts).
386
+
387
+
> [!TIP]
388
+
>
389
+
> You are free to use more of the PWA features like background sync, push notifications, etc. as you see fit. The base template is set up to be a useful starting point for a PWA.
328
390
329
391
### Removing the PWA bits
330
392
331
-
TODO
393
+
You may not need the PWA bits in your app. Here's how you can remove them completely:
394
+
395
+
1. Delete the [`app/src/manifest.webmanifest`](./app/src/manifest.webmanifest) file.
396
+
1. Remove the mentions of this `manifest.webmanifest` file in the [`app/angular.json`](./app/angular.json) file.
397
+
1. Remove the manifest link in the [`index.html`](./app/src/index.html) file.
398
+
1. Delete any icons you don't need in the [`app/src/assets/icons`](./app/src/assets/icons) folder.
399
+
1. Delete the [`app/ngsw-config.json`](./app/ngsw-config.json) file.
400
+
1. Remove the `"serviceWorker": "ngsw-config.json"` bit in the [`app/angular.json`](./app/angular.json) file.
401
+
1. Remove the service worker registration in the [`app/src/app/app.config.ts`](./app/src/app/app.config.ts) file.
402
+
1. Update any tests that also do the service worker registration.
403
+
1. Remove the in-app update notification code in the [`app/src/app/app.component.ts`](./app/src/app/app.component.ts) file.
404
+
1. Remove the `@angular/service-worker` dependency from the [`app/package.json`](./app/package.json) file.
405
+
- You'll need to run `pnpm install` in the top-level `app` folder to remove this dependency from your local `node_modules` folder and to update the `pnpm-lock.yaml` file.
332
406
333
407
### Removing Firebase services (like Functions)
334
408
335
-
TODO
409
+
You may not need all the Firebase services in your app, especially Firebase Functions which requires a paid plan.
410
+
411
+
Here's how to remove Firebase Functions from your app:
412
+
413
+
1. If you want to stay fully within the "no-cost" (aka free) tier of Firebase, make sure you don't upgrade the project in the Firebase Console (for the live project).
1. Delete the security rules file: [`firebase/storage.rules`](./firebase/storage.rules).
426
+
1. Make sure you don't use the `injectStorage` helper function in the frontend app.
427
+
428
+
> [!TIP]
429
+
>
430
+
> These steps are especially useful if you're using this base template to build a functional prototype or internal tool, where you don't need all the Firebase services.
336
431
337
432
### Setting up a custom domain
338
433
339
-
TODO
434
+
> [!NOTE]
435
+
>
436
+
> This guide assumes you have a domain name registered and have access to the DNS settings for this domain.
437
+
438
+
You can set up a custom domain for your Firebase app in the [Firebase Console](https://console.firebase.google.com/).
439
+
440
+
To do this, follow the latest docs from Firebase. As of writing, these are the high level steps we're aware of:
441
+
442
+
1. Custom domain set-up in the Hosting section
443
+
1. Custom domain set-up for the email sender in Authentication
444
+
1. Updated action link for the email templates in Authentication
445
+
1. Updated “authorized domains” in Authentication
446
+
1. Updated config in `environment.live.ts` in the app to reflect the new auth domain
340
447
341
448
### Customizing the Firebase emails
342
449
343
-
TODO
450
+
Some email templates (e.g. for Authentication) are customizable in the [Firebase Console](https://console.firebase.google.com/).
0 commit comments