Skip to content

Commit 8d2547c

Browse files
committed
Initial docs
1 parent bef7cbb commit 8d2547c

17 files changed

+1655
-7
lines changed

.markdownlint.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
default: true
2+
MD013: false
3+
MD028: false
4+
MD036: false

.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"printWidth": 100,
3+
"proseWrap": "never"
4+
}

README.md

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,74 @@
1-
# [FullStacksDev](https://FullStacks.dev) Angular and Firebase simple example progressive web app (PWA)
1+
# The [FullStacksDev](https://FullStacks.dev) Angular and Firebase simple example app
22

3-
Part of the curated **[FullStacksDev](https://FullStacks.dev) Angular and Firebase tech stack**. For solo devs and very small teams.
3+
Part of the curated [**FullStacksDev Angular and Firebase tech stack**](https://FullStacks.dev/TODO). For solo devs and very small teams.
44

5-
**Logbook app** — keep a log of entries.
5+
This is a fairly simple **Logbook app** — to keep a single time-ordered log of text entries — focused on showcasing and learning the tech stack, built using the [base template](https://github.com/FullStacksDev/angular-and-firebase-template).
6+
7+
You can read more about [the purpose and specs of the example apps](TODO) on our website.
8+
9+
## Running the app locally
10+
11+
We highly recommend cloning the source code and running the app locally to get a feel for how it works.
12+
13+
Follow instructions from the base template:
14+
15+
- [Prerequisites](https://github.com/FullStacksDev/angular-and-firebase-template/blob/main/README.md#prerequisites)
16+
- [Local development, testing and deploy commands](https://github.com/FullStacksDev/angular-and-firebase-template/blob/main/README.md#local-development-testing-and-deploy-commands)
17+
18+
## Learning content
19+
20+
All the learning content is within the [`/docs`](./docs) folder. They exist right next to the code and are updated and versioned together. These cover the architecture, design decisions, data models, patterns, features, tech stack capabilities and learnings for this simple example app.
21+
22+
### Recommended approach
23+
24+
Use the index below and GitHub's UI to navigate and read the docs, with useful tools like the tree view sidebar, table of contents sidebar and repository search.
25+
26+
The docs are designed to be **skimmable** and **a reference** to come back to. We recommend going through each, in order, but not necessarily trying to learn everything at once, as you can always come back and dig deeper (especially after getting into the actual code, and building your skills and understanding up).
27+
28+
After a look at the overall architecture, we cover things from a _horizontal_ perspective, i.e. the layers of implementation that make up the whole app (rather than a _vertical_ perspective — feature by feature, which we do in the advanced example app).
29+
30+
### Index
31+
32+
1. [Architecture](./docs/1.architecture.md)
33+
1. [Routes and shell](./docs/2.routes-and-shell.md)
34+
1. [Data model and access](./docs/3.data-model-and-access.md)
35+
1. [Logbook stores](./docs/4.logbook-stores.md)
36+
1. [Logbook UI and flows](./docs/5.logbook-ui-and-flows.md)
37+
1. [Testing](./docs/6.testing.md)
38+
1. [Deployment and monitoring](./docs/7.deployment-and-monitoring.md)
39+
40+
### Key
41+
42+
To make the information skimmable and easier to understand, you'll see the following standout blocks throughout:
43+
44+
| **:brain: Design decision** |
45+
| :-- |
46+
| A decision that was made and why. Can cover any aspect: technical, architectural, design, UX, etc. |
47+
48+
| **:white_check_mark: Pattern** |
49+
| :-- |
50+
| A recommended way of doing something. This term is used loosely and non-formally, just as a way of saying “this is a thing we recommend you do in a particular context”. |
51+
52+
> [!IMPORTANT]
53+
>
54+
> A key point to remember.
55+
56+
> [!NOTE]
57+
>
58+
> Extra info to clarify a point or provide context.
59+
60+
> [!TIP]
61+
>
62+
> A tip or trick worth knowing about.
63+
64+
> [!WARNING]
65+
>
66+
> Gotchas and things to be careful about.
67+
68+
> [!CAUTION]
69+
>
70+
> More severe gotchas and things to watch out for.
71+
72+
## A note about the advanced example app
73+
74+
Throughout the docs we reference [the advanced example app](TODO). This is a more complex version of a logbook app that showcases advanced features and patterns. It's a perfect next step to take after you've understood and learnt from this simple example app.

app/src/app/logbook/data/config.store.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ConfigService } from './db/config.service';
77
describe('ConfigStore', () => {
88
MockInstance.scope();
99

10-
beforeEach(() => MockBuilder(ConfigStore).mock(ConfigService));
10+
beforeEach(() => MockBuilder(ConfigStore, null).mock(ConfigService));
1111

1212
it('should create', () => {
1313
const mockConfig: Config = { categories: ['test'] };

app/src/app/logbook/data/entries-update.store.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { EntriesUpdateStore } from './entries-update.store';
66
describe('EntriesUpdateStore', () => {
77
MockInstance.scope();
88

9-
beforeEach(() => MockBuilder(EntriesUpdateStore).mock(AuthStore).mock(EntriesService));
9+
beforeEach(() => MockBuilder(EntriesUpdateStore, null).mock(AuthStore).mock(EntriesService));
1010

1111
it('should create', () => {
1212
const store = ngMocks.get(EntriesUpdateStore);

app/src/app/shared/auth/data/auth.store.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { AuthStore } from './auth.store';
66
describe('AuthStore', () => {
77
MockInstance.scope();
88

9-
beforeEach(() => MockBuilder(AuthStore).mock(AuthService));
9+
beforeEach(() => MockBuilder(AuthStore, null).mock(AuthService));
1010

1111
it('should create', () => {
1212
MockInstance(AuthService, 'user$', of(null));

docs/.markdownlint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
extends: "../.markdownlint.yml"
2+
MD041: false

docs/.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"printWidth": 100,
3+
"proseWrap": "never"
4+
}

docs/1.architecture.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
| ← Previous || Next → |
2+
| :--------- | :-------------------------------: | ------------------------------------------: |
3+
| - | [Go to index](../README.md#index) | [Routes and shell](./2.routes-and-shell.md) |
4+
5+
# Architecture
6+
7+
This simple example app is built on [the base template](https://github.com/FullStacksDev/angular-and-firebase-template/blob/main/README.md). Make sure to go through the base template's [README](https://github.com/FullStacksDev/angular-and-firebase-template/blob/main/README.md) and [ARCHITECTURE](https://github.com/FullStacksDev/angular-and-firebase-template/blob/main/ARCHITECTURE.md) docs beforehand, to get an understanding of the tech stack components, high level architecture and design decisions, then come back here to learn the specifics of this app.
8+
9+
## Frontend
10+
11+
This simple example app is frontend-heavy in that the majority of the functionality is built client-side, in the Angular app, which consists of:
12+
13+
- A new [`app/src/app/logbook`](../app/src/app/logbook) folder containing the logbook feature (detailed below).
14+
- A new [`app/src/app/shared/models.ts`](../app/src/app/shared/models.ts) file where types are defined for the data model (and other related types), just for use on the frontend.
15+
- Updated routes in the [`app/src/app/app.routes.ts`](../app/src/app/app.routes.ts) file to lazily load the logbook feature routes.
16+
- A link to "Open logbook" in the navigation of the website's static pages (i.e. in [`app/src/app/website/website-shell.component.ts`](../app/src/app/website/website-shell.component.ts)).
17+
18+
| **:white_check_mark: Pattern** |
19+
| :-- |
20+
| We don't use [Angular modules (i.e. `@Module`)](https://angular.dev/guide/ngmodules) for our own code — we've chosen to go all-in on [Angular's recent **standalone** approach](https://angular.dev/guide/components/importing#standalone-components). So we only ever define (and prefer to import) standalone components, directives, etc.<br><br> The base template has configured the Angular CLI generator to always set the `standalone: true` flag on any components, directives, etc. you generate. |
21+
22+
| **:brain: Design decision** |
23+
| :-- |
24+
| Since this app is frontend-heavy we've decided to put all the data model types (and other useful types) in a file within the `app` folder, as opposed to putting these in [`firebase/common/models.ts`](../firebase/common/models.ts) (as provided by the base template) where they would be available to both the frontend and backend. |
25+
26+
> [!NOTE]
27+
>
28+
> In the advanced example app we show how to share types between the frontend and backend.
29+
30+
### The logbook feature
31+
32+
```text
33+
app/src/app/logbook
34+
└─ data
35+
└─ db
36+
└─ {db services}
37+
└─ {stores}
38+
└─ feature
39+
└─ {pages and smart components}
40+
└─ ui
41+
└─ {presentational components}
42+
└─ logbook-shell.component.spec.ts
43+
└─ logbook-shell.component.ts
44+
└─ logbook.routes.ts
45+
```
46+
47+
- The routes and shell files are in the root of the logbook feature folder.
48+
- All other files are split between `data`, `feature` and `ui` subfolders.
49+
- The `data` folder contains the services and stores that handle the data layer.
50+
- All services that wrap Firebase access are in the `data/db` subfolder.
51+
- The `feature` folder contains the pages and smart components that make up the feature itself.
52+
- The `ui` folder contains presentational components that are used by the feature.
53+
54+
We'll dig into these in more detail in later documents.
55+
56+
> [!NOTE]
57+
>
58+
> Think of **smart components** as more involved components that have access and awareness of the broader application state and logic, via stores (and other services). They don't function just as black boxes and are not usually reusable across different parts of the app.
59+
>
60+
> Think of **presentational components** as simple and naive components that only know their inputs and outputs, making no assumptions of the overall application state and structure. They should be easy to test (as a black box) and easy to reuse.
61+
62+
| **:white_check_mark: Pattern** |
63+
| :-- |
64+
| As mentioned in the base template, 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 both the top-level feature folders and these subfolders 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.<br><br>Page and smart components go in the `feature` folder, whilst presentational components go in the `ui`folder.<br><br>This is a recommended folder structure based on [Nx's suggested library types](https://nx.dev/concepts/more-concepts/library-types).<br><br>For features within the `shared` folder you should follow the same structure, except you probably won't need a `feature` subfolder since these are shared bits of code for use elsewhere.<br><br>As things grow you may need to adapt and tweak this structure (e.g. to add another level in the hierarchy) — we'll see how to tackle this in the advanced example app. |
65+
66+
> [!IMPORTANT]
67+
>
68+
> As a reminder, all the Angular components (including ones generated through the Angular CLI) have been configured to use the [`OnPush` change detection strategy](https://angular.dev/best-practices/skipping-subtrees#using-onpush) by default.
69+
>
70+
> This is a more performant approach that [works well with Angular's signals](https://angular.dev/guide/signals#reading-signals-in-onpush-components), and since we use NgRx SignalStore you are unlikely to hit the cases where change detection is not triggered when it should be.
71+
>
72+
> With the caveat that forms _sometimes_ don't behave well with OnPush change detection, so in rare cases you'd need to use the `ChangeDetectorRef` to manually mark a component for change detection — this is something we'll explore in the advanced example app.
73+
>
74+
> As long as you stick to the approaches promoted in the example apps you should not encounter any change detection issues (i.e. where underlying data changes but the UI does not update).
75+
76+
### Data flows, app logic and UI components architecture
77+
78+
Within the frontend app, it's important to have an architecture in place for reasoning about data flows, app logic and UI components with some "rules" to make things predictable and easy to scale up with more features, and manage growing complexity. Thus, knowing where things go and how data flows between backend, services, and components is crucial.
79+
80+
We highly recommend the following generalized data and logic flows — we follow this extensively in the example apps:
81+
82+
```mermaid
83+
sequenceDiagram
84+
participant Db as Db and external access services<br />(within the `/data/db` folder)
85+
participant S as Stores<br />(within the `/data` folder)
86+
participant SC as Page and smart components<br />(within the `/feature` folder)
87+
participant PC as Presentational components<br />(within the `/ui` folder)
88+
S->>Db: Access and update things<br/>(usually via observables)
89+
SC->>S: Bind to state<br/>(using signals)
90+
SC->>S: Trigger app logic<br/>(via store methods)
91+
S->>S: Update state
92+
SC->>PC: Provide input data
93+
PC->>SC : Emit events
94+
```
95+
96+
(Ignore the time-ordering of this sequence diagram, it's just a way to visualize the data flow — it's not a strict sequence of events.)
97+
98+
- Use Angular services to wrap ALL access to databases and external services.
99+
- Use state management "stores" to encapsulate as much of the app's state and behavior as possible, leaving components to focus on UI needs and responding to state changes.
100+
- Use smart components to interact with stores to bind state and trigger application logic.
101+
- Use presentational components (within the template of smart components) to abstract out UI presentation and logic in a way that does not need to know about the overall application state and structure, communicating all actions/events back to the parent smart component.
102+
103+
We'll cover these in more detail, in the context of the simple logbook feature, in later documents.
104+
105+
### Stores
106+
107+
> [!IMPORTANT]
108+
>
109+
> As you can see from the previous diagram, stores are the main mechanism for state management and can be considered the central _hubs_ or _engines_ for your application's state and logic. They are the single source of truth for the app's state and are the only place where this state is updated, as well as being the only place where related app logic is triggered. This makes it easy to reason about the app's state and behavior, and to test and debug it, by decoupling it from the UI.
110+
111+
> [!TIP]
112+
>
113+
> Another approach to thinking about stores and state management is, rather than thinking:
114+
>
115+
> _"How do these actions and events change the user interface?"_
116+
>
117+
> … think:
118+
>
119+
> _"How do these actions and events change the state of the application? How can I use this state to drive UI and flows, or what additional state do I need to model?"_
120+
121+
There are different levels of stores, scoped to particular contexts:
122+
123+
- **Global stores** — a single instance available to be injected in any component or service.
124+
- Marked with `providedIn: 'root'` in the `@Injectable` decorator so Angular knows to make them available globally and only maintain one instance.
125+
- The [`app/src/app/shared/auth/data/auth.store.ts`](../app/src/app/shared/auth/data/auth.store.ts) (provided by the base template) is an example of a global store.
126+
- **Feature stores** — provided at the route level (for lazily loaded feature routes) and available to all components within the feature.
127+
- The stores in the logbook feature are feature stores — they are provided at the route level (covered in a later document).
128+
- **Component stores** — provided and injected in a component and available to it and its children only.
129+
- Can be provided in multiple components, where Angular will create and maintain _separate_ instances. A useful way to have a store that is scoped to a particular part of the UI, but also reused in multiple places (e.g. a component store for a list item).
130+
- The [`app/src/app/login/feature/login-flow.store.ts`](../app/src/app/login/feature/login-flow.store.ts) is an example of a component-level store.
131+
132+
> [!TIP]
133+
>
134+
> When we say an Angular "service" (or "store" — which is just an Angular service under the hood) is "provided" somewhere we mean that it's registered with Angular's dependency injection system so that it can be injected into the services and components within the scope of that provider context (i.e. either globally, or within a smaller context like a particular set of lazily loaded routes or a particular component tree).
135+
>
136+
> You can read more about Angular's dependency injection system in [the official guide](https://angular.dev/guide/di).
137+
138+
> [!NOTE]
139+
>
140+
> Components (both smart and presentational) can sometimes manage their own internal state directly if having a separate component-level store is overkill. For example, a simple form component that manages all form state directly within the component and doesn't _really_ benefit from a separate store attached to the component.
141+
>
142+
> Though note that once the component starts to grow in complexity, it's often a good idea to move the state management to a separate component-level store, to keep the component focused on UI concerns.
143+
144+
## Backend
145+
146+
We rely on [Firebase's security rules](https://firebase.google.com/docs/rules) to control access to Firestore and Realtime Database from the frontend.
147+
148+
We use [Firestore indexes](https://firebase.google.com/docs/firestore/query-data/indexing) to support the queries we make from the frontend and disable ones we don't need (to avoid unnecessary costs).
149+
150+
We don't make use of Firebase Functions in this simple example app, partly so it can run within Firebase's "no-cost" tier, and also to showcase how to build a simple (but capable) web app without them.
151+
152+
> [!IMPORTANT]
153+
>
154+
> If you're used to a more traditional client-server access model where the server controls all access to a database via an API then Firebase's approach of making direct database calls from the client-side may seem counter-intuitive (and even scary) at first. If it helps, consider that there is still _some form of an API_ that these database calls go through — the security rules, which essentially encode the business logic on what can and cannot be accessed, in its own domain specific language (albeit limited to access and basic validation).
155+
>
156+
> You can still achieve a more traditional server-side API style with Firebase Functions and the Firebase Admin SDK, but we don't use that in this simple example app.
157+
158+
> [!NOTE]
159+
>
160+
> We use Firebase functions extensively in the advanced example app, for capabilities that need proper backend support and can't be achieved with just client side code and Firebase's security rules.
161+
162+
> [!WARNING]
163+
>
164+
> It's best to have as much encoded in the codebase as possible, especially the security rules and indexes, in their relevant files, which will be pushed to Firebase as part of the deployment run.
165+
>
166+
> So don't make changes to these directly in the Firebase console, as they _might_ be lost on the next deploy, or you may lose track of what is live and end up with multiple sources of truth.
167+
168+
---
169+
170+
| ← Previous || Next → |
171+
| :--------- | :-------------------------------: | ------------------------------------------: |
172+
| - | [Go to index](../README.md#index) | [Routes and shell](./2.routes-and-shell.md) |

0 commit comments

Comments
 (0)