Creating an Angular directive that deals with transient local variables

I am working on developing a directive that will allow me to create temporary variables within the scope of an HTML tag. This directive will only apply within the specific tag I am rendering. Here is an example use case:

<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>

In this scenario, the ng-local directive creates a variable called opened and initializes it with a value of false. The content inside the directive is a transcluded template. The benefit of this approach is that multiple datepickers on a page can all share the same variable opened, eliminating the need for separate variables in the scope or controller just for temporary use within a div. However, I want this directive to be versatile enough to handle different use cases without creating multiple variations.

While my initial implementation was successful, I encountered a problem where the parent scope variable start was not being accessed correctly by the datepicker component. Since I am not very experienced with the $transclude functionality, I am seeking guidance from someone who can help me troubleshoot this issue. Below is the current version of the directive I have implemented:

(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);
                });
            }
        };
    }]);
})();

Any insights or suggestions would be greatly appreciated!

EDIT

For reference, here's a plunkr link:

https://plnkr.co/edit/pog2bcxEf8mDEb2vIVjP?p=preview

Answer №1

Instead of using transclude in your directive, you can simply create a child or isolate scope.

Here is an example:

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>

Answer №2

Experience the power of nested directives by utilizing an outer directive to encapsulate data and an inner directive to retrieve data from a function provided by the outer directive.

To see a demonstration, click on this link: https://plnkr.co/edit/4n6kf40ZMf7lRCad5ofe?p=preview

Here is the code snippet:

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();
          }
        );
      }
    }
  });

Answer №3

Consider utilizing $parent.localDate instead of the current method.

<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 avoiding $parent, you can utilize the isolated scope and define the desired variable:

<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);
        });
    }
};
}]);

For more details, check out the updated plunker here: https://plnkr.co/edit/4zrNzbSc5IwqqbE2ISE1?p=preview

Answer №4

I was hoping to achieve similar functionality to that of ng-repeat, where I wouldn't have to refer to everything through the parent scope in the transcluded element.

ng-repeat does not utilize an isolate scope but instead, it uses an inherited scope.

To learn more about directive scopes, you can visit the AngularJS $compile Service API Reference -- scope.


Example

This custom directive transcludes its contents multiple times, creating a new inherited scope each time. The number of repetitions is specified by 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);
                })
            }
        }
    }
})

Check out the DEMO on JSFiddle

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the process for retrieving AngularJS parameters in a Java backend system?

I am facing an issue with my angular app where the routes are defined as follows: $routeProvider // Home screen .when('/', { title : 'APP.NAME', bodyClassName : 'home', templateUrl : ...

Learn how to utilize ng-class to assign an ID selector to HTML elements

In my code, I have an icon that is displayed conditionally using ng-class. I am looking to include ID's for both the HTML elements. <i ng-show="ctrl.data.length > 1" ng-class="{'glyphicon glyphicon-chevron-down': !ctrl.isOpen, ...

Combining nested objects into a single object

Can a nested array object be flattened into a single object? In my query, I aim to eliminate the source object and combine all properties into one object (see the desired output below). var result = [ {"_id":"12345", "_type":"feeds", "_s ...

Utilizing titanium to develop a functionality that listens for button presses on any area of the screen

I am trying to simplify the action listener for 9 buttons on a screen. Currently, I have individual event handlers set up for each button, which seems inefficient. Is there a way to create an array of buttons and manipulate them collectively? For example ...

Using default JavaScriptSerializer to bind DateTime to knockout view model

Recently, I started using knockout and encountered a problem with DateTime Serialization and Deserialization when using the JavaScriptSerializer. I modified the gifts model in Steve's koListEditor example from his blog to include a new field for Modi ...

JavaScript is unable to activate the button once it has been disabled

JS I am facing an issue with a form that has save and download buttons. I want the download button to be disabled initially, and then enabled once the form is saved. $("#download_form").attr('disabled', 'disabled'); $('.save ...

Redux: streamlining containers, components, actions, and reducers for seamless organization

Here's the question: When working on a large React/Redux application, what is the most effective and sustainable method for organizing containers, components, actions, and reducers? My take: The current trend leans towards organizing redux elemen ...

Issue with NodeJS: Unable to locate module 'io' (socket.io in combination with express version 4.15.3)

I am a beginner in NodeJS and I am attempting to create a simple chat application using the express and socket.io modules. However, when I try to run the application, I encounter an error on the page where I am utilizing the socket feature. The console dis ...

Managing various encoding methods when retrieving the XML data feed

I'm attempting to access the feed from the following URL: http://www.chinanews.com/rss/scroll-news.xml using the request module. However, the content I receive appears garbled with characters like ʷ)(й)޹. Upon inspecting the XML, I noticed that ...

Is it possible for the Jquery Accordion to retract on click?

Hello everyone, I've created an accordion drop-down feature that reveals content when the header of the DIV is clicked. Everything works fine, but I want the drop-down to collapse if the user clicks on the same header. I am new to JQUERY and have trie ...

Nuxt Vuex global state update does not cause v-for loop components to re-render

I am struggling to effectively use Vuex global state with re-rendering child components in Vue.js. The global state is being mutated, but the data does not update in the v-for loop. Initially, all data is rendered correctly. However, when new data is intr ...

Is it possible to determine the number of JSON properties without the need for a loop?

I have a question about organizing data. I have a vast amount of data with various properties, and I am looking for a way to display each property along with how many times it occurs. For example: 0:[ variants:{ "color":"blue" "size":"3" } ] 1 ...

What is the best way to target the iframe within the wysihtml5 editor?

Currently, I am utilizing the wysiwyg editor called wysihtml5 along with the bootstrap-wysihtml5 extension. As part of my project, I am designing a character counter functionality that will showcase a red border around the editor area once a specific maxl ...

What is the best way to showcase search results using Bootstrap?

I'm currently in the process of developing a Spotify application. Upon executing a GET query for a track search, I aim to showcase the results as interactive buttons featuring the track name and artist. As the query can yield varying numbers of outcom ...

Verify the validity of the user's input

Using knockout.js and knockout.validation, I have created a book view model with properties for the author's name and book title: function BookViewModel(bookObj) { var self = this; self.AuthorName = ko.observable(bookObj.AuthorName) ...

Creating a jsp page content with jquery and retrieving request parameters

I am facing an issue with my JSP page where I need to pass the value of request.getParameter("cfgname") to another content page so that it loads correctly. Currently, the code is displaying null instead of the parameter. This is the main JSP page with par ...

I'm currently working with ReactJS and attempting to retrieve JSON data from a REST API in JIRA, but I'm facing challenges in achieving this

I've been struggling for hours trying to understand why I am unable to access and transfer data in my array from the JSON data in JIRA using the REST API. Basically, I am attempting to retrieve the JSON data from the JIRA website via URL with Basic Au ...

Clicking on the image "Nav" will load the div into the designated target and set its display to none. The div will

Can someone help me with loading a div into a target from an onclick using image navigation? I also need to hide the inactive divs, ensuring that only the 1st div is initially loaded when the page loads. I've tried searching for a solution but haven&a ...

Elements overlapped with varying opacities and responsive to mouse hovering

In this Q/A session, we will explore a JS solution for managing the opacity of overlapping elements consistently during hover. Objective Our goal is to create two transparent and overlapping elements, similar to the red boxes showcased below. These eleme ...

Mastering the art of invoking a JavaScript function from a GridView Selected Index Changed event

In my current setup where I have a User Control within an Aspx Page and using Master Page, there's a GridView in the User Control. My goal is to trigger a javascript function when the "Select" linkbutton on the Gridview is clicked. Initially, I succe ...