I am trying to set value form localStorage() into my variable and use that variable in functions but the variable comes as undefined
code
export class DashboardService {
public token: any;
constructor(
private env: EnvService,
private http: HttpClient,
private storage: NativeStorage
) {
this.storage.getItem('token').then((token) => {
this.token = token.access_token;
console.log('storage token ', token.access_token); // see screenshot below
}).catch(error => console.error(error));
}
getDashboard() {
const headers = new HttpHeaders({
Accept: 'application/json, text/plain',
'Content-Type': 'application/json',
Authorization: this.token
});
console.log('my token ', this.token); // see screenshot below
console.log('my headers ', headers); // see screenshot below
return this.http.get(this.env.Dashboard + '/dashboard', {headers})
.pipe(
tap(data => {
console.log(data);
})
);
}
}
Screenshot
another issue is my request header sends 2 values in header instead of 3 (not sure if it's because of token being undefined or not)
it supposed to send Accept,Content-Type,Authorization instead only sent Accept and Content-Type
Any idea?
Update
this is my component that I call my service above
export class DashboardPage implements OnInit {
schools: any = [];
constructor(
private authService: AuthenticationService,
private menu: MenuController,
private dashboardService: DashboardService
) {
this.getSchool();
}
ngOnInit() {
this.menu.enable(true);
}
logout() {
this.authService.logout();
}
getSchool() {
this.dashboardService.getDashboard().subscribe((res) => {
this.schools = res;
});
}
}
SOLVED
I'd to change both services and component file to get my data, here is my final code
services
getDashboard(): Observable<any> {
const httpOptions = {
headers: new HttpHeaders({
Accept: 'application/json, text/plain',
'Content-Type': 'application/json',
Authorization: this.token.access_token
})
};
return this.http.get(`${this.env.Dashboard}` + '/dashboard', httpOptions).pipe(
map(data => data)
);
}
component
async getSchool() {
this.loading = await this.loadingController.create({
message: 'Please wait...',
spinner: 'dots',
duration: 3000
});
await this.loading.present();
this.dashboardService.getDashboard().subscribe((res) => {
for (const school of res.data) {
this.schools.push(school);
}
this.hideLoading();
});
}
private hideLoading() {
this.loading.dismiss();
}
now my page loads include my data with proper loading screen.
hope it help others as well.
Related
I have my Configuration class:
interface ConfigObject {
apiUrl: string;
identityPoolId: string;
identityPoolRegion: string;
region: string;
userPoolId: string;
userPoolWebClientId: string;
}
class Configuration {
private static _instance: ConfigObject;
public static get Instance() {
return (
this._instance ||
this.getConfig<ConfigObject>().then((config) => {
this._instance = config;
return config;
})
);
}
private static async getConfig<TConfig>(): Promise<TConfig> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data;
}
}
export default Configuration.Instance;
and I want to access it's values in my service:
export default class APIService {
static baseURL: string = `${Config.apiUrl}/mocked`;
Yet at the time of accessing Config.apiUrl is undefined
How can I make sure that the getConfig fetch gets executed and the actual object is returned instead?
You can't make an asynchronous process synchronous. But you can make your module wait to load until you've read that JSON file, by using top-level await, which is now broadly supported in browsers and by bundlers.
async function getConfigData(): Promise<ConfigObject> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data: ConfigObject = await response.json();
return data;
}
const data = await getConfigData(); // *** Module load waits here
class Configuration {
private static _instance: ConfigObject;
public static get Instance() {
if (!this._instance) {
this._instance = data;
}
return this._instance;
}
}
export default Configuration.Instance;
That said, there doesn't seem to be any purpose to the Configuration class, just export the data directly:
async function getConfigData(): Promise<ConfigObject> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data;
}
const data: ConfigObject = await getConfigData(); // *** Module load waits here
export default data;
Side note: Since objects are mutable by default, any module that imports the configuration data can modify it (for instance, if there's a data.example property, by doing data.example = "some value"). Maybe you want it to be mutable, in which case don't do anything, but if you don't you might use Object.freeze to make everything about the object read-only:
// ...
const data: ConfigObject = await getConfigData();
Object.freeze(data); // *** Freezes the object
export default data;
I am using angular 8 to make a SPA.
Firebase is used to authenticate the user both in the client as well as in the backend, so I need to send the jwt token in http.get request to the backend to authenticate the user.
Backend is an API made with django 2.2 and django rest framework which sends the api to be consumed in client application.
auth.service.ts
#Injectable({
providedIn: 'root'
})
export class AuthService {
userData: any; // Save logged in user data
public userToken: string;
constructor(
public afs: AngularFirestore, // Inject Firestore service
public afAuth: AngularFireAuth, // Inject Firebase auth service
public router: Router,
public ngZone: NgZone // NgZone service to remove outside scope warning
) {
/* Saving user data in localstorage when
logged in and setting up null when logged out */
this.afAuth.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
GetToken(): string {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
// this shows the userToken
console.log('token inside getToken method ' + this.userToken);
});
}
});
// this shows userToken as undefined
console.log('before return ' + this.userToken);
return this.userToken;
}
}
api.service.ts
#Injectable({
providedIn: 'root'
})
export class ApiService {
private url = environment.baseUrl;
token: any;
data: any;
constructor(
private http: HttpClient,
private authService: AuthService,
) {}
// old method to get emloyees data
// public getEmployees(): Observable<Employee[]> {
// return this.http.get<Employee[]>(`${this.url}/employee/`);
// }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + this.authService.GetToken()
}),
};
public getEmployees(): Observable<Employee[]> {
// token is undefined here
console.log('token inside getEmployees method ' + this.token);
return this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions);
}
}
The backend is working perfectly which I verified by adding the token in the httpOptions, like so:
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + 'ey.....'
}),
};
But when I try doing the same as given in code it doesn't work.
The user token remains undefined.
Peter's answer has the crux of it: getIdToken() is asynchronous, so by the time your return this.userToken; runs, the this.userToken = idToken; hasn't run yet. You should be able to see this from the output of your console.log statements.
For more on this see How to return value from an asynchronous callback function? I highly recommend studying this answer for a while, as this asynchronous behavior is incredibly common when dealing with web APIs.
The fix for your code is to return a Promise, instead of trying to return the value:
GetToken(): Promise<string> {
return new Promise((resolve, reject) => {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
resolve(idToken);
});
}
});
})
}
In words: GetToken returns a promise that resolves once an ID token is available. If you know the user is already signed in when you call this function, you can simplify it to:
GetToken(): string {
const user = firebase.authentication().currentUser;
return user.getIdToken()
}
The difference is that the second function does not wait for the user to be signed in, so will fail if there is no signed in user.
You then use either of the above functions like this in getEmployees:
public getEmployees(): Observable<Employee[]> {
return new Promise((resolve, reject) =>
this.authService.GetToken().then((idToken) => {
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + idToken
}),
};
this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions)
.then(resolve).catch(reject);
})
})
}
It is undefined here console.log('before return ' + this.userToken); because getIdToken() returns a Promise which means it is asynchronous, therefore the only way to access the userToken is inside the then() method.
I have configured a POST request in my react app like this:
await fetch('http://localhost:8080/api/room', { credentials: 'include' }, {
'method': 'POST',
'headers': {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true
},
'body': JSON.stringify(somedata),
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log('Error', error);
});
In my spring-boot app I have a controller:
#RequestMapping(value = "/room", produces = "application/json", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseEntity<Room> createRoom(#Valid #RequestBody Room room) throws URISyntaxException {
Room result = roomService.saveOrUpdate(room);
return ResponseEntity.created(new URI("/api/room/" + result.getRoomClientId()))
.body(result);
}
In my browser console I get this response:
Response {type: "cors", url: "http://localhost:8080/api/room",
redirected: false, status: 400, ok: false, …}
In my spring-boot app console I get this error:
2019-04-08 16:31:42.742 WARN 86177 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<com.sbpoc.app.db.entity.Room> com.sbpoc.app.db.controller.RoomController.createRoom(com.sbpoc.app.db.entity.Room) throws java.net.URISyntaxException]
Error says, request body is missing?
Update
import uniqid from 'uniqid';
const someData = {
roomNumber: uniqid(),
}
Room Entity:
#Entity
public class Room implements Serializable {
private static final long serialVersionUID = 3433823138738252949L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String roomNumber;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoomNumber() {
return roomNumber;
}
public void setRoomNumber(String roomNumber) {
this.roomNumber = roomNumber;
}
}
I needed to include the credentials option into one object as such:
fetch('http://localhost:8080/api/room',
{
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(data)
}
)
.then(res => console.log(res));
I also made use of #PostMapping instead in the springboot app:
#PostMapping("/room")
public ResponseEntity<Room> createRoom(#Valid #RequestBody Room room) throws URISyntaxException {
log.info("Request to create room: {}", room);
Room result = roomService.saveOrUpdate(room);
return ResponseEntity.created(new URI("/api/room/" + result. getRoomClientId()))
.body(result);
}
Can anyone help what I am doing incorrect, anything missing?
I am getting undefined for --'this.ack.length'
this._activeChannelService.requestChannelChange(this.selectedchannel.channelName)
.subscribe(
ack => {
this.ack= ack;
console.log(" test: ", this.ack.length);
},
err => {
console.log(err);
});enter code here
ack is of time
ack:Iack[];
Iack has two field of type string. result and message
I need to iterate through array of Iack[] to get the result and message
if message=success then call the another service
service
requestChannelChange (name: string): Observable<Iack[]> {
alert('in servicerequestChannelChange');
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
let postchannelname = "channelName=" + name;
let requestt = new IRequest(name);
console.log(JSON.stringify(requestt));
return this._http.post(this._activateChangeUrl, JSON.stringify(requestt),{ headers: headers })
//.map(this.extractData)
.map((res:Response) => res.json() as Iack[])
.do(data => console.log("All: " + JSON.stringify(data)))
.catch(this.handleError);
}
You can use observable in your TS service:
import { Injectable } from '#angular/core';
import { IPost } from './IPost';
import { Http, Response, RequestOptions, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
#Injectable()
export class PostServices {
private _webApiBaseUrl = "http://localhost:62806/v1/Posts"
private _http : Http;
constructor(http : Http){
this._http = http;
}
getAll(): Observable<IPost[]> {
return this._http.get(this._webApiBaseUrl + '/all', this.getHeaders())
.map((response: Response) => response.json())
.do(data => console.log(`All Data: \n ${ JSON.stringify(data) }`))
.catch(this.handleError);
}
private handleError(error: Response){
console.error(error);
return Observable.throw(error.json().error || 'Server Error');
}
private getHeaders()
{
let headers = new Headers();
headers.append("Authorization", "");
headers.append("Content-Type", "application/json");
return new RequestOptions({ headers: headers });
}
}
Usage in your TS class:
import { Component, OnInit } from '#angular/core';
import { IPost } from './IPost';
import { PostServices } from './posts.service';
#Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
posts: IPost[];
errorMessage: string;
private _postService: PostServices;
constructor(postService: PostServices) {
this._postService = postService;
}
ngOnInit() {
this._postService.getAll()
.subscribe(
data => {this.posts = data; console.log("data.length: " + data.length)}, // here
error => this.errorMessage = <any>error
);
}
}
enter code here is executed before this.ack= ack; is executed
This is a function
ack => {
this.ack= ack;
console.log(" test: ", this.ack.length);
}
that you pass to subscribe(...) and the observable calls it when the data arrives which can take a looong time when it's a call to a server.
enter code here is executed immediately.
You'll have to check for success within the service subscription. An observable is an asynchronous call, so any calls you want to make regarding the data in that async call must be made within it to remain a safe call.
So, make your seconds service call, within the subscription.
I'm using Angular2 with Typescript. Say I have a dummy login component and an authentication service to do token authentication. I'm going to set the authenticated variable in one of the map function as soon as I get the token from backend server.
The question is that I can't access the instance variable inside the chaining function. The this inside the chaining function is actually the subscriber of this observable. I know this's a scope issue but can't figure it out.
export class AuthenticationService {
authenticated:boolean = false; //this is the variable I want to access
constructor(public http: Http) {
this.authenticated = !!sessionStorage.getItem('auth_token');
}
login(username, password) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http
.post(
'http://localhost:8080/token-auth',
JSON.stringify({ username, password }),
{ headers }
)
.map(res => res.json())
.map((res) => {
if (res) {
this.authenticated = true; //this is where I want to access the instance variable
sessionStorage.setItem('auth_token', res.token);
}
return res;
});
}
The dummy-login component where the above login() method is called is like this:
export class DummyLoginComponent {
constructor(private auth: AuthenticationService, private router: Router) {
}
onSubmit(username, password) {
this.auth.login(username, password).subscribe((result) => {
if (result) {
this.router.navigate(['Dashboard']);
}
})
}
}
You can just subscribe to the observable instead of mapping it
login(username, password) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let res = this.http
.post(
'http://localhost:8080/token-auth',
JSON.stringify({ username, password }),
{ headers }
)
.map(res => res.json());
res.subscribe(
(res) => {
this.authenticated = true; //this is where I want to access the instance variable
sessionStorage.setItem('auth_token', res.token);
});
return res;
}