I am new to AngularJS and I'm messing around trying to see what I can do. I have a simple form:
<div ng-controller="EditContactCtrl">
<div class="row-fluid" ng-repeat="email in contact.emails">
{{email.type}} - {{email.email_address}}
</div>
<form name="emailForm" ng-controller="EditContactCtrl" ng-submit="addEmail()">
<select class="span4" name="type">
<option vlaue="Work">Work</option>
<option vlaue="Personal">Personal</option>
<option vlaue="Other">Other</option>
</select>
<input class="span6" type="text" name="email_address" placeholder="Email Address">
<input type="submit" class="btn span2 pull-right" value="Add"/>
</form>
</div>
In the controller I have a simple action to push the new email address on the contact object e.g.
function EditContactCtrl($scope, Contacts, $routeParams){
$scope.contact = {emails:[], contact_numbers: [], addresses: []};
$scope.isNew = $routeParams.contactId == '';
if(!$scope.isNew){
$scope.contact = Contacts.get({contactId: $routeParams.contactId});
}
$scope.addEmail = function(){
$scope.contact.emails.push({type:emailForm.type.value, email_address: emailForm.email_address.value});
console.log($scope.contact.emails);
emailForm.reset();
}
}
When I run this code it all goes through fine and I can see the new email address getting added to the list of emails in the contact object. But the problem is that that UI is not getting updated with the new email address. I am expecting it to spit out the new address above it where I have ng-repeat="email in contact.emails"
It looks like you have declared the same controller "EditContactCtrl" within itself. It may be this nesting issue that is causing your problem. Try removing the ng-controller on your <form> and see if this fixes your problem.
Related
In my database i have many users which has many recipes.
Every recipe has some properties and collection of ingredients.
Below is screenshot
Recipe with all properties
So when user display recipe to edit on page should appear (form) recipe with loaded current data. This is kind of working because i can see the data but i think it's no done good.
I have form which is working fine without array (ingredients). Could you tell me how i should add ingredients to my edit form?
I'd be grateful if you see at my code and give me feedback and hints what i should change.
export class RecipeEditComponent implements OnInit {
#ViewChild('editForm') editForm: NgForm;
recipe: IRecipe;
photos: IPhoto[] = [];
ingredients: IIngredient[] = [];
uploader: FileUploader;
hasBaseDropZoneOver = false;
baseUrl = environment.apiUrl;
currentMain: IPhoto;
constructor(private route: ActivatedRoute, private recipeService: RecipeService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.loadRecipe();
}
loadRecipe() {
this.recipeService.getRecipe(this.route.snapshot.params.id).subscribe(recipe => {
this.recipe = recipe;
this.initializeUploader();
})
}
updateRecipe(id: number) {
this.recipeService.editRecipe(id, this.recipe).subscribe(next => {
this.toastr.success('Recipe updated successfully');
this.editForm.reset(this.recipe);
}, error => {
this.toastr.error(error);
});
}
}
HTML
<div class="container mt-4 border" *ngIf="recipe">
<form #editForm="ngForm" id="editForm" (ngSubmit)="updateRecipe(recipe.id)" >
<h5 class=" text-center mt-2">Recipe details:</h5>
<div class="form-group mt-3">
<label for="city">Name</label>
<input class="form-control" type="text" name="name" [(ngModel)]="recipe.name">
</div>
<div class="form-group">
<app-ingredient-editor [ingredients] = "recipe.ingredients"></app-ingredient-editor>
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
<div class="form-group">
<br>
<p>Add recipes</p>
</div>
<h5 class=" text-center mt-4">Description</h5>
<angular-editor cols=100% rows="6" [placeholder]="'Your description'" [(ngModel)]="recipe.description" name="description"></angular-editor>
</form>
<button [disabled]="!editForm.dirty" form="editForm" class="btn btn-success btn-block mb-5 mt-5">Save changes</button>
</div>
For now it's look like:
Form on page
When i delete ingredient name while changing on the console i have following error:
recipe-edit.component.html:12 ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.
Problem is that part of code:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
But i don't know how to make it working..
How to add add array to template-driven form?
In my case i need to display current ingredients and be able to edit them.
I have tried something like this :
<input class="form-control" type="text" name="ingredient[i].name" [(ngModel)]="ingredient[i].name">
<input class="form-control" type="text" name="ingredient[i].amount" [(ngModel)]="ingredient[i].amount">
But id doesn't work
The problem is that the property name on the form must be defined in order for angular to know which input to update. You're binding name to the same property that the editable model is set to which means the user can edit it and in fact delete it, which isn't good.
The solution is to change it to a unique value that doesn't change. This should work:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="name{{ingredient.id}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="amount{{ingredient.id}}" [(ngModel)]="ingredient.amount">
</div>
</div>
Link to stackblitz showing it working: https://stackblitz.com/edit/angular-10-base-template-q243lw?file=src%2Fapp%2Fapp.component.html
Edit: fixed bug in original post and added link to stackblitz
i have a form which sends the following data to the server:
<!-- This form pulls, using JS event handlers, data from the table record selected -->
<form id="edit_form" action="" method="POST" role="form">
{{ csrf_field() }}
{{ method_field('PATCH') }}
<div class="form-group">
<label>ID do Cartão</label>
<input type="text" class="form-control" name="idcartao_edit" placeholder="ID do Cartão" value="" />
</div>
<div class="form-group">
<label>Nome do aluno</label>
<input type="text" class="form-control"
name="nome_aluno_edit" placeholder="Nome do Aluno" value="" />
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" name="email_edit" placeholder="E-mail" value="" />
</div>
<div class="form-group">
<label>Curso</label>
<select id="curso_edit" class="form-control" name="curso_edit">
<option>Seleccionar curso...</option>
#foreach($curso_list as $curso)
<option value="{{$curso->encrypted_id}}">{{$curso->curso}}</option>
#endforeach
</select>
</div>
<div class="form-group">
<label>Triénio</label>
<!-- Trigerred by a JS event handler when a curso_edit option is selected -->
<select id="trienio_edit" class="form-control" name="trienio_edit">
<option>Seleccionar triénio...</option>
</select>
</div>
<!-- Triggered when user opens a bootstrap modal. Pulls the value from the table record selected -->
<input type="hidden" id="idcartao_original" name="idcartao_original" value="" />
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Editar aluno</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Fechar</button>
</div>
</form>
on the server side, the data inputed by the user is processed like this:
public function update(Request $request, Aluno $aluno)
{
$id_aluno = $aluno->where('id_cartao', '=', $request->input('idcartao_original'))->first()->id;
$aluno = $aluno->find($id_aluno);
$aluno->id_cartao = $request->input('idcartao_edit');
$aluno->nome = $request->input('nome_aluno_edit');
$aluno->email = $request->input('email_edit');
$aluno->trienio_id = decrypt($request->input('trienio_edit'));
$aluno->save();
return redirect()->route('alunos');
}
the problem with this form is that the user can change the id to a different one, and the query will load an entirely non-intended different user. like changing the value from 1 to 2, for example, on this input field:
<input type="hidden" id="idcartao_original" name="idcartao_original" value="" />
so instead of loading a model like this:
ID: 1
ID Cartão: 1011000
Nome: Joseph Wilson
E-mail: joseph.67#gmail.com
it will load a model like this:
ID: 2
ID Cartão: 1011001
Nome: John Black
E-mail: johnblackwilliams#gmail.com
and proceed to change the data of a, like i said, an entirely non-intentional different model.
i've already tried to send encrypted ids using an ajax call to the server but considering the inputs can end up like this:
curso_edit option (the encrypted values are the encrypted ids of the "TAI" option, for example)
<option value="eyJpdiI6ImJqZGJKOGVBOFMyQ0huUXdKVHhFMXc9PSIsInZhbHVlIjoidWpROWRrSUFYREszbTF2Q24zTkVoZz09IiwibWFjIjoiM2QxYWViNzU0NWIyN2M2ZGQzMmMwYWJjYWUxZTAyYzBkOGNlNWQ0MDAzMjIyNzA1YWExNzg5ODA2MmNhYjBiMCJ9">TAI</option>
<option value="eyJpdiI6IjRVMWowT0cwYWxVeWpOXC9YaW0yRlJnPT0iLCJ2YWx1ZSI6IkF1VmZoTGdPT3BsTnR3MWV0bXhGdkE9PSIsIm1hYyI6ImY2ZDRiOGIyZDFmMjdmNjhkYzA4ZDMzNGVmNzY2NWZkYzhiMzA1ODljMmM1Njk3ODA1ZGFkZjQ4MWI5ZGM4MzcifQ==">TAL</option>
and so on...
trienio_edit option
<option value="eyJpdiI6ImpmTjBHSk8zTzQ0NmFLR0t1SkxhVXc9PSIsInZhbHVlIjoiOTFYOEluYXFWQk4xMVYxYk1JUHZ0Zz09IiwibWFjIjoiMWZhZDU0NmE2MWQwODliYzg3ZDEzM2Q5NTM3NWJiYTUxMzM5ZTQyMjMwMDBjZDI2OGE4ODEyZjAzNjk3MTVlYyJ9">2015-2018</option>
<option value="eyJpdiI6IjhvUTNGbnNZbG1Ja0d0NktFZFRNaGc9PSIsInZhbHVlIjoicGdKeWtZa1VEeHpDTzQ5QVFhRHcrdz09IiwibWFjIjoiM2EwM2IzZDY3Y2VmZWI4N2QzNTUyZGVlMTUxNjVkZjFiOWUzNGViYjdiNTFiMGZmMDEwMjBhY2JlYTg3ZDg3MiJ9">2014-2017</option>
and so on...
even if the user doesn't have access to the ids in their raw state, he can still inspect the element using chrome or other browser and pull the encrypted id corresponding to the curso/trienio they want to maliciously change (those present in the options)
so even if i send an ajax call to the server in order to pull an encrypted id corresponding to the item being altered in the form like this:
query by corresponding encrypted curso_edit id/value, encrypted trienio_edit id/value, unique email
the user can inspect the javascript code, replace the encrypted curso_edit, trienio_edit value, and e-mail by another one present in the options above.
how can i fix this ?
p.s - dont be too harsh on me. this problem is somewhat difficult to explain and i've tried my best to explain it, if you need more details say
by the way: i've organized the model by id, id cartao, nome, email and by using relationships, trienio and curso. this is somewhat a school management system, and by virtue of that, anybody that knows this info: course (curso), year (trienio), card id (id cartao, those are school cards with an id on it) ,name (nome) and email of a student will easily guess the columns values. i need something stronger and unguessable. and considering a school environment, those informations are very easily obtainable
I have two questions that are related:
First: I have the following directive, who's purpose is to validate whether an input[type=file] is valid or not, however I have no idea how it does it least of all, what the actual code means, here it is:
angular.module('sccateringApp')
.directive('fileModel', ['$parse', function($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
Like I said, I have no idea what the above code actually does, the explanation I got from the forum where I copied that was that it validated an input type file. Is this correct? (So far I haven't been able to verify if it works or not since it doesn't work with the code I'm using at the moment to validate my forms).
Second: Having the form below, using angular form validation it doesn't allow the submit button to be clicked until the actual inputs inside the form match the validation rules (enter a name for the category, and the description should have a max length of 144 characters). I included the directive into the file input, however the actual ng-model for the form ignores the required in the input type file and just verifies the rules are met for the first two inputs.
Here is my form:
<form method="post" role="form" name="newCategoryForm" ng-submit="submitForm()" enctype="multipart/form-data" novalidate>
<div class="row">
<div class="row">
<div class="col s12">
<div input-field>
<input type="text" name="cat-name" id="cat-name" ng-class="{ 'ng-invalid' : newCategoryForm.catname.$invalid && !newCategoryForm.catname.$pristine }"
ng-model="catname" required>
<label>Nombre</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div input-field>
<textarea class="materialize-textarea" name="cat-description" id="cat-description" length="144"
ng-model="catdescription" ng-maxlength="144" required></textarea>
<label>Descripción</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<h6>Imagen de Fondo</h6>
<div class="file-field input-field">
<div class="btn pink darken-2 waves-effect waves-light">
<span>Archivo</span>
<input type="file" name="cat-bgimg" id="cat-bgimg"
file-model="variable" required>
</div>
<div class="file-path-wrapper">
<input class="file-path" type="text">
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-large pink darken-2 waves-effect waves-light center-button" ng-disabled="newCategoryForm.$invalid">Crear Categoría</button>
</form>
The first two inputs get validated correctly, the third one (file input) doesn't and I don't really know why since the directive got included on the input (I know natively, ngModel doesn't validate file inputs).
Any ideas or suggestions of how can I fix this? I'm really new to Angular, and all the tutorials are pretty much useless. I come from 5 years of experience working on jQuery, and the transition to Angular hasn't been easy at all.
The directive posted above is used to make the submit get the data found in the <input type="file"></input>.
Also, a variable should be initialized in the controller so that the values found inside the form are copied to said variable, then this variable needs to be sent as a parameter inside the ng-submit="submitForm().
Example:
angular.module('sccateringApp')
.controller('newSubcategoryController', function (httpcalls, $scope) {
...
$scope.subcategory = [];
...
$scope.submitForm = function(subcategory){
...
$scope.request.insertSubcategory(subcategory);
}
});
Each ng-model inside the form would be:
<input type="text" ng-model="category.name">
So that the category variable found in the controller acquires that value.
I have a Service who can add by a user. It can be one or more.
I have a button to add a Service
<button type="button" class="btn btn-success" ng-click="addService()">
<span class=" glyphicon glyphicon-plus">
</span>Add Service
</button>
When i click Add Service angular should creare a new Service in a list of services.
I have two textareas for Informations of the Service.
<label for="usr">Name:</label>
<input type="text" class="form-control" id="name"></br>
<label for="usr">Service:</label>
<input type="text" class="form-control" id="service"></br>
When i click on the Add Service Button a knew Service Button should be generated with this textareas.
How can generate that and add the new Service to a list of services?
$scope.services = [];
$scope.addService = function() {
var newService = {
name : 'a name',
service : 'a service'
};
$scope.services.push(newService);
}
and the HTML
<div ng-repeat="service in services"><label for="usr">Name:</label>
<input type="text" class="form-control" value="{service.name}"></br>
<label for="usr">Service:</label>
<input type="text" class="form-control" value="{service.service}"></br></div>
You would use ng-model which will take care of 2 way binding fields to scope object
<label for="usr">Name:</label>
<input ng-model="newService.name"></br>
<label for="usr">Service:</label>
<input ng-model="newService.service"></br>
Then in controller
$scope.addService = function() {
// copy and push newService object to array
$scope.services.push(angular.copy($scope.newService));
// reset newService object to clear fields
$scope.newService = {};
}
If you use a form for this you can use angular validation and move the ng-click to ng-submit on the form. ng-submit won't trigger if validation fails
Below code show binding for a List emails but I am having trouble binding the newly added emails to the $scope.emails (does not contain the new email user added). Any idea?
// emails is a List on server side
// email is a string
so i bind ng-model= email but
but doing the below does not work
$scope.contactInformation.Emails.push(email); --> complains about duplicates
<div ng-repeat="email in emails">
<div class="form-group">
<label for="email">Email</label>
<div class="input-group input-group-sm input-group-minimal">
<span class="input-group-addon">
<i class="linecons-mail"></i>
</span>
<input type="text" class="form-control" ng-model="email" />
</div>
<button class="btn btn-primary" ng-show="$last" ng-click="AddEmail()">Add Email</button>
Controller.js
// modelParams.ContactInformation.Emails = new List(string)() when retrieved on server side
$scope.emails = modelParams.ContactInformation.Emails;
$scope.AddEmail = function () {
$scope.contactInformation.Emails.push({ email: null });
};
I'm amending the answer to avoid confusion... Here's is how it should be done - fill in the blanks to fit it for your scenario.
controller.js
// assuming emails is an array of strings (whether defined locally or from a server)
$scope.emails = ["email1", "email2", ...];
$scope.addEmail = function(){
$scope.emails.push(""); // push an empty array as new not-yet-set email
}
The HTML is largely correct. I would have moved the "add" button to outside of the ng-repeat div instead of relying on $last:
<div ng-repeat="email in emails track by $index">
<input type="text" ng-model="email" />
</div>
<button ng-click="addEmail()">Add Email</button>
EDIT:
The example above would, actually, not work because the ng-model within ng-repeat binds to a primitive (string) email.
There are 2 ways to fix:
approach 1
Make an array of objects. If you get an array of strings from the server, you'd need to convert to an array of objects, like so:
$scope.emails = [];
angular.forEach(arrayOfEmailStrings, function(v, k){ $scope.emails.push({value: v}); });
And access like so:
<input type="text" ng-model="email.value" />
approach 2
Use the $index property:
<input type="text" ng-model="emails[$index]" />