+ `,
+ styles: [],
+})
+export class ScreenRouteLayoutComponent {}
diff --git a/examples/angular/src/app/components/theme-toggle/theme-toggle.component.ts b/examples/angular/src/app/components/theme-toggle/theme-toggle.component.ts
new file mode 100644
index 000000000..dbfe6ed8e
--- /dev/null
+++ b/examples/angular/src/app/components/theme-toggle/theme-toggle.component.ts
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component } from "@angular/core";
+import { CommonModule } from "@angular/common";
+
+@Component({
+ selector: "app-theme-toggle",
+ standalone: true,
+ imports: [CommonModule],
+ template: `
+
+ `,
+ styles: [],
+})
+export class ThemeToggleComponent {
+ toggleTheme() {
+ const htmlElement = document.documentElement;
+ const isDark = htmlElement.classList.contains("dark");
+ htmlElement.classList.toggle("dark", !isDark);
+ localStorage["theme"] = htmlElement.classList.contains("dark") ? "dark" : "light";
+ }
+}
diff --git a/examples/angular/src/app/home/home.component.ts b/examples/angular/src/app/home/home.component.ts
index addba1d04..71e2037d8 100644
--- a/examples/angular/src/app/home/home.component.ts
+++ b/examples/angular/src/app/home/home.component.ts
@@ -16,69 +16,24 @@
import { Component, inject } from "@angular/core";
import { CommonModule } from "@angular/common";
-import { RouterModule } from "@angular/router";
-import { Auth, type User, authState } from "@angular/fire/auth";
-import { type Observable } from "rxjs";
+import { AsyncPipe } from "@angular/common";
+import { UserService } from "../services/user.service";
+import { UnauthenticatedAppComponent } from "../app.component";
+import { AuthenticatedAppComponent } from "../app.component";
@Component({
selector: "app-home",
standalone: true,
- imports: [CommonModule, RouterModule],
+ imports: [CommonModule, AsyncPipe, UnauthenticatedAppComponent, AuthenticatedAppComponent],
template: `
-
+ @if (user$ | async; as user) {
+
+ } @else {
+
+ }
`,
- styles: [],
})
export class HomeComponent {
- private auth = inject(Auth);
- user$: Observable = authState(this.auth);
-
- signOut() {
- this.auth.signOut();
- }
+ private userService = inject(UserService);
+ user$ = this.userService.getUser();
}
diff --git a/examples/angular/src/app/home/index.ts b/examples/angular/src/app/home/index.ts
index 099bc938e..d7102cdb5 100644
--- a/examples/angular/src/app/home/index.ts
+++ b/examples/angular/src/app/home/index.ts
@@ -1,17 +1 @@
-/**
- * Copyright 2025 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
export * from "./home.component";
diff --git a/examples/angular/src/app/pirate.ts b/examples/angular/src/app/pirate.ts
new file mode 100644
index 000000000..aa92433ce
--- /dev/null
+++ b/examples/angular/src/app/pirate.ts
@@ -0,0 +1,95 @@
+import { registerLocale } from "@invertase/firebaseui-translations";
+
+export const pirate = registerLocale("pirate", {
+ errors: {
+ userNotFound: "Arrr! No account found with this email address, matey",
+ wrongPassword: "Arrr! Incorrect password, ye scallywag",
+ invalidEmail: "Avast! Enter a valid email address, ye bilge rat",
+ userDisabled: "This account has been marooned, arrr!",
+ networkRequestFailed: "Can't connect to the server, ye land lubber! Check yer internet connection",
+ tooManyRequests: "Too many failed attempts, ye scurvy dog! Try again later",
+ missingVerificationCode: "Enter the verification code, ye scallywag",
+ emailAlreadyInUse: "An account already exists with this email, arrr!",
+ invalidCredential: "The credentials ye provided be invalid, matey",
+ weakPassword: "Ye password ain't long enough! It should be at least 8 characters",
+ unverifiedEmail: "Verify yer email address to continue, ye scallywag",
+ operationNotAllowed: "This operation ain't allowed, arrr! Contact support, matey",
+ invalidPhoneNumber: "The phone number be invalid, ye bilge rat",
+ missingPhoneNumber: "Provide a phone number, ye scallywag",
+ quotaExceeded: "SMS quota exceeded, arrr! Try again later, matey",
+ codeExpired: "The verification code has expired, ye scurvy dog",
+ captchaCheckFailed: "reCAPTCHA verification failed, arrr! Try again, matey",
+ missingVerificationId: "Complete the reCAPTCHA verification first, ye scallywag",
+ missingEmail: "Provide an email address, ye bilge rat",
+ invalidActionCode: "The password reset link be invalid or has expired, arrr!",
+ credentialAlreadyInUse: "An account already exists with this email, arrr! Sign in with that account, matey",
+ requiresRecentLogin: "This operation requires a recent login, ye scallywag! Sign in again",
+ providerAlreadyLinked: "This phone number be already linked to another account, arrr!",
+ invalidVerificationCode: "Invalid verification code, ye scurvy dog! Try again",
+ unknownError: "An unexpected error occurred, arrr!",
+ popupClosed: "The sign-in popup was closed, ye scallywag! Try again",
+ accountExistsWithDifferentCredential:
+ "An account already exists with this email, arrr! Sign in with the original provider, matey",
+ displayNameRequired: "Provide a display name, ye bilge rat",
+ secondFactorAlreadyInUse: "This phone number be already enrolled with this account, arrr!",
+ },
+ messages: {
+ passwordResetEmailSent: "Password reset email sent successfully, arrr!",
+ signInLinkSent: "Sign-in link sent successfully, matey!",
+ verificationCodeFirst: "Request a verification code first, ye scallywag",
+ checkEmailForReset: "Check yer email for password reset instructions, ye bilge rat",
+ dividerOr: "or",
+ termsAndPrivacy: "By continuing, ye agree to our {tos} and {privacy}, arrr!",
+ mfaSmsAssertionPrompt:
+ "A verification code will be sent to {phoneNumber} to complete the authentication process, matey.",
+ },
+ labels: {
+ emailAddress: "Email Address, ye bilge rat",
+ password: "Password, ye scallywag",
+ displayName: "Display Name, ye bilge rat",
+ forgotPassword: "Forgot Password, ye scallywag?",
+ signUp: "Sign Up, Matey",
+ signIn: "Sign In, Matey",
+ resetPassword: "Reset Password, ye scallywag",
+ createAccount: "Create Account, ye bilge rat",
+ backToSignIn: "Back to Sign In, ye scallywag",
+ signInWithPhone: "Sign in with Phone, ye scallywag",
+ phoneNumber: "Phone Number, ye bilge rat",
+ verificationCode: "Verification Code, ye scallywag",
+ sendCode: "Send Code, ye scallywag",
+ verifyCode: "Verify Code, ye scallywag",
+ signInWithGoogle: "Sign in with ye Google Account",
+ signInWithFacebook: "Sign in with ye Facebook Account",
+ signInWithApple: "Sign in with ye Apple Account",
+ signInWithMicrosoft: "Sign in with ye Microsoft Account",
+ signInWithGitHub: "Sign in with ye GitHub Account",
+ signInWithTwitter: "Sign in with ye X Account",
+ signInWithEmailLink: "Sign in with Email Link",
+ sendSignInLink: "Send Sign-in Link",
+ termsOfService: "Terms of Service",
+ privacyPolicy: "Privacy Policy",
+ resendCode: "Resend ye Code",
+ sending: "Firing...",
+ multiFactorEnrollment: "Multi-factor Enrrrrrrollment!",
+ multiFactorAssertion: "Multi-factor Authentication, arrr!",
+ mfaTotpVerification: "TOTP Verification, arrr!",
+ mfaSmsVerification: "SMS Verification, arrr!",
+ generateQrCode: "Generate ye QR Code",
+ },
+ prompts: {
+ noAccount: "Don't have an account, ye scallywag?",
+ haveAccount: "Already have an account, matey?",
+ enterEmailToReset: "Enter yer email address to reset yer password, ye bilge rat",
+ signInToAccount: "Sign in to yer account, matey",
+ smsVerificationPrompt: "Enter the verification code sent to yer phone number, ye scallywag",
+ enterDetailsToCreate: "Enter yer details to create a new account, ye bilge rat",
+ enterPhoneNumber: "Enter yer phone number, matey",
+ enterVerificationCode: "Enter the verification code, ye scallywag",
+ enterEmailForLink: "Enter yer email to receive a sign-in link, ye bilge rat",
+ mfaEnrollmentPrompt: "Select a new multi-factor enrollment method, arrr!",
+ mfaAssertionPrompt: "Complete the multi-factor authentication process, ye scallywag",
+ mfaAssertionFactorPrompt: "Choose a multi-factor authentication method, matey",
+ mfaTotpQrCodePrompt: "Scan this QR code with yer authenticator app, ye bilge rat",
+ mfaTotpEnrollmentVerificationPrompt: "Add the code generated by yer authenticator app, arrr!",
+ },
+});
diff --git a/examples/angular/src/app/routes.ts b/examples/angular/src/app/routes.ts
new file mode 100644
index 000000000..fcafffd4a
--- /dev/null
+++ b/examples/angular/src/app/routes.ts
@@ -0,0 +1,123 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Type } from "@angular/core";
+
+export interface RouteConfig {
+ name: string;
+ description: string;
+ path: string;
+ loadComponent: () => Promise<{ default: Type } | Type>;
+}
+
+export const routes: RouteConfig[] = [
+ {
+ name: "Sign In Screen",
+ description: "A sign in screen with email and password.",
+ path: "/screens/sign-in-auth-screen",
+ loadComponent: () => import("./screens/sign-in-auth-screen").then((m) => m.SignInAuthScreenWrapperComponent),
+ },
+ {
+ name: "Sign In Screen (with handlers)",
+ description: "A sign in screen with email and password, with forgot password and register handlers.",
+ path: "/screens/sign-in-auth-screen-w-handlers",
+ loadComponent: () =>
+ import("./screens/sign-in-auth-screen-w-handlers").then((m) => m.SignInAuthScreenWithHandlersComponent),
+ },
+ {
+ name: "Sign In Screen (with OAuth)",
+ description: "A sign in screen with email and password, with oAuth buttons.",
+ path: "/screens/sign-in-auth-screen-w-oauth",
+ loadComponent: () =>
+ import("./screens/sign-in-auth-screen-w-oauth").then((m) => m.SignInAuthScreenWithOAuthComponent),
+ },
+ {
+ name: "Sign Up Screen",
+ description: "A sign up screen with email and password.",
+ path: "/screens/sign-up-auth-screen",
+ loadComponent: () => import("./screens/sign-up-auth-screen").then((m) => m.SignUpAuthScreenWrapperComponent),
+ },
+ {
+ name: "Sign Up Screen (with handlers)",
+ description: "A sign up screen with email and password, sign in handlers.",
+ path: "/screens/sign-up-auth-screen-w-handlers",
+ loadComponent: () =>
+ import("./screens/sign-up-auth-screen-w-handlers").then((m) => m.SignUpAuthScreenWithHandlersComponent),
+ },
+ {
+ name: "Sign Up Screen (with OAuth)",
+ description: "A sign in screen with email and password, with oAuth buttons.",
+ path: "/screens/sign-up-auth-screen-w-oauth",
+ loadComponent: () =>
+ import("./screens/sign-up-auth-screen-w-oauth").then((m) => m.SignUpAuthScreenWithOAuthComponent),
+ },
+ {
+ name: "Email Link Auth Screen",
+ description: "A screen allowing a user to send an email link for sign in.",
+ path: "/screens/email-link-auth-screen",
+ loadComponent: () => import("./screens/email-link-auth-screen").then((m) => m.EmailLinkAuthScreenWrapperComponent),
+ },
+ {
+ name: "Email Link Auth Screen (with OAuth)",
+ description: "A screen allowing a user to send an email link for sign in, with oAuth buttons.",
+ path: "/screens/email-link-auth-screen-w-oauth",
+ loadComponent: () =>
+ import("./screens/email-link-auth-screen-w-oauth").then((m) => m.EmailLinkAuthScreenWithOAuthComponent),
+ },
+ {
+ name: "Forgot Password Screen",
+ description: "A screen allowing a user to reset their password.",
+ path: "/screens/forgot-password-auth-screen",
+ loadComponent: () =>
+ import("./screens/forgot-password-auth-screen").then((m) => m.ForgotPasswordAuthScreenWrapperComponent),
+ },
+ {
+ name: "Forgot Password Screen (with handlers)",
+ description: "A screen allowing a user to reset their password, with forgot password and register handlers.",
+ path: "/screens/forgot-password-auth-screen-w-handlers",
+ loadComponent: () =>
+ import("./screens/forgot-password-auth-screen-w-handlers").then(
+ (m) => m.ForgotPasswordAuthScreenWithHandlersComponent
+ ),
+ },
+ {
+ name: "OAuth Screen",
+ description: "A screen which allows a user to sign in with OAuth only.",
+ path: "/screens/oauth-screen",
+ loadComponent: () => import("./screens/oauth-screen").then((m) => m.OAuthScreenWrapperComponent),
+ },
+ {
+ name: "Phone Auth Screen",
+ description: "A screen allowing a user to sign in with a phone number.",
+ path: "/screens/phone-auth-screen",
+ loadComponent: () => import("./screens/phone-auth-screen").then((m) => m.PhoneAuthScreenWrapperComponent),
+ },
+ {
+ name: "Phone Auth Screen (with OAuth)",
+ description: "A screen allowing a user to sign in with a phone number, with oAuth buttons.",
+ path: "/screens/phone-auth-screen-w-oauth",
+ loadComponent: () => import("./screens/phone-auth-screen-w-oauth").then((m) => m.PhoneAuthScreenWithOAuthComponent),
+ },
+] as const;
+
+export const hiddenRoutes: RouteConfig[] = [
+ {
+ name: "MFA Enrollment Screen",
+ description: "A screen allowing a user to enroll in multi-factor authentication.",
+ path: "/screens/mfa-enrollment-screen",
+ loadComponent: () => import("./screens/mfa-enrollment-screen").then((m) => m.MfaEnrollmentScreenComponent),
+ },
+] as const;
diff --git a/examples/angular/src/app/screens/email-link-auth-screen-w-oauth/email-link-auth-screen-w-oauth.component.ts b/examples/angular/src/app/screens/email-link-auth-screen-w-oauth/email-link-auth-screen-w-oauth.component.ts
new file mode 100644
index 000000000..331551c38
--- /dev/null
+++ b/examples/angular/src/app/screens/email-link-auth-screen-w-oauth/email-link-auth-screen-w-oauth.component.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { EmailLinkAuthScreenComponent, GoogleSignInButtonComponent } from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
+
+@Component({
+ selector: "app-email-link-auth-screen-w-oauth",
+ standalone: true,
+ imports: [CommonModule, EmailLinkAuthScreenComponent, GoogleSignInButtonComponent],
+ template: `
+
+
+
+ `,
+ styles: [],
+})
+export class EmailLinkAuthScreenWithOAuthComponent {
+ private router = inject(Router);
+
+ onEmailSent() {
+ alert("email sent - please check your email");
+ }
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/email-link-auth-screen-w-oauth/index.ts b/examples/angular/src/app/screens/email-link-auth-screen-w-oauth/index.ts
new file mode 100644
index 000000000..13f2e186d
--- /dev/null
+++ b/examples/angular/src/app/screens/email-link-auth-screen-w-oauth/index.ts
@@ -0,0 +1 @@
+export * from "./email-link-auth-screen-w-oauth.component";
diff --git a/examples/angular/src/app/screens/email-link-auth-screen/email-link-auth-screen.component.ts b/examples/angular/src/app/screens/email-link-auth-screen/email-link-auth-screen.component.ts
index 6489ce99e..55dcefb66 100644
--- a/examples/angular/src/app/screens/email-link-auth-screen/email-link-auth-screen.component.ts
+++ b/examples/angular/src/app/screens/email-link-auth-screen/email-link-auth-screen.component.ts
@@ -14,15 +14,28 @@
* limitations under the License.
*/
-import { Component } from "@angular/core";
+import { Component, inject } from "@angular/core";
import { CommonModule } from "@angular/common";
import { EmailLinkAuthScreenComponent } from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
@Component({
selector: "app-email-link-auth-screen",
standalone: true,
imports: [CommonModule, EmailLinkAuthScreenComponent],
- template: ` `,
+ template: ` `,
styles: [],
})
-export class EmailLinkAuthScreenWrapperComponent {}
+export class EmailLinkAuthScreenWrapperComponent {
+ private router = inject(Router);
+
+ onEmailSent() {
+ alert("email sent - please check your email");
+ }
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/forgot-password-auth-screen-w-handlers/forgot-password-auth-screen-w-handlers.component.ts b/examples/angular/src/app/screens/forgot-password-auth-screen-w-handlers/forgot-password-auth-screen-w-handlers.component.ts
new file mode 100644
index 000000000..e8c09a3ca
--- /dev/null
+++ b/examples/angular/src/app/screens/forgot-password-auth-screen-w-handlers/forgot-password-auth-screen-w-handlers.component.ts
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { Router } from "@angular/router";
+import { ForgotPasswordAuthScreenComponent } from "@invertase/firebaseui-angular";
+
+@Component({
+ selector: "app-forgot-password-auth-screen-w-handlers",
+ standalone: true,
+ imports: [CommonModule, ForgotPasswordAuthScreenComponent],
+ template: `
+
+ `,
+ styles: [],
+})
+export class ForgotPasswordAuthScreenWithHandlersComponent {
+ private router = inject(Router);
+
+ goToSignIn() {
+ this.router.navigate(["/screens/sign-in-auth-screen"]);
+ }
+
+ goToForgotPassword() {
+ this.router.navigate(["/screens/forgot-password-auth-screen"]);
+ }
+
+ goToSignUp() {
+ this.router.navigate(["/screens/sign-up-auth-screen"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/forgot-password-auth-screen-w-handlers/index.ts b/examples/angular/src/app/screens/forgot-password-auth-screen-w-handlers/index.ts
new file mode 100644
index 000000000..227203450
--- /dev/null
+++ b/examples/angular/src/app/screens/forgot-password-auth-screen-w-handlers/index.ts
@@ -0,0 +1 @@
+export * from "./forgot-password-auth-screen-w-handlers.component";
diff --git a/examples/angular/src/app/app.component.css b/examples/angular/src/app/screens/forgot-password-auth-screen/forgot-password-auth-screen.component.ts
similarity index 51%
rename from examples/angular/src/app/app.component.css
rename to examples/angular/src/app/screens/forgot-password-auth-screen/forgot-password-auth-screen.component.ts
index 2ef08e386..ceb20bc35 100644
--- a/examples/angular/src/app/app.component.css
+++ b/examples/angular/src/app/screens/forgot-password-auth-screen/forgot-password-auth-screen.component.ts
@@ -13,3 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+import { Component } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { ForgotPasswordAuthScreenComponent } from "@invertase/firebaseui-angular";
+
+@Component({
+ selector: "app-forgot-password-auth-screen",
+ standalone: true,
+ imports: [CommonModule, ForgotPasswordAuthScreenComponent],
+ template: ` `,
+ styles: [],
+})
+export class ForgotPasswordAuthScreenWrapperComponent {
+ onPasswordSent() {
+ alert("password reset email sent - please check your email");
+ }
+}
diff --git a/examples/angular/src/app/screens/forgot-password-auth-screen/index.ts b/examples/angular/src/app/screens/forgot-password-auth-screen/index.ts
new file mode 100644
index 000000000..6cc32654d
--- /dev/null
+++ b/examples/angular/src/app/screens/forgot-password-auth-screen/index.ts
@@ -0,0 +1 @@
+export * from "./forgot-password-auth-screen.component";
diff --git a/examples/angular/src/app/screens/mfa-enrollment-screen/index.ts b/examples/angular/src/app/screens/mfa-enrollment-screen/index.ts
new file mode 100644
index 000000000..1402c2ecd
--- /dev/null
+++ b/examples/angular/src/app/screens/mfa-enrollment-screen/index.ts
@@ -0,0 +1 @@
+export * from "./mfa-enrollment-screen.component";
diff --git a/examples/angular/src/app/screens/mfa-enrollment-screen/mfa-enrollment-screen.component.ts b/examples/angular/src/app/screens/mfa-enrollment-screen/mfa-enrollment-screen.component.ts
new file mode 100644
index 000000000..4e7c7fd8e
--- /dev/null
+++ b/examples/angular/src/app/screens/mfa-enrollment-screen/mfa-enrollment-screen.component.ts
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { Router } from "@angular/router";
+import { MultiFactorAuthEnrollmentScreenComponent } from "@invertase/firebaseui-angular";
+import { FactorId } from "firebase/auth";
+
+@Component({
+ selector: "app-mfa-enrollment-screen",
+ standalone: true,
+ imports: [CommonModule, MultiFactorAuthEnrollmentScreenComponent],
+ template: `
+
+ `,
+ styles: [],
+})
+export class MfaEnrollmentScreenComponent {
+ FactorId = FactorId;
+ private router = inject(Router);
+
+ onEnrollment() {
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/oauth-screen/oauth-screen.component.ts b/examples/angular/src/app/screens/oauth-screen/oauth-screen.component.ts
index a952ec62f..80690c2dd 100644
--- a/examples/angular/src/app/screens/oauth-screen/oauth-screen.component.ts
+++ b/examples/angular/src/app/screens/oauth-screen/oauth-screen.component.ts
@@ -14,15 +14,57 @@
* limitations under the License.
*/
-import { Component } from "@angular/core";
+import { Component, inject, signal } from "@angular/core";
import { CommonModule } from "@angular/common";
-import { OAuthScreenComponent } from "@invertase/firebaseui-angular";
+import {
+ OAuthScreenComponent,
+ GoogleSignInButtonComponent,
+ FacebookSignInButtonComponent,
+ AppleSignInButtonComponent,
+ GitHubSignInButtonComponent,
+ MicrosoftSignInButtonComponent,
+ TwitterSignInButtonComponent,
+} from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
@Component({
selector: "app-oauth-screen",
standalone: true,
- imports: [CommonModule, OAuthScreenComponent],
- template: ` `,
+ imports: [
+ CommonModule,
+ OAuthScreenComponent,
+ GoogleSignInButtonComponent,
+ FacebookSignInButtonComponent,
+ AppleSignInButtonComponent,
+ GitHubSignInButtonComponent,
+ MicrosoftSignInButtonComponent,
+ TwitterSignInButtonComponent,
+ ],
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
styles: [],
})
-export class OAuthScreenWrapperComponent {}
+export class OAuthScreenWrapperComponent {
+ themed = signal(false);
+ private router = inject(Router);
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/phone-auth-screen-w-oauth/index.ts b/examples/angular/src/app/screens/phone-auth-screen-w-oauth/index.ts
new file mode 100644
index 000000000..3d0a30e0d
--- /dev/null
+++ b/examples/angular/src/app/screens/phone-auth-screen-w-oauth/index.ts
@@ -0,0 +1 @@
+export * from "./phone-auth-screen-w-oauth.component";
diff --git a/examples/angular/src/app/screens/phone-auth-screen-w-oauth/phone-auth-screen-w-oauth.component.ts b/examples/angular/src/app/screens/phone-auth-screen-w-oauth/phone-auth-screen-w-oauth.component.ts
new file mode 100644
index 000000000..9fac00203
--- /dev/null
+++ b/examples/angular/src/app/screens/phone-auth-screen-w-oauth/phone-auth-screen-w-oauth.component.ts
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { PhoneAuthScreenComponent, GoogleSignInButtonComponent, ContentComponent } from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
+
+@Component({
+ selector: "app-phone-auth-screen-w-oauth",
+ standalone: true,
+ imports: [CommonModule, PhoneAuthScreenComponent, GoogleSignInButtonComponent, ContentComponent],
+ template: `
+
+
+
+
+
+ `,
+ styles: [],
+})
+export class PhoneAuthScreenWithOAuthComponent {
+ private router = inject(Router);
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/phone-auth-screen/phone-auth-screen.component.ts b/examples/angular/src/app/screens/phone-auth-screen/phone-auth-screen.component.ts
index 2dd4fab2c..add17ca3f 100644
--- a/examples/angular/src/app/screens/phone-auth-screen/phone-auth-screen.component.ts
+++ b/examples/angular/src/app/screens/phone-auth-screen/phone-auth-screen.component.ts
@@ -14,15 +14,24 @@
* limitations under the License.
*/
-import { Component } from "@angular/core";
+import { Component, inject } from "@angular/core";
import { CommonModule } from "@angular/common";
import { PhoneAuthScreenComponent } from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
@Component({
selector: "app-phone-auth-screen",
standalone: true,
imports: [CommonModule, PhoneAuthScreenComponent],
- template: ` `,
+ template: ` `,
styles: [],
})
-export class PhoneAuthScreenWrapperComponent {}
+export class PhoneAuthScreenWrapperComponent {
+ private router = inject(Router);
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/sign-in-auth-screen-w-handlers/index.ts b/examples/angular/src/app/screens/sign-in-auth-screen-w-handlers/index.ts
new file mode 100644
index 000000000..d498e8f0b
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-in-auth-screen-w-handlers/index.ts
@@ -0,0 +1 @@
+export * from "./sign-in-auth-screen-w-handlers.component";
diff --git a/examples/angular/src/app/screens/sign-in-auth-screen-w-handlers/sign-in-auth-screen-w-handlers.component.ts b/examples/angular/src/app/screens/sign-in-auth-screen-w-handlers/sign-in-auth-screen-w-handlers.component.ts
new file mode 100644
index 000000000..ce7a7d296
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-in-auth-screen-w-handlers/sign-in-auth-screen-w-handlers.component.ts
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { Router } from "@angular/router";
+import { SignInAuthScreenComponent } from "@invertase/firebaseui-angular";
+
+@Component({
+ selector: "app-sign-in-auth-screen-w-handlers",
+ standalone: true,
+ imports: [CommonModule, SignInAuthScreenComponent],
+ template: `
+
+ `,
+ styles: [],
+})
+export class SignInAuthScreenWithHandlersComponent {
+ private router = inject(Router);
+
+ goToForgotPassword() {
+ this.router.navigate(["/screens/forgot-password-auth-screen"]);
+ }
+
+ goToSignUp() {
+ this.router.navigate(["/screens/sign-up-auth-screen"]);
+ }
+
+ onSignIn(credential: unknown) {
+ console.log(credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/sign-in-auth-screen-w-oauth/index.ts b/examples/angular/src/app/screens/sign-in-auth-screen-w-oauth/index.ts
new file mode 100644
index 000000000..2697e1510
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-in-auth-screen-w-oauth/index.ts
@@ -0,0 +1 @@
+export * from "./sign-in-auth-screen-w-oauth.component";
diff --git a/examples/angular/src/app/screens/sign-in-auth-screen-w-oauth/sign-in-auth-screen-w-oauth.component.ts b/examples/angular/src/app/screens/sign-in-auth-screen-w-oauth/sign-in-auth-screen-w-oauth.component.ts
new file mode 100644
index 000000000..dd0048828
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-in-auth-screen-w-oauth/sign-in-auth-screen-w-oauth.component.ts
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import {
+ SignInAuthScreenComponent,
+ ContentComponent,
+ GoogleSignInButtonComponent,
+ FacebookSignInButtonComponent,
+ AppleSignInButtonComponent,
+ GitHubSignInButtonComponent,
+ MicrosoftSignInButtonComponent,
+ TwitterSignInButtonComponent,
+} from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
+
+@Component({
+ selector: "app-sign-in-auth-screen-w-oauth",
+ standalone: true,
+ imports: [
+ CommonModule,
+ SignInAuthScreenComponent,
+ ContentComponent,
+ GoogleSignInButtonComponent,
+ FacebookSignInButtonComponent,
+ AppleSignInButtonComponent,
+ GitHubSignInButtonComponent,
+ MicrosoftSignInButtonComponent,
+ TwitterSignInButtonComponent,
+ ],
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ styles: [],
+})
+export class SignInAuthScreenWithOAuthComponent {
+ private router = inject(Router);
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/sign-in-auth-screen/sign-in-auth-screen.component.ts b/examples/angular/src/app/screens/sign-in-auth-screen/sign-in-auth-screen.component.ts
index 8b1e7d458..2475549dd 100644
--- a/examples/angular/src/app/screens/sign-in-auth-screen/sign-in-auth-screen.component.ts
+++ b/examples/angular/src/app/screens/sign-in-auth-screen/sign-in-auth-screen.component.ts
@@ -14,15 +14,24 @@
* limitations under the License.
*/
-import { Component } from "@angular/core";
+import { Component, inject } from "@angular/core";
import { CommonModule } from "@angular/common";
import { SignInAuthScreenComponent } from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
@Component({
selector: "app-sign-in-auth-screen",
standalone: true,
imports: [CommonModule, SignInAuthScreenComponent],
- template: ` `,
+ template: ` `,
styles: [],
})
-export class SignInAuthScreenWrapperComponent {}
+export class SignInAuthScreenWrapperComponent {
+ private router = inject(Router);
+
+ onSignIn(credential: UserCredential) {
+ console.log("sign in", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/sign-up-auth-screen-w-handlers/index.ts b/examples/angular/src/app/screens/sign-up-auth-screen-w-handlers/index.ts
new file mode 100644
index 000000000..64db728c0
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-up-auth-screen-w-handlers/index.ts
@@ -0,0 +1 @@
+export * from "./sign-up-auth-screen-w-handlers.component";
diff --git a/examples/angular/src/app/screens/sign-up-auth-screen-w-handlers/sign-up-auth-screen-w-handlers.component.ts b/examples/angular/src/app/screens/sign-up-auth-screen-w-handlers/sign-up-auth-screen-w-handlers.component.ts
new file mode 100644
index 000000000..8aa94af46
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-up-auth-screen-w-handlers/sign-up-auth-screen-w-handlers.component.ts
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { Router } from "@angular/router";
+import { SignUpAuthScreenComponent } from "@invertase/firebaseui-angular";
+
+@Component({
+ selector: "app-sign-up-auth-screen-w-handlers",
+ standalone: true,
+ imports: [CommonModule, SignUpAuthScreenComponent],
+ template: ` `,
+ styles: [],
+})
+export class SignUpAuthScreenWithHandlersComponent {
+ private router = inject(Router);
+
+ goToSignIn() {
+ this.router.navigate(["/screens/sign-in-auth-screen"]);
+ }
+
+ onSignUp(credential: unknown) {
+ console.log(credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/sign-up-auth-screen-w-oauth/index.ts b/examples/angular/src/app/screens/sign-up-auth-screen-w-oauth/index.ts
new file mode 100644
index 000000000..cc1567d01
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-up-auth-screen-w-oauth/index.ts
@@ -0,0 +1 @@
+export * from "./sign-up-auth-screen-w-oauth.component";
diff --git a/examples/angular/src/app/screens/sign-up-auth-screen-w-oauth/sign-up-auth-screen-w-oauth.component.ts b/examples/angular/src/app/screens/sign-up-auth-screen-w-oauth/sign-up-auth-screen-w-oauth.component.ts
new file mode 100644
index 000000000..c172ead67
--- /dev/null
+++ b/examples/angular/src/app/screens/sign-up-auth-screen-w-oauth/sign-up-auth-screen-w-oauth.component.ts
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, inject } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import {
+ SignUpAuthScreenComponent,
+ ContentComponent,
+ GoogleSignInButtonComponent,
+ FacebookSignInButtonComponent,
+ AppleSignInButtonComponent,
+ GitHubSignInButtonComponent,
+ MicrosoftSignInButtonComponent,
+ TwitterSignInButtonComponent,
+} from "@invertase/firebaseui-angular";
+import type { UserCredential } from "firebase/auth";
+import { Router } from "@angular/router";
+
+@Component({
+ selector: "app-sign-up-auth-screen-w-oauth",
+ standalone: true,
+ imports: [
+ CommonModule,
+ SignUpAuthScreenComponent,
+ ContentComponent,
+ GoogleSignInButtonComponent,
+ FacebookSignInButtonComponent,
+ AppleSignInButtonComponent,
+ GitHubSignInButtonComponent,
+ MicrosoftSignInButtonComponent,
+ TwitterSignInButtonComponent,
+ ],
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ styles: [],
+})
+export class SignUpAuthScreenWithOAuthComponent {
+ private router = inject(Router);
+
+ onSignUp(credential: UserCredential) {
+ console.log("sign up", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/screens/sign-up-auth-screen/sign-up-auth-screen.component.ts b/examples/angular/src/app/screens/sign-up-auth-screen/sign-up-auth-screen.component.ts
index 4f9b43493..6671ecba7 100644
--- a/examples/angular/src/app/screens/sign-up-auth-screen/sign-up-auth-screen.component.ts
+++ b/examples/angular/src/app/screens/sign-up-auth-screen/sign-up-auth-screen.component.ts
@@ -14,15 +14,24 @@
* limitations under the License.
*/
-import { Component } from "@angular/core";
+import { Component, inject } from "@angular/core";
import { CommonModule } from "@angular/common";
import { SignUpAuthScreenComponent } from "@invertase/firebaseui-angular";
+import { Router } from "@angular/router";
+import type { UserCredential } from "firebase/auth";
@Component({
selector: "app-sign-up-auth-screen",
standalone: true,
imports: [CommonModule, SignUpAuthScreenComponent],
- template: ` `,
+ template: ` `,
styles: [],
})
-export class SignUpAuthScreenWrapperComponent {}
+export class SignUpAuthScreenWrapperComponent {
+ private router = inject(Router);
+
+ onSignUp(credential: UserCredential) {
+ console.log("sign up", credential);
+ this.router.navigate(["/"]);
+ }
+}
diff --git a/examples/angular/src/app/components/header/index.ts b/examples/angular/src/app/services/user.service.ts
similarity index 64%
rename from examples/angular/src/app/components/header/index.ts
rename to examples/angular/src/app/services/user.service.ts
index 7df717022..50548a8b1 100644
--- a/examples/angular/src/app/components/header/index.ts
+++ b/examples/angular/src/app/services/user.service.ts
@@ -14,4 +14,17 @@
* limitations under the License.
*/
-export * from "./header.component";
+import { Injectable, inject } from "@angular/core";
+import { Auth, type User, authState } from "@angular/fire/auth";
+import type { Observable } from "rxjs";
+
+@Injectable({
+ providedIn: "root",
+})
+export class UserService {
+ private auth = inject(Auth);
+
+ getUser(): Observable {
+ return authState(this.auth);
+ }
+}
diff --git a/examples/angular/src/index.html b/examples/angular/src/index.html
index 781564eb6..e53fbd415 100644
--- a/examples/angular/src/index.html
+++ b/examples/angular/src/index.html
@@ -18,12 +18,15 @@
- AngularSsr
+ Firebase UI for Angular
-
+
+
diff --git a/examples/angular/src/styles.css b/examples/angular/src/styles.css
index 5e23eb6d6..c0f242db4 100644
--- a/examples/angular/src/styles.css
+++ b/examples/angular/src/styles.css
@@ -16,4 +16,5 @@
/* You can add global styles to this file, and also import other style files */
@import "tailwindcss";
+@custom-variant dark (&:where(.dark, .dark *));
@import "@invertase/firebaseui-styles/tailwind";
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 9d95db2ab..770160ba2 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -5,13 +5,13 @@
"dist"
],
"type": "module",
- "main": "./dist/fesm2022/firebase-ui-angular.mjs",
- "module": "./dist/fesm2022/firebase-ui-angular.mjs",
+ "main": "./dist/fesm2022/invertase-firebaseui-angular.mjs",
+ "module": "./dist/fesm2022/invertase-firebaseui-angular.mjs",
"typings": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
- "default": "./dist/fesm2022/firebase-ui-angular.mjs"
+ "default": "./dist/fesm2022/invertase-firebaseui-angular.mjs"
}
},
"scripts": {
diff --git a/packages/angular/src/lib/auth/forms/email-link-auth-form.spec.ts b/packages/angular/src/lib/auth/forms/email-link-auth-form.spec.ts
index 57673edaa..a1c9a5edc 100644
--- a/packages/angular/src/lib/auth/forms/email-link-auth-form.spec.ts
+++ b/packages/angular/src/lib/auth/forms/email-link-auth-form.spec.ts
@@ -224,7 +224,9 @@ describe("", () => {
component.emailSentState.set(true);
fixture.detectChanges();
- expect(screen.getByText("Check your email for a sign in link")).toBeInTheDocument();
+ const successMessage = screen.getByText("Check your email for a sign in link");
+ expect(successMessage).toBeInTheDocument();
+ expect(successMessage).toHaveClass("fui-success");
});
it("should handle FirebaseUIError and display error message", async () => {
diff --git a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts
index 320e7b028..8be54e5c9 100644
--- a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts
+++ b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Component, effect, output, signal } from "@angular/core";
+import { Component, effect, Output, EventEmitter, signal } from "@angular/core";
import { CommonModule } from "@angular/common";
import { injectForm, injectStore, TanStackAppField, TanStackField } from "@tanstack/angular-form";
import { UserCredential } from "@angular/fire/auth";
@@ -27,6 +27,9 @@ import { injectEmailLinkAuthFormSchema, injectTranslation, injectUI } from "../.
@Component({
selector: "fui-email-link-auth-form",
standalone: true,
+ host: {
+ style: "display: block;",
+ },
imports: [
CommonModule,
TanStackField,
@@ -38,7 +41,7 @@ import { injectEmailLinkAuthFormSchema, injectTranslation, injectUI } from "../.
],
template: `
@if (emailSentState()) {
-