Why does this state get reset? - angularjs

In the following code, why $scope.text get reset when a new area is switched in? I think its value should be persisted because its defined in the top level scope.
<div ng-controller="Ctrl">
<select ng-model="selection" ng-options="item for item in items">
</select>
<hr/>
<div ng-switch on="selection" >
<div ng-switch-when="settings" ng-controller="Ctrl1">
Enter val :
<input ng-model="text" />{{text}}
</div>
<span ng-switch-when="home" ng-controller="Ctrl2">Home Span</span>
<span ng-switch-default>default</span>
</div>
</div>
Controllers:
var myApp = angular.module('myApp',[]);
function Ctrl($scope) {
$scope.items = ['settings', 'home', 'other'];
$scope.selection = $scope.items[0];
$scope.text = "Enter val";
}
function Ctrl1($scope) {
console.log('hi')
}
function Ctrl2($scope) {
console.log('hi2')
}
http://jsfiddle.net/KDWh8/

When you are working with primitive values in angular scopes, you cannot overwrite a value in a parent scope from a child scope. This is because angular uses javascript prototypal inheritance.
What you could do in this case is create an object in the parent scope, then you can update the values on that in the child scope. Because you are not overwriting the object (only properties attached to it) the references work.
"the rule of thumb is, if you use ng-model there has to be a dot somewhere." Miško Hevery

Related

Re-render NgRepeat based on user typed value input field

I have div which has some markup inside it. Basically some form fields which I want to save too on submission.
Outside this div, I have an input field where user can type an integer. Based on this integer, I would like to show or ng-repeat items.
Here's the sample code matching my problem. Kept a span inside of any form elements to focus on main issue of ng-repeat.
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.myNumber = 3;
$scope.getNumber = function(num) {
return new Array(num);
$scope.$apply();
}
$scope.$watch('myNumber', function(newVal,OldVal){
$scope.myNumber = newVal;
//alert(newVal);
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="ctrlParent">
<input type="text" ng-model="myNumber" ng-blur="getNumber(myNumber)"/>
<ul>
<li ng-repeat="i in getNumber(myNumber) track by $index">
<span>{{$index+1}}</span>
</li>
</ul>
</div>
</div>
here's what I tried :
Watching the variables and assigning newValue of input to scope variable does not help.
I tried using ng-blur to call the same function with updated value. Oh yes, ng-change was tried too.
Used $scope.$apply() inside getNumber function out of desperation to manually update the changes.
How do I proceed with it ? Is there any way to update the ng-repeat?
It is not an issue of ng-repeat, you have used input of type text.
Change it to number or use parseInt.
You do not need to use ng-blur, $watch, $scope.$apply() for detecting the changes in your case.
You got this part wrong: <input type="text" ng-model="myNumber">, in which you should change the input to type number.
new Array() accept number and you pass text over it.
var app = angular.module('myapp', []);
app.controller('ctrlParent', function($scope) {
$scope.myNumber = 3;
$scope.getNumber = function(num) {
return new Array(num);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="ctrlParent">
<input type="number" ng-model="myNumber" />
<ul>
<li ng-repeat="i in getNumber(myNumber) track by $index">
<span>{{$index+1}}</span>
</li>
</ul>
</div>
</div>

AngularJS, ng-repeat on radio and $scope.watch

I'm using this example: https://jsfiddle.net/qnw8ogrk/1/ to create my radio-buttons.
I would like to be able to use $scope.watch on the radio-buttons, but I'm not able to use:
$scope.watch('selected', ...
What should I assign to .watch to be able to detect whenever a user clicks on a radio button?
Actual mistake was $scope.watch should be $scope.$watch Having watcher over a scope variable would not be the good idea. Instead of placing watcher over ng-model I'd suggest you to use ng-change which will only fires up when radio button ng-model value gets changed.
<label ng-repeat="option in options">
<input type="radio" ng-change="test()" ng-model="$parent.selected" ng-value="option" />{{option}}
</label>
And then don't use get rid of $parent notation for accessing outer scope of ng-repeat you could switch to use controllerAs pattern, and then change ng-controller directive to use alias of controller. Also that will change the controller implementation, bind value to this(context) instead of $scope
Markup
<div ng-controller="mainController as vm">
<label ng-repeat="option in vm.options">
<input type="radio" ng-change="vm.test()" ng-model="vm.selected" ng-value="option" />{{option}}
</label>
</div>
Controller
appModule.controller('mainController', function($scope) {
var vm = this;
vm.selected = 'red';
vm.options = ['red', 'blue', 'yellow', 'green'];
vm.test = function() {
alert('Changed');
}
});
Forked Plunkr
I agree with #PankajParker, but it is possible from you controller as well. You just got the syntax wrong, it is $scope.$watch, not $scope.watch
Have a look at this: https://jsfiddle.net/qnw8ogrk/32/
Fiddle: https://jsfiddle.net/stevenng/qnw8ogrk/33/
Markup
<div ng-app="app">
<div ng-controller="mainController">
<label ng-repeat="option in options">
<input type="radio" ng-change="picked(this)" ng-model="$parent.selected" ng-value="option"/>{{option}}
</label>
</div>
</div>
Script
var appModule = angular.module('app', []);
appModule.controller('mainController', function($scope) {
$scope.selected = 'red';
$scope.options = ['red', 'blue', 'yellow', 'green'];
$scope.picked = function($scope) {
alert('you picked: ' + $scope.selected);
}
});

Can't access the variable from newly created filter variable of ng-repeat in angular

I have a ng-repeat like this :
<a class="item item-avatar" ng-click="loadProfile({{student.pi_id}})"
ng-repeat="student in filteredStudents = (students | filter:model.txtSearch)" >
<img src="./img/man.png">
<h2>{{student.pi_name}}</h2>
<p>{{student.pi_hp}}</p>
</a>
the question is how can I access the filteredStudents variable? as I can't access it by using $scope.filteredStudents; in the controller
When you are applying the filter in view the filtered value is not accessible in controller.
If you need the filtered data in your controller you need to do filtering in your controller like:
$scope.filteredData = $filter('filter')($scope.filteredStudents, $scope.model.txtSearch)
Inject filter in your controller
function YourController($scope, $filter)
{
}
then, use the filter in controller itself
$scope.filteredStudents = $filter('yourFilterName')(model.txtSearch);
then in html,
<a class="item item-avatar" ng-click="loadProfile({{student.pi_id}})" ng-repeat="student in filteredStudents" >...</a>
Check this SO question
Controllers execute before the view is rendered, which is why the controller function $scope is empty when it executes. You could access the $scope.filteredStudents from within an event handler, such as ngClick:
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
$scope.test = function() {
alert ($scope.items);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<input type="text" ng-model="name" /> {{name}}
<div ng-repeat="i in items = ([1,2,3,4] | filter:name )">
{{i}}
</div>
<button ng-click="test()">Hey</button>
</div>
Alternatively, you can access $scope.filteredStudents by using $timeout:
app.controller('ctrl', function($scope,$timeout) {
$timeout(function() {
// $scope.filteredStudents is accessible here...
alert($scope.filteredStudents);
});
});
This works because the $timeout hander executes after the view is rendered. The javascript enclosure allows the $timeout handler to execute, and still access the controller's function context.

Use an Angular directive to generate html from an array

I'm trying to use an Angular directive to create a form where the user can specify the number of children and, for each child, an edit box appears allowing the childs date of birth to be entered.
Here's my HTML:
<div ng-app>
<div ng-controller="KidCtrl">
<form>
How many children:<input ng-model="numChildren" ng-change="onChange()"/><br/>
<ul>
<li ng-repeat="child in children">
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Here's the JS:
var app=angular.module('myApp', []);
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{dob: "1/1/90"}, {dob: "1/1/95"}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function() {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
};
});
And here's a jsFiddle
The problem is that it's just not working.
If I enter 1 in the numChildren field then it shows 1 bullet point for the list element but it doesn't show any of the HTML.
If I enter 2 in the numChildren field then it doesn't show any list elements.
Can anyone explain what I'm doing wrong?
Many thanks ...
Your main issue is that the directive childDOB is never rendered. Even though your controller works because 1.2.x version of angular has global controller discover on. It will look for any public constructors in the global scope to match the controller name in the ng-controller directive. It does not happen for directive. So the absence of ng-app="appname" there is no way the directive gets rendered. So add the appname ng-app="myApp" and see it working. It is also a good practice not to pollute global scope and register controller properly with controller() constructor. (Global look up has anyways been deprecated as of 1.3.x and can only be turned off at global level.)
You would also need to add track by in ng-repeat due to the repeater that can occur due to increasing the length of the array based on textbox value. It can result in multiple array values to be undefined resulting in duplicate. SO:-
ng-repeat="child in children track by $index"
Fiddle
Html
<div ng-app="myApp">
<div ng-controller="KidCtrl">
<form>How many children:
<input ng-model="numChildren" ng-change="onChange()" />
<br/>
<ul>
<li ng-repeat="child in children track by $index">{{$index}}
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Script
(function () {
var app = angular.module('myApp', []);
app.controller('KidCtrl', KidCtrl);
KidCtrl.$inject = ['$scope'];
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{
dob: "1/1/1990"
}, {
dob: "1/1/1995"
}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function () {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
}
});
})();

Child Directive Model not Updating with Parent Changes Using AJAX call

My directive model is not receiving parent model changes when using an AJAX call to populate model. It should be noted child changes are passed to parent. I have seen other questions close to this but none that seemed to successfully this issue of child not receiving parent updates, while being able to update parent.
The "ngModel" is annotated as required in directive, passed in through template and successfully populated with AJAX call. The "ngModel" is given bidirectional access in the directive scope using ngModel : '=' I included parent html, child html, and angular directive + controller used. I have annotated each to explain usage as well as included annotated log.
Thank you in advance for any advice/pointers/help.
define([ 'angular' ], function(angular) {
'use strict';
return angular.module('app', [])
.controller('addDataCtrl', ['$scope', 'Data', 'User', '$location', '$routeParams', function($scope, Dataset, User, $location, $routeParams) {
// log when data.testEnumeratedType is updated
$scope.$watch('data.testEnumeratedType', function() {
console.log("initialEnumeratedType has been updated to : ", $scope.data.testEnumeratedType);
});
// RESTful call for data
if($routeParams.id) {
$scope.data = Data.get({id: $routeParams.id});
}
}]);
.directive('ngEnumTypeSelect', function() {
return {
restrict : 'A',
require : 'ngModel',
scope : {
ngEnumDomain : '#', // bind scope's ngEnumDomain with ngEnumDomain
// value declared in enclosing template.
ngModel : '=',
},
templateUrl : 'selectEnumeratedTypes.html',
link : function(scope, element, attrs, ctrl) {
scope.getEnumTypes(attrs.ngEnumDomain);
// update this child scope model with parent value.
scope.$watch(ctrl, function() {
scope.currentEnumType = ctrl.$viewValue;
console.log(" Directive model changed to : ", scope.currentEnumType);
});
// update parent scope's model with selected value
scope.$watch('currentEnumType', function (value) {
console.log(" Setting parent model value to : ", value);
ctrl.$setViewValue(value);
console.log(" Parent model value is : ", ctrl.$viewValue);
});
},
controller : ['$scope', 'EnumeratedType', '$location', function($scope, EnumeratedType, location) {
$scope.getEnumTypes = function(enumDomain) {
$scope.enumTypes = [];
EnumeratedType.getEnumTypes( { type : enumDomain }, function(enumType) {
var enumeratedValues = enumType.enumeratedValues;
for (var index = 0 ; index < enumeratedValues.length ; index++) {
$scope.enumTypes.push(enumeratedValues[index].value);
}
});
};
$scope.updateCurrentEnum = function(enumType) {
$scope.currentEnumType = enumType;
};
}],
};
});
});
Annotated Log :
initialEnumeratedType has been updated to : undefined
Directive model changed to : undefined directives.js:140
// below AJAX call is made, parent model value is updated to Trials,
// but change is not communicated to child directive.
initialEnumeratedType has been updated to : Trials
// when value is selected, changed is propagated to parent.
Setting parent model value to : None Selected
Parent model value is : None Selected
HTML
<!-- Parent html -->
<!-- The value is populated below - meaning model is populated via RESTful call -->
<div class="form-group">
<label for="imageName" class="col-sm-2 control-label"></label>
<div class="col-sm-4"></div>
<label for="testEnumeratedType" class="col-sm-2 control-label">Type As Stored</label>
<div class="col-sm-4">
<input type="text" class="form-control" id="testEnumeratedType" placeholder="{{dataset.testEnumeratedType}}" ng-model="dataset.testEnumeratedType">
</div>
</div>
<!-- The value is not initially populated below - meaning the parent model is not updating child model -->
<div class="form-group">
<label for="imageName" class="col-sm-2 control-label">Name</label>
<div class="col-sm-4">
<input type="text" class="form-control" id="imageName" placeholder="{{dataset.name}}" ng-model="dataset.name">
</div>
<label for="enumTypeSelect" class="col-sm-2 control-label">Type</label>
<div style="padding-left: 30px" class="col-sm-4" id="enumTypeSelect" data-ng-enum-type-select="{{dataset.testEnumeratedType}}" data-ng-enum-domain="Type" ng-model="dataset.testEnumeratedType"></div>
</div>
<!-- Child html -->
<div class="row">
<!-- Create dropdown with button and list values -
Display value {{currentEnumType}} is not being initially updated-->
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
{{currentEnumType}} <span class="caret"/>
</button>
<ul class="dropdown-menu" role="menu">
<li><a ng-click="updateCurrentEnum(enumType)">{{enumType="None Selected"}}</a></li>
<li class="divider"></li>
<li ng-repeat="enumType in enumTypes">
<a ng-click="updateCurrentEnum(enumType)">{{enumType}}</a>
</li>
</ul>
</div>

Resources