append String to ngModel to get an expression - html

I have a Business Object Car{id:2,name:ford,modelNo:123} I also have a MetaData class for CarMetaData{columnName:string,type:text,required:true}
each attribute has it's own CarMetaData object
how can I display Car data in a form in such a way using an Array of CarMetaData objects
In the template.html file
<form #carForm='ngForm'>
<div class="form-group" *ngFor="let metaData of metadata" >
<input [(ngModel)]="car[metaData.columnName]" name="metaData.columnName"
type="text">
</div>
</form>
In the component file
car:Car;
metadata:CarMetaData[];
The above method isn't working because of the [] in car[meta.columnName] in the
ngModel is there a way an expression can be worked out in *ngFor or [(ngModle)] to calculate columName

Very stupid mistake. Please try this:
<div *ngFor="let meta of metaData;">
<input [(ngModel)]="car[meta.columnName]" name="{{meta.columnName}}" />
{{meta.columnName}}
</div>
Old answer:
But first - your loop (meta in metaData) is wrong as you loop array, not object. So you better do let meta of metaData, let i = index.
And the interesting part is how ngModel sets the value of inputs in ngFor. If you try this:
<div *ngFor="let meta of metaData; let i = index">
<input [(ngModel)]="car[meta.columnName]" name="meta.columnName" />
{{meta.columnName}}
</div>
You will see that input value is the same of all inputs (it was picked from the first ngFor iteration and repeated to all inputs). However, {{meta.columnName}} prints correct values. So there is some scoping issue. Other strange thing - its definitely related with ngForm and input's name property. If you move that outside of the form - all happens as expected. And if you:
<div *ngFor="let meta of metaData; let i = index">
<input [(ngModel)]="car[meta.columnName]" name="{{metaData[i].columnName}}" />
{{meta.columnName}}
</div>
Inside the form - again, all works well. So that might be your workaround.
Here is a DEMO. Hopefully someone will explain it further.

I'd suggest to put both objects in a wrapper-object. e.g.
export class CarWrapper {
constructor(
car: Car,
carMetaData: CarMetaData
) {}
}
Then build a method that joins both objects in this wrapper
private carWrapper: Array<CarWrapper> = [];
private fillCarWrapper(): void {
// your code here
}
And in your template you act like this
<form #carForm='ngForm'>
<div class="form-group" *ngFor="let wrapper of carWrapper">
<input [(ngModel)]="wrapper.car" name="wrapper.carMetaData.columName" type="text">
</div>
</form>

Related

Inputfield unfocus after typing 1 character

I can't seem to write down a full word without being unfocused after each character I type in the inputfield. Trying to understand why that is.
AngularJS
var module = angular.module("myModule", []);
module.controller("myController", function($scope) {
$scope.prop = {};
});
HTML
<div ng-app="myModule">
<div ng-controller="myController">
<button ng-show="!prop.dropdownType"
ng-click="prop.dropdownType = ['']">Init</button>
<div ng-hide="!prop.dropdownType" ng-repeat="(key, value) in prop.dropdownType">
<input type="text" ng-model="prop.dropdownType[key]">
</div>
<button ng-hide="!prop.dropdownType"
ng-click="prop.dropdownType.push('')" >Add options</button>
</div>
</div>
EDIT: created a quick code where you can see what i mean. Just run the code, initialize the inputfield and try to type a word: https://jsfiddle.net/wk173q0a/
I was able to fix your code by making the following change:
<div ng-hide="!prop.dropdownType">
<input ng-repeat="type in prop.dropdownType track by $index" type="text" ng-model="type">
</div>
The problem is that you are updating the key for the values in which you are iterating over. This is kicking off a digest cycle and you are losing focus. Also, the add button did not work because you were adding identical objects with no tracking.
Lastly, you will want to iterate over an array of objects to be able to maintain the reference in ng-model. Otherwise, all the changes will be lost once you add a new value to your array.
This is a great read on understanding the digest cycle:
https://www.thinkful.com/projects/understanding-the-digest-cycle-528/
This is happening because you are updating the list/object that controls your ng-repeat.
prop.dropdownType may start as [''], but as soon as you type into your input, you are updating the prop.dropdownType object. AngularJS sees that you have changed the prop.dropdownType and it refreshes the dom with the new input. If you typed the character A, the prop.dropdownType will now have a key of A (and a value of null?) and the input you see is now a different object.
If you change your ng-model to be a separate array or some other property, this issue should go away.
This is happening because of ng-repeat in tag. So, in this case try below 2 methods to solve the input element focus issue.
1) Use track by $index
<div ng-hide="!prop.dropdownType" ng-repeat="(key, value) in prop.dropdownType track by $index">
<input type="text" ng-model="prop.dropdownType[$index]">
</div>
2) Wrap your strings into objects. E.g. prop.dropdownType = [{value: 'string1'}, {value: 'string2'}, ...]:
<div ng-hide="!prop.dropdownType" ng-repeat="(key, value) in prop.dropdownType">
<input type="text" ng-model="prop.dropdownType[$index].value">
</div>

Working with custom components for input generates "No value accessor for form control with path X->0->Y"

I have a working form taking the following HTML markup. No errors or warnings.
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
I transformed it into a custom component, which also works, as shown below.
<app-input-text [info]="'Title'"
formControlName="targetField"
ngDefaultControl></app-input-text>
In my next view, I need to use FormArray as follows - still working code.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
</div>
</div>
Now, I expected that combining both (i.e. being able to use custom input component and being able to form array for components) would post no problem. However, the sample below doesn't work.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<app-input-text [info]="'Title'"
formControlName="targetField"
class="col-sm-6"></app-input-text>
</div>
</div>
It generates the following error.
No value accessor for form control with path: 'stuff -> 0 -> targetField'
The custom component is design like this (although given that it works in the explicit markup example, I'm not sure if it's relevant information). The only (wild) guess I have might be that value isn't jacked into the form array field somehow.
export class InputTextComponent implements OnInit {
constructor() { this.value = new EventEmitter<string>(); }
#Input() info: string;
#Output() value: EventEmitter<string>;
onEdit(value: any): void { this.value.emit(value); }
}
The group and array creating in the current view is done like this (not sure if this is of any relevance neither, as it works for the explicit HTML markup case).
this.form = builder.group({
id: "",
stuff: builder.array([
builder.group({ targetField: "aaa" }),
builder.group({ targetField: "bbbb" }),
builder.group({ targetField: "cc" })
])
});
Is there a limitation in Angular in this regard that I'm not aware of? I'm rather sure there's not and that I'm just doing something fairly clever simply missing a tiny detail.
I do understand the error but I can't see how it relates to the code. The form can't find the 0th element in the array or that element has no field of that name. Since I do get to see a few rows, I know there must be a 0th element. Since I specified the name of the field, I know there is indeed such. What else am I missing?

how to bind component variable to form object instance property

I am not quite sure how to bind a variable from my component class to a property value in my form object. The form needs to display this value and add it to the ngModel so that it can become part of the object instance.
I am struggling with the syntax and keep getting the errorNo value accessor for form control with name: 'per_print'
Error: No value accessor for form control with name: I think I need to use the [(ngModel)]="myObject.property" syntax, but I am not getting this from an input into the form, but from a binded variable on the class.
<div class="form-group">
<label class="label-text" >Per Print</label>
<div class="input-group" style="width:150px;">
<h4 [(ngModel)]="job_entry.per_print" name="per_print"
value='pricePerPrint' class="form-control"
>
{{pricePerPrint | currency:'GBP':true:'1.2-2'}}
</h4>
</div>
</div>
job_entry is my object which properties I am setting through the form. pricePerPrint is a variable on the class. I want to set this variable to one of the form instance properties. How to do this? I thought I could do it through value, as in the value of the <h4> tag, but this error persists.
You could use [hidden] input field with the value you want, so that this value will be added to your form. This means though, that you need to use pricePerPrint as the ngModel. But ngModel for your job_entry is possibly not needed. You could build the form as such, so that the object you get from the form can be assigned directly to job_entry:
onSubmit(obj) {
this.job_entry = obj;
}
Also check the Demo for that.
So your code would look like this:
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm.value)">
<input [hidden]="isHidden" name="per_print"
[(ngModel)]="pricePerPrint" [value]="pricePerPrint"/>
<h4>Price: {{pricePerPrint}}</h4>
<button type="submit">Submit</button>
</form>
where isHidden is set to true.
Other option I see, if you want to use [(ngModel)]="job_entry.per_print, you need to assign whatever value you have in pricePerPrint to job_entry.per_print.

AngularJS ng-modal do not return latest value from form input

I am still new towards AngularJS, I made a simple textarea to handle user input using angular model binding like below code (noted that my ng-app and ng-controller are being injected somewhere else but it is within the entire <div></div>):
HTML:
<div ng-controller="StatusCtrl">
//some other HTML
<div class="sPTabs-holder">
<tabset>
<tab heading="Status">
<div>
<form class="statusPost" enctype="multipart/form-data">
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<textarea type="text" ng-model="inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
</div>
</div>
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<button style="width: 12%;" ng-click="postStatus()" class="btn btn-primary btn-sm" type="button">Share</button>
</div>
</div>
</form>
</div>
</tab>
<tab heading="Image">Image</tab>
</tabset>
</div>
</div>
JS:
'use strict';
var Status = angular.module('Status',['ui.bootstrap','ngResource','ngSanitize'])
Status.controller('StatusCtrl', ['StatusService','$resource','$scope','$http', '$timeout', '$sce',
function StatusCtrl(StatusService, $resource, $scope, $http, $timeout, $sce) {
//Usable models
$scope.inputStatus;
//Html-bind
$scope.makeTrust = function(html){
return $sce.trustAsHtml(html);
}
$scope.postStatus = function(){
if ($scope.inputStatus == null){
console.log('Blank post alert');
alert('You cannot post with blank statuses!');
}else{
console.log($scope.inputStatus);
}
}
}]);
My problem is whenever I click on the submit button angular will always pop me with the empty input error even though I have input in the textarea. At first I thought that I made a mistake in my model binding so I have tried out to echo the value in html using {{inputStatus}}, things appeared as it was typed and also when I try to define a default value in $scope.inputStatus = 'default value', the console does indeed echoed 'default value', but the problem is it doesn't store anything that is being typed in the form. What have i done wrong in my code?
Noted that I am not so familiar on how to setup AngularJS in JSFiddle. I apologize in advance if you would like to see the working demo.
**Update 1 - I have narrow down the problem, apparently the problem only occur when I am using angular tabs by Angular Bootstrap. So what happen is if you revise the HTML code, there is this <tabset> section. When declaring the ng-controller after the <tabset> section and everything works like a charm but if you declare it before the <tabset> section, that is where everything mess up.
You should initialize $scope.inputStatus in your controller, otherwise it will pop out an alert windows if you haven't input anything in the textarea (which will initialize or update $scope.inputStatus).
So you change your controller to
$scope.inputStatus = "";
Then everything will work, here is a working demo.
update
If you are using <tabset>, then you are facing child scope problem. <tabset> will create a child scope inside your controller, which means, the scope bind to tabset is the child of scope bind to StatusCtrl.
There are two ways to fix this problem. The first one is accessing the parent scope directly by changing your ngModel to below
<textarea type="text" ng-model="$parent.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
The second one is easier but may looks like a trick, use Dot notation like #lcycook mentioned. In your controller StatusCtrl, declare a dictionary called data
$scope.data = {
inputStatus: ""
};
Then you can access the inputStatus by data.inputStatus anywhere inside the controller scope and you don't need to care about the child scope.
While there is no direct evidence, I suspect your text area is masked inside a child scope. This is common for new AngularJS developers.
While you are learning which directive creates a child scope (e.g. ng-if, ng-repeat), you can avoid this problem with "Dot notation". Which is, wrapping the model inside an object.
You can do this by initializing your ng-model or at least the wrapper object in your controller.
$scope.data = {};
// OR
$scope.data = {inputStatus=''};
Then in your template
<textarea type="text" ng-model="data.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
Process it in your controller by referring it as $scope.data.inputStatus.
Some people even argue you are doing it wrong if you don't do this for any ng-model, but I find thinking wrapper object name is hard so I still use "dotless" one if I know the there is no child scope.

How to manipulate the HTML controls using directives

I have a scenario, like i want to use a single HTML file for displaying the form(to get inputs) and use the same HTML for displaying the form (to display the filled value).
Scenario 1 (Form input):
<input ng-model="a"/>
I want the above line to be like in scenario 2
Scenario 2 (Form display):
<label>{{a}}</label>
If i pass a value into a function(or directive) like "Form" then scenario 1 should come otherwise scenario 2 will come.
Any help will be highly appreciated
Solution using ng-switch: demo
<div ng-app>
<div ng-controller="ctrl">
<select ng-model="selection" ng-options="item for item in items">
</select>
<div ng-switch on="selection">
<input type="text" ng-model='a.val' ng-switch-when="one" />
<label ng-switch-when="two">{{a.val}}</label>
</div>
</div>
</div>
<script>
function ctrl($scope) {
$scope.items = ['one','two'];
$scope.selection = 'one';
$scope.a = {val:''};
}
</script>
Note that ng-switch creates new scopes, so in order to use the model from the controller I had to change a to an object, of which a reference is passed to the child scopes (which doesn't happen for primitives).