Angular2: Very strange/sudden issue when using ngSubmit to pass a String to a function - html

I am busy with a Angular2 web app and running into a very strange issue... I have a model driven form that uses ngSubmit to pass a barcode value to a findProduct() function. Everything worked fine about an hour ago, but now when I click the Scan button the page reloads and I don't get any output in the google dev tools console. I've tried to console.log the barcode in the findProduct() function but still no output in the console, the page just reloads whenever the button is clicked. Any idea what I'm doing wrong/How I can debug this kind of issue?
my code:
html template:
<form role="form" [formGroup]="productForm" (ngSubmit)="findProduct(productForm.value.barcode)">
<input class="form-control" type="number" placeholder="Enter Barcode" [formControl]="productForm.controls['barcode']">
<small [hidden]="productForm.controls['barcode'].valid || (productForm.controls['barcode'].pristine && !productForm.submitted)">barcode required</small>
<button class="btn btn-submit pull-right" [disabled]="!productForm.valid">Scan</button>
</form>
controller:
findProduct(barcode: string) {
console.log(barcode);
this.product.GoodsReceivedID = this.GRNIDKey;
this.restService.findCaptureItem(this.GRNIDKey, barcode)
.subscribe(
(res) => {
console.log(res);
this.product = res;
},
(res) => {
console.log(res);
});
}
Service:
findCaptureItem(GRNIDKey: string, barcode: string) : Observable<any> {
return this.http.get(this.API_URL + 'PurchaseOrder/FindPurchaseOrderItem/' + GRNIDKey + '/' + barcode)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'server error'));
}

It turns out the issue occurred on the line:
this.product.GoodsReceivedID = this.GRNIDKey;
The issue was that this.product has been declared but not yet initialized... I think it's really strange that there was no Warning/Error in either the Google developer console or angular-cli (Windows command prompt)...

Related

azure ad authentication issue with razor handlers

I'm experiencing a strange issue using msal.js for user authentication in a web app that already has a standard login page. I'm totally new to Azure AD, so probably I'm missing something basic. I've been pointed out to msal.js by aonther person working for the same company, but he uses PHP while our app is a NET Core 3 app. The standard login works perfectly, firing the code behind handler when the form containing username and password is submitted. This is the javascript snippet I've added to the page in order to handle the Azure AD authentication:
<script type="text/javascript">
const appCfg =
{
auth:
{
clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // Carlsberg Prospect.
authority: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/', // Url autorità con id tenant della directory predefinita.
redirectUri: 'https://localhost:44385/Index'
}
};
var msalObj = new msal.PublicClientApplication(appCfg);
function openLogin()
{
msalObj.loginPopup().then
(function (idToken)
{
$('#LoginModel_rmUsername').val(idToken.account.username);
$('#LoginModel_rmPassword').val(idToken.account.username);
$('#main-login-btn').trigger('click');
}
);
}
The call to loginPopup opens a popup where the user has to insert his mail and password, and the promise works well, giving me the token associated with the user.
The jquery statement simply simulates the user clicking the login button, but the handler never fires, and instead the borwser redirects to an empty page showing a 400 error code. By the mean of the dev tools, the network tab shows the same call made from the standard login and the button click simulated after the response from the Azure AD authentication.
I'm using the latest msal.js version (2.24.0). I'm not a huge fan of NET Core for web apps, but the project is a legacy of a person not working anymore for us.
EDIT
Better adding some code to try to explain my problem.
This is the code inside Index.cshtml:
#page
#model IndexModel
#{
Layout = "_LayoutLogin";
}
#{
ViewBag.Title = "Login";
}
#{
string test = (Model.testEnvironment ? " block" : "none");
}
<div style="text-align:center;padding:.125rem;display:#test">
<div style="border-radius:.25rem;color:white;background-color:#18A754;"><strong>** TEST **</strong></div>
</div>
<div class="album py-5 bg-light">
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3">
<center>
<h6><strong>Sia Field</strong> - Application Login</h6>
</center>
<div class="card mb-12 shadow-sm">
<div class="card-body">
<form method="POST" id="login-form" asp-page-handler="LogIn">
<p class="card-text text-center">
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.LoginModel.Azienda)
<select asp-for="LoginModel.Azienda" class="form-control" asp-items="#Model.ListOfAziende"></select>
#Html.ValidationMessageFor(m => m.LoginModel.Azienda, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.LoginModel.Username)
#Html.TextBoxFor(m => m.LoginModel.Username, new { placeholder = "Username", #class = "form-control" })
#Html.ValidationMessageFor(m => m.LoginModel.Username, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.LoginModel.Password)
#Html.PasswordFor(m => m.LoginModel.Password, new { placeholder = "Password", #class = "form-control" })
#Html.ValidationMessageFor(m => m.LoginModel.Password, "", new { #class = "text-danger" })
</div>
#Html.HiddenFor(m => m.LoginModel.rmUsername)
#Html.HiddenFor(m => m.LoginModel.rmPassword)
<button id="main-login-btn" class="btn btn-primary cbutton" style="width: 100%;">Login</button>
<button type="button" id="login-button" class="btn btn-info" style="width: 100%;margin-top:.75rem;" onclick="openLogin();">Entra con l'account corporate Microsoft</button>
</p>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
const appCfg =
{
auth:
{
clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
authority: 'https://login.microsoftonline.com/common/', // Url autorità con id tenant della directory predefinita.
redirectUri: 'https://localhost:44385/index'
}
};
const req =
{
// prompt: 'login'
};
var msalObj = new msal.PublicClientApplication(appCfg);
function openLogin()
{
msalObj.acquireTokenPopup(req)
.then
(
function (response)
{
$('#LoginModel_Username').val(response.account.username);
$('#LoginModel_Password').val(response.account.username);
$('#main-login-btn').trigger('click');
}
).catch
(
function (error)
{
console.log(error);
}
);
}
</script>
and this is an excerpt of the code inside Index.cshtml.cs:
public async Task<IActionResult> OnPostLogIn()
{
try
{
// Verification.
if (ModelState.IsValid)
{
DSUtenti dsUsers = new DSUtenti();
// Initialization.
IQueryable<Utenti> loginInfo = dsUsers.GetMyUtente(Convert.ToInt32(this.LoginModel.Azienda), this.LoginModel.Username, this.LoginModel.Password);
if (loginInfo != null && loginInfo.Count() > 0)
{
// Initialization.
Utenti logindetails = loginInfo.First();
// Login In.
await this.SignInUser(logindetails, false);
The handler OnPostLogIn is correctly fired if I do a "normal" login, supplying username and password and clicking on the 'Login' button, but if I use 'Entra con l'account corporate Microsoft' button the call to acquireTokenPopup() opens the Microsoft Login Popup, and when the promise returns the correct token with the needed info on the user, on the subsequent (simulated) press of the Login button the `OnPostLogin' handler never get fired, returning an HTTP 400 error. Obviously if at this moment I supply the credentials in the standard fields and press again the Login button returns the same error, until I refresh the page.
Sorry for the long edit post.
• I would suggest you to please correctly use the error handling in authentication flows with redirect methods (loginRedirect, acquireTokenRedirect) to register the callback which is called with success or failure after the redirect using handleRedirectCallback() method. Also, the methods for pop-up experience (loginPopup, acquireTokenPopup) return promises such as you can use the promise pattern (.then and .catch) to handle them as shown below : -
myMSALObj.acquireTokenPopup(request).then(
function (response) {
// success response
}).catch(function (error) {
console.log(error);
});
• Also, you can use the acquireTokenPopup or acquireTokenRedirect method to remediate the issue by calling an interactive method as below: -
// Request for Access Token
myMSALObj.acquireTokenSilent(request).then(function (response) {
// call API
}).catch( function (error) {
// call acquireTokenPopup in case of acquireTokenSilent failure
// due to consent or interaction required
if (error.errorCode === "consent_required"
|| error.errorCode === "interaction_required"
|| error.errorCode === "login_required") {
myMSALObj.acquireTokenPopup(request).then(
function (response) {
// call API
}).catch(function (error) {
console.log(error);
});
}
});
For more details on resolving the issue regarding the login pop-up sign-in, kindly refer to the documentation link below: -
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-error-handling-js

Angular: Re-render asynchronous data in image tag

I am using Angular to fetch user profile picture from backend(Node.js/Express). Everything is working perfectly except for one thing. The Angular does not re-render the HTML that displays the profile picture incase, the user has updated his picture or if no picture is present and user uploads his first image. As expected, the Angular is rendering the HTML only once and isn't re-rendering again. I don't know how can I wait for asynchronous data in HTML as I am directly targeting an endpoint in HTML instead of TS.
Here's my code:
userProfile.component.html
<div class = "imgClass">
<img class = "img-thumbnail rounded-circle imgclass"
src="http://localhost:3000/api/getProfilePhoto?id={{cookieData}}">
//angular is sending request to the above endpoint to fetch the image only once at the time
application starts or user logs in. How can I send a request again?
<div class="middle">
<div class="text"><button type="button" name="button" (click) = "selectImage()" class = "btn
btn-outline-primary"> <i class="bi bi-plus"></i> </button></div>
<input type="file" id="imgUpload" (change) = "handleImageInput($event.target.files)">
</div>
</div>
userProfile.component.ts
selectImage()
{
document.getElementById('imgUpload').click();
}
handleImageInput(files: FileList)
{
this.imageUpload = files.item(0);
this.uploadImage();
}
uploadImage()
{
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {params, responseType: "text"})
.subscribe(responseData => {
this.imageChanged = true; //I have tried using this as *ngIf in HTML but it is not working either
}
,error => {
console.log("Image uploading failed" + error.message);
})
}
Does anybody know how can I send the request to an endpoint in HTML once user changes/uploads his first picture?
You need to trigger the image fetch request for each update/upload requests. Or you could adjust the backend to return the image data from the update/upload requests.
Option 1: manually fetch image for each update/upload requests
Use RxJS switchMap operator to switch to image fetch request after the uploading has completed. It'll not be fetched if the uploading failed.
profileImage: any;
selectImage() {
document.getElementById('imgUpload').click();
}
handleImageInput(files: FileList) {
this.imageUpload = files.item(0);
this.uploadImage();
}
uploadImage() {
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {
params,
responseType: "text"
}).pipe(
tap(null, error => console.log("Image uploading failed" + error.message)),
switchMap(_ => this.http.get(`http://localhost:3000/api/getProfilePhoto?id${this.cookieData}`))
).subscribe(
image => {
this.profileImage = image;
},
error => {
console.log("Image fetching failed" + error.message);
}
);
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">
Option 2: Return the image from upload/update request
Adjust the backend to return the image data from the Upload POST request.
profileImage: any;
uploadImage() {
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {
params,
responseType: "text"
}).subscribe(
image => {
this.profileImage = image;
},
error => {
console.log("Image uploading failed" + error.message);
}
);
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">
As a sidenote, using document.getElementById() in Angular will search the whole DOM, not just the individual component. In relatively complex apps, it might lead to performance issues. Instead try to use an event handler or if it's not possible, use Angular ViewChild with a template reference parameter to get an element from the current component's DOM.
if the webservice resolving the image url returns an Observable, you can make the call from typescript like below
imageData$: Observable<number>;
getImage(id): Observable<string> {
this.imageData$=http.get(url?id=<some_id>);
return this.imageData$
}
and the adding async pipe on it
<img class = "img-thumbnail rounded-circle imgclass" [src]="imageData$ | async">
Basically The async pipe subscribes to an Observable or Promise and
returns the latest value it has emitted. When a new value is emitted,
the async pipe marks the component to be checked for changes. When the
component gets destroyed, the async pipe unsubscribes automatically to
avoid potential memory leaks.

Can not store json obj in state when i use useEffect to get an object from an API

Im trying to show an object's properties on a modal, but nothing seems to happen after i fetch it. I've tried without using the useEffect hook, and it does store the item but then i cant access the properties, i asked about it, and a user told me to use use Effect. But now, nothing seems to be stored...
This is my code:
import React, {useState, useEffect } from 'react';
const Modal = ({ handleClose, show, id }) => {
const showHideClassName = show ? "mod displayBlock" : "mod displayNone";
const [peliSeleccionada, setPeli] = useState([]);
useEffect(() => {
fetch(`http://localhost/APIpeliculas/api/pelicula/read_single.php/?ID=${id}`)
.then(res => res.json())
.then(
result => {
alert(result); //the alerts dont even pop up
setPeli(result);
alert(peliSeleccionada);
});
}, []);
return (
<div className={showHideClassName}>
<section className="mod-main">
<h5>EDITAR: </h5>
<label>
{ peliSeleccionada.Nombre }
</label>
<div className="btn-grupo">
<button type="button" className="btn btn-success btn-lg btn-block">Guardar cambios</button>
<button onClick={handleClose} type="button" className="btn btn-secondary btn-lg btn-block">Cerrar</button>
</div>
</section>
</div>
);
};
export default Modal;
The alerts i put inside my useEffect function dont even pop up, and i also get this error on the console as soon as i enter the page:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
Also I want to access my object's properties, which are: ID, Nombre, Categoria, and Director. Is this the correct way to do it? { peliSeleccionada.Nombre }
useEffect is run after the component renders, similarly to how componentDidMount works.
What this means, putting it very simply, is that the component will return and then fire the fetch.
There is an issue with your peliSeleccionada state, you declare it as an array but call peliSeleccionada.Nombre like if it was an object. This means that on first render it will print undefined for the peliSeleccionada.Nombre.
An approach to have this is to pair it with a loading state.
const Modal = () => {
const [loaded, setLoading] = useState(true);
const [peliSeleccionada, setPeli] = useState({});
useEffect( () => {
fetch()
.then(res => {
setPeli(res) // parse this accordingly
}) // or chain of thens if needed
.catch(err => {
setLoading(false);
console.log(err) // something failed in the fetch. You could have another state to mark that the fetching failed; close the modal, show an error, etc.
})
}, [])
if(!loaded) return 'Loading...'
return (<div>Data goes here</div>) // be careful if the fetch failed, it won't show data!
}
Last but not least, the error is happening in the fetch and the message states exactly that, that you are not catching it. If it is in the fetch call, the above code works. If it is in a parsing, it might require a try/catch around the useEffect

React setState cannot assign fileList instead assigns string of first fileName

To begin with, I'm making a simple social media application. I'm trying to submit a form which has text, images, and videos. My frontend where the form is submitted is made with React and server is ran with node.js mounted on nginx. I was trying to append the inputted files into FormData with code below:
handleSubmit = function (e) {
e.preventDefault();
const formData = new FormData();
formData.append("textBody", this.state.textBody)
for (let i = 0; i < this.state.imgInput.length; i++) {
formData.append("imgInput", this.state.imgInput.files[i], "img"+i.toString())
fetch("mywebsite.com/api/submitArticle", {
body: formData,
method: "POST",
credentials: 'include',
}).then((response) => console.log(response))
return false;
}.bind(this)
handleChange = function (e) {
e.preventDefault();
if (e.target.name === 'imgInput') {
this.setState({
imgInput: e.target.files,
showSpan: false
})
}
}.bind(this)
<form onSubmit={this.handleSubmit}>
<textarea id='textBody' name='textBody' onFocus={removeSpan} onBlur={checkSpanOn} onChange={this.handleChange}/>
<input type="file" id="imgInput" name="imgInput" accept="image/*" ref={this.imgRef} multiple={true} onChange={this.handleChange}/>
<input type="submit" id="submitButton" name="submitButton" formEncType="multipart/form-data" />
</form>
But React gave me this error upon submitting the form:
TypeError: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'.
at "formData.append("imgInput", this.state.imgInput.files[i], "img"+i.toString())".
So when I console logged what e.target.files before setState in handleChange, I got normal FileList with all the image files listed. But when I console loggedd this.state.imgInput after setState in handleChange, I got String of C://fakepath/filename, not fileList. (Initially state.imgInput was null. When I saw other examples and codes, e.target.files was fileList so I'm puzzled elsewhere I made mistake.
I was spending half my day on this problem and I'm 5 sec before fainting so any advice would be appreciated :) Thank you for reading.
yes this happening because the event is gone you need to store the event.target in variable + the files will be in imgInput not imgInput.files so here it is:
handleSubmit = e => {
e.preventDefault();
const formData = new FormData();
formData.append("textBody", this.state.textBody);
for (let i = 0; i < this.state.imgInput.length; i++) {
formData.append("imgInput", this.state.imgInput[i], "img" + i.toString());
fetch("mywebsite.com/api/submitArticle", {
body: formData,
method: "POST",
credentials: "include"
}).then(response => console.log(response));
}
};
handleChange = e => {
e.preventDefault();
const target = e.target;
if (target.name === "imgInput") {
this.setState(current => ({
...current,
imgInput: target.files,
showSpan: false
}));
}
};

Type 'PolicyData[]' is missing the following properties from type 'Observable<PolicyData[]>'

Working with returning the json response from my service to a dropdown select at the UI while working on my angular application, but get this issue before i could even run my code
Guidance on solving this issue would be really helpful, thanks in advance...
Type 'PolicyData[]' is missing the following properties from type 'Observable': _isScalar, source, operator, lift, and 5 more.ts(2740)
The below is the code i have used
policy.component.ts
public policyinc: Observable<PolicyData[]> = of([]);
getIncCompany() {
this.policyService.getIncCompany()
.subscribe(data => {
console.log(data);
this.policyinc = data,
(error: any) => this.errorMessage = <any>error,
console.log(this.policyinc);
}
);
}
The service.ts file
getIncCompany(): Observable<PolicyData[]> {
const headers = new HttpHeaders({ 'Content-Type': 'application/json'});
let body = '{}';
return this.http
.post<PolicyData[]>(this.apiUrl, body, { headers: headers })
.pipe(
tap(data => console.log('getIncCompany: ' + JSON.stringify(data))),
catchError(this.handleError)
);
}
And the html component this way
<div class="col-lg-4 m-form__group-sub">
<mat-form-field class="example-full-width" appearance="outline">
<mat-label> Handicapped Discount
</mat-label>
<mat-select placeholder="Select">
<mat-option value="option" *ngFor="let polB of policyinc">{{ polB.bankName }}</mat-option>
</mat-select>
</mat-form-field>
</div>
policyInc is declared as an Observable
In getIncCompany you subscribe to policyService.getIncCompany, then assign return data to this.policyinc, but the return data is not an Observable.
Try making policyinc simply PolicyData[];
tap operator can be used to log to console
public policyinc: PolicyData[];
getIncCompany() {
this.policyService.getIncCompany()
.pipe(tap(data => console.log(data)))
.subscribe(
{
next: data => this.policyinc = data,
error: error => this.errorMessage = error
});
}
Also, "In the future, subscribe will only take one argument: either the next handler (a function) or an observer object." from Subscribe is deprecated: Use an observer instead of an error callback