Обнаружение изменений внутри службы в другой службе (Angular 7) - PullRequest
0 голосов
/ 02 марта 2019

Я работаю с angular только несколько месяцев и в настоящее время перегружен услугами.Я использую 3 угловых сервиса, которые взаимодействуют друг с другом.AuthService, configService и guardService.

В моем guardService метод checkBusinessCase (bcase) проверяет текущий бизнес-случай и устанавливает его с помощью метода this.authService.setActiveBusinessCase (bcase) внутри моего AuthService.

GuardService выглядит следующим образом:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { UserAuthService } from './user-auth.service';
import { Constants } from '../../constants';
import { catchError, map } from 'rxjs/operators';

@Injectable()
export class BcaseRequiredGuard implements CanActivate {

  constructor(private router: Router,
              private authService: UserAuthService) {

  }

  canActivate(next: ActivatedRouteSnapshot,
              state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    if (next.data['bcase']) {
      return this.checkBusinessCase(next.data['bcase']);
    }
    if (next.data['bcaseRegExp']) {
      return this.checkBusinessCase(new RegExp(next.data['bcaseRegExp']));
    }
    this.router.navigate([Constants.routing.error, 'invalidBusinessCase']);
    return false;

  }

  checkBusinessCase(bcase): Observable<boolean> {
    return this.authService.isLoggedIn().pipe(
      map(isLoggedIn => {
        const accept = isLoggedIn && this.authService.hasBusinessCase(bcase);
        if (accept) {
          console.log('This guard approved that the Business case ' + bcase + ' is assigned to ' + this.authService.user.user_name);
          this.authService.setActiveBusinessCase(bcase);
        } else {
          console.log('This guard approved that the Business case ' + bcase + ' is not assigned to ' + this.authService.user.user_name);
          this.router.navigate([Constants.routing.home]);
        }
        return accept;
      }),
      catchError((err) => {
        console.error(Constants.errors.loginCheckFailed, err);
        this.router.navigate([Constants.routing.error, 'loginCheckFailed']);
        return of(false);
      }));
  }

}

Именно этот бизнес-пример мне нужен в моем configService в методе getAutoConfiguredBCLanguage () .Когда я пытаюсь получить к нему доступ с помощью this.authService.activeBusinessCase , я получаю неопределенное значение, потому что configService вызывается перед authService.

configService выглядит следующим образом:

import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import 'moment/locale/de';
import { UserAuthService } from 'app/auth/user-auth.service';

@Injectable()
export class ConfigurationService {

  defaultLanguage = 'en';
  activeBusinessCase = '';

  languages = {
    en: 'English',
    de: 'German',
    es: 'Spanish',
    ja: 'Japanese',
    pt: 'Portuguese'
  };

  // Business cases with available translation files
  businessCases = {
    case1: '_case1',
    case2: '_case2',
    case3: '_case3'
  };

  constructor(
    private translate: TranslateService,
    private authService: UserAuthService
  ) {

  }
  // Get business case specific language
  // TODO: Get currently logged in business case
  getAutoConfiguredBCLanguage(){
    const browserLang = this.translate.getBrowserLang();
    console.log('BROWSER LANGUAGE: ', browserLang);
    console.log('ACTIVE BUSINESS CASE: ', this.activeBusinessCase);
    if (this.languages.hasOwnProperty(browserLang) && this.activeBusinessCase !== undefined) {
      switch (this.activeBusinessCase) {
        case 'CASE1':
          return browserLang.concat(this.businessCases.case1);
        case 'CASE2':
          return browserLang.concat(this.businessCases.case2);
        case 'CASE3':
          return browserLang.concat(this.businessCases.case2);
        default:
          return browserLang;
      }
    } else if (this.languages.hasOwnProperty(browserLang) && this.activeBusinessCase === undefined) {
      console.log('No currently active business case.');
      return browserLang;
    } else {
      return this.defaultLanguage;
    }
  }

}

И это authService, где установлено свойство activeBusinessCase, значение которого мне нужно:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Constants } from '../../constants';
import { EMPTY, Observable } from 'rxjs';
import { PathLocationStrategy } from '@angular/common';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { translate } from '../shared/translation-util';
import { environment } from '../../environments/environment';
import { LoginError } from '../model/auth/LoginError';
import { User } from '../model/auth/User';
import { UiInfo } from '../model/auth/UiInfo';
import { ChangePasswordError } from '../model/auth/ChangePasswordError';
import { Title } from '@angular/platform-browser';
import { catchError, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
import { LoginResponse } from '../model/auth/LoginResponse';

@Injectable()
export class UserAuthService {

  activeBusinessCase: string;

  constructor(private router: Router,
              private httpClient: HttpClient,
              private pls: PathLocationStrategy,
              private titleService: Title) {

  }

  setActiveBusinessCase(bcase: string) {
    this.activeBusinessCase = bcase;
    this.setTitle(bcase);
  }

}

Как я могуполучить текущее экономическое обоснование (activeBusinessCase) в моем configService?


ОБНОВЛЕНИЕ:

После принятия изменений fridoo и подгоняя их под мои нужды, теперь все выглядит более логично.Но теперь у меня есть сообщения об ошибках в другом месте, которые я не могу обработать.Вот прерывистые строки кода и сообщений об ошибках в консоли.

В моем http.service.ts:

  getResource(key: string, lang: string): Observable<any> {
    const headers = new HttpHeaders({'Accept': 'text/html'});
    return this.httpClient.get('/resources/' + key,
      {
        headers: headers,
        responseType: 'text',
        params: new HttpParams()
          .set('businessCase', this.authService.activeBusinessCase ? this.authService.activeBusinessCase :
            environment.default_business_case)
          .set('lang', lang)
      }).pipe(
      catchError(() => {
        return this.translateService.get('Not-available').pipe(
          map(res => '<h4 style="text-align: center">' + res + '</h4>'));
      })
    );
  }

Сообщение об ошибке:

Свойство activeBusinessCase является частным и доступно только в пределах класса UserAuthService.

My user-auth.service.ts

  login(name: string, password: string, imTid: string): Observable<UiInfo> {
    return this.loginWithBackend(name, password, imTid).pipe(
      tap(() => {
        this.user.user_name = translate('default-user');
        // TODO: Check if loggedoff is obsolete?
        if (this.loggedOff) {
          this.pls.back();
        } else if (this.redirectUrl) {
          this.router.navigate([this.redirectUrl]);
          this.redirectUrl = null;
          console.log(Constants.texts.loginSuccessRedirect);

        } else {
          console.log('Active Business Case', this.activeBusinessCase);
          if (this.activeBusinessCase) {
            this.router.navigate([Constants.routing.explorer + this.activeBusinessCase.toLowerCase()]);
          } else {
            const err = new LoginError('Business case is missing');
            throw err;
          }
        }
        this.loggedOff = false;
      }));

Сообщение об ошибке:

Свойство 'toLowerCase' не существует для типа 'BehaviorSubject'.

My home.component.ts

export class HomeComponent implements OnInit {

  routing = Constants.routing;

  constructor(private router: Router,
              private authService: UserAuthService) {

  }

  ngOnInit() {
    this.router.navigate([Constants.routing.explorer + this.authService.activeBusinessCase.toLowerCase()]);
  }

}

Сообщение об ошибке:

Свойство activeBusinessCase является закрытым и доступно только в классе UserAuthService.

1 Ответ

0 голосов
/ 02 марта 2019

Поскольку activeBusinessCase устанавливается асинхронно, и вы хотите получать уведомления об изменениях значений в другой части вашего приложения, это должно быть Subject.Используйте BehaviorSubject, чтобы вы сразу получили текущее значение при подписке.

// ----- UserAuthService -----

public activeBusinessCase$ = new BehaviorSubject<string>(null);

setActiveBusinessCase(bcase: string) {
  this.activeBusinessCase.next(bcase);
  this.setTitle(bcase);
}

Прослушайте изменения значения activeBusinessCase$ в вашем ConfigurationService и отобразите входящие значения на нужный языкreturn.

// ----- ConfigurationService -----

// will emit a new language every time activeBusinessCase changes,
// this behavior could be changed depending on your specific needs
getAutoConfiguredBCLanguage$(): Observable<string> {
  return this.authService.activeBusinessCase$
    .pipe(
      // use 'distinctUntilChanged' to only emit values that are different from the previous,
      distinctUntilChanged(),
      map(abc => this.getAutoConfiguredBCLanguage(abc))
    );     
}

private getAutoConfiguredBCLanguage(activeBusinessCase: string): string {
  // no changes here apart from the input parameter - I omitted the code for brevity
  // use the parameter activeBusinessCase instead of this.activeBusinessCase here
}

Подпишитесь на getAutoConfiguredBCLanguage$, где вам нужен язык.

// ----- AppComponent -----

initLanguage() {
  this.configService.getAutoConfiguredBCLanguage$()
    .pipe(takeUntil(this.destroy$)) // unsubscribe when the component gets destroyed
    .subscribe(lang => {
      // use the language here
    });
}

private destroy$ = new Subject<void>();

onDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

A BehaviorSubject имеет функцию getValue, которая возвращает последнее значение, выданное BehaviorSubject.Вы можете использовать эту функцию для получения текущего activeBusinessCase, но делать это только в том случае, если вы знаете, что делаете, т.е. если вы действительно хотите получить текущее значение в конкретное время , которое вы называете getValue.Помните, что вы имеете дело с асинхронными действиями в вашем приложении.Если вы хотите быть уверены, что получите значение activeBusinessCase$ после того, как оно было установлено, позвонив по номеру this.authService.setActiveBusinessCase, вы должны подписаться на BehaviorSubject.Если вас не волнует, было ли уже установлено значение activeBusinessCase$, и вы просто хотите использовать текущее значение activeBusinessCase$, вы можете использовать getValue (которое затем может вернуть null, которое было установлено в качестве начального значения activeBusinessCase$).

Мое предложение:

// ---- HomeComponent ----

ngOnInit() {
  // To navigate to a route once the activeBusinessCase has a non null value
  // subscribe but only take the first non null element and no further values afterwards
  this.authService.activeBusinessCase$
    .pipe(first(Boolean))
    .subscribe(bc => this.router.navigate([Constants.routing.explorer + bc.toLowerCase()]));
}

// ---- SmagHttpService & UserAuthService ----

// Don't subscribe to activeBusinessCase$ in here!! Instead use
this.authService.activeBusinessCase$.getValue()
// when you need the current value (which might not have been set yet, 
// but you already have null checks here and if this part of your app worked before 
// there will be no differences)

// Example:
const businessCase = this.authService.activeBusinessCase$.getValue();
if (businessCase) {
  this.router.navigate([Constants.routing.explorer + businessCase.toLowerCase()]);
}

businessCase ? businessCase : environment.default_business_case
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...