Reputation: 2288
What I'm trying to do here is create a directive that allows me to set temporary variables that only apply within the html tag that I'm rendering. The use case is something like this:
<div class="input-group" ng-local="opened = false" ng-blur="opened = false;">
<input type="text" class="form-control" uib-datepicker-popup="longDate" ng-model="start" is-open="opened" ng-focus="opened = true;" />
<span class="input-group-btn">
<button type="button" ng-click="opened = true;" class="fa fa-calendar" ></button>
</span>
</div>
The idea here is that the ng-local directive creates a variable opened
and sets that variable to an initial value false
. Everything inside of the directive is a transcluded template. The benefit here is that I can have mutliple datepickers on a page, all who use the same variable opened
without having to have a bunch of different variables all located on the scope in a controller that is only used as a temp variable for the content inside a div. However, as this will be used in a bunch of different ways, I don't want to have to make a different directive for each use case.
My first attempt at this went pretty well. However, I'm running into an issue where the parent scope variable start
is not being accessed correctly from the datepicker. I'm not very familiar with the $transclude functionality, so I'm hoping somebody can point me in the right direction. This is the directive I've currently written:
(function () {
angular.module('myApp').directive('ngLocal', [function () {
return {
restrict: 'A',
transclude: 'element',
replace: false,
scope: {
ngLocal: '@'
},
link: function ngLocalLink(directiveScope, element, attrs, ctrl, $transclude) {
$transclude(directiveScope, function ngLocalTransclude(clone, scope) {
element.empty();
element.replaceWith(clone);
scope.$eval(directiveScope.ngLocal);
});
}
};
}]);
})();
Thanks in advance!
EDIT
Here's a plunkr link
https://plnkr.co/edit/pog2bcxEf8mDEb2vIVjP?p=preview
Upvotes: 1
Views: 1281
Reputation: 48968
I was hoping to get functionality similar to that of
ng-repeat
where you don't have to address everything by the parent scope in the transcluded element.
ng-repeat
doesn't use isolate scope. It uses inherited scope.
For more information on directive scopes, see AngularJS $compile Service API Reference -- scope.
This custom directive transcludes its contents multiple times, each time creating a new inherited scope. The number of repetitions is determined by reading the repeat
attribute.
angular.module('myApp').directive('repeat', function () {
return{
scope: false,
transclude: 'element',
link : function(scope, element, attrs, ctrl, transcludeFn){
var parent = element.parent();
var repeatNum = attrs.repeat;
for(var i = 1;i<= repeatNum;++i){
var childScope = scope.$new();
childScope.$index = i;
transcludeFn(childScope, function (clone) {
parent.append(clone);
})
}
}
}
})
The DEMO on JSFiddle
Upvotes: 0
Reputation: 1264
Try using $parent.localDate
instead.
<div class="input-group" ng-local="localOpen = false">
<input type="text" class="form-control" uib-datepicker-popup="longDate" is-open="localOpen" ng-model="$parent.localDate" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" ng-click="localOpen = true;" >OPEN</button>
</span>
</div>
If you don't want to use $parent you could use the isolated scope, and set the variable you want to use:
<div class="input-group" ng-local="localOpen = false" date="localDate">
<input type="text" class="form-control" uib-datepicker-popup="longDate" is-open="localOpen" ng-model="date" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" ng-click="localOpen = true;" >OPEN</button>
</span>
</div>
angular.module('myApp').directive('ngLocal', [function () {
return {
restrict: 'A',
transclude: 'element',
replace: false,
scope: {
ngLocal: '@',
date: '='
},
link: function ngLocalLink(directiveScope, element, attrs, ctrl, $transclude) {
$transclude(directiveScope, function ngLocalTransclude(clone, scope) {
element.empty();
element.replaceWith(clone);
scope.$eval(directiveScope.ngLocal);
});
}
};
}]);
Here is the forked plunker: https://plnkr.co/edit/4zrNzbSc5IwqqbE2ISE1?p=preview
Upvotes: 0
Reputation: 84
Try nested directives. The outer directive encapsulates the data you wish to share, and the inner get the data from a function provided by the outer.
link to demo: https://plnkr.co/edit/4n6kf40ZMf7lRCad5ofe?p=preview
The code:
angular.module('myapp', [])
.directive('outer', function () {
return {
restrict: 'E',
transclude: true,
scope: {
value: '='
},
template: function(element, attrs) {
return '<div>outer! value = {{value}}<div ng-transclude></div></div>';
},
controller: function($scope) {
this.getValue = function() {
return $scope.value;
}
}
}
})
.directive('inner', function () {
return {
restrict: 'E',
template: function(element, attrs) {
return '<div>inner! value = {{value}}</div>';
},
require: '^outer',
link: function (scope, element, attrs, parentCtrl) {
scope.$watch(
function() {
return parentCtrl.getValue();
}, function(oldValue, newValue) {
scope.value = parentCtrl.getValue();
}
);
}
}
});
Upvotes: 0
Reputation: 13381
You not need transclude in your directive, just create child or isolate scope.
Sample
angular.module('myApp', ['ngAnimate', 'ui.bootstrap']);
// CONTROLLER
angular.module('myApp').controller('myController', function($scope) {
$scope.dates = {
workingDate : new Date(),
brokenDate1 : new Date(),
brokenDate2 : new Date(),
localDate : new Date(),
}
});
// DIRECTIVE
angular.module('myApp').directive('ngLocal', [
function() {
return {
restrict: 'A',
replace: false,
scope: true //directive have own scope
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-1.1.0.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<div ng-app="myApp">
<div ng-controller="myController">
<h4>This one works</h4>
<div class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="longDate" is-open="workingOpen" ng-model="dates.workingDate" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" ng-click="workingOpen = true" >OPEN</button>
</span>
</div>
<br/>
<br/>
<br/>
<h4>This is the problem I'm trying to solve</h4>
<h4>Both datepickers use "brokenOpen" so they both open whenever either is clicked</h4>
<div style="width: 40%; display: inline-block;" ng-local>
<div class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="longDate" is-open="brokenOpen" ng-model="dates.brokenDate1" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" ng-click="brokenOpen = true" >OPEN</button>
</span>
</div>
</div>
<div style="width: 40%; display: inline-block;" ng-local>
<div class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="longDate" is-open="brokenOpen" ng-model="dates.brokenDate2" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" ng-click="brokenOpen = true" >OPEN</button>
</span>
</div>
</div>
<br/>
<br/>
<br/>
<h4>This is using my directive</h4>
<h4>The date does not update correctly to the parent scope</h4>
<div class="input-group" ng-local="localOpen = false">
<input type="text" class="form-control" uib-datepicker-popup="longDate" is-open="localOpen" ng-model="dates.localDate" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" ng-click="localOpen = true;" >OPEN</button>
</span>
</div>
<label>See how the date is not updating: {{dates.localDate}}</label>
</div>
</div>
Upvotes: 0