ng-model does not bind within ng-repeat, while everything else does - javascript

I know there are a ton of questions already on this topic, I tried the solutions I found but it doesnt seem to work.
Basically I have this directive called basicMunch that goes through an array of objects and creates some html.
Everything works nicely and bind other than the ng-modal
Here is the code for the directive:
> munches.directive('basicmunches', function() { return {
> require: 'ngModel',
> template:`<div class='columns'> <div ng-repeat="mu in basicMunches" class="card column col-4 col-sm-12">
> <div class="card-header">
> <h4 class="card-title">{{mu.name}}</h4>
> <h6 class="card-subtitle">{{mu.type}}</h6>
> </div>
> <div class="card-body">
> {{mu.text}}
> </div>
> <div class="card-footer">
> <div class="form-group">
> <label class="form-switch">
> <input ng-model="mu.tag" name="{{mu.tag}}" type="checkbox" />
> <i class="form-icon"></i> Turn On
> </label>
> </div>
> </div>
> </div></div>` } });
Here is the array:
$scope.basicMunches = [
{"name":"The New York TImes",
"text":"Get the top headlines from the NYT every morning",
"type":"News", "tag":"nyt"
},
{"name":"Wall Street Journal",
"text":"Get the top headlines from the WSJ every morning",
"type":"News", "tag":"wsj"
}
];
Here is what i see in the console
I have tried doing $parent.mu.tag and $parent.$parent.mu.tag, but that didnt work as it shouldnt have because it isnt nested in some other scope (atleast not that I know of)
I tried doing the name of the controller.mu.tag, that too doesnt work
I tried doing mu['tag'] and that doesnt work either
I tried using {{ and that doesnt work
I changed it to ng-bind={{mu.tag}} and that does work
It is weird to me that it works for ng-bind but it doesnt work for ng-model....
Anyhow, anyone have any ideas?

It seems like what you are wanting is to bind the ng-model property to the value of mu.tag, rather than to mu.tag itself.
Due to the way that ng-model is parsed, you need to use a variation of the bracket syntax in order to make this possible. The proper syntax here is $parent[mu.tag] which will look on the $parent object for the property named by the value of mu.tag. The parent of the ng-repeat is $scope, so this ends up with the property on the correct object.
<input ng-model="$parent[mu.tag]" name="{{mu.tag}}" type="checkbox" />
http://plnkr.co/edit/RZNDa2XVUoZp1z7QeN46?p=preview

Have the checkbox inputs fill properties of an object with property accessor bracket notation:
<fieldset ng-repeat="mu in munchies">
<input ng-model="choices[mu.tag]" name="{{mu.tag}}"
type="checkbox" />
Turn On
</fieldset>
The DEMO
angular.module("app",[])
.controller("ctrl",function($scope) {
var vm = $scope;
vm.choices = {};
vm.munchies = [
{"name":"The New York TImes",
"text":"Get the top headlines from the NYT every morning",
"type":"News", "tag":"nyt"
},
{"name":"Wall Street Journal",
"text":"Get the top headlines from the WSJ every morning",
"type":"News", "tag":"wsj"
}
];
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl">
Subscriptions {{choices | json}}
<fieldset ng-repeat="mu in munchies">
<h3>{{mu.name}}</h3>
<p>{{mu.text}}</p>
<input ng-model="choices[mu.tag]" name="{{mu.tag}}"
type="checkbox" />
Turn On
<br/>Subscribed {{choices[mu.tag]}}
</fieldset>
</body>

Related

use ngFor loop in Angular 6 to dynamically create array of input elements and add dynamical validation based on template reference variables

I would like to create dynamically 3 input tags in Angular 6 to not copy/paste html code because that input elements have similar html and functionality.
For this purpose I created an array "reusableItems" inside component and initialize it :
let numberOfInputElements = 3;
for (let i = 0; i < numberOfInputElements; i++) {
this.reusableItems.push({
answer: 'Answer ' + (i +1),
passwordRecoveryAnswer: this.user['passwordRecoveryAnswer' + (i + 1)]
});
}
Then I put code inside my html :
<div *ngFor="let item of dropDownDataManagerService.reusableItems" >
<li class="col-xs-12 pl-lg pr0 pv-sm bd1-bottom">
<div class="col-xs-4 ph0 pt"> {{item.answerTitle}}</div>
<div class="col-xs-8">
<input type="text" name={{item.answer}} ref-{{item.answer}}="ngModel" class="col-sm-12 k-textbox ph0"
[(ngModel)]=item.passwordRecoveryAnswer
[pattern]="[a-z]"
required autocomplete="off"/>
</div>
</li>
</div>
It seems works fine but then I need to add error messages when these fields will be empty and not match to pattern. Something like :
<div *ngIf="__{{item.answer}}__.errors?.required ">
{{'Please provide an answer' | translate}}
</div>
<div *ngIf="__{{item.answer}}__.errors?.pattern">
{{'Pattern is not match'}}
</div>
I don't know what should i put inside ngIf condition.
How can I do it if my template reference variables are comes from array?
Is anyone have ideas?
Thanks
Angular creates unique template reference variable for each embedded template so that you can use the same template reference variable name inside ngFor loop:
<div *ngFor="let item of reusableItems">
<li class="col-xs-12 pl-lg pr0 pv-sm bd1-bottom">
<div class="col-xs-4 ph0 pt"> {{item.answerTitle}}</div>
<div class="col-xs-8">
<input type="text" name={{item.answer}} ref-answer="ngModel" class="col-sm-12 k-textbox ph0" [(ngModel)]="item.passwordRecoveryAnswer"
[pattern]="'[a-z]'" required autocomplete="off" />
<div *ngIf="answer.errors?.required">
{{'Please provide an answer'}}
</div>
<div *ngIf="answer.errors?.pattern">
{{'Pattern is not match'}}
</div>
</div>
</li>
</div>
In the code above I use the same name for each input in array
ref-answer="ngModel" // or you can also use #answer="ngModel

Apply $scope ng-click event to hidden element after it is displayed

Please ask me for better explanation. I have built a global search function into the header of my site. I want to display a separate input box for mobile search that uses the same ng-click event but the input isn't displayed when the page loads. I am having trouble getting the hidden input value on the mobile ng-click once it is displayed.
The areas of concentration are the search click function is not finding the correct ng-model when the function is triggered. I think it is because since the hidden elements are not available on load the ng-model="searchQueryStringMobile" isn't applied to the scope somehow.
My question is how do I get ng-model="searchQueryStringMobile" applied in the scope after it has been displayed posthumously or post-click ng-click="flipNav('search')" so that it does not return undefined when you activate the ng-click="loadSearchResults"?
Here is the code:
HTML:
<div ng-controller="HeaderCtrl as header" class="container">
<div id="jesusSearchTop">
<input ng-model="searchQueryString" class="jesusSearchInput autoCompSearch" type="search" placeholder="Search..." autocomplete="off" />
<select ng-model="searchDDL.item" class="jesusSearchSelect" ng-options="item.name for item in searchDDL track by item.id"></select>
<div class="jesusSearchHolder">
<img class="goSearch" ng-model="jesusSearch" ng-click="loadSearchResults('norm')" src="/EMR4/img/gui_icons/searchIcon.png" alt="Search EMR" />
</div>
</div>
<div id="siteControls">
<div id="siteSearch" class="siteControl" ng-click="flipNav('search')"></div>
</div>
<div ng-switch="dd" class="dropDown">
<div ng-switch-when="none" style="display:none"></div>
<div ng-switch-when="search" class="dropMenu listStyle4" id="Search">
<input ng-model="searchQueryStringMobile" class="jesusSearchInput" type="text" placeholder="Search..." autocomplete="off" />
<select ng-model="searchDDL.item" class="jesusSearchSelect" ng-options="item.name for item in searchDDL track by item.id"></select>
<div class="jesusSearchHolder">
<img class="goSearch" ng-model="jesusSearchMobile" ng-click="loadSearchResults('mob')" src="/EMR4/img/gui_icons/searchIcon.png" alt="Search EMR" />
</div>
</div>
</div>
<div class="clr"></div>
</div>
Controller:
app.controller('HeaderCtrl', function ($scope, $http, $location, populateDDL) {
$http.get(badge.credentials[7].home+'data.JSON')
.success(function(data, status, headers, config) {
$scope.header = data.header;
$scope.searchOptions = new populateDDL('tblWebMenuItems',badge.credentials[1].key).
then(function(response) {
$scope.searchDDL = response.tblWebMenuItems
$scope.searchDDL.item = $scope.searchDDL[0];
});
})
.error(function(data, status, headers, config) {
console.log(data+', '+status+', '+headers+', '+config);
});
$scope.flipNav = function(choice){
if ($scope.dd === choice) {
console.log(choice);
$scope.dd = "none";
}else {
$scope.dd = choice;
}
};
$scope.loadSearchResults = function(uv) {
var loader;
if (uv === "mob") {
loader = $scope.searchQueryStringMobile;
}else if (uv === "norm") {
loader = $scope.searchQueryString;
}
console.log(uv+' - '+loader);
if (loader == null || loader < 2) {
alert('Please refine your search and continue, Thank you!');
}else {
$location.path("/search/"+$scope.searchDDL.item.name.toLowerCase()+"/");
$location.search("type",$scope.searchDDL.item.name.toLowerCase());
$location.search("query", loader);
}
};
});
i have tested your code and found that it is because of the ng-switch.As ng-switch creates its own new scope which is child scope of its parent's, so if you use ng-model=$parent.searchQueryStringMobile , then it will work fine or If you use ng-show instead of ng-swtich ,it will work because ng-show doesnt create new child scope, it just sets the markup's css property display to noneand $parent allows you to access items of parent scope from child scope.In your example, $scope.searchQueryStringMobile is in the parent scope of ng-switch's scope. Here is the working plunk click
you can change your ng-switch markup to this
<div ng-switch="dd" class="dropDown" >
<div ng-switch-when="none" style="display:none"></div>
<div ng-switch-when="search" class="dropMenu listStyle4" id="Search">
<input ng-model="$parent.searchQueryStringMobile" class="jesusSearchInput" type="text" placeholder="Search..." autocomplete="off" />
<select ng-model="searchDDL.item" class="jesusSearchSelect" ng-options="item.name for item in searchDDL track by item.id"></select>
<div class="jesusSearchHolder">
<img class="goSearch" ng-model="jesusSearchMobile" ng-click="loadSearchResults('mob')" src="/EMR4/img/gui_icons/searchIcon.png" alt="Search EMR" />
</div>
</div>
</div>
Notice the ng-model for the input element in the above code.
Your problem is quite simple one. ng-switch just like ng-if creates new scope, so when you are using ng-model, yu are assign property to this new scope and not the scope used by your controller.
Solution would be to use controller as syntax or use property of some object created on scope. To illustrate this I create example for you.
As you can see {{a}} does not work outside new scope, but {{x.b}} works just fine.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-init="x = {}; show = 'first'">
<button type="button" ng-click="show = 'first'">show first</button><br>
<button type="button" ng-click="show = 'second'">show second</button><br>
a = {{a}}<br>
x.b = {{x.b}}
<div ng-switch="show">
<div ng-switch-when="first">
<input type="text" ng-model="a">
</div>
<div ng-switch-when="second">
<input type="text" ng-model="x.b">
</div>
</div>
</div>

Angular data - having trouble binding updated values to scope (using slider)

I'm trying to use Angular to create a tool that accepts user input from a slider tool, and automatically updates an "estimate" field whenever the values are changed. However, the data only seems to be binding on the way to the view; the user input is displayed, but doesn't seem to register in the scope.
Here's the HTML:
<h3 ng-controller="calcController" ng-model="estimate" class="floating-menu">${{estimate}}</h3>
<form ng-controller="calcController" method="post" action="/estimate">
<div class="container">
<div class="vals">${{values.insurance}}</div>
<div id="insurance-slider">
<slider floor="0" ceiling="250" value="125" step="25" precision="2" ng-model="values.insurance" ng-change="changeVal()" translate="currencyFormatting"></slider>
</div>
<div class="container">
<div class="vals">${{values.lease}}</div>
<div id="lease-slider">
<slider floor="0" ceiling="250" value="125" step="25" precision="2" ng-model="values.lease" translate="currencyFormatting"></slider>
</div>
</div>
</form>
Here's the Angular:
//create the module
angular.module('Breeze', ['ngRoute', 'ngResource', 'ui.slider'])
.config(function($routeProvider) {
$routeProvider
// route for the calculator page
.when('/', {
templateUrl: 'partials/calculator.html',
controller: 'calcController'
});
})
// create the controller and inject Angular's $scope
.controller('calcController', function($scope, $http) {
$scope.values = {
// Default values
insurance: 125,
lease: 125,
};
$scope.estimate = $scope.values.insurance + $scope.values.lease
}
// Formatting for scale bar
$scope.currencyFormatting = function(value) {
return value.toString() + " $";
}
})
I've tried adding a $watch and a $broadcast function but can't seem to get them working.
This is what it looked like in Angular:
$scope.$watch("values", function(newVal, oldVal, scope) {
scope.estimate = addVals();
})
$scope.changeVal = function() {
$scope.estimate = addVals();
console.log('hi')
$scope.estimate = $scope.values.insurance + $scope.values.lease
}
Ideas?
You are using two nested ng-controller="calcController"in your HTML. Hence two instanciations of the controller (two $scope) which will have their own estimate value, so when you update the inner estimate, the upper one is not aware of that.
You should remove the second ng-controller in your <form> (if the controller were different, then you would put the estimate variable in an object such as $scope.model.estimate so properties inheritance between the two controllers actually work. But in your case there is no reason to have two controllers).
Also, you should remove that ng-model="estimate" in your <h3> tag, which has no effect.
Just setting them equal in the beginning wont work. In your view, instead of having an estimate field just use this
<div ng-controller="calcController" ng-app="Breeze">
<h3 ng-model="estimate" class="floating-menu">${{values.insurance + values.lease }}</h3>
<form method="post" action="/estimate">
<div class="container">
<div class="vals">${{values.insurance}}</div>
<div id="insurance-slider">
<slider floor="0" ceiling="250" value="125" step="25" precision="2" ng-model="values.insurance" ng-change="changeVal()" translate="currencyFormatting"></slider>
</div>
<div class="container">
<div class="vals">${{values.lease}}</div>
<div id="lease-slider">
<slider floor="0" ceiling="250" value="125" step="25" precision="2" ng-model="values.lease" translate="currencyFormatting"></slider>
</div>
</div>
</div>
</form>
</div>
This will keep them adding as the values change.
Another thing that would be breaking is that you have two controller calls to the same controller and no app. Need to change it as below with the new div and remove the rest of the ng-controllers.
Also, make sure that you have the ui slide javascript file included on your page.
Did you try to bind to object instead of primitive type?
$scope.values.estimate = $scope.values.insurance + $scope.values.lease
see similar question here:
Angularjs: 2 way binding not working in included template
Here is a fiddle http://jsfiddle.net/U3pVM/9330/

How to dynamically generate DOM with Angular.js?

I'm getting started with Angular.js and I'm wondering how to do something along the lines of this (pseudocode):
<li ng-repeat="item in items">
<# if(item.dataType == "string") { #>
<input type="text" />
<# } else if(...) { #>
<input type="password" />
<# } #>
</li>
I know the above code is not angularish, and I know that for simple processing I could use a conditional ng-hide or ng-show or something similar. But for complex behavior, if I had to perform various data checks and business logic, how could I dynamically generate DOM elements with Angular.js?
Within the angular world, DOM manipulation is accomplished using angularjs directives. Here is the angular documentation on directives: https://docs.angularjs.org/guide/directive, you would do well to read through this.
Here is some sample code that will accomplish the idea of your psuedo code:
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function ($scope){
$scope.items = [
42, "hello, world!", 3.14, "i'm alive!"
]
});
myApp.directive('myInputDirective', function () {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function (scope, element, attrs) {
if (typeof scope.current === "string") {
element.append('<input type="text">');
} else {
element.append('<input type="password">');
}
}
}
});
and here's how the html would look:
<div ng-controller="MyController">
<ul ng-repeat="item in items" ng-init="current = item">
<my-input-directive></my-input-directive>
</ul>
</div>
Here is a plnkr with the working example: http://plnkr.co/edit/iiS4G2Bsfwjsl6ThNrnS?p=preview
Directives are how the DOM is manipulated in angular. First thing to notice is that angular has a set of directives that come out of the box, we're using a few above (ng-repeat, ng-init, ng-controller). Above we've created a custom directive that will analyze the data type of each item in the items array of our MyController controller, and append the correct html element.
I imagine that you already understand the ng-repeat directive, so I'll skip that. I'll explain what I'm doing with the ng-init directive though. The ng-init directive allows you to evaluate an expression in the current scope. What this means is that we can write an expression that is evaluated in our current controllers scope, in this case the MyController scope. I am using this directive to create an alias for our current item named current. We can use this inside our directive to check what type the current item in the array iteration is.
Our directive myInputDirective, is returning an object with a few different properties. I won't explain them all here (I'll let you read the documentation), but I will explain what the link function is and what I am doing with it. A link function is typically how we modify the DOM. The link function takes in the current scope (in this case the scope of MyController), a jqLite wrapped element that is associated with the directive, and the attrs which is a hash object with key-value pairs of normalized attribute names and values. In our case, the important parameters are the scope, which contains our current variable, and the element, which we will append the correct input onto. In our link function, we're checking the typeof our current item from our items array, then we are appending an element onto our root element based on what the type of the current item is.
For this particular problem, what I'm doing above is overkill. But based off of your question I figured you were looking for a starting point for more advanced uses of angular apart from the built in directives that angular provides. These are somewhat advanced topics in angular, so I hope that what I've said make some sense. Check out the plunker and play around with it a bit, and go through some of the tutorials on https://docs.angularjs.org/guide. Hope this helps!
You can use ng-show to conditionally hide and show elements e.g.:
<input ng-show="item.dataType === 'string'" type="text"/>
<input ng-show="..." type="password"/>
Assuming your object looks like this:
$scope.items = [
{
dataType: 'string',
value: 'André Pena'
},
{
dataType: 'password',
value: '1234'
},
{
dataType: 'check',
value: true
}
];
Option #1 - ng-switch plunker
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div ng-switch="item.dataType">
<div ng-switch-when="string" ><input type="text" ng-model="item.value" /></div>
<div ng-switch-when="password" ><input type="password" ng-model="item.value" /></div>
<div ng-switch-when="check" ><input type="checkbox" ng-model="item.value" /></div>
</div>
</li>
</ul>
</body>
Option #2 - ng-show plunker
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div ng-show="item.dataType == 'string'" ><input type="text" ng-model="item.value" /></div>
<div ng-show="item.dataType == 'password'" ><input type="password" ng-model="item.value" /></div>
<div ng-show="item.dataType == 'check'" ><input type="checkbox" ng-model="item.value" /></div>
</li>
</ul>
</body>
Option #3 - ng-hide plunker
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div ng-hide="!(item.dataType == 'string')" ><input type="text" ng-model="item.value" /></div>
<div ng-hide="!(item.dataType == 'password')" ><input type="password" ng-model="item.value" /></div>
<div ng-hide="!(item.dataType == 'check')" ><input type="checkbox" ng-model="item.value" /></div>
</li>
</ul>
</body>
You should use the ng-if directive.
<input ng-if="item.dataType === 'string'" type="text"/>
<input ng-if="..." type="password"/>
The problem with using ng-show like #rob suggested, is that it only uses CSS to hide the element, which is not ideal if you want the two inputs to have the same name/ID.
ng-if will remove the element from the DOM if the condition is not true.
for a problem this simple there's no need to go and implement your own directive.

How can a data-bind to an element within a Kendo-Knockout listview?

I have a rather sophisticated template for Kendo ListView using knockout-kendo.js bindings. It displays beautifully. My problem is that I need to use the visible and click bindings in parts of the template, but I can't get them to work. Below is a simplified version of my template. Basically, deleteButtonVisible determines whether the close button can be seen, and removeComp removes the item from the array.
<div class='template'>
<div >
<div style='display:inline-block' data-bind='visible: deleteButtonVisible, event: {click: $parent.removeComp}'>
<img src='../../../Img/dialog_close.png'></img>
</div>
<div class='embolden'>#= type#</div><div class='label1'> #= marketArea# </div>
<div class='label2'> #= address# </div>
<!-- more of the same -->
</div>
The view model:
function CompViewModel() {
var self = this;
self.compData = ko.observableArray().subscribeTo("compData");
self.template = kendo.template(//template in here);
self.removeComp = function (comp) {
//do something here
}
}
html:
<div class="row" >
<div class="col-md-12 centerouter" id="compDiv" >
<div class="centerinner" id="compListView" data-bind="kendoListView: {data: compData, template: template}"></div>
</div>
</div>
finally, sample data:
{
type: "Comparable",
marketArea: "",
address: "2327 Bristol St",
deleteButtonVisible: true
},
Take in count that the deleteButtonVisible must be a property on the viewModel linked to the view.You are not doing that right now. The click element can v¡be access from the outer scope of the binding and remove the $parent.He take the method from the viewmodel. Take in count that every thing that you take on the vie must be present on the view model for a easy access.

Categories

Resources