typescript - Angular-fabrikleverantör med en dynamisk parameter


Jag har följande leverantörsfabrik för en dynamiskt beräknad Firebase Firestore-sökväg:

const meetingServiceFactory = (db: AngularFirestore, authService: AuthService ) => {

  const userId = authService.user;
  return new MeetingService('/users/' + userId + '/meetings', db);

};

export let meetingServiceProvider =   { provide: MeetingService,
                                        useFactory: meetingServiceFactory,
                                        deps: [AngularFirestore, AuthService] };

Här är MeetingService:

@Injectable(
  {
    providedIn: 'root'
  }
)
export class MeetingService extends FirestoreDataService<Meeting> {

  constructor(path: string, db: AngularFirestore) {
    super(path, db);
  }


  setProposalFlag(meetingKey: string) {
    return from(this.db.doc(this.path + `/${meetingKey}`).update({hasProposal: true})
      .then(result => console.log('changed active Flag on meeting: ', result))
      .catch(err => console.log(err)));
  }

  saveMeeting(meeting: Meeting) {

    return of(meeting).pipe(
      map(meetingToSave =>  {
       return  {...meetingToSave, key: this.db.createId() };
      }),
      switchMap( finalMeeting => this.addItemWithKey(finalMeeting.key, finalMeeting) )
    );
  }

  getAll() {
    return this.getItems();
  }
}

Detta ärver från FirestoreDataService<T> så att jag har en slags CRUD-tjänst att återanvända:

@Injectable()
export class FirestoreDataService<T> {

  protected itemCollection: AngularFirestoreCollection<T>;
  protected items$: Observable<T[]>;
  protected snapshot$: Observable<DocumentChangeAction<T>[]>;
  protected state$: Observable<DocumentChangeAction<T>[]>;

  constructor(protected path: string, protected db: AngularFirestore) {
    this.itemCollection = this.db.collection<T>(path);
    this.items$ = this.itemCollection.valueChanges();
    this.snapshot$ = this.itemCollection.snapshotChanges();
    this.state$ = this.itemCollection.stateChanges();
  }

  getItems(): Observable<T[]> {
    return this.items$;
  }

  getItemWithKey( key: string): Observable<T> {
    // used to use stateChanges here.
    // as the doc states, this only emits recent changes,
    // so if we subscribe in an async pipe, it will not reemit and subsequent observables will be empty.
    return this.itemCollection.doc<T>(key).valueChanges();
  }

  addItem(data: T) {
    return from(this.itemCollection.add(data)
                                          .catch(err => console.log('Error while adding item: ', err)));
  }

  addItemWithKey(key: string, data: T) {
    return from(this.itemCollection.doc(key).set(data)
                                          .catch(err => console.log('Error while adding item: ', err)));
  }

  deleteItem(key: string) {
    return from(this.itemCollection.doc(key).delete()
                                          .catch(err => console.log('Error while deleting item: ', err)));
  }
}

, men det beror uppenbarligen på beslut som tas vid körning baserat på userId. Verkar fungera bra inom ng serve men vid transpiling för produktion får jag: ERROR in : Kan inte lösa alla parametrar för FirestoreDataService i /web-client/src/app/core/services/firestore-data.service.ts: (?, [object Object]).

Jag antar att det beror på att jag inte känner till den exakta sökvägen vid kompileringstid. Är det så? Om inte, vad kan problemet vara med den här uppsättningen? Och hur skulle jag kunna fixa det/förbättra det? Jag använder Angular 7 just nu. Tack för hjälpen!

Svar #1

Jag löste problemet genom refaktorering. Här är det i hopp om att det kan vara användbart för någon.

  1. Angular-dokumentationen om DI tillhandahåller omfattande dokumentation om hur man använder injektorn, vilket också är användbart vid testning.
  2. Det finns dock ingen beskrivning för mitt exakta användningsfall, eftersom DI behöver ha definierade parametrar vid transpilerings-tid. Jämför detta med ett ramverk som till exempel Spring där du kan göra värdeinjektion när kontexten startar. Dumma gamla jag trodde att det skulle vara samma här, men naturligtvis måste även Spring ha definierade beans vid uppstart.
  3. Som en följd, när transpileraren (inklusive aot) inte kan avgöra var en parameter ska komma ifrån, kastar den ett fel.

Om någon är intresserad, här är hur jag refaktorerade min MeetingService:

@Injectable(
  {
    providedIn: 'root'
  }
)
export class MeetingService extends FirestoreDataService<Meeting> {

   constructor(authService: AuthService,
          db: AngularFirestore) {
     const path = `/users/${authService.user}/meetings`;
     super(path, db);
     this.auth = authService;
   }

...

}

Observera att detta bara fungerar eftersom injicering av tjänster på detta sätt gör parametrarna privata för konstruktorn, annars skulle det första anropet behöva vara super(path, db);