AngularJS Select Default Option from Select when user input changes - angularjs

This question is specific to AngularJS 1.6.5 and above and does not apply to any previous versions.
I have a form with some ng-options selects and some text inputs. When a user selects the None / Custom option, a text input appears to the right. Once the user starts typing in that input, the None / Custom option is deselected and a new unknown option is added.
This is the result of a bug fix introduced in AngularJS 1.6.5. Previously when the user typed in the input, the None / Custom select remained selected.
My question is: how can I recreate the original pre-1.6.5 behavior?
To see an example of what I am talking about, check out these two fiddles. The first fiddle demonstrates the original behavior, the second demonstrates the current (undesired) behavior. Simply select the None / Custom option and type something in the adjacent text input.
Example Fiddles
AngularJS 1.6.4 example (good)
AngularJS 1.6.5 example (bad)
HTML code:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js" type="text/javascript"></script>
</head>
<body ng-app="myapp">
<div class="wrapper" ng-controller="someCtrl">
<div class="row" ng-repeat="t in utmTags">
<div class="col-xs-1"><label style="color:#666;">{{t}}</label></div>
<div class="col-xs-2">
<select class="form-control flat inline-dropdown skinny" ng-options="o.value as o.key for o in utmOptions" ng-model="company[t]">
<option ng-value="company[t]" label="None / Custom"></option>
</select>
</div>
<div class="col-xs-2">
<input type="text" class="form-control flat skinny" style="width:180px;" placeholder="Add tag, without spaces" ng-if="!utmVariables.includes(company[t])" ng-model="company[t]" ng-pattern="utmRegex" />
<div ng-if="utmVariables.includes(company[t])">e.g. {{utmOptionsExamples[company[t]]}}</div>
</div>
</div>
</div>
</body>
</html>
Javascript Code:
var app = angular.module('myapp', ['ui.bootstrap']);
app.controller('someCtrl', ['$scope', function($scope) {
$scope.utmOptions = [
{ key: '{AmbassadorFirstNameLastInitialID}', value: '{AmbassadorFirstNameLastInitialID}' },
{ key: '{AdminFirstNameLastInitial}', value: '{AdminFirstNameLastInitial}' },
{ key: '{ContentDomain}', value: '{ContentDomain}' },
{ key: '{ShareDate}', value: '{ShareDate}' },
{ key: '{ShareID}', value: '{ShareID}' },
{ key: '{SocialPlatform}', value: '{SocialPlatform}' }
];
$scope.utmOptionsExamples = {
'{AmbassadorFirstNameLastInitialID}': 'TracJ-1318',
'{AdminFirstNameLastInitial}': 'ShanA',
'{ContentDomain}': 'entrepreneur.com',
'{ShareDate}': '2017-08-23',
'{ShareID}': '79131',
'{SocialPlatform}': 'Twitter'
};
$scope.utmRegex = /^[a-z0-9\-\._]*[\{\}]{0}$|^{[a-z0-9\-\._]*}$/i;
$scope.utmTags = ['utm_medium', 'utm_source', 'utm_campaign', 'utm_term', 'utm_content'];
$scope.utmVariables = ['{AmbassadorFirstNameLastInitialID}', '{AdminFirstNameLastInitial}', '{ContentDomain}', '{ShareDate}', '{ShareID}', '{SocialPlatform}'];
$scope.company = {
utm_medium: undefined,
utm_source: undefined,
utm_campaign: undefined,
utm_term: undefined,
utm_content: undefined
}
}]);
app.run();

Related

angularJS what's a good way to put dictionary in input

I have a dictionary of data in the controller and I'm displaying it using ng-repeat. The key is the Title and the value is placed as the value field of an input. I want the user to be able to edit the values and then submit the form. What's the best way I can handle all the input? I've tried ng-model but I can't change the values of the dictionary directly so I'm leaning towards making another dictionary to store the new data. That doesn't seem very efficient though so I'm wondering if there's a better way.
edit: I have this interface and add some values.
export interface Iint {
[title: string] : string;
}
this is in the constructor
this.hashMap : Iint = {};
this.hashMap["Next Title"] = "data";
this.hashMap["Next Value"] = "more data;
In the html I want each of the values (data, more data) to appear in it's own input text box where the user can edit and change the values in the dictionary. I need validation and other things before the user can save and update the data so I'm unsure of if I should be making a duplicate array.
Check this example out. I implemented it in Angular v1 before you edited your answer.
https://plnkr.co/edit/9Y33BDQTngPZx2Vpx5Zz?p=preview
script.js
var app = angular.module('myApp',[]);
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.dict = {
"Title1" : "Hello World !",
"Title2" : "Beautiful day",
"Title3" : "How about that!?"
};
$scope.submitForm = function() {
console.log($scope.dict);
alert(JSON.stringify($scope.dict));
};
}]);
index.html:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="myApp">
<h1>Dictionary Inputs</h1>
<div ng-controller="MyCtrl">
<form name="myForm" ng-submit="submitForm()">
<ul>
<li ng-repeat="(key,val) in dict">
{{key}} : <input type="text" ng-model="$parent.dict[key]">
</li>
</ul>
<button type="submit"> Submit</button>
</form>
<br/>
<div>
$scope.dict : {{dict}}
</div>
</div>
</body>
</html>
Implementing it in Angular v2 might be on similar lines.
It is possible to get ngRepeat to iterate over the properties of an object using the following syntax:
<div ng-repeat="(key, value) in myObj"> ... </div>
Reference: https://docs.angularjs.org/api/ng/directive/ngRepeat#iterating-over-object-properties

Angular - Data Bind Issue

So the main functionality I want is here, which is selecting an option from the drop-down menu and having it populate my input field.
JSFiddle:
<div ng-app="app" ng-controller="MainCtrl">
Two things I want to fix:
When typing into the input ("Email Subject") field I don't wan't it to change the drop-down menu option.
I want the input field to initialize with it's placeholder value of ("Email Subject") instead of initializing with "Select a Canned Response"
I'm assuming this means making the input field have a one-way data bind, but I'm not sure how to do this, any help is appreciated.
<div ng-app="app" ng-controller="MainCtrl">
<input ng-model="CannedResponse" placeholder="Email Subject"><!--change this-->
<div class="item item-input item-select" >
<div class="input-label"> </div>
<select ng-model="newSelectedITem" ng-options="CannedResponse as CannedResponse.label for CannedResponse in CannedResponses"
ng-change="yourFunction()"> <!--change this-->
<option value="{{item.value}}"> </option>
</select>
</div>
</div>
js code
angular.module('app', [])
.controller('MainCtrl', function($scope) {
$scope.CannedResponses = [{
label: 'Selet a Canned Response',
value: 0
}, {
label: 'Hi there whats up',
value: 1
}, {
label: 'Here is another response',
value: 2
}, {
label: 'Why not select this one?',
value: 3
}];
$scope.newSelectedITem = $scope.CannedResponses[0] //ADD THIS (X1)
$scope.CannedResponse='';//add this
$scope.yourFunction = function() {//add this function
$scope.CannedResponse = $scope.newSelectedITem.label;
};
});
see where I wrote change this. There where you have to change your code.

AngularJS edit JSON array by reference

I am giving AngularJS a bash and am trying a small test app where I have a list on the left and based on what is selected, update a form on the right to allow editing of parameters. This is the HTML
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app="tstApp" ng-controller="tstCtrl">
<div>
<select size="2" style="width: 100px"
ng-model="selected"
ng-options="i as i.name for i in items track by i.name"
/>
</div>
<div>
<input type="text" name="descr" value="{{selected.descr}}"/><br/>
<input type="text" name="ref" value="{{selected.ref}}"/><br/>
</div>
<script src="test.js"></script>
</body>
</html>
and this is the JavaScript
var tstapp = angular.module('tstApp', [])
tstapp.controller('tstCtrl', function($scope) {
$scope.items = [
{
"name" : "ITEM1",
"descr" : "This is item1",
"ref" : "Reference1"
},
{
"name" : "ITEM2",
"descr" : "This is item2",
"ref" : "Reference2"
}
];
$scope.selected = $scope.items[0];
});
The problem with this is that when you actually change the description in the edit field, it remains like that and don't reflect the value of the array item any-more. I think the reason is probably obvious in that 'selected' is a copy of the array item and not the item it self. I can't seem to figure out how to edit the array item directly though that is currently selected.
You have to bind it as ng-model in order to get changes
Like this
<input type="text" name="descr" ng-model="selected.descr"/>
JSFIDDLE

Is this a bug? Angular removes object params when corresponding input is not valid

Please see this:
http://plnkr.co/edit/soubKCayeLAqgDOel8AL?p=preview
<!DOCTYPE html>
<html ng-app="testApp">
<head>
<script data-require="angular.js#1.4.1" data-semver="1.4.1" src="https://code.angularjs.org/1.4.1/angular.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="testCtlr">
<pre>{{contacts | json}}</pre>
<div ng-repeat="contact in contacts">
<div ng-repeat="email in contact.emails">
<input type="email" name="email" ng-model="email.email">
<input type="text" name="text" ng-model="email.text">
</div>
</div>
</body>
</html>
var app = angular.module('testApp', []);
app.controller('testCtlr', ["$scope", function ($scope) {
$scope.contacts = [
{
id:'',
name:'',
emails:[
{
email: 'e1',
text: 'fghfgh'
},
{
email: 'e2',
text: 'hjkhjk'
}
],
}
];
}]);
you see if you change the text in the email text box it is removed from the obj unless it is a valid email...
Its not bug, its desired behaviourof angular, when value is not valid , it is not binded to ngModelController ,
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
But there is possibility to attach custom email validator ($parsers)
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController#$parsers
You can push parser to array of parser so it will be executed as last, and return modelValue so you will "override" angular validator and bind invalidValue to ngModel.
It is not a bug, this is by design. The input type email uses regex to validate email address inputs. If the input is not valid it is removed from the model.
On another note - if you wrap your stuff in a form - you can determine the validation state of the inputs. Also, as you are using a repeat, you will need an index.
Here is an example of what I mean:
<form name="myForm">
<div ng-repeat="contact in contacts">
<div ng-repeat="email in contact.emails track by $index">
<div ng-show="myForm.email{{$index}}.$invalid">Invalid email.</div>
<input type="email" name="email{{$index}}" ng-model="email.email">
<input type="text" name="text" ng-model="email.text">
</div>
</div>
</form>
I have updated your Plunker here.
These are the validation states you can use:
//[formName].[inputFieldName].property
myForm.email1.$pristine;
// Boolean. True if the user has not yet modified the form.
myForm.email1.$dirty
// Boolean. True if the user has already modified the form.
myForm.email1.$valid
// Boolean.True if the the form passes the validation.
myForm.email1.$invalid
// Boolean. True if the the form doesn't pass the validation.
myForm.email1.$error

How can I use Angular to output dynamic form fields?

I want to render a form, based on a dynamic field configuration:
$scope.fields = [
{ title: 'Label 1', type: 'text', value: 'value1'},
{ title: 'Label 2', type: 'textarea', value: 'value2'}
];
This should output something that behaves like:
<div>
<label>{{field.title}}<br />
<input type="text" ng-model="field.value"/>
</label>
</div>
<div>
<label>{{field.title}}<br />
<textarea ng-model="field.value" rows="5" cols="50"></textarea>
</label>
</div>
The simple implementation would be to use if statements to render the templates for each field type. However, as Angular doesn't support if statements, I'm lead to the direction of directives. My problem is understanding how the data binding works. The documentation for directives is a bit dense and theoretical.
I've mocked up a bare bones example of what I try to do here: http://jsfiddle.net/gunnarlium/aj8G3/4/
The problem is that the form fields aren't bound to the model, so the $scope.fields in submit() isn't updated. I suspect the content of my directive function is quite wrong ... :)
Going forward, I need to also support other field types, like radio buttons, check boxes, selects, etc.
The first problem you are running into regardless of the directive you are trying to create is using ng-repeat within a form with form elements. It can be tricky do to how ng-repeat creates a new scope.
This directive creates new scope.
I recommend also instead of using element.html that you use ngSwitch instead in a partial template.
<div class="form-row" data-ng-switch on="field.type">
<div data-ng-switch-when="text">
{{ field.title }}: <input type="text" data-ng-model="field.value" />
</div>
<div data-ng-switch-when="textarea">
{{ field.title }}: <textarea data-ng-model="field.value"></textarea>
</div>
</div>
This still leaves you with the problem of modifying form elements in child scope due to ng-repeat and for that I suggest using the ngChange method on each element to set the value when an item has changed. This is one of the few items that I don't think AngularJS handles very well at this time.
You might consider Metawidget for this. It uses JSON schema, but is otherwise very close to your use case. Complete sample:
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
<script type="text/javascript">
angular.module( 'myApp', [ 'metawidget' ] )
.controller( 'myController', function( $scope ) {
$scope.metawidgetConfig = {
inspector: function() {
return {
properties: {
label1: {
type: 'string'
},
label2: {
type: 'string',
large: true
}
}
}
}
}
$scope.saveTo = {
label1: 'value1',
label2: 'value2'
}
$scope.save = function() {
console.log( $scope.saveTo );
}
} );
</script>
</head>
<body ng-controller="myController">
<metawidget ng-model="saveTo" config="metawidgetConfig">
</metawidget>
<button ng-click="save()">Save</button>
</body>
</html>
The type attribute can be changed when the element is out of DOM, so why not a small directive which removes it from DOM, changes it type and then add back to the same place?
The $watch is optional, as the objective can be change it dynamically once and not keep changing it.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.rangeType = 'range';
$scope.newType = 'date'
});
app.directive('dynamicInput', function(){
return {
restrict: "A",
link: linkFunction
};
function linkFunction($scope, $element, $attrs){
if($attrs.watch){
$scope.$watch(function(){ return $attrs.dynamicInput; }, function(newValue){
changeType(newValue);
})
}
else
changeType($attrs.dynamicInput);
function changeType(type){
var prev = $element[0].previousSibling;
var parent = $element.parent();
$element.remove().attr('type', type);
if(prev)
angular.element(prev).after($element);
else
parent.append($element);
}
}
});
span {
font-size: .7em;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<h2>Watching Type Change</h2>
Enter Type: <input ng-model="newType" /><br/>
Using Type (with siblings): <span>Before</span><input dynamic-input="{{newType}}" watch="true" /><span>After</span><Br>
Using Type (without siblings): <div><input dynamic-input="{{newType}}" watch="true" /></div>
<br/><br/><br/>
<h2>Without Watch</h3>
Checkbox: <input dynamic-input="checkbox" /><br />
Password: <input dynamic-input="{{ 'password' }}" value="password"/><br />
Radio: <input dynamic-input="radio" /><br/>
Range: <input dynamic-input="{{ rangeType }}" />
</div>
Tested in latest Chrome and IE11.

Resources