Tips for transferring the ngRepeat "template" to an ngDirective with transclude functionality

Example: http://plnkr.co/edit/TiH96FCgOGnXV0suFyJA?p=preview

In my ng-directive named myDirective, I have a list of li tags generated using ng-repeat within the directive template. My goal is to define the content of the li tag as part of the myDirective declaration and utilize transclusion to display the desired text / html content. This approach allows for a clear separation of concerns where the directive does not need to be aware of the structure of the source item, shifting the responsibility to the caller to arrange the content of the li.

An example syntax could be:

<my-directive source="vm.source">{{label}} and {{id}}</my-directive>

or even

<my-directive source="vm.source"><a href="#{{id}}">{{label}}</a></my-directive>

The ngRepeat template (inside myDirective) appears like this:

template: '<ul><li ng-repeat="item in source" ng-transclude></li></ul>',

However, I am encountering difficulty getting the transclusion to function within ng-repeat. I am utilizing the latest version of angular 1.2.19. Specifically, the transclusion is functioning but not the expression passed in at the directive level.

Your assistance would be greatly appreciated!

I struggled with coming up with a more descriptive question title. Feel free to suggest improvements.

Update: I accepted the answer provided by @pixelbits since it aligned with my needs. However, in practice, I implemented the solution proposed by @Norguard as it adheres more closely to Angular conventions.

Answer №1

When transcluding contents in Angular, keep in mind that they are compiled against the child scope of your parent scope, known as the transclusion scope (different from the directive's isolated scope). One way to handle this is by defining the template in your parent HTML and manually compiling it against your directive's isolated scope, although this approach may not be typical in Angular development.

HTML

<html ng-app="myApp">
  <body>
    <section ng-controller="myController as vm">
      <my-directive source="vm.data">
        <span>{{item.name}} - {{item.age}}</span>
      </my-directive>
    </section>
  </body>
</html>

It's important to note that the inner HTML of 'my-directive' makes use of 'item', which needs to be defined within the directive's scope.

Directive

function myDirective($compile) {
    return {
        restrict: 'E',
        scope: {
            source: '='
        },
        compile: function(element, attr) {
          // During compilation, extract element's content for manual handling.
           var template = angular.element('<ul><li ng-repeat="item in source">' + element.html() + '</li></ul>');
           element.empty();

           // Linking function
           return function(scope, element, attr) {
             // Append the template
             element.append(template);

             // Compile and link the template against the isolated scope.
             $compile(template)(scope);
           }              
        }          
    };
}

Check out the Demo on Plunker

Answer №2

Jeff, it appears that your transcludes are facing the wrong direction.

The functionality of transcludes seems to be operating in a reverse manner compared to your expectations.

When you have a transcluding directive and insert content within it, the content is actually reading from the parent of the transclude directive rather than the directive itself.

In your scenario, considering :

<div ng-controller="parentController as parent">
    <transcluding-directive>
        {{ key }} {{ val }}
    </transcluding-directive>
</div>

There exist two main issues here.

Firstly, ng-transclude resides as a sibling to the content it transcludes.
Both elements would inherit parent as their parent scope.
The transcluded content does not fetch data from within the transcluding directive.
This separation avoids conflicts; if the transcluding directive shared property names with the transcluded content, it would overwrite the latter and lead to unexpected behavior.

In essence, behind the scenes, its functioning may resemble this:

<transcluding-directive></transcluding-directive>
<div>{{ transcludedContent }}</div>

Subsequently, the content gets extracted and appended to the node identified by

transcludingDirective.querySelector("[ng-transclude"])
.

This isn't an exact explanation of how it works, but it depicts the outcome (excluding custom compiling/transcluding routines within the directive).

The second error becomes apparent once you comprehend the first issue:
{{ label }} and {{ id }} within vm are properties on vm's $scope object.

Since they do not exist in that specific $scope, they are therefore undefined, hence resulting in '' + 'and' + '' within your templates. You're generating an <li> for each directive item, yet interpolating undefined and undefined for all items.

The directive written should ideally be either specialized (knowing how to construct such a list) or generic (receiving the list as input)

<specific-list-maker items="vm.list"></specific-list-maker>

or a composite directive which includes more general directives inside its template...

 <specific-list items="vm.list"></specific-list>

 <!-- specific-list.html -->
 <generic-drawer>
     <generic-toggle ng-repeat="item in items">{{ item.label }} {{ item.id }}</generic-toggle>
 </generic-drawer>

This kind of composition aligns well with Angular (as well as Polymer and potentially future technologies like Web Components).

To demonstrate, I've implemented a project where elements filter down akin to:

<page-type-a></page-type-a>

<!-- page-type-a.html -->
<card-collection cards="page.items"></card-collection>

<!-- card-collection.html -->
<header ><!-- ... header stuff --></header>
<card ng-repeat="card in cards"></card>

<!-- card.html -->
<header><h1>{{ title }}</h1></header>
<blockquote>{{ content }}</blockquote>
<cite>{{ author }}</cite>


<page-type-b></page-type-b>
<!-- page-type-b.html -->
<card-collection cards="page.items"></card-collection>

Each component focuses solely on its assigned tasks.

While other methods could work (e.g., introducing "key" and "value" attributes, then extracting values for composing your internal list), attempting those extra steps might prove overly complex in this situation.

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

Unable to access .root in the unmounted test renderer while performing React Native unit testing

Hello, I am currently in the process of testing my app using react-native-testing-library. Unfortunately, when I run the test, it fails. I'm encountering an error that says: Can't access .root on unmounted test renderer There appears to be a ...

Submitting data from a JavaScript frontend to a PHP backend

I've exhausted all options in attempting to resolve this issue. The javascript code is designed to send a list of product IDs to a PHP page. When products are selected, the submit button should activate the submit function. function submit() { ...

Obtaining the following class name from elements

I am struggling with extracting classes from a block of HTML code: <div class="container"> <div class="item first">...</div> <div class="item second">...</div> <div class="item third">...</div> <div cla ...

efforts to activate a "click" with the enter key are unsuccessful

I'm attempting to enhance user experience on my site by triggering the onclick event of a button when the enter key is pressed. I've tried various methods below, but all seem to have the same issue. Here is my HTML (using Pug): input#userIntere ...

The save changes feature is not effectively syncing with the database

So, I have a button that triggers a javascript function, which initiates an AJAX request to call an actionresult that is supposed to update my database. Javascript Function function changeDepartment() { // Initializing and assigning variables va ...

Access to an Express route in Node JS can only be granted upon clicking a button

Is it feasible to create an express route that can only be accessed once a button is clicked? I want to prevent users from entering the route directly in the URL bar, and instead require them to click a specific button first. I'm curious if this can b ...

What is the alternative to using toPromise() when utilizing await with an Observable?

This website mentions that "toPromise is now deprecated! (RxJS 5.5+)", however, I have been utilizing it recently with AngularFire2 (specifically when only one result is needed) in the following manner: const bar = await this.afs.doc(`documentPath`).value ...

Creating compressed files using JavaScript

I am currently working on unzipping a file located in the directory "./Data/Engine/modules/xnc.zip" to the destination folder "./Data/Engine/modules/xnc". Once I have completed writing to these files, I will need an easy method to rezip them! While I wou ...

sending the express application to the route modules

Currently, I am developing an express 4 api server with no front end code. Rather than structuring my project based on file types such as routes and models, I have decided to organize it around the business logic of the project. For example, within my Use ...

Handling errors in XMLHttpRequest using Axios in React JS

I am currently utilizing the REACT-JS framework for my FRONT-END development: Below is the action I am calling from REDUX-REACT export function UserLogin(values) { var headers = { 'Access-Control-Allow-Origin': '*', ...

Tips for accessing the TextField value when clicking a button event in React Material UI

I'm currently working on a feature where I need to retrieve the input value from a search TextField. Upon clicking the "Search" button, I aim to call an API using the search input. Here's my progress so far: const handleSearchSubmit = async () =& ...

How can scope binding be reversed in AngularJS when implementing transclusion?

Utilizing transclude in directives proves to be extremely beneficial when you want the scope of your controller to be linked to the HTML being passed in. But how can you achieve the opposite? (accessing the directive's scope from the controller) Her ...

Transforming a JavaScript JSON object into a string representation

Currently in the process of constructing a website utilizing Tornado Websocket, and I've come to understand that Tornado Websocket only accepts a specific type of json format: {"key1":1,"key2":2,"key3":3} The goal is to fill element attributes with ...

Having trouble retrieving alert message after submitting form using jquery

Trying to submit a form using jQuery, but when clicking on the submit button it displays a blank page. I understand that a blank page typically means the form has been submitted, but I want to show an alert() for testing purposes instead. However, it only ...

Error: The default export is not a component compatible with React in the specified page: "/"

I'm facing an issue while building my next app. Despite using export default, I keep encountering an error that others have mentioned as well. My aim is to create a wrapper for my pages in order to incorporate elements like navigation and footer. vi ...

Creating an environment variable using the package.json script

I'm trying to set the process.env.ENV variable as either TEST or null using a script in my package.json file. The command below is not working when I run it through package.json (though it works fine when directly executed in cmd). script { "star ...

Steps for disabling a spinner directive in AngularJS for particular $http calls

This specific directive is designed to automatically toggle a loading icon on or off whenever there are $http requests happening. Here's the code snippet: angular.module('directive.loading', []) .directive('loading', [& ...

issue with accessing instant state modification via useEffect

I am currently learning about React Hooks While I try to render a list from an array and remove the first element on click, the state gets updated in the handleclick function but it doesn't render correctly export default function App() { const [na ...

Displaying data in JSON format retrieved from a MySQL database

Greetings everyone! I am currently working on a website built with CodeIgniter. In one of my functions, I need to fetch data from MySQL and display the result in JavaScript as part of an Ajax call. Here is my PHP function for retrieving data: public func ...

Harvesting information from an HTML table

I have a table displaying the following values: turn-right Go straight turn-left How can I extract only the 2nd value, "Go straight"? Here is the code snippet I tried: var text = $('#personDataTable tr:first td:first').text(); The code above ...