I´m working on an app like google forms, we have a form with questions, we get these questions from an api and dynamically render the form. the problem is that when i want to add validation to the form with angular reactive forms, the mat-select stops working and every time i select a value, the select is clear.
I already try to adapt this https://medium.com/aubergine-solutions/add-push-and-remove-form-fields-dynamically-to-formarray-with-reactive-forms-in-angular-acf61b4a2afe with no luck
template
<mat-horizontal-stepper [linear]="true" #stepper style="background: transparent">
<mat-step *ngFor="let materia of materias;let m = index;" [stepControl]="createForm(materia.id)">
<ng-template matStepLabel>{{materia.descripcion}}</ng-template>
<form [formGroup]="getForm(materia.id)">
<div fxLayout="column" fxLayoutAlign="space-around stretch" fxLayoutGap="10px">
<div>
<mat-card *ngFor="let competencia of materia.competencias">
<mat-card-header>
<mat-card-title>{{competencia.nombre}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<div fxLayout="column" fxLayoutGap="30px" formArrayName="{{createFormArray(materia.id,competencia.id)}}">
<div fxLayout="row">
<i>{{competencia.descripcion}}</i>
</div>
<div fxLayout="column" fxLayoutGap="2px">
<span *ngFor="let competenciaSimple of competencia.competenciasSimples; let i = index;" fxLayout="row">
<mat-form-field appearance="outline"
*ngIf="competenciaSimple !== null && competenciaSimple !== undefined" fxFlex>
<mat-select (selectionChange)="onSelected($event,competenciaSimple)"
[value]="getSelectedValue(competenciaSimple)" [formControl]="createControl(materia.id,competencia.id)" [id]="i">
<span *ngFor="let fase of competenciaSimple.fases">
<mat-option *ngFor="let conductaObservable of fase.conductasObservables"
[value]="conductaObservable">
{{conductaObservable.descripcion}}
</mat-option>
</span>
</mat-select>
</mat-form-field>
<!-- <input type="hidden" value="competenciaSimple" [(ngModel)]="calificacion.conductasObservables[i].competenciaSimple"/> -->
</span>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
<div fxLayout="row">
<button mat-flat-button type="button" matStepperPrevious *ngIf="!isFirstPage" color="primary"
fxFlex="20">ANTERIOR</button>
<span fxFlex="80"></span>
<button mat-flat-button type="button" *ngIf="!isLastPage" color="primary" fxFlex="20">SIGUIENTE</button>
<button mat-flat-button type="button" (click)="onSubmit()" *ngIf="isLastPage" color="accent"
fxFlex="20">TERMINAR</button>
</div>
</div>
</form>
</mat-step>
</mat-horizontal-stepper>
component.ts
forms = new Array<FormGroup>();
getForm(index: number): FormGroup {
return this.forms[index];
}
createForm(index: number): FormGroup {
const form = this.formBuilder.group({});
this.forms[index] = form;
return form;
}
createFormArray(index: number, name: string): string {
const array = this.formBuilder.array([]);
const arrayName = `competencia_${name}`;
this.forms[index].addControl(arrayName, array);
return arrayName;
}
createControl(index: number, arrayName: string): FormControl {
const control = this.formBuilder.control(false);
const an = `competencia_${arrayName}`;
const array = (this.forms[index].get(an));
(array as FormArray).push(control);
return control;
}
getControlId(): string {
this.controlNumber += 1;
return this.controlNumber.toString()
}
The idea is to validate the mat-select as required so the stepper cant advance when no value is selected, that works if a add the Validator, but the mat-select doesn´t persist the value so if i remove the [formControl]="createControl(materia.id,competencia.id)" the mat-select work but no validation result.
There are several popular open source Angular Form Libraries (you can take a look at the source code and see how they dynamically add validation's to a control):
GitHub: ng-dynamic-forms
GitHub: ngx-formly
GitHub: angular-schema-form (AngularJS) - Generate forms from a JSON schema
GitHub: ngx-schema-form - HTML form generation based on JSON Schema
GitHub: angular2-json-schema-form - Angular 2 JSON Schema Form builder
Related
I have an Angular application where the user has multiple choices with buttons and when a button is pressed another component will display. The component depends on the user's choice.
One thing I'm trying to implement is the styling of the buttons to make it clear which choice has been selected.
<div fxLayout="row" fxLayoutAlign="space-between center" fxFill>
<div *ngFor="let button of buttons">
<button
mat-stroked-button
*ngIf="button.type === 'button'"
(click)="buttonPressed()"
ngxScrollTo
>
{{ button.text }}
</button>
</div>
<div fxLayout="row" fxLayoutAlign="space-between center">
<div *ngIf="item.hasSomeProperty | isTypeButton">
<button mat-mini-fab (click)="buttonPressed()">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
I have also attached a picture of what im trying to achieve here:
Any help would be much appreciated.
Simply use [ngClass] or [ngStyle]:
<div *ngFor="let button of buttons">
<button
mat-stroked-button
*ngIf="button.type === 'button'"
[ngClass]="{'disabledButton': !button.selected}"
(click)="buttonPressed(button)"
ngxScrollTo
>
{{ button.text }}
</button>
</div>
Assuming that your button model contains the "selected" property (or you have some other model storing information which button was actually clicked):
buttonPressed(button: Button) {
// Mark only button as selected
this.buttons.forEach(b => b.selected = b === button);
}
And of course add some class in the css:
.disabledButton {
opacity: 0.75;
}
Note - writing this from top of my head (since no stackblitz was provided) so some adjustments might be needed.
I am working on an ionic/angular application. My requirement is to pickup files from the device file manager and upload to server after submitting. I did this using the pick file functionality in ionic cordova.
But I'm getting a problem in adding validators to this pickfile. Even without adding files and clicking submit its taking. I want to add required field for this as I added for the subject line.
Below is my code.
HTML file:
<form [formGroup]="sendDocsForm">
<ion-item>
<ion-label>Subject: </ion-label>
<ion-input type="text" formControlName="subject" required></ion-input>
</ion-item>
<div class="error-messages">
<ion-label
*ngIf="sendDocsForm.get('subject').hasError('required') && (signupMobile.get('subject').dirty || signupMobile.get('subject').touched)"
color="danger">
Enter Subject line</ion-label>
</div>
<ion-item>
<div (click)="pickFile()">
<ion-icon name="add"></ion-icon>
<p style="margin:0px;">Add Files</p>
</div>
</div>
<div class="class-addfiles">
<div *ngFor="let item of docPaths; let i=index">
<p (click)="editDocRules(item)">{{ item.name }} </p>
</div>
</div>
Submit
.TS file:
this.sendDocsForm = this.fb.group({
subject: new FormControl ('', Validators.compose([ Validators.required])),
});
pickFile() {
const filter = { mime: 'application/pdf' };
this.fileChooser.open().then(fileuri => {
this.filePath.resolveNativePath(fileuri).then(resolvednativepath => {
console.log(resolvednativepath);
this.returnPath = resolvednativepath;
this.fileName = resolvednativepath.substring(
resolvednativepath.lastIndexOf('/') + 1
);
this.fileType = this.fileName.substring(
this.fileName.lastIndexOf('.') + 1
);
const docPath: ItemDoc = new ItemDoc(
Date.now(),
this.fileName,
this.returnPath
);
this.docPaths.push(docPath);
});
});
}
I want to make the {{item.name}} in the p Tag of the add file as a required field but I don't understand how to do this. Please let me know how I can do this.
Thank you.
I need to get all the strings contained into Assignes array which is a property of Atm object and put them in all the dynamic input, one for each string giving opportunity of updating them or deleting.
How can i do by using FormArray?
atm: Atm; //has an array of string as property
get Assignes() {
return this.virtualForm.get('Assignes') as FormArray;
}
addAssigne() {
this.Assignes.push(this.formBuilder.group({ point: '' }));
}
deleteAssigne(index) {
this.Assignes.removeAt(index);
}
buildForm() {
this.form = new FormGroup({
model: new FormControl(this.atm.Model , Validators.required),
vendor: new FormControl(this.atm.Vendor , Validators.required),
Assignes : this.formBuilder.array([this.formBuilder.group({point : ''})]),
}
<div formArrayName="Assignes">
<div *ngFor="let item of Assignes.controls; let pointIndex=index" [formGroupName]="pointIndex">
<mat-form-field appearance="outline">
<mat-label>Assigne</mat-label>
<input matInput formControlName="point" [readonly]=!isAdmin placeholder="Assigne"/>
</mat-form-field>
<button color="primary" *ngIf="isAdmin" mat-raised-button (click)="deleteAssigne(pointIndex)">Delete assigne</button>
</div>
<button color="primary" *ngIf="isAdmin" mat-raised-button (click)="addAssigne()">Add assigne</button>
</div>
You can do that simply even without using formArray. You just need to run the for loop on the array and render the dynamic input fields.
The code should look like the below:
<div *ngFor="let item of atm; let pointIndex=index" >
<mat-form-field appearance="outline">
<mat-label>Assigne</mat-label>
<input matInput name="assigne{{i}}" [(ngModel)]="item" placeholder="Assigne"/>
</mat-form-field>
<button color="primary" *ngIf="isAdmin" mat-raised-button (click)="deleteAssigne(pointIndex)">Delete assigne</button>
</div>
<button color="primary" *ngIf="isAdmin" mat-raised-button (click)="addAssigne()">Add assigne</button>
The .ts file should get updated by:
atm: Atm; //has an array of string as property
addAssigne() {
this.atm.push('');
}
deleteAssigne(index) {
this.atm.splice(index,1);
}
I am making a basic toggle app in angular.I have two toggle buttons.I am displaying a message in toggle.I want to call the same function on toggle change but do different things depending on which button was toggled.Is it possible or do I have to use two different functions? Thanks in advance for the help.
Here is my template code:
<mat-card class="card" fxLayout="column" fxLayoutAlign="center center" >
<form class="example-form" [formGroup]="formGroup" (ngSubmit)="onFormSubmit()" ngNativeValidate>
<mat-action-list>
<mat-list-item > <mat-slide-toggle
(change)="onChange($event);displayMessage($event)" formControlName="Policy1" >
<span class="label">{{message}}</span></mat-slide-toggle></mat-list-item>
<mat-list-item > <mat-slide-toggle (change)="onChange($event)" formControlName="Policy2">Policy2</mat-slide-toggle></mat-list-item>
</mat-action-list>
<p>Form Group Status: {{ formGroup.status}}</p>
<button mat-raised-button [disabled]="disable" class="button" type="submit">Save Settings</button>
</form>
</mat-card>
The funtion that I'm calling from the toggle button:
displayMessage(e){
if(e.checked)
{
this.message = 'Toggled';
}
else
this.message = 'Slide';
}
The $event object from the (change) emitter has a srcElement that you can refer to when you want to find out which element caused the change.
If you want to retrieve the id of the element that called the function you do so with event.srcElement.id
You can pass more arguments to the handler:
<mat-slide-toggle *ngFor="let item of thingsToDo; let i = index"
(change)= "onToggle($event, item.id, i)">
onToggle = (event, id, indexInArray) => { //...
As I mentioned in title, I want to iterate by button click over my json response. See below my json file:
{
"id": 2,
"name": "Słownictwo",
"flashcardLists": [
{
"id": 17,
"frontside": "dasfasdv",
"backside": "csdascd"
},
{
"id": 18,
"frontside": "dsadsaad",
"backside": "sdasdadad"
},
{
"id": 19,
"frontside": "dasdsadd",
"backside": "sdaddsa"
}
]
}
FlashcardHTML:
<div class="flip-container" (click)="flip()" [class.flipped]="flipped" >
<mat-card-header></mat-card-header>
<mat-card-content *ngFor="let flashcard of flashcardLists" class="flipper">
<mat-card class="front">
{{flashcard.frontside}}
</mat-card>
<mat-card class="back">
{{flashcard.backside}}
</mat-card>
</mat-card-content>
</div>
I have component where in one time I want only one "frontside" and "backside".
Next, it will be replaced by button clicked which will increase counter but I don't know how to do that. I tried something like this flashcard[0].frontside, but it was KO. Maybe someone has encountered the same problem and can help me.
For all answers thanks in advance
If I understand properly, you wanna be flipping a single card.
Something like this should do:
<mat-card-content *ngFor="let flashcard of flashcardLists" class="flipper">
<mat-card [class.front]="!flashcards[i].isFlipped"
[class.back]="flashcards[i].isFlipped"
(click)="flashcards[i].isFlipped = !flashcards[i].isFlipped "
>
{{ flashcards[i].isFlipped ? flashcard.backside : flashcard.frontside }}
</mat-card>
</mat-card-content>
Just add this map of card states to y0our component:
flashcards: { [key: string]: boolean } = {};
You don't need an *ngFor in your mat-card-content, instead bind it to a flipCard variable, which will change on button iterate click. As for the front or back, use another variable isFront which you'll update on flip button click. something like this:
component.html:
<mat-card>
<mat-card-header></mat-card-header>
<mat-card-content class="flipper">
<div class="front" *ngIf="isFront">
{{flashcard.frontside}}
</div>
<div class="back" *ngIf="!isFront">
{{flashcard.backside}}
</div>
</mat-card-content>
<mat-card-actions>
<button (click)="iterate()" class="btn-flashcard" mat-button mat-raised-button id="previous">
Następna
</button>
<button mat-button (click)="flip()" [class.flipped]="flipped">LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
component.ts:
flashcardList: any[] // use your flashCard class instead of any
flashcard: any; // use your flashCard class instead of any
isFront = true;
ngOnInit() {
// init first card
// flashcard = first card
}
iterate() {
// logic for getting the next card
// flashcard = next card
// e.g.
let index = this.flashcardList.indexOf(this.flashcard);
this.flashcard = this.flashcardList[index + 1];
}
flip() {
this.isFront = !this.isFront;
}