I am working with Angular reactive forms and async validation and instead of a normal value I want to send Json file for validation.
My form Looks like
createGoogleCloudForm = new FormGroup(
{
name: new FormControl('', [Validators.required, Validators.pattern(RegexUtil.cloudName)]),
organizationId: new FormControl(''),
config: new FormControl(),
//zones: new FormControl({ value: '', disabled: true }, Validators.required),
},
undefined,
//[GoogleCloudCredentialsValidator.checkGoogleCloudCredentials(this.cloudCredentialsCheckerService)]);
My HTML file looks like
<form class="form" [formGroup]="createGoogleCloudForm" (ngSubmit)="createGoogleCloudCredential()">
<div class="image-upload">
<label for="background-upload" class="button button--outline display--inline-flex">Choose Config File</label>
<span class="file-name">
{{ googleCloudFormData?.get('config')?.name | truncate: 40:'...'
}}
</span>
<input
type="file"
id="background-upload"
class="hidden-input"
formControlName="config"
(change)="fileChange($event.target.files, 'config')"
/>
</div>
</div>
<div class="display--flex justify--content--end">
<button class="button button--primary display--inline-flex" type="submit" [disabled]="!createGoogleCloudForm.valid">
<svg-icon key="circle-plus"></svg-icon> Add Cloud Credentials
</button>
</div>
</form>
The file change method is like
fileChange(files: File[], controlName: string) {
console.log(files);
if (files && files.length > 0) {
this.googleCloudFormData.append(controlName, files[0]);
console.log('adding');
this.createGoogleCloudForm.setAsyncValidators([
GoogleCloudCredentialsValidator.checkGoogleCloudCredentials(this.cloudCredentialsCheckerService),
]);
}
}
The Async Validator is
static checkGoogleCloudCredentials(cloudCredentialsCheckerService: CloudCredentialsCheckerService): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> =>
// half a second delay to prevent BE requesting after each user's key stroke
timer(500).pipe(
switchMap(() => cloudCredentialsCheckerService.checkGoogleCloud(control.get('config').value)),
map(() => null),
catchError(() => of({ invalidGoogleCloudCredentials: true })),
);
}
and the service
checkGoogleCloud(file: File[]) {
return this.http.post(`${this.baseUrl}/google`, { file });
}
The problem is when I upload the file the async validator does not get fired, I want to send the file with the request.
Any ideas?
Related
// register-page.ts
this.registerationForm = new FormGroup(
{
username: new FormControl(null,
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(30),
Validators.pattern('^[a-zA-Z-0123456789]*$'),
]
),
// accountService.ts
validateUsername(username: string): Observable<any> {
return this.httpManager.post(authServer + "username-validator", new ValidateUsernameRequest(username)).pipe(
map(
(response: Response) => {
return response.data;
}
)
);
}
// register-page.html
<ion-item [ngClass]="username==null ? 'at-beginning':''">
<ion-label position="floating">Kullanıcı Adı</ion-label>
<ion-input name="username" formControlName="username" inputmode="text" class="ion-text-lowercase"
placeholder="Kullanıcı Adı" (onchange)=(checkUsername($event)) (keypress)=(onKeyUp($event)) (keydown.space)="$event.preventDefault()">
</ion-input>
</ion-item>
<div class="err" *ngIf="formControls.username.errors">
<div *ngIf="formControls.username.errors.required">Kullanıcı adı zorunlu.</div>
<div *ngIf="formControls.username.errors.minlength">Kullanıcı adınız çok kısa.</div>
<div *ngIf="formControls.username.errors.maxlength">Kullanıcı adınız çok uzun.</div>
<div *ngIf="formControls.username.errors.pattern">Kullanıcı adınız özel karakter içeremez.</div>
<div *ngIf="formControls.username.errors.checkUsername">Kullanıcı adınız alınmış.</div>
</div>
I've tried to code a validator for the username that checks its availability whenever the user make changes on the input. I've fight for it two days but I'm just stuck. I understood that I need to use an async function that subscribes the data came from accountService.validateUseranem(control.value) but somehow I failed to make it work.
Can someone help me on this?
I did the following and fixed my own problem.
Created username-validator.directive.ts
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { AccountService } from 'src/app/services/yon/auth/account.service';
export function existingUsernameValidator(userService: AccountService): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
return userService.validateUsername(control.value).pipe(map(
(data) => {
console.log("The result is : " + data)
return (!data) ? { "usernameExists": true } : null;
}
));
};
}
Then used it inside of the validators of username
this.registerationForm = new FormGroup(
{
username: new FormControl(null, {
validators: [
Validators.required,
Validators.minLength(3),
Validators.maxLength(20),
],
asyncValidators: [existingUsernameValidator(this.accountService)],
updateOn: 'change'
}),
email: new FormControl(null, {
validators: [
Validators.required,
Validators.email],
asyncValidators: [existingEmailValidator(this.accountService)],
updateOn: 'change'
}),
like this
Also;
updateOn: 'change'
Understands if the input changes on that control and whenever the input changes, it checks the value for validation.
And becasue that I need to send request to API and use that value returns as a response , my validator needed to be an asynchronous validator. Also the sync and aysnc validators need to be splitted as far as I understand, like I did.
So I am facing with an error which it doesn't send the cropped image to the backend with multer.
I can upload the file in frontend and then I crop the image but the cropped image is not sent in the backend, but it is the uploaded img sent to the backend.
I am using the cropper.js at Angular 11 version with a module that someone did develop for Angular.
this is the cropper.js https://fengyuanchen.github.io/cropperjs/
And this is the module https://github.com/matheusdavidson/angular-cropperjs
So my idea it is like this.
Upload photo -> crop it and save it in a folder at backend.
The save works but it saves the default img not the cropped img.
When user uses click event to saveImage , take the cropped image and save it in the backend.
This is my UI and methods.
<angular-cropper #angularCropper
[cropperOptions]="imgConfig"
[imageUrl]="imgUrl | safeurl"></angular-cropper>
<div class="btn-group">
<label class="btn btn-primary btn-upload" for="inputImage" title="Upload image file" >
<input type="file" class="sr-only" id="inputImage" name="file" accept="image/*" (change)="fileChangeEvent($event)">
<span class="docs-tooltip" data-toggle="tooltip" title="" data-original-title="Import image with Blob URLs">
<span class="fa fa-upload"></span>
</span>
</label>
</div>
<button type="button" class="btn btn-primary" data-method="crop" title="Crop" (click)="saveImage()">
<span class="docs-tooltip" data-toggle="tooltip" title="" data-original-title="cropper.crop()">
<span class="fa fa-check"></span>
</span>
</button>
TS file
imgUrl;
imageURL;
imageCrop;
#ViewChild("angularCropper", {static: false}) public angularCropper: CropperComponent;
public imgConfig = {
aspectRatio : 3/4,
dragMode : "move",
background : true,
movable: true,
rotatable : true,
scalable: true,
zoomable: true,
viewMode: 1,
checkImageOrigin : true,
checkCrossOrigin: true,
width: 0,
height: 0,
};
fileChangeEvent(event: any): void {
this.imgUrl = URL.createObjectURL(event.target.files[0]);
this.imageCrop = event.target.files[0];
}
saveImage() {
this.angularCropper.cropper.crop();
this.imageService.addImage(this.imageCrop).subscribe((res: any) => {
if (res.body) {
this.imageService.getImageByID(res.body._id).subscribe((t: Image) => {
this.imageURL = t.imageUrl;
console.log(this.imageURL);
});
}
}, (err: any) => {
console.log(err);
});
}
And this is my service
#Injectable({
providedIn: "root"
})
export class ImageService {
apiUrl = environment.backend;
constructor(private http: HttpClient) { }
addImage(file: File): Observable<any> {
const formData = new FormData();
formData.append("file", file);
const header = new HttpHeaders();
const params = new HttpParams();
const options = {
params,
reportProgress: true,
headers: header
};
const req = new HttpRequest("POST", `${this.apiUrl}/images/${file.name}`, formData, options);
return this.http.request(req);
}
getImageByID(id: string): Observable<any> {
const url = `${this.apiUrl}/${id}`;
return this.http.get<Image>(url).pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse): any {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
return throwError(
'Something bad happened; please try again later.');
}
}
you will the cropped image from the output of cropper component.. export is the output name
i need create a form after send request to database and get reponse from server and fill form with that data .
i write this code for send request and fill form from that response :
data: any;
accountSettingFG: FormGroup;
constructor(private formBuilder: FormBuilder, private accountsettingService: AccountSettingService) {
}
ngOnInit(): void {
this.FetchData().then(res=>{
this.InitialForm();
});
}
InitialForm(): void {
this.accountSettingFG = this.formBuilder.group({
twoFactorAuthentication: [this.data.twoFactorAuthentication],
email: [this.data.email],
sms: [this.data.sms]
})
}
FetchData() {
let promiss = new Promise((resolve, reject) => {
this.accountsettingService.GetListItem('/SiteOption/AccountSecurity')
.toPromise()
.then(res => {
this.data= res.result['accountSecuritySettingsModel']
resolve();
}, msg => {
reject(msg);
})
})
return promiss;
}
and this is my Html Code :
<form
class="form"
id="postform"
[formGroup]="accountSettingFG"
(ngSubmit)="onSubmit()"
autocomplete="off"
>
<mat-slide-toggle color="primary" formControlName="twoFactorAuthentication">
{{ "SETTING.ACCOUNT_SCURITY.TOW_FACTOR_ACUTHENTICATION" | translate }}
</mat-slide-toggle>
<mat-slide-toggle color="primary" formControlName="email">
{{ "SETTING.ACCOUNT_SCURITY.EMAIL" | translate }}
</mat-slide-toggle>
<mat-slide-toggle color="primary" formControlName="sms">
{{ "SETTING.ACCOUNT_SCURITY.SMS" | translate }}
</mat-slide-toggle>
</form>
but when i run the project it show me this error :
ERROR Error: formGroup expects a FormGroup instance. Please pass one in.
Example:
<div [formGroup]="myGroup">
<input formControlName="firstName">
</div>
In your class:
this.myGroup = new FormGroup({
firstName: new FormControl()
});
at Function.missingFormException (forms.js:2283)
at FormGroupDirective._checkFormPresent (forms.js:7490)
at FormGroupDirective.ngOnChanges (forms.js:7280)
at checkAndUpdateDirectiveInline (core.js:33257)
at checkAndUpdateNodeInline (core.js:46077)
at checkAndUpdateNode (core.js:46016)
at debugCheckAndUpdateNode (core.js:47039)
at debugCheckDirectivesFn (core.js:46982)
at Object.updateDirectives (account-option.component.html:3)
at Object.debugUpdateDirectives [as updateDirectives] (core.js:46970)
whats the problem ? how can i solve this problem ?
I'd suggest initialising the form with empty values before sending the request. This should avoid the issue. Therefore, you need to update the form when your response is received.
I'm not familiar with Angular > 1. Might be that it's introducing other issues. Anyway, I hope it helps.
data: any;
accountSettingFG: FormGroup;
constructor(private formBuilder: FormBuilder, private accountsettingService: AccountSettingService) {
}
ngOnInit(): void {
this.accountSettingFG = this.formBuilder.group({
twoFactorAuthentication: [''],
email: [''],
sms: ['']
});
this.FetchData().then(res => {
this.InitialForm();
});
}
InitialForm(): void {
this.accountSettingFG.setValue(this.data);
}
FetchData() {
return this.accountsettingService.GetListItem('/SiteOption/AccountSecurity')
.toPromise()
.then(res => {
return res.result['accountSecuritySettingsModel'];
})
}
I'm trying to render a dynamic FormArray (When "+" is clicked it should add a new), but always when I put some file in the input box the Message ("Nenhum Arquivo Selecionado" which means "File Doesn't Exist") stays on the screen.
However, if I check the info on this.filterForm.get('Documents'), the row is filled correctly.
Does anyone have a sugestion to fix this error?
protocolo.component.ts
items: FormArray;
filterForm = new FormGroup({
IdProtocolo: new FormControl(),
Documentos: this.formBuilder.array([ this.createItem() ]
);
ngOnInit() {
this.items = this.filterForm.get('Documentos') as FormArray;
}
createItem(): FormGroup{
return this.formBuilder.group({
filename: '',
filetype: '',
value: ''
})
}
addItem(){
this.items.push(this.createItem());
}
removeItem(index){
if(this.items.length > 1) {
this.items.removeAt(index);
}
}
onFileChange(event: any, index: any) {
let reader = new FileReader();
if(event.target.files && event.target.files.length > 0) {
let file = event.target.files[0];
reader.readAsDataURL(file);
this.items.at(index).patchValue({
filename: file.name,
filetype: file.type,
value: (reader.result as string).split(',')[1]
})
}
}
protocolo.component.html
<div *ngFor="let item of filterForm.value.Documentos; let i = index;">
<div class="row" style="margin-bottom: 10px;">
<div class="col-md-4">
<input type="file" formControlName="Documentos" (change)="onFileChange($event, i)">
</div>
<div class="col-md-8">
<button class="btn btn-success-tce" (click)="addItem()">+</button>
<button class="btn btn-success-tce" (click)="removeItem(i)"style="margin-left: 5px">-</button>
</div>
</div>
[Updated] Possibly wrong implementation of formArray. I cannot see a formArrayName in your template. I would have implemented this like
In your template
<p> Dynamic File Form </p>
<form [formGroup]="someForm" (submit)="formSubmit()">
<div formArrayName="documents">
<div *ngFor="let item of files?.controls; let i = index;">
<input type="file" placeholder="Upload file" [formControlName]="i" (change)="onFileChange($event, i)"/>
</div>
</div>
<button type="submit"> Submit </button>
</form>
<button type="button" (click)="addFileControl()"> Add File </button>
In your component.
initForm() {
this.someForm = this.fb.group({
documents: this.fb.array([this.fileControl])
})
}
get files() {
return this.someForm.get('documents') as FormArray;
}
get fileControl() {
return this.fb.group({
file_item: [null]
})
}
addFileControl() {
this.files.push(this.fileControl);
}
formSubmit() {
console.log(this.someForm.value);
}
onFileChange(event, i) {
let reader = new FileReader();
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
reader.readAsDataURL(file);
reader.onload = () => {
this.files.controls[i].get('file_item').setValue(reader.result);
// need to run CD since file load runs outside of zone
this.cd.markForCheck();
};
}
}
Here is the stackblitz example. This will give you the output in base64 format but you can also get it in file format by modifying.
onFileChange(event, i) {
if (event.target.files && event.target.files.length) {
this.files.controls[i].get('file_item').setValue(event.target.files;);
}
}
Note:- It is just a rough code but does the job :).
So I am creating a form where people can upload a profile picture to firebase. I want it to upload like profile_image_url: 'urlstringvalue'. However it is uploading like so:
This is how I am creating the form :
ngOnInit() {
this.signUpFormGroup = this._formBuilder.group({
profile_image_url: ''
});
}
and the html:
<form [formGroup]="signUpFormGroup">
<div class="form_input" >
<label for="invoice-upload" class="bold-800 document-upload text-center"
*ngIf="!signUpFormGroup.get('profile_image_url').value">+<br>
<div class="upload text-center">upload</div>
<input formGroupName="profile_image_url" (change)="uploadProfileImage($event)" type="file" class="input-file" id="invoice-upload">
</label>
<img
[src]="signUpFormGroup.get('profile_image_url').value" alt="" class="document-image">
</div>
</form>
I am storing the url like so:
uploadProfileImage(file) {
this._storage.uploadFile(file, `user/${this._auth.currentUserId}/profile`)
.subscribe(() => {
this._storage.downloadURL.subscribe(url => {
this.signUpFormGroup.get('profile_image_url').patchValue(url);
});
});
}
and then finally uploading to firebase database as follows:
setProfileImageAndGender(profile_image_url: string) {
return fromPromise(this.afs.collection('users').doc(this.userDb.uid)
.update({
profile_image_url,
}, ));
}
Anyone have any idea why it is creating the object and not just passing the string?