I'm trying to create a dynamic ng2 search.service. REST services are the underlying protocol for searching for data in my org. Here's a user.service that I originally created which searches for users:
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import { User } from '../entities/user';
#Injectable()
export class UserService {
private headers = new Headers({ 'Content-Type': 'application/json' });
private usersUrl = 'http://localhost:4000/users';
private userUrl = 'http://localhost:4000/users/#id';
constructor(private http: Http) { }
getUser(id: number): Promise<User> {
return this.http.get('http://localhost:4000/users/1')
//return this.http.get(this.userUrl.replace('#id', id))
.toPromise()
//.then(this.extractData)
.then(response => response.json())
.catch(this.handleError);
}
getUsers(): Promise<User[]> {
return this.http.get(this.usersUrl)
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
private extractData(response: Response) {
return response.json();
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
Here's my first attempts at a generic search.service:
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import { User } from '../entities/user';
#Injectable()
export class SearchService
{
private _headers = new Headers({ 'Content-Type': 'application/json' });
private _entityName = '';
private _allEntitiesUrl = 'http://localhost:4000/#entityNames';
private _entityByIdUrl = 'http://localhost:4000/#entityNames/#id';
constructor(private http: Http, entityName: string)
{
this._allEntitiesUrl = entityName + 's';
this._entityByIdUrl = entityName + 's';
}
getEntity(id: number): Promise<User>
{
var url = this._entityByIdUrl.replace('#id', id.toString());
return this.http.get(url)
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
getEntities(): Promise<User[]>
{
return this.http.get(this._allEntitiesUrl)
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
private extractData(response: Response)
{
return response.json();
}
private handleError(error: any): Promise<any>
{
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
The search.service is only partially complete right now. Here are some things that I know need to be updated:
The import statement needs to be made generic or somehow accomodate the different types of entities that could potentially be returned from the search.service. For example, the search.service could potentially return accounts, products, etc:
import { User } from '../entities/user';
I need to figure out how to implement dynamic return types for getEntity/getEntities. I've done some work to implement methods to return generic types from C# in the past but not sure how this would be done with ng2/typescript
I'm sure I'm not the first person who has thought about doing this so hopefully others here who are more fluent in ng2/typescript can explain how I could implement this?
The import statement needs to be made generic or somehow accomodate the different types of entities that could potentially be returned from the search.service. For example, the search.service could potentially return accounts, products, etc:
In TypeScript you can create an interface and then have the different items implement that interface. Your search service can then return the type of you interface. e.g.
class User implements Entity
and the service would then be
getEntities(): Promise<Entity[]>
{
return this.http.get(this._allEntitiesUrl)
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
I need to figure out how to implement dynamic return types for getEntity/getEntities. I've done some work to implement methods to return generic types from C# in the past but not sure how this would be done with ng2/typescript
The other option is as you mention here is to use generics e.g.
getEntities<T>(): Promise<T[]>
{
return this.http.get(this._allEntitiesUrl)
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
which would be called as
getEntities<User>()
Related
I have a little issue from an Angular app to get a code from my own server.
I´ve built up a little Spotify App for learning more about Angular 10 and have a little backend that I only use for get the Bearer code to call the Spotify API, but the fact is that in my Angular front I can´t save the code.
Tis is my service call code to the back:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
#Injectable({
providedIn: 'root'
})
export class SpotifyService {
token: any;
constructor(private http: HttpClient) {
console.log('Spotify service ready');
this.getAccesToken().subscribe(data => this.token = data['access_token']);
}
getAccesToken(){
return this.http.get(environment.server + `${environment.client_id}/${environment.client_secret}`)
.pipe(
map(res => res)
);
}
getQuery(query: any){
const headers = new HttpHeaders({
'Authorization': `Bearer ${this.token}`
});
const url = `https://api.spotify.com/v1/${query}`;
return this.http.get(url, {headers});
}
I´ve checked the recibed data and I get the request perfectly, but can´t save the data of the suscriber into a variable.
Thanks in advance!
Assuming your component code looks like below, you can make some adjustments and try it.
export class SomeComponent implements OnInit {
spotifyData;
user;
token;
constructor(private spotifyService: SpotifyService, private http: HttpClient) { }
ngOnInit() {
this.user = JSON.parse(localStorage.getItem('user'));
console.log(this.user);
this.token = this.user.token;
this.getSpotifyData();
}
getSpotifyData() {
this.spotifyService.getQuery(this.user, { headers: new HttpHeaders().set('Authorization', 'Bearer ' + this.token) }).subscribe(res => {
console.log(res);
if (res) {
spotifyData = res;
} else {
spotifyData = []
}
});
}
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 am new to angularJs2. I have created following service:
import { Injectable, OnInit } from '#angular/core';
import { customType } from '../models/currentJobs';
import { Headers, Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class JobService implements OnInit {
constructor(private http: Http) { }
ngOnInit(): void {
this.getCurrentJobs();
}
private headers: Headers = new Headers({ 'Content-Type': 'application/json' });
private ordersUrl: string = 'http://localhost:35032/api/order/';
public orders: customType[];
getCurrentJobs(): Promise<customType[]> {
var jobs = this.http.get(this.ordersUrl)
.toPromise()
.then(response => {
this.orders = response.json() as customType[];
})
.catch(this.handleError);
return jobs;//this line throws error
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
Following are my Typescript compile configuration of Vs2017
When I compile the code using visual studio 2017 I get following error
**TS2322 Build:Type 'Promise<void>' is not assignable to type 'Promise<customType[]>'.**
Help me to fix this error.
You are not returning anything inside your then which makes jobs be of type Promise<void>. Return the array inside then:
getCurrentJobs(): Promise<customType[]> {
var jobs = this.http.get(this.ordersUrl)
.toPromise()
.then(response => {
this.orders = response.json() as customType[];
return this.orders;
})
.catch(this.handleError);
return jobs;
}
See the chaining behaviour of promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining
I've added the 'catch' operator, and swapped your interface import for an interface definition in the code (as I don't obviously have access to yours). I can't really test this without the rest of your project code, but it looks right to me and doesn't throw any errors in VSC.
import { Injectable, OnInit } from '#angular/core';
import { Headers, Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/catch';
export interface customType{
}
#Injectable()
export class JobService implements OnInit {
constructor(private http: Http) { }
private jobs: Promise<customType[]>;
ngOnInit(): void {
this.jobs = this.getCurrentJobs();
}
private headers: Headers = new Headers({ 'Content-Type': 'application/json' });
private ordersUrl: string = 'http://localhost:35032/api/order/';
public orders: customType[];
getCurrentJobs(): Promise<customType[]> {
return this.http.get(this.ordersUrl)
.map(response => response.json())
.catch(this.handleError)
.toPromise();
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
I was reading angular2 code and I found some confusing syntax for me.
The full code is below.(from https://github.com/domfarolino/angular2-login-seed)
import { Injectable, Inject } from '#angular/core';
//import { Control } from '#angular/common';
import { Http, Response, Headers, RequestOptions, RequestOptionsArgs } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
/**
* Import interfaces that service depends on
*/
import { User } from './user';
#Injectable()
export class UserService {
constructor (private http: Http, #Inject('apiBase') private _apiBase: string) {
}
private _loginApi = this._apiBase + '/authorize/local';
private _logoutApi = this._apiBase + '/logout';
private _authenticatedApi = this._apiBase + '/api/authenticated';
private _registerApi = this._apiBase + '/api/users/register';
private _userExistsApi = this._apiBase + '/api/users/exists';
login(user) {
let body = JSON.stringify(user);
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post(this._loginApi, body, <RequestOptionsArgs> {headers: headers, withCredentials: true})
.map((res: Response) => res)
.catch(this.handleError);
}
authenticated() {
return this.http.get(this._authenticatedApi, <RequestOptionsArgs> {withCredentials: true})
.map((res: Response) => res.json())
.catch(this.handleError);
}
logout() {
return this.http.get(this._logoutApi, <RequestOptionsArgs> {withCredentials: true})
.map((res: Response) => res.json())
.catch(this.handleError);
}
register(user) {
let body = JSON.stringify(user);
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post(this._registerApi, body, <RequestOptionsArgs> {headers: headers, withCredentials: true})
.map((res: Response) => res)
.catch(this.handleError);
}
getUsers() {
return this.http.get(this._apiBase + "/api/users?limit=5&desc=true", <RequestOptionsArgs> {withCredentials: true})
.map((res: Response) => res.json())
.catch(this.handleError);
}
getMe() {
return this.http.get(this._apiBase + '/api/users/me/', <RequestOptionsArgs> {withCredentials: true})
.map((res: Response) => res.json().me)
.catch(this.handleError);
}
private handleError (error: Response) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
return Observable.throw(error || "Server Error");
}
}
and I can't find out what below code as a parameter means.
<RequestOptionsArgs> {headers: headers, withCredentials: true}
Is there anyone can give me an idea?
The syntax <Type> variable is a cast. See Type Assertions on the documentation
Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does. Usually this will happen when you know the type of some entity could be more specific than its current type.
Type assertions are a way to tell the compiler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.
It shows two examples, it is possible to cast using:
<string> somevar
and also with
somevar as string
The two samples are equivalent. Using one over the other is mostly a choice of preference; however, when using TypeScript with JSX, only as-style assertions are allowed.
class is work as a datatype here . ..
Ex .
Class student {
Name : String ,
RollNo: Number
}
now if i declare a variable
Public Students:Obect
Now i can push in students with a object having name and roll no.
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.