I am using Angular 11 to send a file to my node / express.js back end how do I send data along with the file?
I have a schema called sources, and another called files the files schema contains the sources schema id in order to indicate which files belong to which sources.
In my angular app I loop over the data fetched from the source's documents to display them, each source displayed has an option to upload a file.
I want to be able to send the source id along with the file in my post request in order to store it on my database.
Here is the code I used :
source.component.ts
#Component({
selector: 'app-source',
templateUrl: './source.component.html',
styleUrls: ['./source.component.scss'],
})
export class SourceComponent implements OnInit {
showModal: boolean = false;
faUpload = faUpload;
#Input() datasource: {
_id: string;
name: string;
description: string;
imagePath: string;
};
#Input() searchPlaceHolder1: string;
#Input() searchPlaceHolder2: string;
isModalActive: boolean = false;
#Output() messageEvent = new EventEmitter<string>();
select: string = 'not selected yet';
searchText: string = '';
fileArr = [];
sheetArr = [];
fileObj = [];
form: FormGroup;
msg: string;
progress: number = 0;
isButtonVisible: boolean = true;
constructor(
public fb: FormBuilder,
private sanitizer: DomSanitizer,
public dragdropService: DragdropService
) {
this.form = this.fb.group({
txt: [null],
});
}
ngOnInit(): void {}
onSelect() {
this.select = 'selected';
}
sendMessage() {
this.messageEvent.emit(this.datasource.name);
}
upload(e) {
const fileListAsArray = Array.from(e);
fileListAsArray.forEach((item, i) => {
const file = e as HTMLInputElement;
const url = URL.createObjectURL(file[i]);
this.sheetArr.push(url);
this.fileArr.push({ item, url: url });
});
this.fileArr.forEach((item) => {
this.fileObj.push(item.item);
});
// Set files form control
this.form.patchValue({
txt: this.fileObj,
});
this.form.get('txt').updateValueAndValidity();
// Upload to server
this.dragdropService
.addFiles(this.form.value.txt)
.subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Sent:
console.log('Request has been made!');
break;
case HttpEventType.ResponseHeader:
console.log('Response header has been received!');
break;
case HttpEventType.UploadProgress:
this.progress = Math.round((event.loaded / event.total) * 100);
console.log(`Uploaded! ${this.progress}%`);
break;
case HttpEventType.Response:
console.log('File uploaded successfully!', event.body);
setTimeout(() => {
this.progress = 0;
this.fileArr = [];
this.fileObj = [];
this.msg = 'File uploaded successfully!';
}, 3000);
}
});
}
// Clean Url
sanitize(url: string) {
return this.sanitizer.bypassSecurityTrustUrl(url);
}
loading = { 1: false, 2: false, 3: false, 4: false };
doSomething(i: number) {
console.log('Clicked');
this.loading[i] = true;
setTimeout(() => {
this.loading[i] = false;
}, 2000);
}
selectItem() {
this.showModal = true;
}
}
drag-drop.service.ts
#Injectable({
providedIn: 'root',
})
export class DragdropService {
constructor(private http: HttpClient) {}
addFiles(sheets: File) {
var arr = [];
var formData = new FormData();
arr.push(sheets);
arr[0].forEach((item, i) => {
formData.append('txt', arr[0][i]);
});
return this.http
.post('http://localhost:4000/upload-file', formData, {
reportProgress: true,
observe: 'events',
})
.pipe(catchError(this.errorMgmt));
}
errorMgmt(error: HttpErrorResponse) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.log(errorMessage);
return throwError(errorMessage);
}
}
As for the back end code :
app.post("/upload-file", uploads.single("txt"), (req, res) => {
//convert csvfile to jsonArray
if (
req.file.mimetype === "application/vnd.ms-excel" ||
req.file.mimetype === "application/csv" ||
req.file.mimetype === "text / csv"
) {
const fileName = req.file.originalname;
csv({
delimiter: ";",
})
.fromFile(req.file.path)
.then((jsonObj) => {
//insertmany is used to save bulk data in database.
//saving the data in collection(table)
//finding the document using fileName and setting csvData as the jsonObj
sheetModel.findOneAndUpdate(
{ fileName: fileName },
{ $set: { csvData: jsonObj } },
{ upsert: true }, // if name does not exist insert
(err, csvData) => {
if (err) {
res.status(400).json({
message: "Something went wrong!",
});
} else {
res.status(200).json({
message: "File Uploaded Successfully!",
result: csvData,
});
}
}
);
});
}
Just add the additional fields to formData in the same way that you add the files:
formData.append('sourceId', sourceId);
It seems that you are using Multer middleware on the server. According to the documentation, "req.body will hold the text fields, if there were any".
Related
In my Angular app I am having trouble with getting the value from my params and thus data from my API call.
I keep getting null and [object Object] in browser console for my console.log
EDIT: I have noticed that upon loading this page the tag value is overwhritten wit undefined.. image bellow:
Here is the code I am using to get params value and show products with this tag:
EDIT: search.service.ts file where the API is called
searchByTagCall(tag: string) {
return from(Preferences.get({key: 'TOKEN_KEY'})).pipe(
switchMap(token => {
const headers = new HttpHeaders().set('Authorization', `Bearer ${token.value}`);
let params = new HttpParams();
params = params.append('tag', tag);
return this.httpClient.get(`${environment.apiUrl}search`, {headers, observe: 'response', params});
}),
catchError(err => {
console.log(err.status);
if (err.status === 400) {
console.log(err.error.message);
}
if (err.status === 401) {
this.authService.logout();
this.router.navigateByUrl('/login', {replaceUrl: true});
}
return EMPTY;
}),
);
}
EDIT: home.page.ts Code where I click the tag that redirects to the page where products with this tag should be shown:
searchByTag(tag: string) {
this.tagsSubscription = this.searchService.searchByTagCall(tag).subscribe((data: any) => {
this.searchService.tag = data;
this.router.navigate(['/tag-search/', tag]);
},
error => {
console.log('Error', error);
});
}
and html:
<ion-chip *ngFor="let tag of tags">
<ion-label class="tag" (click)="searchByTag(tag.tags)">{{ tag.tags }}</ion-label>
</ion-chip>
tag-search.page.ts:
export class TagSearchPage implements OnInit {
tag: string;
products: any = [];
constructor(
private route: ActivatedRoute,
private searchService: SearchService,
) { }
ngOnInit() {
this.showTagProducts();
}
showTagProducts() {
const tag = String(this.route.snapshot.paramMap.get('tag'));
this.searchService.searchByTagCall(tag).subscribe(
(data: any) => {
console.log('Products with tag: ' + tag + ' ' + data);
},
error => {
console.log('Error', error);
});
}
}
Here is how my JSON response looks like:
[
{
"id": 1283,
"name": "Some product name",
"product_code": "470631"
},
{
"id": 786,
"name": "A different product name",
"product_code": "460263"
}
]
I'm trying to create API factory for node.js, Express, Mongoose Tech stack. I also want to provide full type safety using Typescript and the option to add additional validation for creation and updates. For example I have "validator" like that:
type ICheckIfUnique = <M, T extends Model<M>, K extends keyof M>(
attributes: {
model: T;
key: K;
},
body: M
) => void;
export const checkIfUnique: ICheckIfUnique = async (attributes, body) => {
const { model, key } = attributes;
const value = body[key];
const isUnique = !(await model.findOne({ [key]: value }));
if (!isUnique) {
throw `${key} is not unique!`;
}
};
But I can't get the type right as I get:
I can't also find way to get custom model types for the factory methods:
export const factoryCreateEndpoint =
<T, D extends Model<T>>(model: D, additionalLogic?: any) =>
async (req: Request, res: Response): Promise<Response | undefined> => {
const body = req.body;
if (!body) {
return res.status(400).json({
success: false,
error: `You must provide ${capitalize(
model.collection.name.slice(0, -1)
)}.`,
});
}
if (additionalLogic) {
try {
for (const element in additionalLogic) {
const { validator, additionalVariables } = additionalLogic[element];
await validator(additionalVariables, req.body);
}
} catch (error) {
return res.status(400).json({ success: false, error });
}
}
try {
const object = new model(body);
await object.save();
return res.status(201).json({
success: true,
id: object._id,
message: `${capitalize(model.collection.name.slice(0, -1))} created.`,
});
} catch (error) {
return res.status(400).json({ success: false, error });
}
};
But with this generic type I get:
can some please point where the issue is.
the problem that i encounter is i have a controller that i added an attribute Authorize. So, when i try to access the actionResult GETDATA it says unable to find the action. but if remove the attribute Authorize, it's working as expected.
So everytime i make a request i add a jwt token on the header.
Here are codes:
**Angular 8 HttpInterceptor**
const currentUser = this.authenticationService.currentUserValue;
//if (currentUser && currentUser.authData) {
if (currentUser && currentUser.Token) {
debugger;
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.Token}`,
CurrentTabID: `${currentUser.CurrentTabID}`
}
});
}
**MyController**
[Authorize]
[ApiController]
[Route("[controller]")]
public class PatientController : ControllerBase
{
[HttpGet("GetTestData")]
//--These is the one i can't access
public IActionResult GetTestData()
{
return Ok("");
}
[AllowAnonymous]
[HttpGet("GetTestDataOne")]
public IActionResult GetTestDataOne()
{
return Ok("Hi John");
}
}
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=.; Database=blah;persist security info=True;user id=blah;password=blah;"
},
"AllowedHosts": "*",
"ApplicationSettings": {
"Secret": "1234567890123456",
"ClientURL": ""
}
}
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<PPMPBookingContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:Secret"].ToString());
// configure strongly typed settings objects
//var appSettingsSection = Configuration.GetSection("AppSettings");
//services.Configure<AppSettings>(appSettingsSection);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer="vlad",
ValidAudience="Client"
};
});
// configure DI for application services
services.AddScoped<IUserService, UserService>();
services.AddScoped<IPracticeService, PracticeService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
AccountController
public UserInfo Authenticate(int businessID, string username, string password)
{
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config.GetSection("ApplicationSettings:Secret").Value);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.ID.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
userInfo.Token = tokenHandler.WriteToken(token);
byte[] bytes = Encoding.GetEncoding(28591).GetBytes($"{businessID}{username}");
userInfo.AuthData = System.Convert.ToBase64String(bytes);
user.Password = null;
userInfo.User = user;
userInfo.BusinessID = businessID;
userInfo.Practice = _practiceService.PracticeInfo(businessID);
userInfo.CurrentTabID = Guid.NewGuid().ToString();
return userInfo;
}
I am using angular material. I am handling common error response in the handleError method in my service.ts file. I want to popup material dialog when I get an error instead of alert from service.ts file.
How can I implement this?
I am new to the angular material.
Code:
export class CommonService {
public api = 'https://URl'
public showSpinner: boolean = false;
public auth: boolean = false;
public fetch: boolean = false;
constructor(public http: Http) { }
postCall() {
this.showSpinner = false;
this.auth = false;
this.fetch = false;
var header = {
"headers": {
"content-type": "application/x-www-form-urlencoded",
}, "grant_type": "password",
"scope": "user",
"client_id": "4874eafd0f7a240625e59b2b123a142a669923d5b0d31ae8743f6780a95187f5",
"client_secret": "908f6aee4d4cb27782ba55ae0c814bf43419f3220d696206212a29fe3a05cd88",
"auth_token": "azd4jXWWLagyb9KzgfDJ"
};
return this.http.post(this.api + '/oauth/token.json', header)
.map(response => {
this.showSpinner = true;
this.auth = true;
this.fetch = false;
setTimeout(function () {
let result = response.json();
window.localStorage.setItem('access_token', result.access_token);
}, 4000);
return true;
})
.catch(this.handleError)
}
getCaseStudy() {
this.showSpinner = true;
this.auth = false;
this.fetch = true;
let headers = new Headers();
let token = window.localStorage.getItem('access_token');
headers.append('Authorization', 'Bearer ' + token);
headers.append('content-type', 'application/json');
let Hdata = new RequestOptions({ headers: headers })
return this.http.get(this.api + '/upend URl', Hdata)
.map(response => {
this.showSpinner = false;
this.fetch = false;
this.auth = false;
return response.json()
})
.catch(this.handleError);
}
private handleError() {
return Observable.throw(
alert('problem somewhere')
)
}
}
Thanks in advance.
You can create a component ErrorDialog that would be at root level. In AppComponent subscribe a subject declared in CommonService that will provide boolean value.
In CommonService you can do this as:
private subject = new Subject<any>();
updateDialog(isVisible: boolean) {
this.subject.next({ isVisible: isVisible });
}
getDialogVisibility(): Observable<any> {
return this.subject.asObservable();
}
handleError(error: any) {
...
this.updateDialog(true);
...
}
In your component subscribe getDialogVisibility and whenever value is being changed from service you can get to know if dialog should be displayed.
AppComponent
#Component({
selector: 'app-root',
template:`
<router-outlet></router-outlet>
<error-dialog></error-dialog>
`
})
export class AppComponent implements OnDestroy {
subscription: Subscription;
constructor(private commonService: CommonService) {
this.subscription = this.commonService.getDialogVisibility().subscribe(isVisible => {
if(isVisible) {
openErrorDialog();
}
});
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
openErrorDialog() {
// write your code
}
}
What I'm Using
Angular 5
AngularFire5
Firebase & Firestore
What I'm Trying to Achieve
I am trying to make a simple authentication/login & registration system. I actually have one already made, though I am running into some issues, and I want to make sure I am going about the best way to setup authentication.
What I have So Far
auth.service.ts
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { AngularFireAuth } from 'angularfire2/auth';
#Injectable()
export class AuthService {
authState: any = null;
email = '';
username = '';
password = '';
errorMessage = '';
error: {name: string, message: string} = {name: '', message: ''};
constructor(private afAuth: AngularFireAuth, private router: Router) {
this.afAuth.authState.subscribe((auth) => {
this.authState = auth
});
}
get isUserEmailLoggedIn(): boolean {
if (this.authState !== null) {
return true
} else {
return false
}
}
get currentUser(): any {
return (this.authState !== null) ? this.authState : null;
}
get currentUserId(): string {
return (this.authState !== null) ? this.authState.uid : ''
}
get currentUserName(): string {
return this.authState['email']
}
signUpWithEmail(email: string, password: string) {
return this.afAuth.auth.createUserWithEmailAndPassword(email, password)
.then((user) => {
this.authState = user
})
.catch(error => {
console.log(error)
throw error
});
}
loginWithEmail(email: string, password: string) {
return this.afAuth.auth.signInWithEmailAndPassword(email, password)
.then((user) => {
this.authState = user
})
.catch(error => {
console.log(error)
throw error
});
}
signOut(): void {
this.afAuth.auth.signOut();
this.router.navigate(['/'])
}
onSignUp(): void {
this.clearErrorMessage()
if (this.validateForm(this.email, this.password)) {
this.signUpWithEmail(this.email, this.password)
.then(() => {
this.router.navigate(['/home'])
}).catch(_error => {
this.error = _error
this.router.navigate(['/register'])
})
}
}
onLoginEmail(): void {
this.clearErrorMessage()
if (this.validateForm(this.email, this.password)) {
this.loginWithEmail(this.email, this.password)
.then(() => this.router.navigate(['/home']))
.catch(_error => {
this.error = _error
this.router.navigate(['/login'])
})
}
}
validateForm(email: string, password: string): boolean {
if (email.length === 0) {
this.errorMessage = 'Please enter Email!'
return false
}
if (password.length === 0) {
this.errorMessage = 'Please enter Password!'
return false
}
if (password.length < 6) {
this.errorMessage = 'Password should be at least 6 characters!'
return false
}
this.errorMessage = ''
return true
}
clearErrorMessage() {
this.errorMessage = '';
this.error = {name: '', message: ''};
}
}
link.service.ts
import { Injectable, OnInit } from '#angular/core';
import { AuthService } from './auth.service';
import { AngularFirestore, AngularFirestoreDocument, AngularFirestoreCollection } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
export interface Link { uid: string; url: string; shortURL: string; clicks: number }
#Injectable()
export class LinkService implements OnInit {
url: string;
shortURL: string;
showAlert: boolean;
links: Observable<any>;
constructor(public authService: AuthService, private afs: AngularFirestore) {
this.links = afs.collection('Links').valueChanges();
}
ngOnInit() {
}
createShortURL() {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var length = 6;
for(var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return this.shortURL = text;
}
addLink() {
if (this.authService.isUserEmailLoggedIn) {
this.createShortURL();
this.afs.collection('Links').doc(this.shortURL).set({
'uid': this.authService.currentUserId,
'url': this.url,
'shortURL': this.shortURL,
'clicks': 0
});
this.clearFields();
this.showAlert = false;
} else {
this.showAlert = true;
}
}
clearFields() {
return this.url = '';
}
}
Where I'm Stuck
With the information provided. I am trying to get the currentUserID in the link.service.ts though it is coming back as undefined. However with the addLink() function, this.authService.isUserEmailLoggedIn works perfectly fine, and Im not sure why it is not returning the correct value otherwise.
You need to get id like this.
this.items = this.itemCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data();
data.id = a.payload.doc.id;
return data;
});
});
currentUserId() is a function you should use 'uid': this.authService.currentUserId(),