Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c780bde
feat(angular): Add MFA assertion screen
Ehesp Nov 8, 2025
b545c11
fix(angular): Remove redirect error from forgot password
Ehesp Nov 8, 2025
8ad393e
fix(angular): Align MFA Assertion screen
Ehesp Nov 8, 2025
2490dfe
fix(angular): Align MFA enrollment screen
Ehesp Nov 8, 2025
109ec38
fix(angular): Sync phone auth screen
Ehesp Nov 8, 2025
e6d443e
fix(angular): Align sign in screen
Ehesp Nov 8, 2025
9972d68
fix(angular): Align sign up screen
Ehesp Nov 8, 2025
7b4ffee
feat(angular): Add input form description
Ehesp Nov 8, 2025
14efac1
fix(angular): Align email link form
Ehesp Nov 8, 2025
df5a5d6
fix(angular): Align forgot password form
Ehesp Nov 8, 2025
b27a0fa
fix(angular): Align MFA assertion form
Ehesp Nov 8, 2025
0fdfab9
fix(angular): Align MFA enrollment form
Ehesp Nov 8, 2025
dbdbd23
fix(angular): Align phone auth form
Ehesp Nov 8, 2025
4ca47b5
fix(angular): Align sign in form
Ehesp Nov 8, 2025
9483de6
fix(angular): Align sign up form
Ehesp Nov 8, 2025
cf9e39b
fix(angular): Align SMS MFA assertion form
Ehesp Nov 8, 2025
70e9f4c
fix(angular): Align SMS MFA Enrollment form
Ehesp Nov 8, 2025
4635950
fix(angular): Align TOTP MFA assertion form
Ehesp Nov 8, 2025
70b9434
fix(angular): Align TOTP MFA enrollment form
Ehesp Nov 8, 2025
a7c2bcf
chore: linting
Ehesp Nov 8, 2025
721444e
fix(angular): Align oauth button
Ehesp Nov 8, 2025
2a34562
chore: Run formatting
Ehesp Nov 8, 2025
daba3c7
fix(angular): Add missing exports
Ehesp Nov 8, 2025
35b4fae
chore: Update angular example styling
Ehesp Nov 8, 2025
02ceaa4
fix(angular): Forms use input event emitter to handle conditional ren…
Ehesp Nov 8, 2025
052c495
refactor(angular): Set components to host block by default
Ehesp Nov 8, 2025
d3304aa
fix(angular): Add dependency array + recaptcha SSR check
Ehesp Nov 10, 2025
4f7915c
fix(angular): Update country code option tracking
Ehesp Nov 10, 2025
f2e8173
fix(angular): Minor UI tweaks
Ehesp Nov 10, 2025
919515b
chore(angular): Add redirect handlers to example app
Ehesp Nov 10, 2025
510bde7
chore: Formatting
Ehesp Nov 10, 2025
824ca23
fix(angular): Update tests with latest changes
Ehesp Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
"@angular/platform-server": "^20.2.2",
"@angular/router": "^20.2.2",
"@angular/ssr": "^20.2.2",
"@invertase/firebaseui-angular": "0.0.3",
"@invertase/firebaseui-core": "0.0.3",
"@invertase/firebaseui-styles": "0.0.7",
"@invertase/firebaseui-translations": "0.0.4",
"@invertase/firebaseui-angular": "workspace:*",
"@invertase/firebaseui-core": "workspace:*",
"@invertase/firebaseui-styles": "workspace:*",
"@invertase/firebaseui-translations": "workspace:*",
"@tailwindcss/postcss": "^4.0.6",
"express": "^4.18.2",
"postcss": "^8.5.2",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/angular/public/firebase-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions examples/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
limitations under the License.
-->

<div class="app-container">
<router-outlet />
</div>
<app-theme-toggle />
<app-pirate-toggle />
<router-outlet />
144 changes: 113 additions & 31 deletions examples/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,125 @@
* limitations under the License.
*/

import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
import { Component, computed, inject, input } from "@angular/core";
import { RouterModule, Router } from "@angular/router";
import { CommonModule } from "@angular/common";
import { HeaderComponent } from "./components/header";
import { Auth, multiFactor, sendEmailVerification, signOut, type User } from "@angular/fire/auth";
import { routes } from "./routes";
import { ThemeToggleComponent } from "./components/theme-toggle/theme-toggle.component";
import { PirateToggleComponent } from "./components/pirate-toggle/pirate-toggle.component";
import { MultiFactorAuthAssertionScreenComponent } from "@invertase/firebaseui-angular";
import { injectUI } from "@invertase/firebaseui-angular";

@Component({
selector: "app-root",
selector: "app-unauthenticated",
standalone: true,
imports: [CommonModule, RouterModule, MultiFactorAuthAssertionScreenComponent],
template: `
@if (mfaResolver()) {
<fui-multi-factor-auth-assertion-screen />
} @else {
<div class="max-w-sm mx-auto pt-36 space-y-6 pb-36">
<div class="text-center space-y-4">
<img src="/firebase-logo-inverted.png" alt="Firebase UI" class="hidden dark:block h-36 mx-auto" />
<img src="/firebase-logo.png" alt="Firebase UI" class="block dark:hidden h-36 mx-auto" />
<p class="text-sm text-gray-700 dark:text-gray-300">
Welcome to Firebase UI, choose an example screen below to get started!
</p>
</div>
<div
class="border border-neutral-200 dark:border-neutral-800 rounded divide-y divide-neutral-200 dark:divide-neutral-800 overflow-hidden"
>
@for (route of routes; track route.path) {
<a
[routerLink]="route.path"
class="flex items-center justify-between hover:bg-neutral-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 p-4"
>
<div class="space-y-1">
<h2 class="font-medium text-sm">{{ route.name }}</h2>
<p class="text-xs text-gray-400 dark:text-gray-300">{{ route.description }}</p>
</div>
<div class="text-neutral-600 dark:text-neutral-400">
<span class="text-xl">&rarr;</span>
</div>
</a>
}
</div>
</div>
}
`,
})
export class UnauthenticatedAppComponent {
ui = injectUI();
routes = routes;

mfaResolver = computed(() => this.ui().multiFactorResolver);
}

@Component({
selector: "app-authenticated",
standalone: true,
imports: [CommonModule, RouterOutlet, HeaderComponent],
imports: [CommonModule, RouterModule],
template: `
<app-header></app-header>
<div class="app-container">
<router-outlet></router-outlet>
<div class="max-w-sm mx-auto pt-36 space-y-6 pb-36">
<div class="border border-neutral-200 dark:border-neutral-800 rounded-md p-4 space-y-4">
<h1 class="text-md font-medium">Welcome, {{ user().displayName || user().email || user().phoneNumber }}</h1>
@if (user().email) {
@if (user().emailVerified) {
<div class="text-green-500">Email verified</div>
} @else {
<button class="bg-red-500 text-white px-3 py-1.5 rounded text-sm" (click)="verifyEmail()">
Verify Email &rarr;
</button>
}
}
<hr class="opacity-30" />
<h2 class="text-sm font-medium">Multi-factor Authentication</h2>
@for (factor of mfaFactors(); track factor.factorId) {
<div>{{ factor.factorId }} - {{ factor.displayName }}</div>
}
<button class="bg-blue-500 text-white px-3 py-1.5 rounded text-sm" (click)="navigateToMfa()">
Add MFA Factor &rarr;
</button>
<hr class="opacity-30" />
<button class="bg-blue-500 text-white px-3 py-1.5 rounded text-sm" (click)="signOut()">Sign Out &rarr;</button>
</div>
</div>
`,
styles: [
`
.app-container {
max-width: 1200px;
margin: 0 auto;
}

:host {
display: block;
min-height: 100vh;
background-color: #f9fafb;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
sans-serif;
}
`,
],
})
export class AppComponent {
title = "Firebase UI Angular Example";
export class AuthenticatedAppComponent {
user = input.required<User>();
private auth = inject(Auth);
private router = inject(Router);

mfaFactors = computed(() => {
const mfa = multiFactor(this.user());
return mfa.enrolledFactors;
});

async verifyEmail() {
try {
await sendEmailVerification(this.user());
alert("Email verification sent, please check your email");
} catch (error) {
console.error(error);
alert("Error sending email verification, check console");
}
}

navigateToMfa() {
this.router.navigate(["/screens/mfa-enrollment-screen"]);
}

async signOut() {
await signOut(this.auth);
}
}

@Component({
selector: "app-root",
standalone: true,
imports: [CommonModule, RouterModule, ThemeToggleComponent, PirateToggleComponent],
templateUrl: "./app.component.html",
})
export class AppComponent {}
36 changes: 26 additions & 10 deletions examples/angular/src/app/app.routes.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,63 @@ export const serverRoutes: ServerRoute[] = [
},
/** Static auth demos - good for SSG as they showcase Firebase UI components */
{
path: "sign-in",
path: "screens/sign-in-auth-screen",
renderMode: RenderMode.Prerender,
},
{
path: "oauth",
path: "screens/oauth-screen",
renderMode: RenderMode.Prerender,
},
/** Interactive auth routes - better as CSR for user interaction */
{
path: "sign-up",
path: "screens/sign-up-auth-screen",
renderMode: RenderMode.Client,
},
{
path: "forgot-password",
path: "screens/forgot-password-auth-screen",
renderMode: RenderMode.Client,
},
/** Dynamic auth routes - good for SSR as they may need server-side data */
{
path: "email-link",
path: "screens/email-link-auth-screen",
renderMode: RenderMode.Server,
},
{
path: "email-link-oauth",
path: "screens/email-link-auth-screen-w-oauth",
renderMode: RenderMode.Server,
},
{
path: "phone",
path: "screens/phone-auth-screen",
renderMode: RenderMode.Server,
},
{
path: "phone-oauth",
path: "screens/phone-auth-screen-w-oauth",
renderMode: RenderMode.Server,
},
{
path: "sign-in-oauth",
path: "screens/sign-in-auth-screen-w-oauth",
renderMode: RenderMode.Server,
},
{
path: "sign-up-oauth",
path: "screens/sign-up-auth-screen-w-oauth",
renderMode: RenderMode.Server,
},
{
path: "screens/sign-in-auth-screen-w-handlers",
renderMode: RenderMode.Client,
},
{
path: "screens/sign-up-auth-screen-w-handlers",
renderMode: RenderMode.Client,
},
{
path: "screens/forgot-password-auth-screen-w-handlers",
renderMode: RenderMode.Client,
},
{
path: "screens/mfa-enrollment-screen",
renderMode: RenderMode.Client,
},
/** All other routes will be rendered on the server (SSR) */
{
path: "**",
Expand Down
48 changes: 10 additions & 38 deletions examples/angular/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,23 @@
*/

import { type Routes } from "@angular/router";
import { routes as routeConfigs, hiddenRoutes } from "./routes";
import { ScreenRouteLayoutComponent } from "./components/screen-route-layout/screen-route-layout.component";

const allRoutes = [...routeConfigs, ...hiddenRoutes];

export const routes: Routes = [
{
path: "",
loadComponent: () => import("./home").then((m) => m.HomeComponent),
},
{
path: "email-link",
loadComponent: () => import("./auth/email-link").then((m) => m.EmailLinkComponent),
},
{
path: "email-link-oauth",
loadComponent: () => import("./auth/email-link-oauth").then((m) => m.EmailLinkOAuthComponent),
},
{
path: "forgot-password",
loadComponent: () => import("./auth/forgot-password").then((m) => m.ForgotPasswordComponent),
},
{
path: "oauth",
loadComponent: () => import("./auth/oauth").then((m) => m.OAuthComponent),
},
{
path: "phone",
loadComponent: () => import("./auth/phone").then((m) => m.PhoneComponent),
},
{
path: "phone-oauth",
loadComponent: () => import("./auth/phone-oauth").then((m) => m.PhoneOAuthComponent),
},
{
path: "sign-in",
loadComponent: () => import("./auth/sign-in").then((m) => m.SignInComponent),
},
{
path: "sign-in-oauth",
loadComponent: () => import("./auth/sign-in-oauth").then((m) => m.SignInOAuthComponent),
},
{
path: "sign-up",
loadComponent: () => import("./auth/sign-up").then((m) => m.SignUpComponent),
},
{
path: "sign-up-oauth",
loadComponent: () => import("./auth/sign-up-oauth").then((m) => m.SignUpOAuthComponent),
path: "screens",
component: ScreenRouteLayoutComponent,
children: allRoutes.map((route) => ({
path: route.path.replace(/^\/screens\//, ""),
loadComponent: route.loadComponent,
})),
},
{
path: "**",
Expand Down
Loading