First is that I have a parent component that will show the dialog in initialization. In the dialog the user will provide input and then validated .
I want to call ngOnit again to open the dialog if the response from this.dialogRef.close(res) value is false.
Can we detect that on ngOnchanges? Thanks.
#dialog-component snippet
validateInput() {
console
this.service.validate(key).subscribe(
(res) => {
this.dialogRef.close(res);
},
(err) => {
AppUtils.errorHandler(err, this._notificationService, this.dialog);
}
);
}
#parent component
ngOnInit(): void {
this._router.paramMap.subscribe((paramMap) => {
if (paramMap.get('authkey')) {
const dialogRef = this.dialog.open(DialogComponent, {
panelClass: 'document-management-rename-dialog',
width: '400px',
data: {
key: paramMap.get('authkey').replace('authkey:', '')
}
});
dialogRef.afterClosed().subscribe((result) => {
if (result.isSuccess) {
this.showPage = true;
}
});
}
});
}
ngOnChanges(changes: SimpleChanges) {
console.log('changes' , changes)
}
to re-open the dialog you can use a Subject and each time this Subject emits a value open dialog. Remember to unsubscribe from observables a working example is here: https://stackblitz.com/edit/angular-ivy-4zitic?file=src/app/app.component.ts
private handleDialogResult = new Subject();
public ngOnInit(): void {
this.handleDialogResultSub = this.handleDialogResult
.asObservable()
.subscribe(() => {
this.openDialog();
});
this._router.paramMap.subscribe((paramMap) => {
if (paramMap.get('authkey')) {
this.openDialog();
}
});
}
public openDialog(): void {
const dialogRef = this.dialog.open(DialogComponent, {
panelClass: 'document-management-rename-dialog',
width: '400px',
data: {
key: paramMap.get('authkey').replace('authkey:', '')
}
});
dialogRef.afterClosed().subscribe((result) => {
if (result.isSuccess) {
this.showPage = true;
return;
}
this.handleDialogResult.next(true);
});
}
Related
I wanted to retrieve an information from backend if some email address from input already exists. Based on this information I'm calling a function that make a post that inserts user into database. The problem is that user is inserted only after second click on my SignUp button (function registerUser is called on this button).
Component stuff:
registerUser(form: NgForm) {
let date: Date = new Date();
this.newUser.registrationDate = date;
this.checkEmailStatus(); //IMPLEMENTATION BELOW
if (this.signupForm.valid === true && this.emailStatus) {
this.portfolioAppService.registerUser(this.newUser).subscribe((data) => {
this.clearFields();
this.navigateToLogin();
},
error => console.error(error)
);
}
}
checkEmailStatus() {
this.portfolioAppService.checkEmailStatus(this.newUser.email).subscribe((data: string) => {
if (data != "") {
this.emailStatus = true;
}
else this.emailStatus = false;
},
error => console.error(error)
);
}
Here is my service:
checkEmailStatus(email: string): Observable<string> {
return this.http.get<string>(`/api/Users/CheckEmailStatus_${email}`, this.httpOptions);
}
Here is backend:
[HttpGet]
[Route("~/api/Users/CheckEmailStatus_{email}")]
public string CheckEmailStatus(string email)
{
try
{
User user = _context.Users.Where(u => u.Email == email).FirstOrDefault();
if (user != null)
{
return user.Email;
}
else
{
return "";
}
}
catch (Exception e)
{
throw new Exception("Error!");
}
}
Call to this.portfolioAppService.checkEmailStatus() is asynchronous. So when you check if (this.signupForm.valid === true && this.emailStatus) after the this.checkEmailStatus() call, the variable this.emailStatus is still undefined. To fix it, you could return an observable from the checkEmailStatus() in the component. Try the following
Component
registerUser(form: NgForm) {
let date: Date = new Date();
this.newUser.registrationDate = date;
this.checkEmailStatus().pipe(take(1)).subscribe(status => {
if (this.signupForm.valid === true && status) { // <-- check the status of email address
this.portfolioAppService.registerUser(this.newUser).subscribe((data) => {
this.clearFields();
this.navigateToLogin();
},
error => console.error(error)
);
}
});
}
checkEmailStatus() : Observable<boolean> {
const result = new Subject<boolean>();
this.portfolioAppService.checkEmailStatus(this.newUser.email).subscribe(
(data: string) => {
if (data !== '') {
result.next(true);
}
else result.next(false);
},
error => {
console.error(error);
result.next(false);
}
);
return result.asObservable();
}
I have a Mat Dialog with an input field and some buttons and whenever I press one of those buttons I'm supposed to get the value inserted on the input field, retrieve some info from my database and send it to my component via a Service.
The problem is that whenever I click on the button, I'm executing several requests instead of just one.
The first request returns undefined and because of that my component isn't being populated with the values I want.
The second request retrieves the values I want but because I'm already on my component, the info isn't being propagated via my Service.
Here is my code:
I'm assuming the service is working fine because I'm using it with two other components successfully.
my.service.ts
private ordersSource = new BehaviorSubject({});
currentOrders = this.ordersSource.asObservable();
private returnsSource = new BehaviorSubject({});
currentReturns = this.returnsSource.asObservable();
constructor() { }
setOrders(el){
this.ordersSource.next(el);
}
setReturns(el){
this.returnsSource.next(el);
}
In my mat dialog, I click a button that executes the createReturn(value) function. I already checked that the value is correct via debug so it is also correctly executing the else statement.
The getOrderByID and getOrderByNumber functions (called depending on the value) are being executed several times, however I can't see a reason why that is.
my-dialog.component.ts
private orders: {};
private returns: Return[] = [];
constructor(public rest: RestService, private route: ActivatedRoute, private router: Router,
private dialogRef: MatDialogRef<MyDialogComponent>, #Inject(MAT_DIALOG_DATA) public data: any[],
private myService: MyService, public dialog: MatDialog) { }
private createReturn(value) {
if (value === undefined || value === "") {
this.inputForm.setErrors({ 'invalid': true });
} else {
this.getOrder(value);
if (this.orders !== undefined) {
this.router.navigate(['my-component']);
this.closeDialog();
}
}
}
// Also working as expected, no problem detected here
private getOrder(value) {
if (value.length > 0 && !isNaN(value)) {
this.getOrderByID(value);
} else if (value.length > 0 && isNaN(value)) {
this.getOrderByNumber(value);
}
this.myService.setOrders(this.orders);
this.myService.setReturns(this.returns);
}
private getOrderByID(value) {
this.rest.getOrder(value).subscribe((orderIdData: {}) => {
if (Object.entries(orderIdData).length !== 0) {
this.orders = orderIdData;
this.rest.getReturnByOrderId(value).subscribe((returnOrdIdData: Return[]) => {
if (Object.entries(returnOrdIdData).length !== 0) {
this.returns = returnOrdIdData;
} else {
this.returns = [];
}
}, error => {
if (error.status === 404) {
this.returns = [];
}
});
} else {
this.inputForm.setErrors({ 'invalid': true });
}
}, error => {
this.inputForm.setErrors({ 'invalid': true });
});
}
private getOrderByNumber(value) {
this.rest.getOrderByNumber(value).subscribe((orderNrData: {}) => {
if (Object.entries(orderNrData).length !== 0) {
this.orders = orderNrData;
this.rest.getReturnByOrderNumber(value).subscribe((returnOrdNrData: Return[]) => {
if (Object.entries(returnOrdNrData).length !== 0) {
this.returns = returnOrdNrData;
} else {
this.returns = [];
}
}, error => {
if (error.status === 404) {
this.returns = [];
}
});
} else {
this.inputForm.setErrors({ 'invalid': true });
}
}, error => {
this.inputForm.setErrors({ 'invalid': true });
});
}
And this is the component that router.navigate(['my-component']) redirects to. This component works as intended when I use the service in another component so I assume the problem isn't here.
my-component.component.ts
constructor(public rest: RestService, private route: ActivatedRoute, private router: Router, private myService: MyService) { }
ngOnInit() {
this.myService.currentOrders.subscribe(orderData =>
this.setOrdersArray(orderData));
this.myService.currentReturns.subscribe(returnData =>
this.setReturnsArray(returnData));
this.setOrderValues(this.orders);
this.onChangeReturnType();
}
I tried debugging using Chrome's DevTools and I found out that the request was being executed three to six times when I only want it to be executed once.
Anyone know what might be wrong?
For anyone who might want to know, here is the solution I found...
I finally understood and figured out what was the issue with my code.
Basically I was trying to send data to my service and navigate to my component before the database gave a response with the data I needed. Also, there were multiple http requests because of this https://stackoverflow.com/a/53371896/11033212.
So I made the following changes:
my-dialog.component.ts
private getData(value) {
if (value === undefined || value === "") {
this.inputForm.setErrors({ 'invalid': true });
} else {
this.getOrder(value);
}
}
private getOrder(value) {
if (value.length > 0 && !isNaN(value)) {
this.getOrderByID(value);
} else if (value.length > 0 && isNaN(value)) {
this.getOrderByNumber(value);
}
}
private getOrderByID(value) {
this.rest.getOrder(value).subscribe((orderIdData: {}) => {
if (Object.entries(orderIdData).length !== 0) {
this.orders = orderIdData;
this.rest.getReturnByOrderId(value).subscribe((returnOrdIdData: Return[]) => {
if (Object.entries(returnOrdIdData).length !== 0) {
this.returns = returnOrdIdData;
} else {
this.returns = [];
}
this.createReturn(orderIdData);
}, error => {
if (error.status === 404) {
this.returns = [];
}
this.createReturn(orderIdData);
});
} else {
this.inputForm.setErrors({ 'invalid': true });
}
}, error => {
this.inputForm.setErrors({ 'invalid': true });
});
}
private getOrderByNumber(value) {
this.rest.getOrderByNumber(value).subscribe((orderNrData: {}) => {
if (Object.entries(orderNrData).length !== 0) {
this.orders = orderNrData;
this.rest.getReturnByOrderNumber(value).subscribe((returnOrdNrData: Return[]) => {
if (Object.entries(returnOrdNrData).length !== 0) {
this.returns = returnOrdNrData;
} else {
this.returns = [];
}
this.createReturn(orderNrData);
}, error => {
if (error.status === 404) {
this.returns = [];
}
this.createReturn(orderNrData);
});
} else {
this.inputForm.setErrors({ 'invalid': true });
}
}, error => {
this.inputForm.setErrors({ 'invalid': true });
});
}
// This only happens after my database's response occurs
private createReturn(el) {
this.setData(el);
this.closeDialog();
this.router.navigate(['my-component']);
}
My input field now calls getData() when a click event occurs.
Previously, I was calling the functions to close the dialog and to navigate to my component outside the getOrderByID and getOrderByNumber function requests. That lead the former functions to execute before the requests had a response.
With these changes, my code now waits for the database's response before continuing with its execution.
I'm displaying a LoadingController when the user tries to login. Meanwhile, an API is being called.
I’m able to dismiss the LoadingController when I get a SUCCESS response from subscribe, but when I get an ERROR response, I’m not able to dismiss. Please help!
I’m a professional Python developer and a total newbie to Ionic, just started a day ago. So, please assist as such.
import { Component, OnInit } from '#angular/core';
import { ToastController, LoadingController } from '#ionic/angular';
import { CallapiService } from '../callapi.service';
#Component({
selector: 'app-login',
templateUrl: './login.page.html',
styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {
userEmail = '';
userPassword = '';
loginUrl = 'login/';
loginMethod = 'POST';
postBody = {};
constructor(
public toastController: ToastController,
public loadingController: LoadingController,
private callApiService: CallapiService,
) { }
ngOnInit() {
}
async presentToast(displayMessage) {
const toast = await this.toastController.create({
message: displayMessage,
duration: 2000,
position: 'middle',
});
return await toast.present();
}
async presentLoading(loadingMessage) {
const loading = await this.loadingController.create({
message: loadingMessage,
});
return await loading.present();
}
loginUser() {
if (this.userEmail === '' || this.userPassword === '') {
this.presentToast('Email and password are required.');
}
else {
this.presentLoading('Processing...');
this.postBody = {
email: this.userEmail,
password: this.userPassword,
};
this.callApiService.callApi(this.loginUrl, this.postBody, this.loginMethod).subscribe(
(success) => {
console.log(success);
this.loadingController.dismiss();
},
(error) => {
console.log(error);
this.loadingController.dismiss();
}
);
this.loadingController.dismiss();
}
}
}
Without any service,
Same issue I faced while using Ionic 4 loading controller.
After trial and error I got working solution.
As loading controller functions are using async and await because both are asynchronous functions.
dismiss() function will called before present() function because, dismiss function will not wait until creating and presenting the loader, it will fire before present() as soon function will call.
Below is working code,
loading:HTMLIonLoadingElement;
constructor(public loadingController: LoadingController){}
presentLoading() {
if (this.loading) {
this.loading.dismiss();
}
return new Promise((resolve)=>{
resolve(this.loadingController.create({
message: 'Please wait...'
}));
})
}
async dismissLoading(): Promise<void> {
if (this.loading) {
this.loading.dismiss();
}
}
someFunction(){
this.presentLoading().then((loadRes:any)=>{
this.loading = loadRes
this.loading.present()
someTask(api call).then((res:any)=>{
this.dismissLoading();
})
})
}
this.callApiService.callApi(this.loginUrl, this.postBody, this.loginMethod)
.subscribe(
(data) => {
// Called when success
},
(error) => {
// Called when error
},
() => {
// Called when operation is complete (both success and error)
this.loadingController.dismiss();
});
Source: https://stackoverflow.com/a/54115530/5442966
Use Angular property binding. Create a component to your loading:
import { Component, Input } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Component({
selector: 'app-loading',
template: ''
})
export class LoadingComponent {
private loadingSpinner: HTMLIonLoadingElement;
#Input()
set show(show: boolean) {
if (show) {
this.loadingController.create().then(loadingElem => {
this.loadingSpinner = loadingElem;
this.loadingSpinner.present();
});
} else {
if (this.loadingSpinner) {
this.loadingSpinner.dismiss();
}
}
}
constructor(private loadingController: LoadingController) {}
}
...then in 'login.page.html' use your componente:
...
<app-loading [show]="showLoading"></app-loading>
... in 'LoginPage' create a property 'showLoading' and set it to true or false where you whant:
//.... some source code
export class LoginPage implements OnInit {
showLoading;
userEmail = '';
userPassword = '';
loginUrl = 'login/';
loginMethod = 'POST';
postBody = {};
//.... some source code
loginUser() {
if (this.userEmail === '' || this.userPassword === '') {
this.presentToast('Email and password are required.');
} else {
this.showLoading = true;
this.postBody = {
email: this.userEmail,
password: this.userPassword
};
this.callApiService
.callApi(this.loginUrl, this.postBody, this.loginMethod)
.subscribe(
success => {
console.log(success);
this.showLoading = false;
},
error => {
console.log(error);
this.showLoading = false;
}
);
this.showLoading = false;
}
}
}
This works for me, I reuse the loading component on others pages!
Recommended reading: https://angular.io/start
I actually ran into this exact issue and for me the answer was just to use await.
The functions for both creating and dismissing loaders return promises. What I realized was happening is that the subscribe/promise rejection was halting all other promises from completing. Now, I just await both presenting and dismissing and I have no issue:
async getData() {
//await presenting
await this.presentLoading('Loading...');
try {
let response = await this.httpService.getData();
await this.loadingController.dismiss();
//...
catch(err) {
this.loadingController.dismiss();
//handle error
//...
}
}
async presentLoading(msg: string) {
const loading = await this.loadingController.create({
spinner: 'crescent',
message: msg
});
await loading.present();
}
I hope this simple solution helps!
I did not manage to remove a deleted item from screen.
New or updated items are reflected in screen, but I don't know what to do to reflect deleted ones.
I even created a separated list (using methods findIndex, emit) and splice the element from that list but it didn't work as well.
My provider:
export class AssetTypeService {
private assettypes$: any;
private assettypesRef: any;
private assettypeslist: AssetType[] = [];
constructor(private http: Http) {
this.assettypesRef = firebase.database().ref('asset-type');
this.assettypesRef.on('child_added', this.handleDataChildAdded, this);
this.assettypesRef.on('child_changed', this.handleDataChildAdded, this);
this.assettypesRef.on('child_removed', this.handleDataChildRemoved, this);
this.assettypes$ = new ReplaySubject();
}
get getAssetTypes() {
return this.assettypes$;
}
handleDataChildAdded(snap) {
try {
let type: AssetType = <AssetType>{};
type.id = snap.key;
type.description = snap.val().description;
this.assettypes$.next(type);
} catch (error) {
console.log('catching', error);
}
}
handleDataChildRemoved(snap) {
try {
let index: number = this.findIndex(snap.key);
if (index !== -1) {
this.assettypeslist.splice(index, 1);
this.emit();
}
} catch (error) {
console.log('catching', error);
}
}
private findIndex(key: string): number {
return this.assettypeslist.findIndex((assetType: AssetType) => {
return assetType.id === key;
});
}
private emit(): void {
this.assettypes$.next(this.assettypeslist);
}
saveAssetType(assetType: AssetType) {
if (assetType.id == undefined) {
assetType.id = firebase.database().ref().child('asset-type').push().key;
}
var updates = {};
updates['/asset-type/' + assetType.id] = assetType;
return firebase.database().ref().update(updates);
}
getAssetType(uid: string) {
let assetType: AssetType = <AssetType>{};
return firebase.database().ref('/asset-type/' + uid).once('value').then(function (snapshot) {
assetType = { id: snapshot.key, description: snapshot.val().description };
return assetType;
});
}
deleteAssetType(uid: string) {
return firebase.database().ref('asset-type/' + uid).remove();
}
}
My controller:
ngOnInit() {
this.getAssetTypes();
}
getAssetTypes() {
this.assetTypeService.getAssetTypes.subscribe((data) => {
let alreadyExists = false;
this.types.forEach(function (assetType) {
if (assetType.id == data.id) {
alreadyExists = true;
assetType.description = data.description;
}
});
if (!alreadyExists) {
this.types.push(data);
}
}, (err) => {
console.error(err);
console.log(err);
});
}
deleteAssetType(uid: string) {
this.assetTypeService.deleteAssetType(uid).then(data => {
this.showToast('Asset type deleted successfully');
});
}
I have:
onKeyPress(id, e) {
if(e.key == 'Enter') {
this.saveField(id, e.target.value);
}
}
onBlur(id, e) {
this.saveField(id, e.target.value);
}
saveField(id, date) {
this.setState({
updatingField: true
})
this.context.executeAction(SetJobChaserDate, {date: date, id: id});
this.setState({
editingChaser: false,
editingTrackingSent: false,
updatingField: false
})
}
The problem is, is that it seems the setState after the action fires immediately, thus not giving me the effect in another component.
How can I setState only after the action has completed (regardless of success or fail)?
Here is my action:
import qwest from 'qwest';
export default function SetJobChaserDate(actionContext, payload) {
return qwest.post('jobs/set/chaser-date', {id: payload.id, date: payload.date}, {responseType: 'json'})
.then(function (response) {
actionContext.dispatch('RECEIVED_JOBS_DATA', {data: response.response, clear: false})
})
}
import { EventEmitter } from 'events';
class JobStore extends EventEmitter {
constructor() {
super();
this.jobs = new Map();
this.counts = {};
this.loading = false;
}
handleJobsData(payload) {
if (payload.clear === true) {
this.jobs = new Map();
}
payload.data.jobs.forEach((job) => {
this.jobs.set(job.id, job);
});
if(payload.data.counts) {
this.counts = payload.data.counts;
}
this.loading = false;
this.emit('change');
}
handleReceiving() {
this.loading = true;
this.emit('loading');
}
handleCounts(payload) {
console.log(payload)
}
getCounts() {
return this.counts;
}
getJobs() {
return this.jobs;
}
dehydrate () {
return this.jobs;
}
rehydrate (state) {
}
}
JobStore.dispatchToken = null;
JobStore.handlers = {
'RECEIVED_JOBS_DATA': 'handleJobsData',
'RECEIVED_COUNTS' : 'handleCounts',
'RECEIVING_JOB_DATA': 'handleReceiving'
};
JobStore.storeName = 'JobStore';
export default JobStore;
Update:
componentWillReceiveProps() {
this.context.getStore(JobStore).on('change', () => {
this.setState({
updatingField: false
});
});
}
onKeyPress(id, e) {
if (e.key == 'Enter') {
this.saveField(id, e.target.value);
}
}
onBlur(id, e) {
this.saveField(id, e.target);
}
saveField(id, target) {
console.log(target)
this.setState({
updatingField: true
})
this.context.executeAction(SetJobChaserDate, {date: target.value, id: id});
this.setState({
editingChaser: false,
editingTrackingSent: false
})
}
You are using an updating state, that shows your process is asynchronous. The code you are dispatching is indeed a promise, and dispatching an action when the process is done : 'RECEIVED_JOBS_DATA'.
You should hence move the updatingField to be a component prop that comes from the store, and is changed in your reducer whenever the action starts and ends.
That way, you could just use this.props.updatingField in your component, which value will come from the store and depend on the current state of your external request.
That way, you'll follow one React/Flux best practice of preferring global state instead of local state.