Установка переменной класса Angular внутри метода получения подписки HTTP - PullRequest
1 голос
/ 05 мая 2020

Мне сложно понять, как это работает. Каждая статья, которую я читаю, всегда делает это. Но всегда asyn c, что переменная класса будет установлена ​​только при выполнении обратного вызова.

Итак, как правильно установить переменную класса с данными со стороны сервера.

Не поймите меня неправильно, я полностью понимаю, почему значение this.isAuthorized такое. просто я не понимаю, как правильно установить переменную класса this.isAuthorized.

См. Пример кода ниже;

export class AuthService {
  private apiUrl = environment.api_uri;
  public isAuthorized: boolean = false;

  constructor(private http: HttpClient) { }

  public authorize(): void {
    this.http.get(this.apiUrl+'/auth')
             .subscribe(
               data => {
                this.isAuthorized = true;
                console.log("authorize => "+this.isAuthorized) //displays true
              },
              error => {
                console.log(error);
                console.log("authorize => "+this.isAuthorized) //displays false
              });
    console.log("authorize => "+this.isAuthorized) //display false
  }
}

Вот где я использую вышеуказанный сервис

export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router){}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    this.auth.authorize();

    console.log( this.auth.isAuthorized ); // dksplays false

    if( this.auth.isAuthorized ) return true;

    return this.router.parseUrl("/403");
  }

}

1 Ответ

3 голосов
/ 05 мая 2020

Один из столпов Angular - Rx JS. Итак, нам нужно изменить то, как мы думаем об услугах, методах, переменных.

Давайте поговорим о вашей службе.

AuthService звонит на ваш сервер и выясняет, аутентифицирован ли пользователь. AuthGuard использует эту службу для чего-то. Как видно из синтаксиса canActivate, Angular ожидает, что вы вернете один из следующих типов:

Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree

То, что теперь возвращает AuthGuard, равно boolean | UrlTree. Также можно вернуть Observable<boolean | UrlTree>. Это должно нам кое-что сказать. AuthService имеет внутренний Observable, однако он подписывается на него и не возвращает его. Итак, давайте проведем некоторый рефакторинг

export class AuthService {
  private apiUrl = environment.api_uri;

  constructor(private http: HttpClient) { }

  public authorize(): Observable<boolean> {
    return this.http.get(this.apiUrl+'/auth')
             .pipe(
               map(data => {
                 // some logic here
                 let authorized = ....
                 return authorized;
              }),
              catchError(error => {
                 /** do some error handling
                  * I assume you wouldn't activate routing in case of an error
                  * Also, you need to wrap your return value in an `Observable`
                  * of is the simplest way to do that
                  */
                 return of(false);
              }));
  }
}

Теперь, когда AuthService возвращает Observable, мы можем использовать некоторые операторы поверх этого и вернуть его из AuthGuard.

export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router){}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.auth.authorize().pipe(
       map(isAuthorized => isAuthorized || this.router.parseUrl("/403"))
    );
  }

}

То, что я только что предложил, сделало вашу службу безгражданской, что означает, что она не содержит никаких данных. Он просто делает то, что делает снова, каждый раз, когда кто-то вызывает один из его методов.

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

Давайте поговорим о Subject.

Для получения дополнительной информации вы можете прочитать его здесь

Что такое тема? Rx JS Subject - это особый тип Observable, который позволяет многоадресно передавать значения многим Observers. В то время как простые Observable являются одноадресными (каждый подписанный Observer владеет независимым выполнением Observable), Subjects является многоадресным.

Итак, давайте создадим Subject (из документации)

const subject = new Subject<number>();

const subscriber1 = subject.subscribe(val => console.log(val))
const subscriber2 = subject.subscribe(val => console.log(val))

// this will trigger callback functions of the subscribes above
subject.next(5);

Однако опоздавшие не получат значение 5 (последнее выданное значение), следующий оператор console.log не будет выполняться

const subscriber3 = subject.subscribe(val => console.log(val))

Если вы заботитесь о том, чтобы новички могли получить то, что было отправлено раньше вы можете просто использовать BehaviorSubject

Для получения дополнительной информации вы можете прочитать его здесь


const subject = new BehaviorSubject(123);

// two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

// two subscribers will get new value => output: 456, 456
subject.next(456);

// new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);

// all three subscribers will get new value => output: 789, 789, 789
subject.next(789);

Это очень полезно, если у вас должна быть возможность обновлять "источник" позже. Однако в некоторых случаях для разных компонентов требуются одни и те же данные, и они никогда не меняются, и вы хотите выполнить HTTP-вызов только один раз. Например, пользователь начинает аутентификацию (в вашем случае).

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

Давайте немного отрефакторим AuthService.

Я создам член класса с именем isAuthorized (an Observable)

Я буду использовать тот же logi c, указанный выше, и, поскольку это Observable, он не будет звонить, пока кто-то на него не подпишется.

И, наконец, я добавлю оператор shareReplay, чтобы этот запрос был выполнен только один раз.

export class AuthService {
  private apiUrl = environment.api_uri;
  private isAuthorized = this.http.get(this.apiUrl+'/auth')
             .pipe(
               map(data => {
                 // some logic here
                 let authorized = ....
                 return authorized;
              }),
              catchError(error => {
                 return of(false);               
              }),
              shareReplay()

  );

  constructor(private http: HttpClient) { }

}

Теперь вы можете использовать его следующим образом, и независимо от того, сколько раз вы его используете, базовый HTTP-вызов будет выполнен только один раз.

authService.isAuthorized.subscribe(_ => .....)
...