Share Directive Scope with Transcluded Elements - angularjs

I am writing a reusable modal directive, but the transcluded elements create their own scope. Here is a JSFIDDLE of the issue. Here is how it works.
<button ng-click="show=!show">Show Modal</button>
<modal visible="show">
<button ng-click="show=!show">X</button>
</modal>
Notice that the button to show it works, but the X does not close it, because the inner button is transcluded and creates its own scope. Is there any way to link the transcluded scope to the current scope of the directive? Or just stop it from creating its own scope? Here is the directive.
.directive('modal', function($compile) {
return {
restrict: 'E',
template: "<div ng-style='bgstyling()' ng-transclude></div>",
transclude: true,
link: function(scope, elem, attrs) {
scope.$watch(attrs.visible, function(val) {
elem[0].style.visibility = val ? 'visible' : 'hidden';
});
scope.bgstyling = function() {
return {
'position': 'fixed',
'top': '0',
'left': '0',
'bottom': '0',
'right': '0',
'backgroundColor': attrs.bgColor || '#000',
'opacity': attrs.opacity || '0.85'
}
}
}
}
})
* UPDATE *
I think the answer might have something to do with the transclude function parameter of the link function. This is what I just tried, but still isn't quite working.
link: function(scope, elem, attrs, nullC, transclude) {
transclude(scope, function(clone) {
elem.append($compile(clone)(scope));
});
...

Making the controller responsible for updating the scope helps - the scope is shared after all and you probably want the logic for updating it in the same place.
.controller("testCtrl", function($scope) {
$scope.show = false;
$scope.toggle = function() {
$scope.show = !$scope.show;
};
})
And the template:
<div ng-app="test" ng-controller="testCtrl">
<button ng-click="toggle()">Show Modal</button>
<modal visible="show">
<button ng-click="toggle()">X</button>
</modal>
</div>
Check out this JSFiddle

The close button is specific to the Modal directive so you could just put the button in the directive html and avoid the scoping issue.
jsfiddle
...
template: "<div ng-style='bgstyling()'><button ng-click=\"show=!show\">X</button></div>",
...
You can also access the parent scope if necessary but I prefer to try and keep scopes as simple as possible.
Edit
You can still use transclude like in this jsfiddle
...
template: "
<div ng-style='bgstyling()'>
<button ng-click=\"show=!show\">X</button>
<div ng-transclude></div>
</div>
",
transclude: true,
...

Related

AngularJS model update but view not updating [duplicate]

I need to modify a root scope attribute from within a callback inside a directive. But the directive is in a inner scope created by a switch directive.
HTML
<div ng-app="app" ng-controller='AppController'>
<p>Selected: {{ selected }}</p>
<div ng-switch on="selected">
<div ng-switch-default>
<p>Item: {{ selected }}</p>
<custom-tag selected-item="selected" />
</div>
<div ng-switch-when="New value">
<p>Worked</p>
</div>
</div>
</div>
JavaScript
angular.module('app', [])
.directive("customTag", [function () {
return {
restrict: "E",
replace: true,
template: "<input type='button' value='Click me' />",
link: function (scope, element, attrs) {
element.bind('click', function () {
scope[attrs.selectedItem] = "New value";
scope.$apply();
});
}
};
}]);
function AppController($scope) {
$scope.selected = 'Old value';
}
Fiddle: http://jsfiddle.net/nJ7FQ/
My objective is to be able to display "New value" in the Selected area.
How can I accomplish what I am trying to do? What am I doing wrong?
Besides, as I am trying to make a component. Is there a way to do the same but with an isolated scope?
I updated the fiddle, basically had to go to the parent to get the right "selected" variable, also used the isolate scope = to get two way binding between the value passed in and the internal model.
http://jsfiddle.net/nJ7FQ/2/
angular.module('app', [])
.directive("customTag", [function () {
return {
restrict: "E",
replace: true,
template: "<input type='button' value='Click me' />",
scope: {model:'='},
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.model[attrs.selectedItem] = "New value";
scope.$apply();
});
}
};
}]);
function AppController($scope) {
$scope.selected = 'Old value';
}
and the HTML
<div ng-app="app" ng-controller='AppController'>
<p>Selected: {{ selected }}</p>
<div ng-switch on="selected">
<div ng-switch-default>
<p>Item: {{ selected }}</p>
<custom-tag selected-item="selected" model="$parent" />
</div>
<div ng-switch-when="New value">
<p>Worked</p>
</div>
</div>
</div>
Updated the fiddle to use your original reading of the property from the attribute:
http://jsfiddle.net/nJ7FQ/4/
I improved the jsfiddle a bit:
angular.module('app', [])
.directive("customTag", ['$parse', function ($parse) {
return {
restrict: "E",
replace: true,
template: "<input type='button' value='Click me' />",
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.$apply(function () {
$parse(attrs.selectedItem).assign(scope.$parent, "New value");
});
});
}
};
}]);
function AppController($scope) {
$scope.selected = { 'foo': 'Old value' };
}
http://jsfiddle.net/nJ7FQ/15/
This way, the scope value, you want to change can also be an object property like selected.foo in the example. Also, I removed the scope parameter and told the directive to always use the parent scope. And finally I wrapped the click handler into the $apply callback (see here for example). Better would be, of course, to use ngClick instead of the element.bind().

Creating single custom directive for ng-click to perform multiple controller function

I have the following code:
<div>...
<div class="glyphicon glyphicon-filter ng-click="vm.add()" tabindex="1">
<a href='' tabindex="2"><img id="touch" ng-click="vm.multiply(xyz)"
src="/ui/assets/images/xxx.png"/></a>
<div class="glyphicon glyphicon-filter"ng-click="vm.showId()" tabindex="1" title="Filter">
</div>
..</div>
I want to create a custom single ng-click directive as its recommended for div (ng-click to be used for only buttons). I want to know if there is any way I can create a single directive for all 3 ng-click and call those 3 different functions in link at $apply?
Here you go: http://jsfiddle.net/psevypcs/2/
HTML
<div clicky="test()">test</div>
<div clicky="test2()">test2</div>
AngularJS-Controller
$scope.test = function(){
alert('hy');
};
$scope.test2 = function(){
alert('hy2');
};
AngularJS-Directive
angular.module('myApp')
.directive('clicky', Clicky);
function Clicky() {
return {
restrict: 'A',
scope: {
clicky: '&' // Take yourself as variable
},
link: function(scope, element, attrs){
$(element).on('click', function(e) {
scope.clicky();
});
}
};
}

Angular directive scope - template include vs inline transclude

I have an angular directive for displaying a modal window. It can accept the contents either inline between the HTML tags, or be pointed to a template. When using this directive I seem to have normal access to the $scope when I am using the transcluded inline version of this directive, but when I use a template I do not.
What am I missing here? I've made a smaller sample directive that has the same behavior.
Demo: http://fiddle.jshell.net/ahezfaxj/2
Inline Content Usage
<ang-test show="showBoolean">
<p>Content here!</p>
</ang-test>
Template Usage
<ang-test show="showBoolean" template="'myTemplate.html'"></ang-test>
Directive
app.directive("angTest", function () {
return {
template: function () {
return "<div class='test-container'>" +
" <div ng-if='show && template' ng-include='template'></div>" +
" <div ng-if='show && !template' ng-transclude></div>" +
"</div>";
},
restrict: "E",
replace: true,
transclude: true,
scope: {
template: "#",
show: "="
},
link: function ($scope, $element, attrs) {
if(value){
$element[0].style.display="block";
}else{
$element[0].style.display="none";
}
}
};
});
Please see demo below. You created isolated scope in your directive thus your directive scope is not this same as controller $scope. But you can add as well thing to your directive scope like in example below.
I hope that will help.
var app = angular.module("app", []);
app.controller("BaseCtrl", function ($scope) {
$scope.thing = "Hello!";
$scope.showOne=false;
$scope.showTwo=false;
});
app.directive("angTest", function () {
return {
template: function () {
return "<div class='test-container'>" +
" <div ng-if='show && template' ng-include='template'></div>" +
" <div ng-if='show && !template' ng-transclude></div>" +
"</div>";
},
restrict: "E",
replace: true,
transclude: true,
scope: {
template: "#",
show: "=",
thing:'#'
},
link: function ($scope, $element, attrs) {
//Show/hide when `show` changes
$scope.$watch("show", function (value) {
if(value){
$element[0].style.display="block";
}else{
$element[0].style.display="none";
}
});
}
};
});
.test-container{
padding:5px;
background: #EEE;
}
.transcluded {
color:red
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="BaseCtrl">
Outside Directive: <strong>{{thing}}</strong>
<hr />
<button type="button" ng-click="showOne=!showOne">Toggle One</button>
<ang-test show="showOne">
<p class="transcluded">Inside Included Directive: <strong>--> thing transcluded-->{{thing}}</strong></p>
</ang-test>
<hr />
<script type="text/ng-template" id="myTemplate">
<p>Inside Template Directive: <strong>thing from directive scope -->{{thing}}</strong></p>
</script>
<button type="button" ng-click="showTwo=!showTwo" >Toggle Two</button>
<ang-test show="showTwo" template="myTemplate" thing="{{thing}}"></ang-test>
</div>
</div>

How can i watch for change in a angular directive with $translate

Here's Fiddle Link
How can i watch for change in a directive? In this fiddle example using $translate to translate the content. Everything getting changed except the content in Directive.
HTML looks like this-
<div ng-app='demo'>
<div name="info" ng-controller="myctrl">
<label translate="TERMS_LABEL"></label>
<h4 translate="ZIPCODE_LABEL"></h4>
<p translate="LAST_NAME"></p>
<terms-conditions conditions="TERMS_CONDITIONS" checked="checked"></terms-conditions>
<button type="submit" ng-click="changeLanguage('de')" >Spanish</button>
<button type="submit" ng-click="changeLanguage('en')" >English</button>
</div>
directive looks like
demo.directive("termsConditions",['$translate',function($translate){
return {
restrict:"E",
scope:{
checked:'='
},
link: function(scope, element, attr) {
$translate(attr.conditions)
.then(function (translatedValue) {
scope.conditions = translatedValue;
});
},
template:
"<div class='terms row'><span class='col-md-12'>{{conditions}}</span></div><br><input
type='checkbox' ng-model='checked'><span>Yes, I agree to the terms and condtions</span>"
}
}]);
Is there a reason you can't translate in the template like so?
http://jsfiddle.net/UGLjh/75/
demo.directive("termsConditions",['$translate',function($translate){
return {
restrict:"E",
scope:{
checked:'='
},
link: function(scope, element, attr) {
attr.$observe('conditions', function (untranslatedValue) {
scope.conditions = untranslatedValue;
});
},
template:
"<div class='terms row'><span class='col-md-12'>{{conditions | translate}}</span></div><br><input type='checkbox' ng-model='checked'><span>Yes, I agree to the terms and condtions</span>"
}
}]);
If you know that your attribute won't be interpolated need not use $observe; just stick it on the scope like so:
link: function(scope, element, attr) {
scope.conditions = attr.conditions;
},

How to modify scope from within a directive in AngularJs

I need to modify a root scope attribute from within a callback inside a directive. But the directive is in a inner scope created by a switch directive.
HTML
<div ng-app="app" ng-controller='AppController'>
<p>Selected: {{ selected }}</p>
<div ng-switch on="selected">
<div ng-switch-default>
<p>Item: {{ selected }}</p>
<custom-tag selected-item="selected" />
</div>
<div ng-switch-when="New value">
<p>Worked</p>
</div>
</div>
</div>
JavaScript
angular.module('app', [])
.directive("customTag", [function () {
return {
restrict: "E",
replace: true,
template: "<input type='button' value='Click me' />",
link: function (scope, element, attrs) {
element.bind('click', function () {
scope[attrs.selectedItem] = "New value";
scope.$apply();
});
}
};
}]);
function AppController($scope) {
$scope.selected = 'Old value';
}
Fiddle: http://jsfiddle.net/nJ7FQ/
My objective is to be able to display "New value" in the Selected area.
How can I accomplish what I am trying to do? What am I doing wrong?
Besides, as I am trying to make a component. Is there a way to do the same but with an isolated scope?
I updated the fiddle, basically had to go to the parent to get the right "selected" variable, also used the isolate scope = to get two way binding between the value passed in and the internal model.
http://jsfiddle.net/nJ7FQ/2/
angular.module('app', [])
.directive("customTag", [function () {
return {
restrict: "E",
replace: true,
template: "<input type='button' value='Click me' />",
scope: {model:'='},
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.model[attrs.selectedItem] = "New value";
scope.$apply();
});
}
};
}]);
function AppController($scope) {
$scope.selected = 'Old value';
}
and the HTML
<div ng-app="app" ng-controller='AppController'>
<p>Selected: {{ selected }}</p>
<div ng-switch on="selected">
<div ng-switch-default>
<p>Item: {{ selected }}</p>
<custom-tag selected-item="selected" model="$parent" />
</div>
<div ng-switch-when="New value">
<p>Worked</p>
</div>
</div>
</div>
Updated the fiddle to use your original reading of the property from the attribute:
http://jsfiddle.net/nJ7FQ/4/
I improved the jsfiddle a bit:
angular.module('app', [])
.directive("customTag", ['$parse', function ($parse) {
return {
restrict: "E",
replace: true,
template: "<input type='button' value='Click me' />",
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.$apply(function () {
$parse(attrs.selectedItem).assign(scope.$parent, "New value");
});
});
}
};
}]);
function AppController($scope) {
$scope.selected = { 'foo': 'Old value' };
}
http://jsfiddle.net/nJ7FQ/15/
This way, the scope value, you want to change can also be an object property like selected.foo in the example. Also, I removed the scope parameter and told the directive to always use the parent scope. And finally I wrapped the click handler into the $apply callback (see here for example). Better would be, of course, to use ngClick instead of the element.bind().

Resources