I have a scenario where when i mouse enter on some text i need to get an input box and text should hide and when i leave the text box the box should hide and i should show the text back
but this is not happening
Html
<div ng-app>
<div ng-controller="showCrtl">
<div ng-hide="showme">
<p ng-mouseenter="showme=!showme">
mouse enter
</p>
</div>
<input type="search" ng-if="showme" ng-mouseleave="showme=false">
</div>
</div>
JS:
function showCrtl($scope){
$scope.showme=false;
}
Here is what i have tried DEMO
Any help is appreciated.
The problem is you had primitive value on ng-if directive, You know ng-if does create child scope whenever it renders that element on DOM. To resolve this issue what I'd suggest you to do is, just follow Dot Rule by defining new object. And then define all the property which you want to use inside that object.
If you want do dig deeper how scope inheritance work, you can refer to this answer.
So in your code, you should define an object suppose i.e. $scope.model = {} and then have showme property inside that object itself. And wherever you use showme on view replace that with model.showme.
Demo Fiddle
More convenient way to resolve such kind of scope inheritance issue is, you could use controllerAs pattern. In that you don't use $scope inside controller, you just place with controller context this. While using controller on view you need to create it alias and when you want to get variable value you can use variable reference.
The issue is that you have multiple nested scopes. The showme that is being set in the input box is different from the showme being set in the p. This happens because Angular implicitly adds a new scope for many built-in directives (ng-if is one of them).
You need to be sure that you are always setting and reading the same showme property. The easiest way to do this is to add the showme property to the controller.
Try this:
<div ng-app>
<div ng-controller="showCrtl">
<div ng-hide="showCrtl.showme">
<p ng-mouseenter="showCrtl.showme=!showCrtl.showme">
mouse enter
</p>
</div>
<input type="search" ng-if="showCrtl.showme" ng-mouseleave="showCrtl.showme=false">
</div>
</div>
In fact, as a general rule of thumb, never access the scope directly in your templates since it is very difficult to be sure you are accessing the scope that you think you are accessing. Always access properties through the controller you are working with.
Related
I am using a slider module called slick. I have a tab heading which tracks and displays the index of the slide so the user can see. Issue is that the tab heading slides with each slide as that tab is inside the carousel (obviously).
I moved the tab heading out of the slide, and planned to display to use ng-model and ng-value to bind the data to an element outside of the slide. Like so:
<div>
<p>Blue Renewal {{vm.slide.number}} of {{$ctrl.blueRenewals.length}}</p>
</div>
<slick data="$ctrl.phaseArr" >
<div ng-repeat="item in $ctrl.phaseArr">
<div>
<input type="text" ng-model="vm.slide.number" ng-value="{{$index + 1}}">
</div>
</div>
</slick>
And in the .js:
vm.slide = {
number: '1'
}
Question
Why isn't the value from the input binding to the p element outside of the slick carousel?
It's hard to say with so little code to go on, but I suspect you're encountering a scope issue.
There are at least two, probably at least three levels of scope in play here. Your heading is in your controller accessing properties of its scope. So when you bind to vm.slide.number it's looking on your scope. The 'slick' component likely creates a scope of its own, which may be where vm comes from. If that's the case, your view (on the parent of slick's scope) can't see properties on it.
(The ngRepeat also creates child scopes, but I don't think that's the problem.)
Here is my plnkr: http://plnkr.co/edit/n8cRXwIpHJw3jUpL8PX5?p=preview You have to click on a li element and the form will appear. Enter a random string and hit 'add notice'. Instead of the textarea text you will get undefined.
Markup:
<ul>
<li ng-repeat="ticket in tickets" ng-click="select(ticket)">
{{ ticket.text }}
</li>
</ul>
<div ui-if="selectedTicket != null">
<form ng-submit="createNotice(selectedTicket)">
<textarea ng-model="noticeText"></textarea>
<button type="submit">add notice</button>
</form>
</div>
JS part:
$scope.createNotice = function(ticket){
alert($scope.noticeText);
}
returns 'undefined'. I noticed that this does not work when using ui-if of angular-ui. Any ideas why this does not work? How to fix it?
Your problem lies in the ui-if part. Angular-ui creates a new scope for anything within that directive so in order to access the parent scope, you must do something like this:
<textarea ng-model="$parent.noticeText"></textarea>
Instead of
<textarea ng-model="noticeText"></textarea>
This issue happened to me while not using the ng-if directive on elements surrounding the textarea element. While the solution of Mathew is correct, the reason seems to be another. Searching for that issue points to this post, so I decided to share this.
If you look at the AngularJS documentation here https://docs.angularjs.org/api/ng/directive/textarea , you can see that Angular adds its own directive called <textarea> that "overrides" the default HTML textarea element. This is the new scope that causes the whole mess.
If you have a variable like
$scope.myText = 'Dummy text';
in your controller and bind that to the textarea element like this
<textarea ng-model="myText"></textarea>
AngularJS will look for that variable in the scope of the directive. It is not there and thus he walks down to $parent. The variable is present there and the text is inserted into the textarea. When changing the text in the textarea, Angular does NOT change the parent's variable. Instead it creates a new variable in the directive's scope and thus the original variable is not updated. If you bind the textarea to the parent's variable, as suggested by Mathew, Angular will always bind to the correct variable and the issue is gone.
<textarea ng-model="$parent.myText"></textarea>
Hope this will clear things up for other people coming to this question and and think "WTF, I am not using ng-if or any other directive in my case!" like I did when I first landed here ;)
Update: Use controller-as syntax
Wanted to add this long before but didn't find time to do it. This is the modern style of building controllers and should be used instead of the $parent stuff above. Read on to find out how and why.
Since AngularJS 1.2 there is the ability to reference the controller object directly instead of using the $scope object. This may be achieved by using this syntax in HTML markup:
<div ng-controller="MyController as myc"> [...] </div>
Popular routing modules (i.e. UI Router) provide similar properties for their states. For UI Router you use the following in your state definition:
[...]
controller: "MyController",
controllerAs: "myc",
[...]
This helps us to circumvent the problem with nested or incorrectly addressed scopes. The above example would be constructed this way. First the JavaScript part. Straight forward, you simple do not use the $scope reference to set your text, just use this to attach the property directly to the controller object.
angular.module('myApp').controller('MyController', function () {
this.myText = 'Dummy text';
});
The markup for the textarea with controller-as syntax would look like this:
<textarea ng-model="myc.myText"></textarea>
This is the most efficient way to do things like this today, because it solves the problem with nested scopes making us count how many layers deep we are at a certain point. Using multiple nested directives inside elements with an ng-controller directive could have lead to something like this when using the old way of referencing scopes. And no one really wants to do that all day!
<textarea ng-model="$parent.$parent.$parent.$parent.myText"></textarea>
Bind the textarea to a scope variable's property rather than directly to a scope variable:
controller:
$scope.notice = {text: ""}
template:
<textarea ng-model="notice.text"></textarea>
It is, indeed, ui-if that creates the problem. Angular if directives destroy and recreate portions of the dom tree based on the expression. This is was creates the new scope and not the textarea directive as marandus suggested.
Here's a post on the differences between ngIf and ngShow that describes this well—what is the difference between ng-if and ng-show/ng-hide.
I have just started learning Angular2, and was wondering why the developers decided to use various different wrapper in he html? For instance:
[(ngModel)]="some.property"
(click)="someMethod()"
[src]="some.property"
I'm sure there is a nice logical reason behind it, and I am aware that they're used for differing purposes, but at first glance it seems inconsistent and an unnecessary obstacle to overcome.
Each syntax has its own goal.
1) Event Binding
This is a one way binding from inner to outer component. Called as event. The outer component will call the someMethod when the click event is triggered from the inner component, or from the current tag.
(click)="someMethod()"
Example: Here button's click handler calls the onClickMe() function
#Component({
selector: 'click-me',
template: `
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent {
clickMessage = '';
onClickMe() {
this.clickMessage = 'You are my hero!';
}
}
2) One way data binding
This is a one way binding from the outer into inner. This will pass the some.property to the src property in the inner component or tag.
[src]="some.property"
Example. Here we bind to the innerText property the value of name property
<h1 [innerText]="name"></h1>
or
<h1>{{ name }}</h1>
3) Two way data binding
And this is a two way binding from inner to outer and vice versa. This will do the two things.
[(ngModel)]="some.property"
Example: Here input's value will be updated when username will be updated. And aslo when we type another value into the input, the username will be updated. So here you get two way data bindings. And under the hood with [(ngModel)] it creates one-way and event-bindings. This lines
<input [(ngModel)]="name">
<p>Hello {{ name }}!</p>
are equal to
<input [value]="name" (input)="name = $event.target.value">
<p>Hello {{ name }}!</p>
For a deep knowledge you can look up in the documentation
this relates to visibility and control of binding. square brackets is binding from parent to child.
normal brackets is binding child to parent using event callbacks
both is two way binding.
In angular1 I think there was a lot less control over directional binding.
you can bind from controller to view, etc. But I use the parent child component example for simplicity.
It makes total sense to use different brackets.
There are these three types you have mentioned.
Property Binding []
Event Binding ()
Two-Way Data Binding [()]
The property binding is used to bind properties to an html element.
So you can use this for every single property on an html element.
It is also used by the Angular Directives which you can use from the framework or build yourself.
<img [src]="anImageUrlInTheComponent">
<!-- or -->
<a [routerLink]="'/dashboard'">Dashboard</a>
The event binding is used to bind events like click, mouseover or onSubmit etc.
You can also make your own events on a component with the EventEmitters but this is another subject.
<button (click)="doSomething()">Do Something</button>
<!-- or -->
<form (onSubmit)="submitForm()">
<!-- form content -->
</form>
The last is the Two-Way Data Binding used for instance in NgModel.
I recommend to read this article about Two-Way Data Binding.
Hope this helps :D
I'm creating a custom Angular directive for a slide in menu which needs to watch a couple of attributes and one of those attributes needs to be two way bound to the main controller scope (sometimes). However, sometimes the attribute will not be added by the developer so it needs to be added automatically and set to the default (false). So, the directive can be used like this.
<slide-menu position="right" is-open="menuIsOpen"></slide-menu>
or like this:
<slide-menu></slide-menu>
When used the first way the main controller will be able to open and close the menu by changing the value of the boolean $scope.menuIsOpen.
When used without supplying the is-open attribute it should default to false and is obviously used internally and by a child toggle directive.
An additional complication is that whether the attribute is supplied by the developer or not it should exist in the DOM. so in the second example above the directive would set itself to false by default and add the attribute is-open="false" to the DOM?
The reason for requiring is-open="false/true" in the DOM at all times is that the menu is actually operated using CSS tansitions which use the following selector:
slide-menu[is-active="true"]{
// Slide the menu in using transforms/transitions
}
There is a jsfiddle here which shows how far I have got.
http://jsfiddle.net/jonhobbs/gEPvE/
Obviously it doesn't work, but it shows how I have tried to set a default and how I have tried to use # and & on the isolated scope for a one time binding (the menu position) and a 2 way bound expression for the is-open variable.
I'm clearly a long way from achieving what I need but any advice would really be appreciated.
Have a look at this fiddle http://jsfiddle.net/gEPvE/38/
I took the one you started and updated it to act like you specified.
You can make a two way binding value optional by adding a ? on the scope definition.
Like this
{
scope: {
'isOpen':'=?'
}
}
Now the is-open attribute is optional.
Then you can set the default value in the directive controller, like you had started to do.
Next, in order to synchronize the DOM attribute with the scope value you can use $watch.
$scope.$watch('isOpen', function(val) {
$element.attr('is-open', val);
});
Finally, I changed the second 'slideMenuToggle' directive to wrap/transclude its element in order to add an ng-click handler. This is mainly to avoid any nastiness with calling $scope.$apply yourself.
Let me know if that works for you.
EDIT
Answering your question in the comment, you can pass a value directly without having it be bound to the scope, you just need to wrap the value in quotes.
For example
<div ng-controller='ctrl'>
<hello world='imOnScope'></hello>
</div>
Assuming 'hello' is a directive with a scope of 'world': '=?' then angular will assign a reference to the parent scope's 'imOnScope' object to the directive's $scope.world member, allowing a two way binding scenario.
To just provide a value directly you may do something like this
<div ng-controller="ctrl">
<hello world="'directValue'"></hello>
</div>
In this scenario angular will just assign 'directValue' to the directive's $scope.world member.
You need to add ngTouch to your module.
var app = angular.module('app', ['ngTouch']);
And add this script:
http://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-touch.js
The reason for requiring is-open="false/true" in the DOM at all times
is that the menu is actually operated using CSS tansitions which use
the following selector
Forcing directive attributes to be appropriate for css selectors is terrible idea. As you correctly stated, they are for developers. So add a class to the element dynamically.
It seems that you're misusing &, it would be ok to set up a callback, but since you don't do this, in its current state you can end up with one-way # with confidence.
I guess it can be something like this (just added ngTouch and ng-controller for parent scope).
You could replace
$scope.watch('isOpen', function () {
$element.toggleClass('opened', $scope.isOpen);
});
with
$scope.watch('isOpen', function () {
$attrs.isOpen = !!$scope.isOpen;
});
and get the behaviour you're asking for, easy as that. Ok, it is boolean now, and it reflects the scope, and you can use [is-open=true] selector. But guess what will happen with your binding? Broken. Fortunately, you can do
$scope.watch('isOpen', function () {
$element.attr('is-open', !!$scope.isOpen);
});
instead. Voila, we tricked Angular because it doesn't look after jqlite. But what will will happen with the binding when the directive will be re-compiled for any reason? Again, isOpen's binding is non-existing 'true' or 'false' scope variable. Broken.
I have an element that has both a controller and a directive with an isolate scope applied to it:
scope: {
dirVar: '='
}
The goal is to run certain parts of the directive only if a variable holds true. I'm setting that variable in the controller and trying to pass it into the directive through an attr.
The problem is that when I do something like
<div ng-controller="MyCtrl" my-directive active="ctrlVar"></div>
and try to get active in the directive with scope.active, it always comes up undefined.
Here is an example: http://jsfiddle.net/u3t2u/1/
Any explanation as to why or how to properly do this? I assume the problem is with the controller and directive being applied to the same element and wish to get around that.
Another option would be to remove the directive's isolate scope and have it evaluate an attr passed to it, but I'm not sure how to do that ($parse keeps throwing errors).
That is because your directive is not inside the controller. Try this:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<div my-directive="" active="myValue">
Testing.
</div>
</div>
</div>
Ended up changing the way I structured the directive because it wasn't something that should really have had an isolate scope, and the only reason it did was so it could take expressions and evaluate them to true or false.
So I changed it to use $parse, which left the directive looking something like:
var active = $parse(attrs.isActive);
// Evaluate contents of attrs.isActive
// as if they are variables within its scope,
// which is inherited from parent scopes
if(active(scope)) {
// do something
}
I am not too familiar with certain things like transclude and creating an isolated scope, but this is what I got after reading the docs for Directives and fiddling around:
http://jsfiddle.net/u3t2u/4/
I only changed this portion of the html:
<div ng-controller="MyCtrl">
<div my-directive active="myValue">
Testing.
</div>
</div>
I believe that in this case, you do not actually have to pass a value to the my-directive directive, since you are already using an isolate scope with an =. Sorry if my explanation is not that good. You can read more at http://docs.angularjs.org/guide/directive , under the section Writing directives (long version).