Now that everything works so far we should focus on adding some functionality. First of all: how should the data be stored?
Model
The model should be rather simple. I need a list of lessons and each lesson has a bunch of questions / words. Usually I start by defining a sample document directly in the Firebase editor. I don’t understand why there is no JSON editor built in – or is there? That would make setting up some test documents so much easier.
The image will be referenced by path and loaded on demand. I’ll store them in the Firebase storage. I think I’ll create a folder for each lesson. At runtime based on the identifier of the document and the imageId the path can be constructed.
I leave the audio file / playback feature out for now – let’s start simple. If an image works the concept should work for audio files as well – I guess. To be able to profit from TypeScript’s type safety I created the model interfaces using the ng generate class command. I just realized afterwards that there is also ng g interface… So I had to change the files myself to be interfaces.
ng g class lesson
ng g class difficulty
ng g class word
Here is an example of the lesson.ts
import { Difficulty } from './difficulty'; import { Word } from './word'; export interface Lesson { id: string; name: string; difficulty: Difficulty; word: [Word]; }
Code – Router
The application will provide at least two routes:
- Lessons overview screen
- Training screen
So let me first create these routes and test if the router works as expected. Being a project based on the CLI I tried to use their commands as much as possible (but to be honest I’m not sure I got the full power of it) and sometimes the behavior is a bit confusing for me.
ng g component lessons
ng g component lesson
After having created the components I had to register them in the routing module
const routes: Routes = [ { path : '', redirectTo: 'lessons', pathMatch: 'full'}, { path : 'lessons', component: LessonsComponent}, { path : 'lesson/:lessonId', component: LessonComponent} ];
In addition I extended the app.component.html (the root component) with the router-outlet. Routing in Angular is really power IMHO.
<router-outlet></router-outlet>
Code – Firebase Service
We could directly use the Firebase services inside of the components. But to be honest I think sometimes Firebase is a bit verbose. Don’t get me wrong you get an awesome out of the box experience but some things feel just a bit complicated if you are not used to them. So I tend to put this kind of code into a service, where it can be tested independently and reused in multiple components. One thing always bothered me: it is so common to get a list of documents and then provide a link to drill down / open the details. This was a bit cumbersome in Firebase – but they are listening to us! There is (now??) a feature which makes this process super easy – at least if you read from a collection. You can ask the identifier to be added to the objects automatically.
selectLessons(): Observable<Lesson[]> { return this.firestore .collection<Lesson>('lessons') .valueChanges({ idField: 'id' }); } selectLesson(lessonId: string): Observable<Lesson> { return this.firestore .collection('lessons') .doc<Lesson>(lessonId) .valueChanges() .pipe( map((lesson) => { lesson.id = lessonId; return lesson; }) ); }
See above how to add the lesson id manually when you read a single document. I understand it makes sense to some extent, but the same shortcut would be handy here.
Next steps
Next time we connect the components with the service to load the lesson list and the lesson details. Maybe I should move everything to use snaphot data – but first let’s complete the basic functionality.