I want to:
Obtain param from url
Then send http request to API with that param
If I done it undirectly via browser url box it works great but when I use router.navigate(["/users", "userName"]); it work to slow.
ngOnInit(): void {
this.sub = this._route.params.subscribe(
params => {
this.userName = params['name'];
});
this._usersService.getUser(this.userName)
.subscribe(user => this.user = user,
error => this.errorMessage = <any>error);
}
This is code inside UserComponent.
And the this._userService is sometimes done before this.sub is.
How to fix it?
You can use switchMap in order to switch from params observable to the observable provided by getUser().
// an observable of users
this.user$ = this._route.params
// switch to the observable provided by getUser() which queries the resource
.switchMap(params => this.getUser(params['name']))
// log error
.catch(error => {
console.log('Error occurred - ' + error.message);
// rethrow
return Observable.throw(error);
})
// share a single subscription among the subscribers so that getUser() will not be called for every subscriber
.share();
Then you can project this observable into another one that is useful for your case, for example:
// an observable of user details to be displayed on template
this.userInfo$ = this.user$.map(user => user.username + ' - ' + user.date.toISOString());
And finally you can use the observable in your template:
The user is: {{ userInfo$ | async }}
See the working sample in this PLUNKER
Full code for the UserComponent:
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/of';
#Component({
selector: 'app-user',
template: `The user is: {{ userInfo$ | async }}`
})
export class UserComponent implements OnInit {
public user$: Observable<User>;
public userInfo$: Observable<string>;
constructor(private _route: ActivatedRoute) {
}
ngOnInit() {
// an observable of users
this.user$ = this._route.params
// switch to the observable provided by getUser() which queries the resource
.switchMap(params => this.getUser(params['name']))
// log error
.catch(error => {
console.log('Error occurred - ' + error.message);
// rethrow
return Observable.throw(error);
})
// share a single subscription among the subscribers so that getUser() will not be called for every subscriber
.share();
// an observable of user details to be displayed on template
this.userInfo$ = this.user$.map(user => user.username + ' - ' + user.date.toISOString());
}
private getUser(username: string): Observable<User> {
// occasionally create error
if (username === 'error') {
return Observable.throw(new Error('Occasional error'));
}
// create a user after 300 ms
return Observable.of({username: username, date: new Date()}).delay(300);
}
}
export interface User {
username: string;
date: Date;
}
Related
Following Google's official Angular 4.3.2 doc here, I was able to do a simple get request from a local json file. I wanted to practice hitting a real endpoint from JSON placeholder site, but I'm having trouble figuring out what to put in the .subscribe() operator. I made an IUser interface to capture the fields of the payload, but the line with .subscribe(data => {this.users = data}) throws the error Type 'Object' is not assignable to type 'IUser[]'. What's the proper way to handle this? Seems pretty basic but I'm a noob.
My code is below:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { IUsers } from './users';
#Component({
selector: 'pm-http',
templateUrl: './http.component.html',
styleUrls: ['./http.component.css']
})
export class HttpComponent implements OnInit {
productUrl = 'https://jsonplaceholder.typicode.com/users';
users: IUsers[];
constructor(private _http: HttpClient) { }
ngOnInit(): void {
this._http.get(this.productUrl).subscribe(data => {this.users = data});
}
}
You actually have a few options here, but use generics to cast it to the type you're expecting.
// Notice the Generic of IUsers[] casting the Type for resulting "data"
this.http.get<IUsers[]>(this.productUrl).subscribe(data => ...
// or in the subscribe
.subscribe((data: IUsers[]) => ...
Also I'd recommend using async pipes in your template that auto subscribe / unsubscribe, especially if you don't need any fancy logic, and you're just mapping the value.
users: Observable<IUsers[]>; // different type now
this.users = this.http.get<IUsers[]>(this.productUrl);
// template:
*ngFor="let user of users | async"
I'm on the Angular doc team and one open todo item is to change these docs to show the "best practice" way to access Http ... which is through a service.
Here is an example:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import { IProduct } from './product';
#Injectable()
export class ProductService {
private _productUrl = './api/products/products.json';
constructor(private _http: HttpClient) { }
getProducts(): Observable<IProduct[]> {
return this._http.get<IProduct[]>(this._productUrl)
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
private handleError(err: HttpErrorResponse) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
let errorMessage = '';
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
errorMessage = `An error occurred: ${err.error.message}`;
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
}
console.error(errorMessage);
return Observable.throw(errorMessage);
}
}
The component would then look like this:
ngOnInit(): void {
this._productService.getProducts()
.subscribe(products => this.products = products,
error => this.errorMessage = <any>error);
}
I am integrating Nestjs with firebase admin, the controller is not updating the view. With the service there is not problem, update in real time.
Someone will have some suggestion, what is my wrong in the code?
The Service that inject the controller is:
import { Injectable } from '#nestjs/common';
import * as admin from 'firebase-admin';
export interface Customer {
direction: string,
codLegal: string,
phone: string,
name: string
}
export interface CustomerId extends Customer{
id: string;
}
#Injectable()
export class CustomerService {
constructor() {}
findCustomers(): Promise<any>{
return new Promise((resolve, reject) => {
admin.firestore().collection('/data/LYvBew5FDpjLqcQjA2Ra/info')
.onSnapshot((querySnapshot) => {
const promises: any = [];
querySnapshot.forEach((doc: any) => {
promises.push({
id: doc.id,
data: doc.data() as Customer,
});
});
console.log(promises);
resolve(promises);
})
});
}
}
**The basic controller is: **
import { Controller, Get } from '#nestjs/common';
import { CustomerService } from './services/customer.service';
#Controller('customers') export class CustomerController {
constructor(private readonly customerService: CustomerService) {
}
#Get()
async findAll() {
try {
return await this.customerService.findCustomers();
}catch(err) {
console.log(err);
}
}
}
Talking in terms of HTTP, the controller will not update the view. The view is rendered once you call the findAll route and send to the client.
If you want to show updates to the view in realtime, you should include firebase into your frontend.
I am using wp-json-api plugin at my WordPress endpoint and this requires me to send a nonce token along with user information to register a new user. To get a nonce token I need to make a GET request first and then after getting the token I can POST user data for registration.
Below is the function I have created to get the nonce token:-
public getRegistrationNonce () {
let nonceurl = this.config.wordpressApiBaseUrl + '/api/get_nonce/?controller=user&method=register';
return this.http.get(nonceurl)
.map(result => {
return result.json();
});
}
I am not sure how to return the token data to my registration function that POSTs the data required for registration.
The registration function is given below:-
public register(data) {
let token = this.getRegistrationNonce();
data.push({'nonce' :token});
let registrationurl = this.config.wordpressApiBaseUrl + '/api/user/register';
return this.http.post(registrationurl, data)
.map(result => {
return result.json();
});
}
Please show me how I can achieve this.
In my understanding you have to create a service for these function. You can all it Auth.
Before you do everything, check you have installed https://wordpress.org/plugins/json-api/ because the plugin you have mentioned depends on it. And then you have enable 'User' controller in Wordpress -> Setting -> JSON API.
Then let's come to your Ionic application Auth service
It might look something like this
import { Component } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class Auth {
token: any;
status: any;
constructor(public http: Http) {
}
}
Then add your functions.
import { Component } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class Auth {
token: any;
status: any;
constructor(public http: Http) {
}
getRegistrationNonce() {
let nonceurl = this.config.wordpressApiBaseUrl + '/api/get_nonce/?controller=user&method=register';
this.http.get(nonceurl)
.map(result =>
result.json();
)
.subscribe(data => {
this.token = data;
/** I have checked the returned data and it will be like
{
"status": "ok",
"controller": "user",
"method": "register",
"nonce": "531604a95d"
}
**/
});
}
register(data) {
this.getRegistrationNonce();
let registrationurl = this.config.wordpressApiBaseUrl + '/api/user/register';
this.http.get(registrationurl + '/?username=' + YOUR USERNAME + '&email=' + YOUR EMAIL + '&nonce=' + this.token.nonce + '&display_name='+YOURNAME+'¬ify=both')
//If your site doesn't have SSL/HTTPS you will have to add another parameter '&insecure=cool' at the end of the string
.map(result => {
result.json();
})
.subscribe(data => {
this.status = data;
/*
This will be in a format like,
{
"status": "ok",
"cookie": "a big string",
"user_id": 5
}
*/
});
}
}
My server is developed on Node.js. It is a long-polling service (e.g. chat): it gives the following API:
join() //listening for new events
align(fromId) //retrieving events from an id
send(data) //creating an event
The long-polling is implemented by the join(): it sends a request and the server answers when there is a new event.
Front end with Ionic2
There are 2 pages: Page1 and Page2. Where Page2 is the viewer of my events, where the long-polling communication is running.
So I start from Page1 and then I push() the second page Page2. Until now everything works fine; but if I pop() the Page2 and then push() again the Page2 then I can see that there is still running the join() of the previous instance of my Page2. This behaviour creates duplicated join(): if I push/pop Page2 many times I will have many long-polling communication with the server.
So I'm trying to find a way to kill the join() instance, which is a HTTP.get request, when leaving the page.
Let's see now my code.
This is the provider of my Ionic2 in charge of the communication with the server
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class MyProvider {
...
constructor(private http: Http) {
this.token_access = null;
this.token_room = null;
}
...
join(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
return Observable.create(observer =>{
this.http.get('/localhost/chat/'+this.room,{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
send(message){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
headers.append('x-chat-token',this.getRoomToken());
return Observable.create(observer =>{
this.http.post('/localhost/chat/'+this.room+'/send', JSON.stringify({
event: message
}),{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
align(from){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
headers.append('x-chat-token',this.getRoomToken());
return Observable.create(observer =>{
this.http.post('/localhost/chat/'+this.room+'/align', JSON.stringify({
fromId: from
}),{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
}
The Page1 just push the Page2 with a button that calls the the following code (page1.ts):
...
export class Page1 {
...
constructor(public navCtrl: NavController, public myProviderService: MyProvider) {
}
.....
toPage2(){
this.navCtrl.push(Page2);
}
And my Page2 is implemented by the following code:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { MyProvider } from '../../providers/myprovider';
import { Event } from '../../components/event';
#Component({
selector: 'page-chat',
templateUrl: 'chat.html'
})
export class ChatPage {
eventsList: Array<Event>;
message: any;
last_event: any;
msg: any;
constructor(public navCtrl: NavController, public myProviderService: MyProvider) {
this.last_event = -1;
this.join();
this.eventsList= new Array();
}
join(){
this.myProviderService.join().subscribe(
(data)=>{
if(data.success){
this.last_event = this.last_event + 1;
if(this.last_event == data.event.id){
//up to now all events are correctly received
this.eventsList.push(data.event);
}else{
//some events are missing
this.last_event = this.last_event - 1;
this.align();
}
this.join();
}else{
this.message=data.message;
//TBD sleep....
//this.join();
}
},
(err) => {
this.message="Connectivity with server Lost...";
//TBD sleep....
//this.join();
});
}
align(){
this.myProviderService.align(this.last_event + 1).subscribe((data)=>{
if(data.success){
for (var i=0;i<data.events.length;i++) {
this.eventsList.push(new Event(data.events[i].id,data.events[i].data,data.events[i].user));
this.last_event = this.last_event + 1;
};
}else{
this.message=data.message;
}
},
(err) => {
this.message="Failure receiving messages";
});
}
send(): void{
this.myProviderService.send(this.msg).subscribe((data)=>{
if(data.success){
this.msg='';
}else this.message=data.message;
},
(err) => {
this.message="Error while authenticating";
})
}
ionViewDidLoad() {
}
ionViewDidEnter() {
}
}
So coming back to my question:
How can I kill the join() (kill the HTTP.get request) instance of my Page2 when this is not used, in order to prevent duplicated join()?
I think that if you have a provider that is globally added to Providers section of your app (meaning that it can act as a Singleton service), then you can use the following:
Every time Page 2 calls the join() method of your provider check a hasAlreadyJoined boolean variable in your provider.
This variable is set to true every time the join() method is called.
If the join() has not been called, call it and update the variable accordingly.
So, even though every time Page 2 calls the join() method of MyProvider, this method does the actual http request only if hasAlreadyJoined is false.
For you to be sure that every time a MyProvider instance is initiated it's variables are "static", the provider should be declared at the global Providers section of your app module file, not at the page's providers section.
New to Angular 2, still trying to get my head around certain things. Where I am stuck is I have login service and login component. I send a login request from the login component to the login service to post the username and password to a login API. If successful it posts the token to the localstorage. Where I am stuck is after the token is sent storage I want to return a boolean response back to the login component. Based on the boolean response it will perform execute a function in the component.
I can do everything until I get the response. I don't know how to handle a response back to the login component. Appreciate if someone could point me in the right direction. My code as follows:
LOGIN SERVICE
import { Injectable } from '#angular/core';
import { Token } from './login';
import { APIDOMAIN } from '../../../shared/api';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class LoginService {
url: string = APIDOMAIN;
constructor(private http: Http) { }
login(username: string, password: string) {
console.log('Login API');
let headers = new Headers();
let data = null;
headers.append("Authorization", "Basic " + btoa(username + ":" + password));
headers.append("Content-Type", "application/x-www-form-urlencoded");
this.http.post(this.url+ '/login', data, {headers: headers})
.map(res => res.json())
.subscribe(
token => { console.log(token); localStorage.setItem('id_token',token.token); },
err => { console.log(err);},
() => console.log('Request Complete')
);
}
logout(): void {
localStorage.removeItem('id_token');
}
}
LOGIN COMPONENT
import { Component, OnInit } from '#angular/core';
import { LoginService } from './shared/login.service';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
username: string;
password: string;
constructor(private loginService: LoginService) { }
ngOnInit() {}
login(): void {
this.loginService.login(this.username,this.password)
// PEFORM SOME FUNCTION BASED BOOLEAN RESPONSE
}
}
Here's one solution:
export class LoginService {
status: EventEmitter<boolean> = new EventEmitter();
login(username: string, password: string) {
this.http.post(...)
.map(res => res.json())
.subscribe(token => {
console.log(token);
localStorage.setItem('id_token',token.token);
this.status.emit(true);
});
logout() {
localStorage.removeItem('id_token');
this.status.emit(false);
}
}
export class LoginComponent implements OnInit {
constructor(private loginService: LoginService) { }
ngOnInit() {
this.loginService.status.subscribe(console.info);
}
}