Here’s a concise, end-to-step plan to scaffold an Angular + Material
Firebase app that authenticates users and displays your Firestore prompts collection in a Material list.


1. Install Angular CLI & create project

npm install -g @angular/cli
ng new prompts-app
cd prompts-app

Choose defaults (no routing if you don’t need it; SCSS is optional).


2. Add Angular Material

ng add @angular/material
  • Pick a theme (e.g. Indigo/Pink).

  • Answer Yes to global typography & animations.


3. Create Firebase project & register web app

  1. Go to Firebase Console.

  2. Add project (or use existing).

  3. In Build › Firestore, create a database in test mode (you’ll tighten rules later).

  4. In Project settings › Web apps, click </> and copy the config snippet.


4. Install Firebase SDK and AngularFire

npm install firebase @angular/fire

5. Add your Firebase config

Edit src/environments/environment.ts:

export const environment = {
  production: false,
  firebase: {
    apiKey: "…",
    authDomain: "…",
    projectId: "…",
    storageBucket: "…",
    messagingSenderId: "…",
    appId: "…"
  }
};

(And mirror in environment.prod.ts.)


6. Wire up AngularFire & Material modules

In src/app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AngularFireModule } from '@angular/fire/compat';
import { AngularFireAuthModule } from '@angular/fire/compat/auth';
import { AngularFirestoreModule } from '@angular/fire/compat/firestore';

import { MatToolbarModule } from '@angular/material/toolbar';
import { MatListModule }    from '@angular/material/list';
import { MatCardModule }    from '@angular/material/card';
import { MatButtonModule }  from '@angular/material/button';
import { MatInputModule }   from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';

import { AppComponent } from './app.component';
import { environment } from '../environments/environment';
import { LoginComponent } from './login/login.component';
import { PromptsComponent } from './prompts/prompts.component';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    PromptsComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFireAuthModule,
    AngularFirestoreModule,
    // Material modules
    MatToolbarModule,
    MatListModule,
    MatCardModule,
    MatButtonModule,
    MatInputModule,
    MatFormFieldModule,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

7. Build authentication UI

ng generate component login

login.component.ts:

import { Component } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/compat/auth';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent {
  email = '';
  password = '';
  constructor(private afAuth: AngularFireAuth) {}

  async signIn() {
    await this.afAuth.signInWithEmailAndPassword(this.email, this.password);
  }
  async signOut() {
    await this.afAuth.signOut();
  }
}

login.component.html:

<mat-card class="login-card">
  <h2>Login</h2>
  <mat-form-field appearance="fill">
    <mat-label>Email</mat-label>
    <input matInput [(ngModel)]="email">
  </mat-form-field>
  <mat-form-field appearance="fill">
    <mat-label>Password</mat-label>
    <input matInput type="password" [(ngModel)]="password">
  </mat-form-field>
  <button mat-raised-button color="primary" (click)="signIn()">Sign In</button>
  <button mat-button (click)="signOut()">Sign Out</button>
</mat-card>

Don’t forget to import FormsModule in AppModule if you use [(ngModel)].


8. Create a Prompt model & service

ng generate interface prompt --type=model
ng generate service prompt

prompt.model.ts:

export interface Prompt {
  name: string;
  note: string;
  tags?: string[];
  updated_at: Date;
}

prompt.service.ts:

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/compat/firestore';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import { Observable, of } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';
import { Prompt } from './prompt.model';

@Injectable({ providedIn: 'root' })
export class PromptService {
  constructor(
    private afs: AngularFirestore,
    private afAuth: AngularFireAuth
  ) {}

  getPrompts(): Observable<Prompt[]> {
    return this.afAuth.authState.pipe(
      switchMap(user => {
        if (!user) return of([]);
        return this.afs.collection<Prompt>('prompts', ref =>
          ref.orderBy('updated_at','desc')
        )
        .snapshotChanges().pipe(
          map(actions =>
            actions.map(a => {
              const d = a.payload.doc.data() as any;
              return { ...d, updated_at: d.updated_at.toDate() } as Prompt;
            })
          )
        );
      })
    );
  }
}

9. Display with a Material list

ng generate component prompts

prompts.component.ts:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { PromptService } from '../prompt.service';
import { Prompt } from '../prompt.model';

@Component({
  selector: 'app-prompts',
  templateUrl: './prompts.component.html'
})
export class PromptsComponent implements OnInit {
  prompts$: Observable<Prompt[]>;
  constructor(private ps: PromptService) {}
  ngOnInit() {
    this.prompts$ = this.ps.getPrompts();
  }
}

prompts.component.html:

<mat-toolbar color="primary">My Prompts</mat-toolbar>
<mat-list>
  <mat-list-item *ngFor="let p of prompts$ | async">
    <mat-card class="w-full">
      <mat-card-title>{{ p.name }}</mat-card-title>
      <mat-card-content>
        <p>{{ p.note }}</p>
        <p *ngIf="p.tags?.length">
          <strong>Tags:</strong> {{ p.tags.join(', ') }}
        </p>
      </mat-card-content>
      <mat-card-footer>
        <small>Updated: {{ p.updated_at | date:'medium' }}</small>
      </mat-card-footer>
    </mat-card>
  </mat-list-item>
</mat-list>

10. Protect routes / guard

If you want to prevent unauthenticated access, add:

ng generate guard auth

auth.guard.ts (simplified):

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import { map } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private afAuth: AngularFireAuth, private router: Router) {}
  canActivate() {
    return this.afAuth.authState.pipe(
      map(user => {
        if (user) return true;
        this.router.navigate(['/login']);
        return false;
      })
    );
  }
}

And in your AppRoutingModule:

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'prompts', component: PromptsComponent, canActivate: [AuthGuard] },
  { path: '**', redirectTo: 'prompts' }
];

11. Deploy & secure your rules

  1. Update your Firestore rules to:

    match /prompts/{doc} {
      allow read, write: if request.auth != null;
    }
  2. Test with the Firebase emulator or on your live project.

  3. Deploy:

    ng build --prod
    firebase deploy

Summary

  1. Scaffold Angular + Material.

  2. Register your Firebase app & copy config.

  3. Install & configure AngularFire + Firestore + Auth modules.

  4. Build a login form and guard your routes.

  5. Create a PromptService that pulls prompts only for authenticated users.

  6. Render them with Angular Material’s list/cards.

  7. Lock down your Firestore rules to request.auth != null.

With this in place, signed-in users can view (and—if you add write methods—create/update) all documents in your prompts collection, per your rules.