AngularJS : Access to isolate scope with replace in directive - javascript

I want to know if there is a way to access the scope of directive if I have set the replace option to true. For example, here is the initial jsbin:
http://jsbin.com/ABuSODub/2/edit
When you click on x, the directive closes. close() function is defined on the directive's own isolate scope. However, if I set replace to true, the thing stops working which is understandable because there is no directive anymore.
Can it be made to work? It there a better approach. I really do not want to set close function on parent controller. Or is it some other problem entirely which I do not see?

If all you want to do with the click event is set open = false, you don't need to call a function to do that. Consider this template revision:
'<div class="alert-box {{box}}" data-ng-if="open" data-ng-init="open = false"><span data-ng-transclude></span>×</div>',

Related

AngularJS directive, scope

hey I have a question
If in directive I use scope {}. Then I know that is create local scope. but could someone tell me how it looks. Because I have a problem with understanding it.
It is so that If i add dom attribute, for example card
<div modal-window-card card="card" ng-model="text"></div>
Then whole variable with $scope.card is assigned to this card where next, I can modificate everything in the directive like in function yes?
But why I can't write all value from variable in :
link: function(card) {
console.log(card)
},
And one more thing, whole directive is like in new local scope yes? I mean, all functions, which are located in the directive is in this scope yes?
for example
<buton ng-click="fefe()">start</buton>
If i click start buton it will execute function with the directive, yes? if i set ng-click="$parent.fefe()" then after click it will execute function with parent scope, yes?
You are using link incorrectly. The signature is link(scope, element, attrs). As for the other questions, yes and yes, assuming your buton is within the directive's template.

how to pass angular js variable inside onclick function as parameter

I have tried to pass AngularJS variable as argument value inside onclick() to call javascript function. Can anyone guide me on how to do it?
My code:
<div onclick="deleteArrival({{filterList.id}})" class="table-icon deleteIcon">{{filterList.id}}</div>
You should be using ng-click, there is no reason to use onclick as angular provides you with this functionality
<div ng-click="deleteArrival(filterList.id)"
class="table-icon deleteIcon">{{filterList.id}}</div>
You should then move your function into your AngularJS Controller, and bind it to the scope
$scope.deleteArrival = function(filterListId) { ... };
If you ABSOLUTELY need to use onclick to call an external function, you could change the function to something like this in your scope, still using the ng-click attribute above:
$scope.deleteArrival = function(filterListId) { window.deleteArrival(filterListId); };
However I can't see a reason not to move it into your scope
If you still want to use onclick , this is work for me , I hope it work for you too.
<div id="{{filterList.id}}" onclick="deleteArrival(this.id)" class="table-icon deleteIcon">{{filterList.id}}</div>
Im not going to second guess your reasons for not using ng-click, as other contributors have pointed out you really 'ought'. However if you really want/need to, heres my suggestion by using 'this' and data attributes.
<div data-filterListId="{{filterList.id}}" onclick="deleteArrival(this)" class="table-icon deleteIcon">{{filterList.id}}</div>
function deleteArrival(arrivalElem) {
alert('myId=' + arrivalElem.getAttribute("data-filterListId"));
}
You could easily solve your problem using ng-click but you should have deleteArrival method in your scope.
Markup
<div ng-click="deleteArrival(filterList.id)" class="table-icon deleteIcon">
{{filterList.id}}
</div>
Above thing is easily possible using ng-click directive and having that function inside controller scope, only the thing is you need to assign your java-script function reference to controller scope variable. No need to rewriting the function in your scope again. Pass the reference of function will do the trick.
Markup
<div ng-click="deleteArrival(filterList.id)" class="table-icon deleteIcon">
{{filterList.id}}
</div>
Controller
//assign javascript method reference in controller
$scope.deleteArrival = deleteArrival;
You are not allowed to create a binding for event handler attributes like onclick, onload, onsubmit, etc. in angularjs because,
there is no practical value in binding to these attributes and doing so it only exposes your application to security vulnerabilities like XSS. For these reasons binding to event handler attributes (all attributes that start with on and formaction attribute) is not supported in angularjs.
For your case,
Inside ng-repeat, use ng-click for sending values to your function and declare that function in controller.
See here for documentation of ng-click
Hope this helps !
try this without the curly braces
deleteArrival(filterList.id)
I had a case where I could not use ng-click due to window binding. Did not get direct answer but the below did work for me. I know it is not a good solution but workable in my case.
angular.element(this).scope().ctrlName.variable
You can try using your own class structure. So the click will be as below:
onclick="test(angular.element(this).scope().ctrlName.ObjName.variable)"
$scope.getpop = function(id){
alert(id);
}
<span ng-click='getpop({{x.Code}})' class="btn-default btn">{{ x.title }}</span>
<b>This works perfect for me !</b>
My use case was where my application is relying on a large JavaScript file of functions that are not written in Angular. For this, I added a function in my controller that connects to one of these JS functions:
$scope.controllerFunction = function() {
javaScriptFunction('foo');
}
javascriptFunction(parameter){
let variable = parameter
}
Then in my view, making use of ng-click, I accessed the controller function with:
ng-click="controllerFunction()"
For me, the "onclick="deleteArrival(this.id)" example returns undefined. The above was the best solution.

Angular custom directive - two way binding which always sets attribute to true or false

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.

Angularjs isolates scope directive with ng-repeat

I'm trying to use directive on ng-repeat items each with an isolate scope but it isn't working. I'm looping through each item and coloring it red with the inboxuser-select directive. However, when I put the directive on, it doesn't show any of my scope values. What is the issue here? Thanks
html file
<li class="inbox-chatter" data-ng-
repeat="inboxuser in inboxusers">
<p inboxuser-select selected={{inboxuser}}">{{inboxuser}}</p>
</li>
directive.js
.directive('inboxuserSelect', function() {
return {
restrict: 'A',
scope: {
selected: "#"
},
link: function(scope, element, attrs) {
scope.selected.css('color','red');
}
}
});
The problem is that once you set an isolate scope on the directive then the whole DOM element has that isolate scope. So the inboxuser from your ng-repeat is no longer in scope when data binding occurs (it's on the parent scope).
One option is to set scope to true instead of using an isolate scope so you'll inherit everything from the parent scope.
Or you can stick with an isolate scope, but pass inboxuser in to the directive and display it using a template. Since you're already passing inboxuser in to the directive's scope through selected it'd be easy to just add this to your directive:
template: '{{selected}}',
Also, by the way, you're missing a quote on your <p>. So this might work better for you (note I also removed {{inboxuser}} from within the <p> assuming you'll be using the template to display that instead):
<p inboxuser-select selected="{{inboxuser}}"></p>
To be honest, I don't understand what you really need to do but I have a feeling that this design will not get you there.
However, I fixed your example just for the purposes of explaining how things work.
You can see it live here.
So... when you write:
scope: {
selected: "#"
}
you are actually saying that my isolated scope will hold a single property named selected which will be of type string and will contain whatever {{inboxuser}} evaluates to. And not only this, whenever inboxuser changes in the outter scope, selected will also change in the inner, isolated scope. This is how '#' binding works.
Whatever you put nested in <p inboxuser-select selected="{{inboxuser}}"></p>, is binded to that isolated scope, which does not have an inboxuser property. So, it has to change to:
<p inboxuser-select selected="{{inboxuser}}">{{selected}}</p>
Finally, scope.selected.css('color','red'); should be changed to:
element.css('color','red');
The element argument in link function is the DOM element where the directive instance is applied. scope.selected is just a string.
I suggest you rething your overall design. If you need help, feel free to ask.
If it helps you, you can use AngScope, a tiny firebug extention i've written. It's just a quick way to inspect $scope instances associated to DOM elements inside firebug's DOM inspector.

Genuinely stop a element from binding - unbind an element - AngularJS

I'm trying to find out how I can stop a DOM element from binding data from the scope in angular.
I know that you could do this with if statements and all, but is there a genuine & permanent way to stop binding a element in angular but keep the content that was added?
So say i have this
<div ng-bind=​"content" class=​"ng-binding">​Welcome​</div>​
And i change the model so that the div changes to this.
<div ng-bind=​"content" class=​"ng-binding">​Welcome​ World</div>​
Then I click the button that will unbind it, so if I change the model to 'Welcome Universe', I wan't the <div> to be the same as before. This
<div ng-bind=​"content" class=​"ng-binding">​Welcome​ World</div>​
I know there are many other ways to do this, but i don't know any way to genuinely unbind the element, without cloning it and replacing the old one looping through the attributes and text..ect
Demo thing: http://jsfiddle.net/a9tZY/
So, by doing this, it shouldn't affect the model or other elements that are binding to that model.
Long story short, Tell Angular to leave the element alone forever.
UPDATE
The way to do this is to create a new scope on the element with a directive like so.
yourModule.directive('unbindable', function(){
return { scope: true };
});
And apply it to your element like so
<div unbindable id="yourId"></div>
Then to unbind this element from any updates you do this.
angular.element( document.getElementById('yourId') ).scope().$destroy();
Done, here's a demo.
Demo: http://jsfiddle.net/KQD6H/
So this creates a new scope on the element and only works because all scopes inherit all data from their parent scopes. so the scope is basically the same as the parent scope, but allows you to destroy the scope without affecting the parent scope. Because this element was given it's own scope, when you destroy it it doesn't get the parent scope back like all of the other elements, if that makes sense 0.o
Everything below this line was my original answer,I'll leave it here incase someone prefers this way
I have managed to achieve this genuinely with a unbindable directive.
When you have the unbinable directive set up on the element all that is required to unbind the element is this.
yourElement.attr('unbind', 'true'); // Ref 1
$scope.$broadcast('unbind'); // Ref 2
Here is the directive.
app.directive('unbindable', function(){
return {
scope: true, // This is what lets us do the magic.
controller: function( $scope, $element){
$scope.$on('unbind', function(){ // Ref 3
if($element.attr('unbind') === 'true'){ // Ref 4
window.setTimeout(function(){ $scope.$destroy() }, 0);//Ref 5
}
});
}
}
});
and you set your element up like this.
<h1 unbindable></h1>
So whenever you add the unbind="true" attribute to the h1 and broadcast unbind the element will be unbind-ed
REF-1: Add the unbind true attribute to the element so that the directive knows what element you are unbinding.
REF-2: Broadcast the unbind event across the scopes so that the directive knows that you want to unbind a element - Make sure you add the attribute first. --- Depending on your app layout, you might need to use $rootScope.$broadcast
REF-3: When the unbind event is broadcasted
REF-4: If the element associated with the directive has a true unbind attribute
REF-5: Then destroy the scope made by the directive. We have to use setTimeout because I think angular tries to do something after the $on event and we get a error, so using setTimeout will prevent that error. Although it fires instantly.
This works on multiple elements, here is a nice demo.
Demo: http://jsfiddle.net/wzAXu/2/
This one got me curious, so I did some poking around. At first I tried the "unbind()" method suggested in the other answer, but that only worked with removing event handlers from the element when what you're actually trying to do is remove the angular scope from the element. There may be some neater hidden function in Angular to do this, but this works just fine too:
angular.element(document.getElementById('txtElem')).scope().$destroy();
This retains the model (and updates anything else still bound to it), but removes the binding from the element. Also, in your example above, there is no binding to remove because you aren't binding to any element, just displaying the model expression inline. My example shows this in action: http://jsfiddle.net/3jQMx/1/
You can call the unbind method that stops listening to the element where the ng-model attribute is present. See fiddle: http://jsfiddle.net/jexgF/
angular.element(document.getElementById('txtElem')).unbind()
unbind removes all event listeners, so whenever any changes are made, it wont listen for those and hence not go through the angular loop. I have also assumed that you are not using jQuery, but if you are, you can use a better selector than document.getElementById

Categories

Resources