How to make data reactive behind an array? - arrays

I noticed than Vuejs doesn't observe data change after the first level of an array.
How to change this behaviour?
new Vue({
el: '#container',
data: {
value: [],
},
beforeMount() {
this.value[0] = 'first level'
this.value[1] = []
this.value[1][0] = 'second level'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container">
value[0] :
<input type="text" id="container" placeholder="enter text" v-model="value[0]">
<span>{{ value[0] }}</span>
<hr>
value[1][0] :
<input type="text" id="container" placeholder="enter text" v-model="value[1][0]">
<span>{{ value[1][0] }}</span>
</div>

Your problem is due to the main caveat of Vue reactivity system :
See working snippet below :
new Vue({
el: '#container',
data: {
value: [],
},
methods:{
updateValue(event){
this.$set(this.value, 1, [event.target.value])
}
},
beforeMount() {
this.value[0] = 'first level'
this.value[1] = []
this.value[1][0] = 'second level'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container">
value[0] :
<input type="text" id="container" placeholder="enter text" v-model="value[0]">
<span>{{ value[0] }}</span>
<hr>
value[1][0] :
<input type="text" id="container" placeholder="enter text" #input="updateValue" :value="value[1][0]">
<span>{{ value[1][0] }}</span>
</div>
Note that if you try to do
updateValue(event){
this.value[1][0] = event.target.value
console.log(this.value[1][0]) //the value has changed but the UI is not refreshed
}
you will have the same problem : since what you're doing is basically modifying one item inside your array (same goes for one property within the same object), meaning that your array is the same, Vue has no way to know your UI needs to be re-rendered.
Vue.$set (or this.$set) explicitly tells Vue that a data has been modified, forcing it to re-render corresponding DOM element.
$set take three params : first is the object/array you want to update (this.value), second is the index/property that needs to be modified (1), third is the new value
It would also work if, instead of modifying one array item, you reassign the entire array :
updateValue(event){
this.value = [...this.value[0], [event.target.value]]
}

Related

how to clear checkbox checked status? with vue

when i click next button, i want to remove checkbox checked status,
but i don't use vue to remove it.
this is demo code:
new Vue({
el: "#app",
data: {
id:0,
items: [
{'id':1,'name':'chk-a','options':['a1','b1','c1','d1']},
{'id':2,'name':'chk-b','options':['a2','b2','c2','d2']},
{'id':3,'name':'chk-c','options':['a3','b3','c3','d3']},
{'id':4,'name':'chk-d','options':['a4','b4','c4','d4']},
]
},
methods: {
next: function (id) {
if(id<this.items.length){
this.id++
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<h1>Checkbox</h1>
<div v-for="item in items[id].options">
<input type="radio" name="chk" :id="id">
<label for="two">{{item}}</label>
</div>
<button #click="next(id+1)">Next</button>
</div>
It looks like you have four sections to a form and want the user to select one option from each form. If so, consider having a field selected_option or something similar and use v-model:
new Vue({
el: "#app",
data: {
id:0,
items: [
{'id':1,'name':'chk-a','options':['a1','b1','c1','d1'], 'selected_option': ''},
{'id':2,'name':'chk-b','options':['a2','b2','c2','d2'], 'selected_option': 'c2'},
{'id':3,'name':'chk-c','options':['a3','b3','c3','d3'], 'selected_option': ''},
{'id':4,'name':'chk-d','options':['a4','b4','c4','d4'], 'selected_option': ''},
]
},
methods: {
next: function (id) {
if(id<this.items.length){
this.id++
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<h1>Checkbox</h1>
<div v-for="item in items[id].options">
<input type="radio" :name="items[id].name" :id="id" v-model="items[id].selected_option" :value="item">
<label for="two">{{item}}</label>
</div>
<button #click="next(id+1)">Next</button>
</div>
Note that the above has 'c2' automatically selected in the second section. This is to show you an example of default values for v-model using checkboxes and to show you what the expected data will look like. Retrieving these values later can be done easily by iterating through items and pulling out your corresponding selected_option values.
Likewise, you can remove the checked status by simply setting the value to '' for any given selected_option field.

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

Select a value from typeahead shows Objec object

I am working on Single Page Application and using Angular-TypeAhead when i write something in textbox it shows me suggestions but when i select the suggestion then the values of Textbox become Object object instead of name
here is my HTML Markup
<div class="bs-example" style="padding-bottom: 24px;" append-source>
<form class="form-inline" role="form">
<div class="form-group">
<label><i class="fa fa-globe"></i> State</label>
<input type="text" class="form-control" ng-model="selectedState" ng-options="state.FirstName for state in states" placeholder="Enter state" bs-typeahead>
</div>
</form>
</div>
and here is my AngularJS code
var app = angular.module('mgcrea.ngStrapDocs', ['ngAnimate', 'ngSanitize', 'mgcrea.ngStrap'])
.controller('TypeaheadDemoCtrl', function ($scope, $templateCache, $http) {
$scope.selectedState = '';
$http.get('/api/servicesapi/GetAllClients')
.then(
function (result) {
//success
$scope.states = result.data;
},
function () {
//error
});
});
see the images here
Change ng-options="state.FirstName for state in states" to ng-options="state as state.FirstName for state in states"
This is the solution in Angular 4 and not angularJS
I added [inputFormatter]="formatMatches" to format the input (ngmodel) and [resultFormatter]="formatMatches" to format the data displayed
<input id="typeahead-format" type="text" [(ngModel)]="clickedItem"
name="profile" (selectItem)="selectedItem($event)"
[resultFormatter]="formatter" [inputFormatter]="formatter"
[ngbTypeahead]="search" #instance="ngbTypeahead"/>
and the formatter function is like this:
formatter = (x: { label: string }) => x.label;
x: is the object
For anyone using angular bootstrap and finding the same issue, I spent too long on this.
Essentially you need to consider the model to be a string on the way out and then converted to a string from the complex model returned from the api call.
You then need to hook into the OnSelect method to extract the complex object and store this (or the unique id form the object) in a separate variable. It is right then in the documents, but it is not given much prominence given that you would normally want a key / value pair from a typeahead. Not just a string.
https://valor-software.com/ngx-bootstrap/#/typeahead#on-select
Here is a code snippet from a typeahead which works fine, to set the search as the [name] value from a complex object and the selectedObject to be the whole item.
<pre class="card card-block card-header">Search: {{ search | json }}</pre>
<pre class="card card-block card-header">Selected: {{ selectedOption | json }}</pre>
<ng-template #customItemTemplate let-model="item" let-index="index">
<div>
<p>This is: {{ model.name }} Number: {{ model.id }} Index: {{ index }}</p>
<p>Some secondary information about {{ model.name }}</p>
</div>
</ng-template>
<input [(ngModel)]="search"
[typeahead]="suggestions$"
typeaheadOptionField="name"
(typeaheadOnSelect)="onSelect($event)"
[typeaheadAsync]="true"
typeaheadWaitMs="500"
[typeaheadMinLength]="3"
[typeaheadItemTemplate]="customItemTemplate"
class="form-control"
placeholder="Enter a street name">
<div class="alert alert-danger" role="alert" *ngIf="errorMessage">
{{ errorMessage }}
</div>
then in my component I have
ngOnInit(): void {
this.suggestions$ = new Observable((observer: Observer<string>) => {
observer.next(this.search);
}).pipe(
switchMap((query: string) => {
if (query) {
return this.YOURSERVICE.YOURMETHOD(query);
}
return of([]);
})
);
}
onSelect(event: TypeaheadMatch): void {
this.selectedOption = event.item;
}
your method should return an interface with a [name] property
This should be enough to get going. This is very old, but I passed through here before finding what I needed.

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