mrK
mrK

Reputation: 2288

Angular directive for temporary local variables

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

Answers (4)

georgeawg
georgeawg

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.


Example

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

Ignacio Villaverde
Ignacio Villaverde

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

Amonn
Amonn

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

Grundy
Grundy

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

Related Questions