#javascript #angular #typescript #rxjs #observers
#javascript #угловой #typescript #rxjs #наблюдатели
Вопрос:
Я прочитал много документации, статей и другой темы о том, как вложить наблюдателей в RxJS и Angular, я все еще что-то упускаю и не могу получить результат в конце.
Вот мой код :
page.ts
export class LiabilitiesPage implements OnInit {
constructor(
private liabilityService: LiabilityService,
private router: Router
) {}
refreshLiabilities() {
// Get the liabilities
console.log('refreshing') // passing there
this.liabilityService.getAllLiabilities().subscribe(
(response: Liability[]) => {
console.log(response); // <=== Never pass there !
if (response) {
this.liabilities = response;
} else {
// empty response code
}
}, error => {
// response error code (never passing there either)
}
}
}
ответственность.service.ts
// all the needed imports
@Injectable({
providedIn: 'root'
})
export class LiabilityService {
constructor(
private authService: AuthService,
private http: HttpClient,
) {}
// first try : Do not send the http request
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions()
.pipe(
tap(options => this.http.get<Liability[]>(this.url 'me/', options))
);
}
// try 2 : Doesn't work either
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions()
.pipe(
switchMap(options => this.http.get<Liability[]>(this.url 'me/', options)), // at this point I tried pretty much every operators (map, mergeMap etc.)
withLatestFrom()
);
}
/* this code was working before that I transformed the authService.getOptions in observable (it was just returning the options synchronyously before)
getAllLiabilities(): Observable<Liability[]> {
return this.http.get<Liability[]>(this.url 'me/', this.authService.getOptions());
}*/
}
auth.service.ts
public getOptions(): Observable<any> {
return new Observable((observer) => {
this.storage.get('authToken').then((token) => {
console.log('passing') // Pass here
if (token amp;amp; typeof token.auth_token !== 'undefined') {
console.log('passing') // pass here as well
this.isLoggedIn = true;
this.token = token.auth_token;
}
// it is returning the value
return {
headers: this.headers.set('Authorization', 'Bearer ' this.token),
params: new HttpParams()
};
})
});
}
Я перепробовал почти все возможные комбинации операторов, чтобы заставить его работать в liabilityService без какого-либо успеха.
Проблема :
Проблема в том, что мой page.ts
подписывается на this.http.get<Liability[]>(this.url 'me/', options)
наблюдателя, но ни один запрос xhr не выполняется. Наблюдатель http get никогда не выполняется, и я не понимаю, чего мне там не хватает.
Я только начинаю экспериментировать с Angular, но, если я правильно понял, операторы должны выполнять сопоставление и выравнивание, но, похоже, этого никогда не произойдет.
Дополнительный вопрос :
Я тоже не понимаю, почему исходный код :
return this.http.get<Liability[]>(this.url 'me/', this.authService.getOptions());
возвращает Observable<Liability[]>
и с помощью switchMap :
switchMap(options => this.http.get<Liability[]>(this.url 'me/', options))
Он возвращает Observable<HttpEvent<Liability[]>>
Если у кого-то есть подсказка и время, чтобы ответить мне на это, это было бы удивительно
Ответ №1:
У вас проблема с обратным вызовом promise then()
:
this.storage.get('authToken').then((token) => {
return something; // this won't work.
})
вместо этого вы можете использовать from
, который будет convert
вашим обещанием наблюдаемому.
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
public getOptions(): Observable<any> {
return from(this.storage.get('authToken')).pipe(map(token => {
return headers with token.
}));
}
Итак, вы могли бы переписать свой код следующим образом:
служба аутентификации:
private token: string | null = null;
public getOptions(): Observable<any> {
return this.getToken().pipe(
map(token => {
return {
headers: this.headers.set('Authorization', 'Bearer ' token),
params: new HttpParams()
};
})
);
}
private getToken(): Observable<string | null> {
if (this.token) {
return of(this.token);
}
return from(this.storage.get('authToken')).pipe(
map(token => token?.authToken || null),
tap(token => this.token = token)
);
}
затем вы можете использовать switchmap:
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions().pipe(
switchMap(options => this.http.get<Liability[]>(this.url 'me/', options))
);
}
Обновить
Причина получения HttpEvent<T>
заключается в том, что когда перегрузка .get()
получает any
объект, он полностью оставляет обработку событий http на ваше усмотрение. Если вы хотите, чтобы он возвращал указанный тип элемента, вы должны выполнить надлежащую перегрузку. Вы можете добиться этого, делая это следующим образом:
Вместо того, чтобы возвращать все параметры, мы возвращаем только заголовки, которых должно быть достаточно, потому что у нас действительно недостаточно, чтобы сказать об остальных параметрах.
служба аутентификации
private token: string | null = null;
public createTokenHeaders(): Observable<HttpHeaders> {
const headers = new HttpHeaders();
return addToken(headers);
}
public addToken(headers: HttpHeaders): Observable<HttpHeaders> {
return this.getToken().pipe(
map(token => headers.set('Authorization', 'Bearer ' (token || '')))
);
}
private getToken(): Observable<string | null> {
if (this.token) {
return of(this.token);
}
return from(this.storage.get('authToken')).pipe(
map(token => token?.authToken || null),
tap(token => this.token = token)
);
}
Затем используйте его следующим образом:
getAllLiabilities(): Observable<Liability[]> {
const url = this.url 'me/';
const headers = new HttpHeaders();
return this.authService.addToken(headers).pipe(
switchMap(updatedHeaders => this.http.get<Liability[]>(url, { headers: updatedHeaders }))
);
}
или:
getAllLiabilities(): Observable<Liability[]> {
const url = this.url 'me/';
return this.authService.createTokenHeaders().pipe(
switchMap(headers => this.http.get<Liability[]>(url, { headers }))
);
}
Примечание: Убедитесь, что вы используете заголовки, возвращенные из вызова addToken . Повторное использование вашего собственного экземпляра headers
не будет работать, потому что установка заголовка всегда возвращает новый HttpHeaders
объект. Он неизменяем.
Комментарии:
1. Это потрясающий ответ! Спасибо @Silverrmind. Ваше решение работает, но остается дополнительный вопрос. TSLint выделяет возврат служебной функции, поскольку считает, что возвращаемый тип является наблюдаемым on
HTTPEvent
. Мы находимся в случае моего бонусного вопроса. Можем ли мы избежать такого поведения? (другой способ просто изменить возвращаемый тип getAllLibalities)2. @JulienBourdic Я обновил свой ответ. Возможно, это проясняет ситуацию.
3. Извините, что вернулся к вам так поздно. Я хочу еще раз поблагодарить вас за ваше время и ваши кристально четкие объяснения того, как все работает! Надеюсь, ваш ответ поможет многим людям. (Я отредактировал ваш ответ, чтобы сделать создание заголовков обязательным только для страниц, которым необходимо его расширить, и исправить небольшую проблему с видимостью метода)
4. @JulienBourdic Всегда пожалуйста. Я принял редактирование для модификатора -> public, но отклонил необязательный аргумент. Для таких случаев я бы предложил создать дополнительный метод, который вызывается
createTokenHeaders(): Observable<HttpHeaders>
. Это может вызвать addTokenHeader с недавно созданным объектом headers. Также я бы предложил выполнять проверки null вместо undefined, потому что проверки null перехватывают как undefined, так и null.5. Да, это была моя точка зрения (для проверки типов), я всегда буду стараться использовать строгий тип, чтобы контролировать, как все работает, и избегать не отлаживаемых побочных эффектов. Это просто привычка, и у такого подхода есть свои плюсы и минусы :). Спасибо за издание (ваша идея действительно элегантна), это был действительно поучительный и интересный разговор. Я рад, что создал этот поток, и благодарен, что вы присоединились к нему: D