1

I'm working on a module federation angular nx project, with firebase and currently trying to get a header shared UI working with the Auth service.

The flow is basically:

Auth service in service library -> header in UI library -> host application

Auth.Service.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { UserModel } from '@veda/shared/model';

import {
  Auth,
  GoogleAuthProvider,
  authState,
  createUserWithEmailAndPassword,
  sendEmailVerification,
  updateProfile,
  signInWithPopup,
} from '@angular/fire/auth';
import { doc, docData, setDoc } from '@angular/fire/firestore';
import { Firestore } from '@angular/fire/firestore';
import { Observable, of, switchMap } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  user$: Observable<UserModel | null>;
  constructor(
    private auth: Auth,
    private afs: Firestore,
    private router: Router
  ) {
    this.user$ = authState(auth).pipe(
      switchMap((user) => {
        return user
          ? (docData(doc(this.afs, 'users', user.uid)) as Observable<UserModel>)
          : of(null);
      })
    );
  }

  async emailSignUp(email: string, password: string) {
    const credential = await createUserWithEmailAndPassword(
      this.auth,
      email,
      password
    );
    await updateProfile(credential.user, {
      displayName: credential.user.displayName,
    });
    await sendEmailVerification(credential.user);
    return this.updateUserData(credential.user);
  }

  async googleSignIn() {
    const provider = new GoogleAuthProvider();
    await signInWithPopup(this.auth, provider)
      .then((result) => {
        const user = result.user;
        this.updateUserData(user);
        this.router.navigate(['/']);
      })
      .catch((error) => {
        console.log(error);
      });
  }

  async SignOut() {
    await this.auth.signOut();
    this.router.navigate(['/']);
  }

  private updateUserData(user: any) {
    const userRef = doc(this.afs, 'users', user.uid);
    const data = {
      uid: user.uid,
      email: user.email,
      displayName: user.displayName,
      photoURL: user.photoURL,
    };
    return setDoc(userRef, data, { merge: true });
  }
}

shared-service.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { firebaseEnv } from '@veda/shared/enviroment';
import {
  provideAnalytics,
  getAnalytics,
  ScreenTrackingService,
  UserTrackingService,
} from '@angular/fire/analytics';
import { provideAuth, getAuth } from '@angular/fire/auth';
import { provideFirestore, getFirestore } from '@angular/fire/firestore';
import { provideFunctions, getFunctions } from '@angular/fire/functions';
import { provideMessaging, getMessaging } from '@angular/fire/messaging';
import { providePerformance, getPerformance } from '@angular/fire/performance';
import { provideStorage, getStorage } from '@angular/fire/storage';
import { AuthService } from './auth/auth.service';

@NgModule({
  imports: [
    CommonModule,
    provideFirebaseApp(() => initializeApp(firebaseEnv)),
    provideAnalytics(() => getAnalytics()),
    provideAuth(() => getAuth()),
    provideFirestore(() => getFirestore()),
    provideFunctions(() => getFunctions()),
    provideMessaging(() => getMessaging()),
    providePerformance(() => getPerformance()),
    provideStorage(() => getStorage()),
  ],
  providers: [ScreenTrackingService, UserTrackingService, AuthService],
})
export class SharedServiceModule {}

header.component.html (just pasting a part of the use of the auth service.)

<div *ngIf="auth.user$ | async; then authenticated; else signin"></div>

header.component.ts

import { Component, Input } from '@angular/core';
import { NavlinkModel } from '@veda/shared/model';
import { AuthService } from '@veda/shared/service';

@Component({
  selector: 'veda-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css'],
})
export class HeaderComponent {
  @Input() title = '';
  @Input() navlinks: NavlinkModel[] = [];

  constructor(public auth: AuthService) {}

shared-ui.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { RouterModule } from '@angular/router';
import { AuthService } from '@veda/shared/service';

@NgModule({
  imports: [CommonModule, RouterModule],
  declarations: [HeaderComponent],
  exports: [HeaderComponent],
  providers: [AuthService],
})
export class SharedUiModule {}


app.component.html

<veda-header title="Veda" [navlinks]="navlinks"></veda-header>
<router-outlet></router-outlet>

app.component.ts


import { Component, Input } from '@angular/core';

@Component({
  selector: 'veda-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  @Input() navlinks = [{ path: '', name: 'Home', guard: false }];
}


app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { SharedUiModule } from '@veda/shared/ui';
import { LandingComponent } from './landing/landing.component';
import { AuthComponent } from './auth/auth.component';
import { AuthService } from '@veda/shared/service';

@NgModule({
  declarations: [AppComponent, LandingComponent, AuthComponent],
  imports: [
    BrowserModule,
    SharedUiModule,
    RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
  ],
  providers: [AuthService],
  bootstrap: [AppComponent],
})
export class AppModule {}

I being trying for a good part of the day, the code itself does seem to find the AuthService and no errors are displayed in linting or in vscode, but when I serve the host using nx serve host --open

The console displayed the error code

NullInjectorError: R3InjectorError(AppModule)[AuthService -> Auth -> Auth -> Auth]: 
  NullInjectorError: No provider for Auth!
    Angular 6
    AuthService_Factory auth.service.ts:21
    Angular 6
    HeaderComponent_Factory header.component.ts:10
    Angular 5
    AppComponent_Template app.component.html:1
    Angular 31
    webpackJsonpCallback jsonp chunk loading:77
    <anonymous> Angular

Not sure why this is happening, hopefully someone can help me.

Tried to export just the Auth service and use it in the correct places, tried using

@Injectable({
  providedIn: 'root',
})

and adding the provider and shared-service module to the libraries and apps that consume auth service

  • After watching this video: https://www.youtube.com/watch?v=j38ufd8Q86w I was able to find the reason, simply add the SharedUiModule in the imports of the module.ts that will consume the authservice, after you have the export in the index.ts file. – Gustavo Cabezal Apr 16 '23 at 22:29

0 Answers0