ng-if and scope variable control interaction [duplicate] - javascript

In the code below the 2nd checkbox does not work when the 1st checkbox is clicked.
http://plnkr.co/edit/JF0IftvWx7Ew3N43Csji?p=preview
HTML:
<html ng-app="App">
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-animate.min.js"></script>
<link rel="stylesheet" type="text/css" href="animations.css" />
<script type="text/javascript" src="script.js"></script>
</head>
<body>
Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/>
Show when checked:
<span ng-if="checked==true" class="animate-if">
<input type="checkbox" ng-model="checked1" ng-init="checked1=true" />
</span>
<br>
<span ng-if="checked1==true" class="animate-if">
test <input type="checkbox" ng-model="checked2" ng-init="checked2=true" />
</span>
</body>
</html>

As noted ng-if creates it's own scope.
You're setting checked1 inside what I'm calling "Inner Scope 1". Then using it in "Outer Scope". "Outer Scope" can't see into "Inner Scope 1" so javascript creates a new variable checked1 on "Outer Scope". Now you have two entirely different variables both calledchecked1- one on "Outer Scope" and one on "Inner Scope1". This is not what you want.
To fix this you need to set checked1 on the same scope as you'll use it- "Outer Scope". "Outer Scope" is the parent of "Inner Scope1" so we can use $parent.checked1 like so:
<input type="checkbox" ng-model="$parent.checked1" ng-init="checked1=true" />
Now there's only one copy of checked1- the one on "Outer Scope". And it works, check it out on this updated plunker: http://plnkr.co/edit/XaPWYvYLrFRZdN2OYn6x?p=preview

ngIf creates a different scope. Use $parent:
<span ng-if="checked==true" class="animate-if">
<input type="checkbox" ng-model="$parent.checked1"
ng-init="$parent.checked1=true" />
</span>
<br>
<span ng-if="checked1==true" class="animate-if">
test <input type="checkbox" ng-model="$parent.checked2"
ng-init="$parent.checked2=true" />
</span>
Plunk

You can use ngShow instead of ngIf. ngShow helps because it doesn't create child scope. See result here.

The issue comes from the fact that you are using primitive type (boolean) for your data model. Generally it is admit that you should not use primitive types for two way data binding. If you want to know why I encourage you to read this article important part being :
This means that, if a property changes within a local scope, the original/parent version of the property isn’t updated with those changes.
In your case what is happening is :
- You are using primitive type boolean
- ngIf creates/destroy its scope while inheriting from its parent scope
- Every change made within the ngIf local scope is never propagated to the parent.
- Hence no update on the nested if.
To fix the issue please use a JS object to hold your check boxes values as follows :
<body>
Click me:
<input type="checkbox" ng-model="checkboxes.first" ng-init="checkboxes = {first : true, second:true, third:true}" />
<br/>Show when checked:
<span ng-if="checkboxes.first==true" class="animate-if">
<input type="checkbox" ng-model="checkboxes.second" />
</span>
<br>
<span ng-if="checkboxes.second==true" class="animate-if">
test <input type="checkbox" ng-model="checkboxes.third"/>
</span>
</body>
Note that the check boxes are now bound to boolean values inside a js object.
Modifyied working Plunker over here

I think the issue has to do with the fact that you are not nesting the ng-if. The two span tags are siblings.
So, my theory is that when the compile phase occurs, the ng-if that the Third depends on cannot be executed. So, it cannot build up the entire Third part of the template.
This plunker is working (with nested elements).
<div>First <input type="checkbox" ng-model="first" ng-init="first = true"/></div>
<div ng-if="first">
Second <input type="checkbox" ng-model="second" ng-init="second = true"/>
<div ng-if="second">
Third <input type="checkbox" ng-model="third" ng-init="third = true"/>
</div>
</div>
If I change my plunker to this, it is not working (with siblings):
<div>First <input type="checkbox" ng-model="first" ng-init="first = true"/></div>
<div ng-if="first">
Second <input type="checkbox" ng-model="second" ng-init="second = true"/>
</div>
<div ng-if="second">
Third <input type="checkbox" ng-model="third" ng-init="third = true"/>
</div>

Related

Selenium with Python -- finding element without proper identifiers

I am using selenium with python to write the code. I am looking to pull the information from a text box. The box auto fills as other information is being filled out. Inspecting the box gives the following code:
<input type="tel" autocomplete="off" name="amount" step="any" class="form-
control ng-pristine ng-untouched ng-valid ng-isolate-scope ng-not-empty"
placeholder="" tw-focusable="" show-decimals="$ctrl.showDecimals" tw-number-
input-formatter="" ng-change="$ctrl.changedAmount()" ng-
model="$ctrl.ngModel" ng-disabled="$ctrl.ngDisabled" disabled="disabled"
style="">
The issue is that there is already another input box that has the name "amount", so I can't do a simple selection by name. I am thinking this would require me to use a CSS selector but everything I have tried so far has not worked. Please let me know what I can try.
Looks like you need to use CSS or XPath locators.
Its hard to tell how exactly you can find that element since you haven't provided a source of the entire page but here are some tips.
In the worst case when you cant find any combination of attributes that will uniquely identify the element you need to rely on dom nodes hierarchy, i.e. in order to find first input on the following page:
<!DOCTYPE html>
<html>
<head>
<title>Dummy page</title>
</head>
<body>
<div>
<div>
<input type="text">
</div>
<p>
<input type="text">
</p>
<input type="text">
</div>
</body>
</html>
You can use XPath locator that might look similar to this one:
//div/div/input
But that's the worst case, usually you can use more flexible locators based on element attributes that less likely to be affected by page structure modifications. Let's say each of our inputs from the page above has "name" and "disabled" attributes.
<div>
<div>
<input name="input1" disabled="" type="text">
</div>
<p>
<input name="input1" disabled="disabled" type="text">
</p>
<input name="input2" disabled="" type="text">
</div>
Then we can find first input using the following locator:
//input[#name="input1" and #disabled=""]
Hope that helps.

AngularJS : ng-model binding in ng-repeat

I'm looping through a series of data and want to dynamically bind a model.
My problem is that when looping through elements, it seems as if Angular creates a new scope for each iteration, so the models are not the same in the three iterations.
I've made a simplified example of my code that does not work;
http://jsfiddle.net/Fizk/uurL65e5/
<div ng-app="">
<p ng-repeat="key in [1,2,3]">
<input type="text" ng-model="contact.name" />
{{contact}}
</p>
</div>
As opposed to the non-dynamic way that works:
http://jsfiddle.net/Fizk/d0smns1v/
<div ng-app="">
<p>
<input type="text" ng-model="contact.name" />
{{contact}}
</p>
<p>
<input type="text" ng-model="contact.name" />
{{contact}}
</p>
<p>
<input type="text" ng-model="contact.name" />
{{contact}}
</p>
</div>
The real code is a bit more complicated, and I cannot just hardcode the number of fields, since it's dynamically fetched from an api.
I've looked through tons of questions regarding dynamic model binding and looked through the documentation, but with no luck.
Can anyone shed some light on how I can make all three fields use the same model, so they'll update nicely?
Angular 1.3 added a controllerAs option which should solve all your issues when dealing with child scopes and scope inheritance.
This is considered best practice today. I've created a plunker for you.
<div ng-app="myApp" ng-controller="myCtrl as vm">
<p ng-repeat="key in [1,2,3]">
<input type="text" ng-model="vm.contact.name" />
{{contact}}
</p>
</div>
<script>
angular.module("myApp", []).controller("myCtrl", function() {
var vm = this;
// use vm.value instead of $scope.value
});
</script>
I highly recommend reading this article: Understanding Scopes.
And to understand the new controllerAs syntax you should check out: Exploring Angular 1.3: Binding to Directive Controllers.
If you assign contact to be an object in the parent scope before creating the child scope (I'm using ng-init to do this but it would make more sense in a controller), it will work as the child scopes will inherit the reference to the same object.
http://jsfiddle.net/uurL65e5/1/
<div ng-app="" ng-init="contact = {}">
<p ng-repeat="key in [1,2,3]">
<input type="text" ng-model="contact.name" />
{{contact}}
</p>
</div>
you need to define your model before, usually you define your model in your controller but for instance this works :
<div ng-app="">
<input type="text" ng-model="contact.name" />
<p ng-repeat="key in [1,2,3]">
<input type="text" ng-model="contact.name" />
{{contact}}
</p>
</div>
Just use $parent
<div ng-app="">
<p ng-repeat="key in [1,2,3]">
<input type="text" ng-model="$parent.contact.name" />
{{contact}}
</p>
</div>
Fiddle - http://jsfiddle.net/ujmd0pc9/

AngularJs Error: [ngModel:nonassign]

I have started to learn AngularJS and this is what amazes me, at the beginning even a four lines of code does not work properly and I have no clue
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<div data-ng-app="">
<input type="text" ng-model="name='Rocky'">
Your name is {{name}}
</div>
On typing something in the textbox, my expression does not change.
It shows the below error in the console.
TypeError: r is not a function
You can't initialize to Rocky inside ng-model. Try this:
<div data-ng-app="">
<input type="text" ng-model="name" ng-init="name='Rocky'">
Your name is {{name}}
</div>
Docs
This error occurs when expression the ngModel directive is bound to is a non-assignable expression.
You need to initialize using ngInit directive. You cannot initialize using ngModel
The ngInit directive allows you to evaluate an expression in the current scope.
<input type="text" ng-init="name='Abhinav'" ng-model="name" />
DEMO
Using ng-value instead of ng-model worked for me.

Change class of div based on radio button inside it?

I have the view below. What I want is to change the class of the div based on whether or not the radio button inside it is checked. What am I doing wrong? Note: I'm a newbie to AngularJS and this project is me trying to learn Angular..
<div class="col-use-select" ng-repeat="show in shows" ng-class="{show[name]:'test'}[tvshow.chosen]">
<input id="{{show.name}}" type="radio" name="checkbox" ng-model="tvshow.chosen" ng-value="show.name">
<label for="{{show.name}}">{{show.name}}</label>
</div>
You are applying ng-class to div of ng-repeat hence it is assigned to all div's. You need to create a child div, add ng-class to that div with your condition. it will toggle the class of only that child div
<div class="col-use-select" ng-repeat="show in shows">
<div ng-class="test:tvshow.chosen">
<input id="{{show.name}}" type="radio" name="checkbox" ng-model="tvshow.chosen" ng-value="show.name">
<label for="{{show.name}}">{{show.name}}</label>
</div>
</div>
What I got from the question is that you want to change the class on a particular radio button selection. The html markup that you have shown is not a valid one when assigning a class on conditional basis in angular.
Please find one below which will change the css class of the div on selection of option and then choose another class on the basis of alternate selection
<div ng-app ng-controller="test">
<div class="col-use-select" ng-repeat="show in shows" ng-class="{test:tvshowChosen=='abc',test2:tvshowChosen=='def'}">
<input id="{{show.name}}" type="radio" name="checkbox" ng-model="$parent.tvshowChosen" ng-value="show.name">
<label for="{{show.name}}">{{show.name}}</label>
</div>
</div>
In this example the line:
ng-class="{test:tvshowChosen=='abc',test2:tvshowChosen=='def'}"
Signifies that if tvshowChosen value is abc then test class will be applied or in the second case test2 will be applied.
This line:
ng-model="$parent.tvshowChosen"
Over here $parent is used as ng-repeat creates its own isolated scope. So $parent will target the parent scope and not individual isolated scopes so that the selection can be a valid one.
Working Fiddle

Is there a better way to "link" text inputs?

I want to have the input boxes linked so that when you type something in one, it shows up in the other (and vice versa). Here's a "codepen" that shows how I'm doing it currently.
http://codepen.io/anon/pen/yEAbk/
It's pretty simple, but I feel like there should be an easier way to accomplish this. There is also a problem with this method that I illustrated in the codepen. You'll notice the button that fills one of the boxes with a string. Even though the content has been changed, the "onchange" event doesn't run, and so the other input box is not updated.
Is there an easier way to do this that will fix the problems I've been having?
This is exactly the kind of problem databinding is meant to solve. There are lots of libraries out there, but the most popular currently is AngularJS (by google). http://angularjs.org/
See demo: http://jsfiddle.net/34ZCs/2/ both inputs are bound to variable yourName:
<div ng-app>
<div>
<input type="text" ng-model="yourName" placeholder="A">
<input type="text" ng-model="yourName" placeholder="B">
</div>
</div>
Use "onkeyup" instead of "onchange". Then the next textbox will be updated instantly.
Do something like this
<input type="text" name="a" id="a" onkeyup="fillBoth('a','b')" onfocus="fillBoth('a','b')" />
<input type="text" name="b" id="b" onkeyup="fillBoth('b','a')"
onfocus="fillBoth('a','b')"/>
<button type="button" id="clicky" onclick="fillWithX()">click here for xxx</button>
And you JavaScript should be updated like this.
function fillWithX() {
document.getElementById('a').value = 'xxx';
document.getElementById('a').focus();
}
Slight change in your event handler might do the trick. Take a look: http://codepen.io/anon/pen/budnp/
<input type="text" name="a" id="a" onchange="fillBoth('b', 'a');" />
<input type="text" name="b" id="b" onchange="fillBoth('a', 'b');" />
<button type="button" id="clicky" onclick="fillWithX()">click here for xxx</button>
and the script:
function fillBoth(copyTo, copyFrom) {
document.getElementById(copyTo).value = document.getElementById(copyFrom).value;
}

Categories