Вход

Просмотр полной версии : A2: получить данные от апи и объявить переменные глобально


Vadya
25.12.2019, 11:35
Помогите получить данные пользователя из АПИ, присвоить переменным, которые использовать в любом из компонентов

Делаю так:

АПИ:

{"login": "test", "ban": 0}

Корневой app.module.ts:

...
import { UserService } from './shared/user.service';
...
providers: [ UserService ],
...


user.service.ts
Вот тут начинаются проблемы, мое понимание углового пока на уровне копипаста


import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';

export class User {
login: string;
ban: number;
}

@Injectable()
export class UserService {

constructor(private http: HttpClient) { }

user: User

ngOnInit() {
this.http.get('/server/api/userService').subscribe((data: User) => {
this.user = data;
test = this.user['login']; // <- В этом моменте непонятности
}
);
}
}

Помогите хотя бы для начала записать в консоль логин пользователя, typescript пока вообще не понимаю

Правильно ли вообще я вижу алгоритм?
1) Сервисом загружаю данные с сервера, присваиваю переменным значения
2) Загружаю сервис в главный модуль
3) В любом из компонентов, без дополнительных инклудов могу использовать переменные {{user.login}}

sniffysko
25.12.2019, 13:55
Здравствуйте. Я бы вам советовал прочитать о таком явлении как сервисы и dependency injection. Хотябы здесь: metanit.com (https://metanit.com/web/angular2/4.1.php)

Ваш код по сути просто недописан. У вас есть сервис, который уже читает данные из АПИ. Сам факт объявления его как сервис делает его доступным внутри всего приложения. Осталось расширить интерфейс сервиса методами доступа к даннам из внешних компонентов. Дописываем UserService:


getUserInfo(){
return this.user;
}


Встраиваем сервис в компонент. Например в AppComponent. Опускаю здесь ненужные подробности и возможно допуская кое-какие ошибки. Вам ведь нужен подход? :о) :


export class AppComponent {

currentUser: User;

constructor(
userServise: UserService // Вот момент встраивания сервиса
){}

onInit(){
this.currentUser = this.userServise.getUserInfo();
}
}

Vadya
26.12.2019, 09:15
sniffysko, спасибо за помощь, но пока не получается.

Вот так работает, но в пределах одного компонента. А на остальные не распространяется. А я хотел, чтобы переменные {{ user?.login }} были видны глобально, во всех шаблонах


app.component.ts


import { Component } from '@angular/core';
//import { UserService, User } from './shared/user2.service';
import { HttpClient } from '@angular/common/http'


export class User {
login: string;
ban: number;
}


@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})


export class AppComponent {

user: User

constructor(private http: HttpClient) {}

ngOnInit() {
this.http.get('/server/api/userService').subscribe((data: User) => (this.user = data))
}

}

destus
26.12.2019, 14:31
Vadya,
Ваш код не работает, потому что такие хуки как ngOnInit они как бы для компонентов. Снаружи никто метод этого класса не вызывает судя по коду.


import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import { of } from 'rxjs';

export class User {
login: string;
ban: number;
}

@Injectable()
export class UserService {

constructor(private http: HttpClient) {}

user: User

getUser() {
if (this.user) {
return of(this.user);
}
return this.http.get('/server/api/userService').pipe(
tap((data: User) => this.user = data)
);
}
}

Vadya
27.12.2019, 07:30
Перенес этот код в отдельный файл, user.module.ts
В app.module.ts сделал импорт и providers: [ UserService ]
Теперь непонятно, как этот сервис соберет данные и расшарит их для всех компонентов. Ведь даже запроса к апи он не делает
Мне где то надо единожды вызвать функцию getUser? Пока не понимаю даже как вызвать ее в шаблоне компонента

destus
27.12.2019, 09:23
Vadya,
Нужно вызывать метод getUser сервиса UserService в каждом из компонентов, нуждающихся в юзере. Например, так

export class AppComponent {

currentUser: User;

constructor(
userServiсe: UserService // Вот момент встраивания сервиса
){}

onInit(){
this.userServiсe.getUser().subscribe(user => this.currentUser = user);
}
}

Vadya
27.12.2019, 09:47
Получается по сути это как у меня здесь: https://javascript.ru/forum/518052-post3.html (#3)?
То есть если мне нужны данные пользователя в header.component и footer.component, то из каждого компонента будет произведен запрос к серверу?

destus
27.12.2019, 10:19
из каждого компонента будет произведен запрос к серверу?
конкретно в этой реализации UserService`a - да. Вот пример, как будет правильнее https://stackoverflow.com/a/36291681/8230845

Vadya
27.12.2019, 12:20
Вот теперь все встало на свои места. Я почему-то думал, что в угловом как-то можно сделать все в одном месте и после этого не трогать модули компонентов.

Vadya
29.12.2019, 09:57
У меня еще один вопрос появился. Данные пользователя я получил, а в роутер передать через функцию не могу. Вот код с комментарием, где не получается:

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router";
import { Observable } from "rxjs";
import { UserService } from './user.service';
import { Injectable } from '@angular/core';


@Injectable()
export class AuthGuard implements CanActivate {

user: any;

constructor(private userService: UserService, private router: Router) { }

public isAuthenticated(): any {
this.userService.getUser().subscribe(data => {
this.user = data;
if(this.user['login'] !== false) {return true;}
});
}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : Observable<boolean> | boolean{
if(this.isAuthenticated()) { // <- Сюда не проходит true
return true;
} else {
this.router.navigate(['/login']);
}

}

}

sniffysko
29.12.2019, 12:25
Правильно. Не работает. Поэтому есть 2 совета:
1. Вынести функцию isAuthenticated из гуарда в сервис юзеров. Это, так сказать, разделение ответственносте. Гуарды гуардят, а юзерсервисы юзерсервисят.
2. Проставление типов any облегчает жизнь нерадивому разрабу, но следующего по пути дзена просветляют и научает. Ибо, если бы у вас стоял не any, а Observable<User>, то вы бы поняли, что совать его в условный оператор бессмысленно. Функция асинхронная. Тут конечно вопрос: у вас пользователь, которого вы сравниваете в 17 строке где-то хранится локально? Если да, то и асинхрон не нужен. Если вы его читаете с сервера, то нужен. Но тут такая ситуация, ежли вы по каждому перемещению по роутам будете сервер дергать, то это очень не хорошо.
Итог:
1. isAuthenticated -- несем в изер сервис
2. Юзера читаем при инициализации и храним в том же юзер сервисе, либо создаем authService.
3. При перемещении по роуту избавляемся от асинхрона и выполняем обычные синхронные функции.
4. Если асинхронный подход нужен, то функцию isAuthenticated надо переписать под Observable.
5. Типизируем! Ангуляр довольно сложен, а типы помогут вам определиться с тем, что вы ожидаете получить. Если вас разочаровывают ошибки в процессе написания это говорит, что вы что-то не поняли и надо копать в этом направлении.

Vadya
30.12.2019, 11:31
Для меня это пока темный лес. Сохранил на потом, а пока хоть как-то сделать, но чтобы заработало.
Уже вторые сутки методом тыка пытаюсь вернуть логин из функции.

public isAuthenticated(): any {

this.userService.getUser().subscribe(data => (this.user = data));
// Здесь как то надо увидеть this.user.login

}

sniffysko
30.12.2019, 13:37
Для меня это пока темный лес. Сохранил на потом, а пока хоть как-то сделать, но чтобы заработало.

public isAuthenticated(): any {

this.userService.getUser().subscribe(data => (this.user = data));
// Здесь как то надо увидеть this.user.login

}
Вы не ответили зачем вам асинхронный запрос. Асинхрон нужен только если вы лезете на бэк за данными пользователя. Асинхрон это не прерогатива сервисов, а типов запроса. Если пользователь хранится в сервисе, то:
Для Юзер сервиса дописываем функцию:

export class UserService{
isAutentificated: boolean = false;
userInfo: UserInfo

constructor(
http: HttpService
){}

// Синхронная!
isAuthenticated(): boolean {
return this.isAutentificated;
}

// Асинхронная
authUser(data: {login: string, password}){
this.gttp.post('api/url').subscribe(
(data: UserInfo) => {
if(data){
this.userInfo = data;
this.isAutentificated = true;
}
}
);
}
}


authUser вызываем после того, как в форме логина вводим логин и пароль. Из компонента логина.
А теперь ваш код в гуарде:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : Observable<boolean> | boolean{
if(this.userService.isAuthenticated()) { // Сейчас придет. Функция синхронная.
return true;
} else {
this.router.navigate(['/login']);
}
}


Если isAuthenticated все-же должен быть асинхронным, то подход другой.
Что-то типа такого (вы же понимаете, что и код функции isAuthenticated будет через обсерверы и не такой как в примере выше):

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : Observable<boolean> | boolean{
return this.userService.isAuthenticated()
.subscribe( (data: boolean) => {
return data;
});
}


Что-то типа так. Пусть меня поправят более опытные товарищи. Пишу навскидку. Но смысл таков: если функция асинхронная, подписываемся на нее и полученный в подписке рузультат возвращаем. Когды вы выставите тыпы в вашем коде среда разработки подскажет вам какие функции можно вписать.

destus
30.12.2019, 15:12
Это в userService

public isAuthenticated(): Observable<boolean> {
return this.getUser().pipe(
map(data => !!data['login'])
);
}

это в authGuard

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : Observable<boolean>{
return this.userService.isAuthenticated().pipe(
tap((isAuthenticated: boolean) => {
if (!isAuthenticated) {
this.router.navigate(['/login'])
}
})
);
}

Vadya
31.12.2019, 11:30
sniffysko, destus, огромное спасибо за примеры. Буду вникать поглубже, но теперь уже только после нг.
Если честно, то я даже не понимаю пока как оформлять функцию синхронной или наоборот. Жс у меня со скрипом всегда шел и понять его никак не могу. Вообще я делаю простую админку, не для массового использования, с целью познакомится с ангуляром. Это последнее из того что осталось сделать, чтобы был каркас, дальше уже думаю справлюсь самостоятельно. Я так вижу: мне нужена синхронная функция, так как проверка авторизации нужна только при переходе между страницами и при совершении каких-либо действий на сервере. Зачем асинхронная в данном случае не понимаю, но было бы интересно.
Всех фронтовиков с наступающим! И до новых встреч )

Vadya
03.01.2020, 09:56
Не актуально

А можете объяснить (неудобно наглеть, но желательно с примером), почему в этой конструкции не работает this.router.navigate(['/login']);
Там конечно легко накостылировать на ЖС, но хочется правильно все сделать

// this.router.navigate(['/login']); // Тут работает
return this.userService.isAuthenticated().pipe(
tap((isAuthenticated: boolean) => {
if (!isAuthenticated) {
this.router.navigate(['/login']); // А тут нет
} else {
return true;
}
})
);

В общем, как часто бывает, немного затупил - зацикливалось
Хотел сделать так: { path: 'login', component: LoginComponent, canActivate: [AuthGuard] }
Чтобы страницу логина было видно в canActivate и если авторизация есть, то переадресация на главную
return this.userService.isAuthenticated().pipe(
tap((isAuthenticated: boolean) => {
if (!isAuthenticated && route.url.join() != 'login') { // но почему-то это условие не срабатывает (&& route.url.join() != 'login'). Если нет авторизации бросает на страницу логина, а потом с нее же на нее. Это я так понимаю, а вообще в корень отправляет. Без ошибок в консоли
this.router.navigate(['/login']);
} else {
return true;
}