ng-repeat to render empty string - javascript

I want to render an array of possible authors as inputs so that which authors are assigned can be edited.
.form-multiple-item(ng-repeat="author in story.authors")
input(type='text', ng-model="author")
Problem is if I want to add an additional blank input after the existing inputs. How can I do that?
.form-multiple-item(ng-repeat="author in story.authors")
input(type='text', ng-model="author")
input(type='text', ng-model="author")
For example this wouldn't work as author wouldn't be on the correct scope.
If I have story.authors = [""] I want to render an blank input for the user to fill out. But that wouldn't work either as "" just gets ignored by ng-repeat instead of rendering an empty input to be filled. How do I do I either render an empty input or perhaps get another ng-model somewhere inserted into an array in another scope.

I think the Angular way of doing this to "put a dot in your model". Your authors model should be an object instead of a string: { name: '' } With the aforementioned object, you should be able to represent an empty input in your ng-repeat.
The ng-model in the <input> would look like this:
<input type="text" ng-model="author.name" />

#Harry You asked why complicate things?
The reason is so that assignments work. The input controller will assign its value to whatever's in "ng-modal". If it's a normal variable, then the link to the original list will be lost. If it's an attribute of an object, then the link is preserved. Consider the two scenarios:
for author in authors:
author = "joe"
See how authors doesn't get changed?
for author in authors:
author.name = "joe"
Now the connection is preserved.
Does that make sense?

Related

Angular Form Array - Dynamically Add Value to Nested Form Group

I followed the this video to create a reactive form to input data into an Angular application I am working on. It does what I want for the most part, however, I have added an additional control("setNumber") to be added in the reactive form array, but instead of inputting a value through the input fields of "name" and "gender", to enter it into the form I would like the value to auto-populate to the getUserForm group/submittable form automatically based on the iteration of the component .
I would ideally like it to display next to name and gender as well as be placed within the form
I put the code on StackBlitz here, where I just have setNumber(core>service>exerciseInput.service.ts) as its own input field and it does indeed update the form to be submitted...but for some reason there I am getting a type error on stackblitz that I am not getting in VSC. But its the code I am using and it works fine on my machine.
Application view
Anyway from stackblitz I believe I should be able to use property binding somewhere to pass i as a value into the component(not just the view as shown in the span string interpolation) so that the form automatically populates i as the setNumber within the userArray, but I've had no luck in my attempts over the last few days.
The fix should really be something incredibly easy I'm overlooking in the following block of code in input-array.component.html but I just cant get it to work.
<div *ngFor="let u of userArray.controls; index as i">
<span>Set {{ i + 1 }}</span>
<app-input [inputFormGroup]="$any(u)"></app-input>
<button (click)="removeUser(i)">Delete</button>
</div>
I would be incredibly grateful for any help!
Thank you
I'm trying to solve your problem and one of the solution is patching your FormArray via pipe, but I think there you need to implement ControlValueAccessor interface and make communication between parent and child Forms without mutation. Pay attention to IndexedFormPipe
Stackblitz
In your stackblitz, the type errors were fixed by adding form1: FormGroup in InputArrayComponent (I had to add types elsewhere too). Also, there were errors with regards to importing scss files that weren't there.
In any case, I may have misunderstood your question, but if you simply want to pass the value of i+1 to the input component and set that value to the FormControl of "setNumber", you simply add an Input() value to InputComponent (I've called it index):
#Input() index: number;
Then in the InputArrayComponent template I pass i+1 to the inputComponent
<app-input [index]="i+1" [inputFormGroup]="$any(u)"></app-input>
And in ngOninit() for the inputArray I assign the value:
this.inputFormGroup.get('setNumber').setValue(this.index);
Here's the stackblitz:
https://stackblitz.com/edit/angular-ivy-2rwlwu?file=src/app/views/input/input.component.html
--- EDIT ---
Your comment made clear an issue with deleting users – the index doesn't update. To have the index update automatically as users are deleted, you need to have the logic be performed any time the input changes, which can be done with a set function:
#Input() set i(value: number) {
this.index = value;
this.inputFormGroup.get('setNumber')?.setValue(value);
};
The issue with this is that it overwrites any changes to setNumber that the user may have made. There are ways around it, but my gut tells me it doesn't make sense for this value to be edited anyway.
So you could simply replace the input with a label that holds the value of index. The value will still appear in the form group (and in the submitted result), but there's no way for the user to edit it.
I've forked the stackblitz again with the differences:
https://stackblitz.com/edit/angular-ivy-hhhz9m?file=src/app/views/input/input.component.html

label/data mechanism in "OO.ui.ComboBoxInputWidget"

OOUI's OO.ui.ComboBoxInputWidget allows to set an array of
OO.ui.MenuOptionWidget objects in its menu.items config field.
Such an item can have a label and a data field. The data field can
be of type object 1.
Now, if I use a data field of typeobject the value of the
OO.ui.ComboBoxInputWidget will be "[Object object]", as it tries to cast
the data value to a string when a user selects an option item.
So it looks like OO.ui.ComboBoxInputWidget allows only data of type
string in its options. Is that correct?
That would also mean that there is no "label/data" mechanism of the input
field itself. If I've got the following options
[
{ label: "Rot", data: "red" },
{ label: "Gelb", data: "yellow" },
{ label: "Grün", data: "green" }
]
and the user selects the option with label "Gelb" the input field shows
"yellow", not "Gelb". The code example in the official documentation shows this behavior [2]. Did I miss something? Is it possible to show a
label to the user but retrieve the data (object) when calling
getValue on such a field?
1 https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.MenuOptionWidget-cfg-data
[2] https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.ComboBoxInputWidget
This question was originally posted on the wikitech-l mailing list. You can find the thread here: http://markmail.org/message/fesegc3yljqcytzt
In general, the 'data' property for items inside the GroupElement can be strings or objects, as they represent some state of your item. In OO.ui.mixin.GroupElement, the method getItemFromData can then return a specific item based on its data property. If you use an Object for the data, OOUI will use its OO.getHash() to basically stringify your object, so it can make sure it retrieves the right one.
The property 'data' actually comes all the way up the hierarchy chain from OO.ui.Element (if you look at that method, the description of that parameter is the same) -- and at that level, it definitely allows for any sort of data, be it a string or an object.
However, when dealing with specific cases, like that of the ComboBoxInputWidget (the terminology of "input widget" usually suggests something inside a form in OOjs-UI) means that putting the data as an object doesn't make sense usually. Unless your use case requires something very different, we usually want ComboBoxInputWidget to have the 'value' => 'label' pair, so it uses its 'data' property as the 'value' and expects a string.
As for the second part of your question, I am not 100% sure I understood it (please correct me if so) but from what I understand, if you set your OO.ui.MenuOptionWidget items with their data as the value ('red' / 'yellow' / 'green' etc) and the label as the mw.msg that the user sees, then it should work out of the box.
So if you look at the example given in the docs for ComboBoxInputWidget you can set your item's data to the color (value) and the label to the word you want to display, and when the user picks an option, the label needs to show in the ComboBoxInputWidget.
Be aware, though, that if you listen to 'choose' or 'select' event from this input widget, you get the selected item, so if you're projecting that choice into some other input, you should ask for the label (item.getLabel()) and not the data.
Thanks for your explanations.
It looks like OOUI does not support something like that (yet).
An example: You've got a option value "Q7186" and a label value "Marie
Curie". When the user selects "Marie Curie" the user interface will
say: "Q7186". A user might be confused.
I know that some UI frameworks handle this by showing the label of
the selected item to the user in a DIV element or something else and
storing the actual value in an internal variable. When it comes to form
submission and the need to provide an actual "value string" they use a
hidden field and maybe a valueField config option in case the "value"
is not a string but an object (thus allowing to set the string value of
the hidden field from a field within the value object).
Maybe that's something for future development.

Copy empty values in AngularJS

I know how to copy the object via angular.copy(object). But the problem is that it doesn't copy empty values. Is there any way to copy that too?
For now say I have a form which when edited for any field should update the object back to the table row in which I am reflecting the filled in object.
I am capturing the filled in values as user object. Yah ng-model being user.name , user.phone and so on. On clicking update button I am simply doing
$scope.formElements[index] = angular.copy(user);
Now formElements array is formed when we add a record in a separate function.
So Angular's default behaviour is that it doesn't defines the key for empty values. So after copying my array gets shifted to left displaying only filled up values.
Any way to copy filled plus empty values when copying user object ?
full code is available at Update form after editing any field value AngularJs
Instate of copy you can use angular.toJson and JSON.parse method to duplicate "user" json.
so it will be look like this.
$scope.formElements[index] = angular.toJson(user);
$scope.formElements[index] = JSON.parse($scope.formElements[index]);
this will definitely help
Its better to define your user model like this in your controller
$scope.user = {
name: '',
phone: ''
}
There is no need of extra copying
If I understand your question properly, you can do similar to this
$scope.formElements = [];
$scope.update = function() {
$scope.formElements.push($scope.user);
}
HTML:
<input type="text" placeholder="user" ng-model="user.name">
<input type="text" placeholder="phone" ng-model="user.phone">
<input type="submit" ng-click="update()">
NOTE:
This will create key for blank value also. But the problem is, for the first element it will give undefined if user object values are null.
That is expected behaviour, because for the very first time if you pass empty values Angular will not know what key it needs to create.
If you handle very first scenario then rest will work.
To handle define your model object inside controller
$scope.user = {
name: '',
phone: ''
}

Editing a delimited string using multiple input fields in AngularJS

I am trying to do the following quite unsuccessfully so far.
I have an string that is semicolon separated. Say a list of emails, so
'email1#example.com;email2#example.com;email3#example.com'
What I am trying to accomplish is split this string (using split(';')) into an array of strings or array of objects (to aid binding). Each of the items I would like to bind to different input elements. After editing I want to read the concatenated value again to send to my backend.
Problem is that when editing one of the split inputs, the original item value is not update (which makes sense as I am guessing the individual items are copies of parts of the original), but I am wondering if there is a way to do something like that.
Note that I want this to go both ways, so watching the individual inputs and updating the original one manually, would just fire an infinite loop of updates.
I have tried a few different ways, including creating an items property get/set using Object.defineProperty to read and right to the string (set was never fired).
take a look at this plnker
You can construct a temporary array on each field update in order to do the string replacement of the old segment with the new value. In order to tackle the lost focus problem you will have to use the ngReapeat's track by $index. The internal array will not be recreated unless you add the separator to your original string.
Here is the complete solution on Plunker
Your main issue is your ng-model attribute on your repeated input element. I would start with making use of ng-repeat's $index variable to properly bind in ng-model. In your original Plunker 'name' is NOT a scope property you can bind to, so this should be changed to ng-model="names[$index]"
Here is a Plunker to reflect this. I made quite a few changes for clarity and to have a working example.
NOTE: You will find that when editing fields directly bound to a repeater, every change will fire a $digest and your repeated <input> elements will refresh. So the next issue to solve is regaining focus to the element you are editing after this happens. There are many solutions to this, however, this should be answered in a different question.
Although binding to a string primitive is discouraged, you could try ng-list.
<form name="graddiv" ng-controller="Ctrl">
List: <input name="namesInput" ng-list ng-model="vm.names"/>
<ul>
<input ng-repeat="name in vm.names track by $index" ng-model="name" ng-change="updateMe($index, name)"/>
</ul>
You'll need both track by $index and an ng-change handler because of the primitive string binding.
function Ctrl($scope) {
$scope.vm = {}; // objref so we can retain names ref binding
$scope.vm.names = ['Christian', 'Jason Miller', 'Judy Dobry', 'Bijal Shah', 'Duyun Chen', 'Marvin Plettner', 'Sio Cheang', 'Patrick McMahon', 'Chuen Wing Chan'];
$scope.updateMe = function($index, value){
// ng quirk - unfortunately we need to create a new array instance to get the formatters to run
// see http://stackoverflow.com/questions/15590140/ng-list-input-not-updating-when-adding-items-to-array
$scope.vm.names[$index] = value; // unfortunately, this will regenerate the input
$scope.vm.names = angular.copy($scope.vm.names); // create a new array instance to run the ng-list formatters
};
}
Here's your updated plunkr

How to properly clean form with invalid input from AngularJS controller?

I have an AngularJS form that contains - among other fields - one of type url. The latter is important as this forces the corresponding input to be a valid URL.
Under certain conditions (for instance, a modal dialog with such a form is to be closed), I want to clear that form programmatically. For that purpose, I implemented method reset that basically clears the corresponding form model by setting $scope.formData = {}. Thus, it sets the form model to a new, blank object.
While that assignment clears all valid fields in the rendered HTML form, it does not clear invalid fields, like an invalid URL. For instance, if the user would provide invalid input ht://t/p as URL, that input would not be removed from the rendered form.
I think this is due to the fact that any invalid URL is not reflected by the model - such an invalid URL just wouldn't "make" it to the model because it does not pass validation in the NgModelController#$parsers array. Thus, in the model - there is no URL at all. Consequently, resetting the form model to {} cannot actually change the model's URL as it has not been set yet.
However, if method reset explicitly sets field $scope.formData.url = "", the invalid URL will be cleared properly (at least, the rendered form won't show it anymore). This is caused by the explicit change of the URL in the model. However, now, model variable formData.url contains the empty string (well, not surprisingly), while by using = {}, all fields would be undefined instead.
While assigning individual fields to "" works as workaround for simple forms, it quickly becomes cumbersome for more complex forms with many fields.
Thus, how could I programmatically reset the form efficiently and effectively - including all invalid input fields as well?
I created a Plunker at http://plnkr.co/c2Yhzs where you can examine and run a complete example showing the above effect.
Specify the type of your button as reset. That will not only call the ngClick function, it will also clear the content of the HTML form.
<button type="reset" ng-click="resetFormData()">Reset</button>
I think this solution is moderately elegant: your plnkr reviewed
The big difference is the initialization of your model object.
I think things gets messed up when a variable becomes undefined, it doesn't get updated anymore.. it should be connected (veeeery) deeply with how validation works (docs link)
Returning undefined in that case makes the model not get updated, i think this is exactly what happens behind the curtain
PS: you can recycle resetImplicitly for all your forms in the webapp :)
After trying several answers without success in similar questions, this worked for me.
In my controller:
$scope.cleanForm = function() {
$scope.myFormName.$rollbackViewValue();
};
Just call with some ng-click or any way you want.
Cheers
The Thing is tag is of type "url" which means
if user will enter specifically a valid url then only it will set values of model
If user will expicitly reset it which means setting model values to "" will again make textbox empty .
It is looking like it is setting the values but actually not ,so when you set its value to "" .Angular will set modal value to ""
Lets take another example : put replace "text" with "email"
<input type="email" ng-model="formData.name" />
<br />URL:
<input type="url" ng-model="formData.url" />
<br />
In above code If you will enter invalid email it will not set the values of email's model.
You probably need to make a copy of the model in its pristine state and set the model to pristine when you reset.
There's a good example here:
http://www.angularjshub.com/examples/forms/formreset/
The url form fields are passed into the model only if they are valid. Thus in case of an invlaid-url entry in the form, the scope variable is not assigned with the model and clearing the forms entry by assigning an empty object to the model will still persist the value at the UI front.
The best alternative to this is to assign the model associated with the form data with a null. A similar answer appears here:
https://stackoverflow.com/a/18874550/5065857
ng-click="formData={};"
just give like this ,
<button ng-click="formData={}">(1) Reset Full Data: formData = {}</button>
Reset your form data directly in ng-click itself.

Categories

Resources