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.
Related
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);
});
}
I am trying to build a JavaScript class that allows me to interact with my Home Assistant server via web sockets (ws library.) The script is intended to be executed in the node.js environment.
const WebSocket = require('ws');
class HomeAssistantWebSocket {
constructor(config = {}) {
this.config = config;
this.initialize();
}
config;
initialized = false;
initializeErrors = [];
authenticated = false;
ws = null;
authenticate = () => {
let {
config,
ws,
serialize
} = this;
console.log("Attempting to authenticate...");
ws.send(serialize({
"type": "auth",
"access_token": config["access_token"]
}));
return true;
}
openConnection = () => {
let {
ws
} = this;
ws.on('open', () => {
console.log("Connection opened!");
});
ws.on('message', (data) => {
this.handleMessage(data);
});
}
deserialize = (string) => {
try {
return JSON.parse(string);
} catch (error) {
return false;
}
}
handleMessage = (data) => {
let {
authenticate,
deserialize,
ws
} = this;
data = deserialize(data);
console.log(data);
if(data["type"] === "auth_required") {
authenticate();
}
if (data["type"] === "auth_ok" && !this.authenticated) {
this.authenticated = true;
console.log("Successfully authenticated");
}
if (data["type"] === "auth_ok") {
ws.send(JSON.stringify({
"id": 20,
"type": "subscribe_events",
}));
}
}
initialize = () => {
let {
config,
initialized,
initializeErrors,
} = this;
if (Object.keys(config).length < 1) {
initializeErrors.push("No config present.");
} else if (!config.hasOwnProperty("access_token") && typeof config["access_token"] === "string") {
initializeErrors.push("Config must contain a valid access_token.");
} else if (!config.hasOwnProperty("home_assistant_url") && typeof config["home_assistant_url"] === "string") {
initializeErrors.push("Config must contain a valid home_assistant_url");
}
if (this.initializeErrors.length === 0) {
this.ws = new WebSocket(config["home_assistant_url"]);
this.openConnection();
initialized = true;
console.log("Attempting to open connection...");
} else {
console.log("Failed to initialize:");
this.initializeErrors.forEach((e) => {
console.log(e);
});
}
return true;
}
serialize = (json) => {
try {
return JSON.Stringify(json);
} catch (error) {
return false;
}
}
}
const haWS = new HomeAssistantWebSocket({
"access_token": "redacted_access_token",
"home_assistant_url": "ws://homeassistant.local:8123/api/websocket"
});
I am running in to an issue where my code ceases execution after the authentication phase. My code prints the following in the console and then the script stops executing. No errors are present.
Connection opened!
{ type: 'auth_required', ha_version: '2021.2.3' }
Attempting to authenticate...
I have verified my code does properly connect to the web socket api and is communicating with the home assistant server. Does anyone see anything wrong with my code that would cause the script to stop execution/garbage collect the ws on message to prevent further messages from being received?
I have a very basic example working as expected outside of a class that makes it pass the authentication phase and leaves the socket open and receives data as expected. Any help would be greatly appreciated.
serialize = (json) => {
try {
return JSON.stringify(json);
} catch (error) {
return false;
}
}
I found the issue in the serialize function. I had an improper reference to the JSON.stringify function. In my code it was JSON.Stringify. It should be JSON.stringify.
It's always the little things...
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'm working with a angular and i'm trying to apply some AuthGard on some Paths.
The problem is canActivate() renders the content before it checks with the SecurityContext, after a verification that no SecurityContext is applied then a redirection to the default page (login) page is applied.
This is the portion of code responsible for this.
app.routing.ts
{
path: 'admin',
canActivate: [AuthGard],
component: HomeComponent,
children : [
{
path: 'add-merchant-admin',
component : AddMerchantAdminComponent,
},
{
path: 'list-merchant-admin',
component : ListMerchantAdminComponent,
}
]
},
AuthGard.ts
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
this._authService.getRoles().subscribe(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
this.error = false;
}
}
} else {
this._router.navigate(['login']);
this.error = true;
}
}, err => {
this._router.navigate(['login']);
this.error = true;
}
);
return !this.error;
};
AuthService
getRoles() {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers, withCredentials: true});
return this.http.get('http://10.0.0.239:8080/**/**/RolesResource/getRole', options)
.map((res) => res)
.catch((error: any) => Observable.throw(error.text() || 'Server error'));
}
All Services are correctly injected,
Normally a redirection to protected area or default page should be applied after the verification is made using getRole() method.
The problem you are having is that this._authService.getRoles() makes a network call which is asynchronous. return !this.error; is being fired before the network call is being returned so !this.error does not change and is therefore still truthy.
To solve this issue you should be able to return an observable as follows:
return this._authService.getRoles().map(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
return false;
}
}
} else {
this._router.navigate(['login']);
return true;
}
}).catch((err) => {
this._router.navigate(['login']);
return Observable.of(false);
}
);
Something like this should work
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this._authService.getRoles()
.map(response => JSON.parse(response.text())[0].authority)
.do(role => localStorage.setItem('role', role))
.map( role => role === 'ROLE_ADMIN')
.catch(() => this._router.navigate(['login']));
};
You can try with return observable, which can be updated either true or false.
instead of returning return !this.error; which is always true, try to return
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this._authService.getRoles().map(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
return false;
}
}
} else {
this._router.navigate(['login']);
return true;
}
}, err => {
this._router.navigate(['login']);
return Observable.of(false);
}
);
};
Edited
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.