it's me again :)
I am still at the very beginning with angularJS and I have just encountered a problematic issue.
I've got an array with some data that I want to be rendered on the page that's why I use ng-repeat but I also need to include another ng-repeat in the previous one.
I have the general ng-repeat="dialog in dialogWindows" and lower in the DOM ng-repeat="input in dialog.inputs", but the second ngRepeat dousnt work and it reports no errors in the coonsole. can You help me please?
Here is the JS:
var antroApp = angular.module('antroApp', []);
function dialogWindows($scope){
$scope.dialogWindows = [
{id:0,
idName:"pigmentation",
number:"1",
name:"Pigmentation",
answer1:"Clear complexion",
answer2:"Semi-swarthy complexion",
answer3:"Swarthy complexion",
answer4:"",
answer5:"",
answer6:"",
inputs:[{id:0,a:"a1",answer:"a"},
{id:1,a:"a2", answer:"b"}],
}
];
}
antroApp.controller('antroApp', antroApp);
and here is my HTML:
<div ng-controller="dialogWindows">
<div ng-repeat="dialog in dialogWindows">
<div id="{{dialog.idName}}" class="bold abs">
<div class="questionContainer rel">
<div class="menu abs">
<ul class="menuList">
<li id="menuStart" class=" unbold">Start</li>
<li id="menuAbout" class=" unbold">About</li>
<li id="menuTech" class=" unbold">Technology</li>
<li id="menuContact" class=" unbold">Contact</li>
</ul>
</div>
<div class="questionHeader"><div class="textGradient unbold tgHeaderXY">{{dialog.number}}.{{dialog.name}}</div></div>
<div class="empty"> </div>
<div class="questionBody">
<div ng-repat="input in dialog.inputs">
<input type="radio" id="radio1" name="sex" value="male">
<label for="radio1" class="answer abs {{input.a}}">{{input.answer}}</label>
</div>
</div>
Next <i class="icon-arrow-right icon-white"></i>
<i class="icon-pencil tgHeaderIcon icon-3x abs"></i>
</div><!--/pigmentation-->
</div><!--/ng-repeat-->
</div><!--/ng-controller-->
Any help will be appreciated.Thanks
Just single mistake;
set <div ng-repeat="input in dialog.inputs">
instead <div ng-repat="input in dialog.inputs">
As a side note:
use <pre>{{input|json}}</pre> as basic debugger to detect the issue
see Fiddle
In your nested loop you have to use ng-repeat, instead of ng-repat. If you would strip off example markup from unnecessary garbage before posting question, you would probably find typo yourself.
Then, you're missing ng-app="antroApp" directive in example.
Then, controller is dialogWindows, not antroApp:
antroApp.controller('dialogWindows', dialogWindows);
Missing closing div, typo as per Nenad answer, same naming for App and Controller, same naming for controller and scope variable... ouch my mind hurts, anyways, here is the result at jsbin
EDIT: (as per minitech request)
this an alternative version, example in jsbin has unnecessary code in question removed
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
</head>
<body ng-app="myApp"> <!-- *myApp is antroApp app -->
<div ng-controller="myCtrl"> <!-- *myCtrl is dialogWindows ctrl -->
<div ng-repeat="dialog in data"> <!-- data is dialogWindows scope var -->
<div class="bold abs">
<div class="questionContainer rel">
<div class="menu abs">
<ul class="menuList">
<li id="menuStart" class=" unbold">Start</li>
<li id="menuAbout" class=" unbold">About</li>
<li id="menuTech" class=" unbold">Technology</li>
<li id="menuContact" class=" unbold">Contact</li>
</ul>
</div>
<div class="questionHeader">
<div class="textGradient unbold tgHeaderXY">{{dialog.number}}.{{dialog.name}}</div>
</div>
<div class="empty"> </div>
<div class="questionBody">
<div ng-repeat="input in dialog.inputs">
<input type="radio" id="radio1" name="sex" value="male" />
<label for="radio1" class="answer abs {{input.a}}">{{input.answer}}</label>
</div>
</div> Next <i class="icon-arrow-right icon-white"></i>
<i class="icon-pencil tgHeaderIcon icon-3x abs"></i>
</div><!--/questionContainer--> <!-- *missing div -->
</div><!--/pigmentation-->
</div><!--/ng-repeat-->
</div><!--/ng-controller-->
<script>
var App = angular.module('myApp', []); //*myApp is in use now
App.controller('myCtrl', ['$scope', //*myCtrl is in use now
function ($scope) {
$scope.data = [{
id: 0,
idName: "pigmentation",
number: "1",
name: "Pigmentation",
answer1: "Clear complexion",
answer2: "Semi-swarthy complexion",
answer3: "Swarthy complexion",
answer4: "",
answer5: "",
answer6: "",
inputs: [{
id: 0,
a: "a1",
answer: "a"
}, {
id: 1,
a: "a2",
answer: "b"
}]
}];
}]);
</script>
</body>
</html>
Related
I am new in AngularJS but I have basic HTML and JavaScript knowledge.
I have a list of links on page, and a search box. I want to filter list elements dynamically according to the text typed in the box. I have found some example but they use external lists- not a list typed on same html file.
Exactly this: https://code.ciphertrick.com/demo/angularajaxsearch/
As you guess I cannot make filtered elements visible/unvisible like this. How can I filter them (all html codes must be in 1 page so we cannot call another js file, controller or etc.)
<!DOCTYPE html>
<html>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<div class="bar">
<input type="text" class="search" ng-model="searchString" />
</div>
<ul class="data-ctrl">
<li ng-repeat="i in berkay | searchFor:searchString">
</li>
</ul>
<ul name="berkay">
<li>google</li>
<li>bbc</li>
<li>microsoft</li>
</ul>
</body>
</html>
Note: If there is a way with just Javascript or etc. without using angular js, it is welcome as well.
Last Edit: All 3 answers are correct and working fine right now.
Could you try this. I hope it works for you :)
<!DOCTYPE html>
<html>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $filter) {
$scope.searchString=[{Text:"https://google.com"},{Text:"https://bbc.com"},{Text:"https://microsoft.com"}];
$scope.searchString2=$scope.searchString;
$scope.$watch('search', function(val)
{
$scope.searchString= $filter('filter')($scope.searchString2, val);
});
});
</script>
<div ng-app="myApp" ng-controller="myCtrl">
<div class="bar">
<input type="text" class="search" ng-model="search" />
</div>
<ul>
<li ng-repeat="item in searchString">
<a href="{{item.Text}}">{{item.Text}}</>
</li>
</ul>
</div>
</body>
</html>
Please check this updated answer. It will redirect when you click on it.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script>
var app = angular.module("app", []);
app.controller("myCtrl", function($scope) {
$scope.berkay = [{
name: "google",
link: 'https://google.com'
}, {
name: "bbc",
link: 'https://bbc.com'
}, {
name: "microsoft",
link: 'https://microsoft.com'
}];
});
</script>
<div ng-app="app" ng-controller="myCtrl">
<div class="bar">
<!--
1. If you want to search everything(both link and name), just use searchString
<input type="text" class="search" ng-model="searchString">
2. If you want to search only name change searchString to searchString.name
<input type="text" class="search" ng-model="searchString.name">
3. If you want to search only link change searchString to searchString.link
<input type="text" class="search" ng-model="searchString.link">
-->
<input type="text" class="search" ng-model="searchString" />
</div>
<ul class="data-ctrl">
<li ng-repeat="i in berkay | filter:searchString">
{{i.name}}
</li>
</ul>
</div>
You can add javascript inside HTML itself
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script>
var app = angular.module("app", []);
app.controller("myCtrl", function($scope) {
$scope.berkay = [{
name: "google",
link: 'https://google.com'
}, {
name: "bbc",
link: 'https://bbc.com'
}, {
name: "microsoft",
link: 'https://microsoft.com'
}];
});
</script>
<div ng-app="app" ng-controller="myCtrl">
<div class="bar">
<!--
1. If you want to search everything(both link and name), just use searchString
<input type="text" class="search" ng-model="searchString">
2. If you want to search only name change searchString to searchString.name
<input type="text" class="search" ng-model="searchString.name">
3. If you want to search only link change searchString to searchString.link
<input type="text" class="search" ng-model="searchString.link">
-->
<input type="text" class="search" ng-model="searchString" />
</div>
<ul class="data-ctrl">
<li ng-repeat="i in berkay | filter:searchString">
{{i.name}}
</li>
</ul>
</div>
<div ng-app="appPage" ng-controller="appController">
<div class="nav">
<h1 class="logo">Todlio</h1>
<i class="icon setting" style="color:#fff;font-size:1.8em;;position:absolute;top:11px;right:12px;"/></i>
</div>
<div class="todo">
<div class="todo_column">
<div style="font-weight700;text-align:center; margin:20px;">
<a href="#/add" ng-click="addTodo()" class="ui basic button">
<i class="add square icon"></i>
Add
</a>
</div>
<ul>
<a href="#/"><li ng-href="#/" ng-click="detail($index)" ng-repeat="todo in todos">
<h3 ng-model="title">{{ todo.title }}</h3>
<h6>{{ todo.note_date }}</h6>
</li></a>
</ul>
</div>
<div class="todo_full">
<div class="todo_title" ng-scope="todo in todos">
<span><h1>{{ title }}</h1></span>
<span class="check">
<i style="font-size:2em;" class="square outline icon"></i>
<i class="write icon" style="font-size:1.8em;"></i>
</span>
</div>
<h4>Note:</h4>
<p class="todo_note">{{ note }}
</p>
</div>
</div>
</div>
Controller
app.controller("appController", function ($scope) {
$scope.todos = [
{title: "Call Vaibhav", note: "", note_date: ""},
{title: "Grocery", note: "Lemons, Apple and Coffee", note_date: ""},
{title: "Website design for Stallioners", note: "UI/UX on xyz#mail.com", note_date: ""},
{title: "Fill MCA form", note: "First search for all the colleges", note_date: "" }
];
$scope.detail = function(x){
$scope.todos.title = $scope.title;
$scope.todos.note = $scope.note;
};
I want to get the clicked list item title and the note attached to it to the different div below
Can anybody please help. Its a todo app the left half has the list to todos and the right half has a note attached to it or anything checked or not.
There are a few ways to do this. One easy way is as follows:
Define a $scope variable with a name like $scope.currentTodo.
In the repeat loop, the ng-click would set $scope.currentTodo=todo
This current variable will hold the todo object so you can use {{ $scope.currentTodo.title }} in place of {{title}}
Ditch the ng-scope
I m new in Anguar js .
I have created a controller and pass the data but my controller not working can u please help me .
My code is this
Angular code is
var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
$scope.person=[
{name:"Raj", gender:"M"},
{name: "raja", gender:"M"},
{name:"sevitra" gender:"F"}
]
});
HTML
Code is
<body ng-app="myApp">
<div controller="myController">
<a href="javascript:void()">
<button>Add New Field</button>
</a>
<div class="advance-menu-wraper">
<ul>
<li>
{{"person[0].name"}} + {{"person[0].gender"}}
<div class="head-text">Field 1:</div>
<div class="description-text">
How many staff members are proficient in Oracla programing
</div>
</li>
<li>
<div class="head-text">Field 2:</div>
<div class="description-text">
<form name="addForm">
<textarea rows="2"></textarea>
<div class="send-btn">
<button>
<i class="fa fa-check">Submit</i>
</button>
</div>
</form>
</div>
</li>
</ul>
</div>
</div>
</body>
Demo link
Your expression won't work:
{{"person[0].name"}} + {{"person[0].gender"}}
yields: "{{"person[0].name"}} + {{"person[0].gender"}}" in your html.
The correct expression would be:
{{person[0].name + person[0].gender}}
Moreover you have an syntax error in your array. The last object misses a comma.
This is a working plunkr: http://plnkr.co/edit/R9ojp8TWd7AloRrlPlZh?p=preview
You need to use the ngController directive
change
<div controller="myController">
to
<div ng-controller="myController">
{name:"sevitra" gender:"F"} should be {name:"sevitra", gender:"F"}
controller="myController" should be ng-controller="myController"
{{"person[0].name"}} + {{"person[0].gender"}} should be {{person[0].name}} + {{person[0].gender}}
three things which need to be change that i can see
change the controller to
app.controller('myController', [ '$scope',function($scope) {
change the <div controller="MyController"> to <div ng-controller="MyController"
and in the {{ " Person[0].Name "}} and {{ " Person[0].gender "}} remove the quote marks so it becomes {{Person[0].Name}} and {{Person[]0.gender}}
I have a simple loop with ng-repeat like this:
<li ng-repeat='task in tasks'>
<p> {{task.name}}
<button ng-click="removeTask({{task.id}})">remove</button>
</li>
There is a function in the controller $scope.removeTask(taskID).
As far as I know Angular will first render the view and replace interpolated {{task.id}} with a number, and then, on click event, will evaluate ng-click string.
In this case ng-click gets totally what is expected, ie: ng-click="removeTask(5)". However... it's not doing anything.
Of course I can write a code to get task.id from the $tasks array or even the DOM, but this does not seem like the Angular way.
So, how can one add dynamic content to ng-click directive inside a ng-repeat loop?
Instead of
<button ng-click="removeTask({{task.id}})">remove</button>
do this:
<button ng-click="removeTask(task.id)">remove</button>
Please see this fiddle:
http://jsfiddle.net/JSWorld/Hp4W7/34/
One thing that really hung me up, was when I inspected this html in the browser, instead of seeing it expanded to something like:
<button ng-click="removeTask(1234)">remove</button>
I saw:
<button ng-click="removeTask(task.id)">remove</button>
However, the latter works!
This is because you are in the "Angular World", when inside ng-click="" Angular all ready knows about task.id as you are inside it's model. There is no need to use Data binding, as in {{}}.
Further, if you wanted to pass the task object itself, you can like:
<button ng-click="removeTask(task)">remove</button>
Also worth noting, for people who find this in their searches, is this...
<div ng-repeat="button in buttons" class="bb-button" ng-click="goTo(button.path)">
<div class="bb-button-label">{{ button.label }}</div>
<div class="bb-button-description">{{ button.description }}</div>
</div>
Note the value of ng-click. The parameter passed to goTo() is a string from a property of the binding object (the button), but it is not wrapped in quotes. Looks like AngularJS handles that for us. I got hung up on that for a few minutes.
this works. thanks. I am injecting custom html and compile it using angular in the controller.
var tableContent= '<div>Search: <input ng-model="searchText"></div>'
+'<div class="table-heading">'
+ '<div class="table-col">Customer ID</div>'
+ ' <div class="table-col" ng-click="vm.openDialog(c.CustomerId)">{{c.CustomerId}}</div>';
$timeout(function () {
var linkingFunction = $compile(tableContent);
var elem = linkingFunction($scope);
// You can then use the DOM element like normal.
jQuery(tablePanel).append(elem);
console.log("timeout");
},100);
Above answers are excellent. You can look at the following full code example so that you could exactly know how to use
var app = angular.module('hyperCrudApp', []);
app.controller('usersCtrl', function($scope, $http) {
$http.get("https://jsonplaceholder.typicode.com/users").then(function (response) {
console.log(response.data)
$scope.users = response.data;
$scope.setKey = function (userId){
alert(userId)
if(localStorage){
localStorage.setItem("userId", userId)
} else {
alert("No support of localStorage")
return
}
}//function closed
});
});
#header{
color: green;
font-weight: bold;
}
<!DOCTYPE html>
<html>
<head>
<title>HyperCrud</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<!-- NAVBAR STARTS -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">HyperCrud</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>About</li>
<li>Contact</li>
<li class="dropdown">
Apps<span class="caret"></span>
<ul class="dropdown-menu">
<li>qAlarm »</li>
<li>YtEdit »</li>
<li>GWeather »</li>
<li role="separator" class="divider"></li>
<li>WadStore »</li>
<li>chatsAll</li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>Login</li>
<li>Register</li>
<li>Services<span class="sr-only">(current)</span></li>
</ul>
</div>
</div>
</nav>
<!--NAVBAR ENDS-->
<br>
<br>
<div ng-app="hyperCrudApp" ng-controller="usersCtrl" class="container">
<div class="row">
<div class="col-sm-12 col-md-12">
<center>
<h1 id="header"> Users </h1>
</center>
</div>
</div>
<div class="row" >
<!--ITERATING USERS LIST-->
<div class="col-sm-6 col-md-4" ng-repeat="user in users">
<div class="thumbnail">
<center>
<img src="https://cdn2.iconfinder.com/data/icons/users-2/512/User_1-512.png" alt="Image - {{user.name}}" class="img-responsive img-circle" style="width: 100px">
<hr>
</center>
<div class="caption">
<center>
<h3>{{user.name}}</h3>
<p>{{user.email}}</p>
<p>+91 {{user.phone}}</p>
<p>{{user.address.city}}</p>
</center>
</div>
<div class="caption">
DELETE
UPDATE
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<a href="/regiser/">
<img src="http://img.bhs4.com/b7/b/b7b76402439268b532e3429b3f1d1db0b28651d5_large.jpg" alt="Register Image" class="img-responsive img-circle" style="width: 100%">
</a>
</div>
</div>
</div>
<!--ROW ENDS-->
</div>
</body>
</html>
HTML:
<div ng-repeat="scannedDevice in ScanResult">
<!--GridStarts-->
<div >
<img ng-src={{'./assets/img/PlaceHolder/Test.png'}}
<!--Pass Param-->
ng-click="connectDevice(scannedDevice.id)"
altSrc="{{'./assets/img/PlaceHolder/user_place_holder.png'}}"
onerror="this.src = $(this).attr('altSrc')">
</div>
</div>
Java Script:
//Global Variables
var ANGULAR_APP = angular.module('TestApp',[]);
ANGULAR_APP .controller('TestCtrl',['$scope', function($scope) {
//Variables
$scope.ScanResult = [];
//Pass Parameter
$scope.connectDevice = function(deviceID) {
alert("Connecting : "+deviceID );
};
}]);
Here is the ng repeat with ng click function and to append with slider
<script>
var app = angular.module('MyApp', [])
app.controller('MyController', function ($scope) {
$scope.employees = [
{ 'id': '001', 'name': 'Alpha', 'joinDate': '05/17/2015', 'age': 37 },
{ 'id': '002', 'name': 'Bravo', 'joinDate': '03/25/2016', 'age': 27 },
{ 'id': '003', 'name': 'Charlie', 'joinDate': '09/11/2015', 'age': 29 },
{ 'id': '004', 'name': 'Delta', 'joinDate': '09/11/2015', 'age': 19 },
{ 'id': '005', 'name': 'Echo', 'joinDate': '03/09/2014', 'age': 32 }
]
//This will hide the DIV by default.
$scope.IsVisible = false;
$scope.ShowHide = function () {
//If DIV is visible it will be hidden and vice versa.
$scope.IsVisible = $scope.IsVisible ? false : true;
}
});
</script>
</head>
<body>
<div class="container" ng-app="MyApp" ng-controller="MyController">
<input type="checkbox" value="checkbox1" ng-click="ShowHide()" /> checkbox1
<div id="mixedSlider">
<div class="MS-content">
<div class="item" ng-repeat="emps in employees" ng-show = "IsVisible">
<div class="subitem">
<p>{{emps.id}}</p>
<p>{{emps.name}}</p>
<p>{{emps.age}}</p>
</div>
</div>
</div>
<div class="MS-controls">
<button class="MS-left"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
<button class="MS-right"><i class="fa fa-angle-right" aria-hidden="true"></i></button>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="js/multislider.js"></script>
<script>
$('#mixedSlider').multislider({
duration: 750,
interval: false
});
</script>
I'm writing a little database query app.
What i'm trying to do: Each time a checkbox is clicked, i'd like for a query that includes the selected fields to be generated and inserted into the textarea.
The problem: For some reason, with every click, its showing the query from the previous click event, not the current one.
Here's the markup:
<div class="application container" ng-controller="OQB_Controller">
<!-- top headr -->
<nav class="navbar navbar-default navbar-fixed-top navbar-inverse shadow" role="navigation">
<a class="navbar-brand">
Algebraix Database Client
</a>
<ul class="nav navbar-nav navbar-right">
<!--<li>Clear Queries</li>-->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-import"></span> Load Data <b class="caret"></b></a>
<ul class="dropdown-menu">
<li>Default Data</li>
<li>Custom Import</li>
<!-- <li class="divider"></li> -->
</ul>
</li>
<li>
<a href="" class="queries-clear">
<span class="glyphicon glyphicon-remove"></span> Clear Queries
</a>
</li>
</ul>
</nav>
<!-- left column -->
<div class="col-md-4">
<div class="well form-group">
<ul>
<li ng-repeat="option in options">
<input type="checkbox" class="included-{{option.included}}" value="{{option.value}}" ng-click="buildQuery()" ng-model="option.included"> {{option.text}}
</li>
</ul>
</div>
</div>
<!-- right column -->
<div class="col-md-8">
<form role="form" id="sparqlForm" method="POST" action="" class="form howblock">
<div class="form-group">
<!--<label>Query</label>-->
<textarea type="text" name="query" class="form-control" rows="10" placeholder="Write your SPARQL query here">{{query}}</textarea>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit Query" data-loading-text="Running Query..." />
</div>
</form>
</div>
</div>
And in my controller, i am doing the following:
var OQB_Controller = function($scope) {
console.log('OQB_CONTROLLER');
$scope.query = 0;
$scope.options = [
{ text: "checkbox1", value: "xyz123", included: false }
,{ text: "checkbox2", value: "abcRRR", included: false }
,{ text: "checkbox2", value: "abcRRR", included: false }
];
$scope.buildQuery = function() {
console.log('click');
var lines = [];
lines.push("SELECT *");
lines.push("WHERE {");
lines.push(" ?s ?p ?o .");
for(var i = 0; i<$scope.options.length; i++) {
var line = $scope.options[i];
console.log( line.value, line.included, i );
if( line.included ) {
lines.push(" OPTIONAL { ?s "+line.value+" ?o } .");
}
}
lines.push("}");
lines.push("LIMIT 10");
var _query = lines.join("\n");
$scope.query = _query;
};
};
To reiterate, every time the build query method is called, the state of the included booleans is from one click event prior. this has the symptoms of the classic javascript problem of the keyup vs keydown and the state of the event... however, i'm not sure if that is what is happening here.
is there a better way to do build the query (than what i'm currently doing) and populate the textarea based on the checked boxes?
use ng-change instead of ng-click because it is more appropriate for this particular desired behavior. See the ng-change documentation below:
The ngChange expression is only evaluated when a change in the input
value causes a new value to be committed to the model.
It will not be evaluated:
if the value returned from the $parsers transformation pipeline has
not changed if the input has continued to be invalid since the model
will stay null if the model is changed programmatically and not by a
change to the input value