How do you create an AngularJS directive with HTML content?

I am currently working on a directive that aims to load a webpage, make it accessible in a service, and also have its content available in the scope within the directive's element.

Here is a simplified explanation of what I am trying to achieve:

<cms-page page-id="829">
    <h1>Testing</h1>
    <ul ui-sortable ng-model="pageData.sections.main">
        <li ng-repeat="element in pageData.CmsPage.sections.main track by $index">
            <div ng-include="'/templates/cms/elements/' + element.element_type + '.html'"></div>
        </li>
    </ul>
    <pre>{{pageData | json}}</pre>
</cms-page>

The challenge I am facing is that the {{pageData}} is not displaying. How can I create a directive that will display the existing markup and correctly parse the markup along with child directives?

Below is the code for my directive:

angular.module(cms).directive('cmsPage', ['CmsPage', 'CmsPagesService', function(CmsPage, CmsPagesService) {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-transclude></div>',
        scope: {
            pageId: '@pageId'
        },
        controller: function($scope) {
            $scope.pageData = {};
            CmsPagesService.get($scope.pageId).then(function(result) {
                if (result.status == 'success') {
                    $scope.pageData = result.data;
                } else {
                    throw 'Page failed to load.';
                }
            });
            $scope.$watch('pageData', function() {
                CmsPage.setPage($scope.pageData);
            });
        }
    };
}]);

Answer №1

Your issue stems from the scope of the transcluded content. It may seem like the HTML within the <cms-page> tag inherits the isolated scope of the cms-page directive, but that is not the case. Transcluded content will typically adhere to the scope from which it originated.

So essentially, you end up with two parallel scopes - the isolated scope of the directive where pageData exists, and the original scope of your markup where pageData is not defined (and this is the scope to which your transcluded HTML is connected). There are a couple of ways to address this issue:

Using Directive Template

It appears that transclusion may not be necessary in this scenario. You can simply have the HTML of the directive within the directive itself:

HTML:

<cms-page page-id="829"></cms-page>

Directive:

template: // the original HTML to be transcluded

Benefits of this approach include:

  • If the directive is used in multiple places, the nested markup does not need to be duplicated
  • pageData is automatically bound to the correct scope
  • Your markup is clearer and easier to understand for others

If a variable template is required, you can dynamically select one using the template function:

HTML:

<cms-page page-id="829" page-template="templateA"></cms-page>

Directive:

template: function(tElem, tAttrs) {
  if(tAttrs.pageTemplate) {
    return '<div>...</div>';
  }  // and so on...
}

Manual Transclusion

If using a directive template is not an option, you can manually transclude the content and specify the scope to which it should be connected. You can achieve this by using the transclude function in the link function:

transclude: true,
template: '<div></div>',
scope: {
  pageId: '@'
}
link: function(scope, elem, attrs, ctrl, transclude) {
  transclude(scope, function(clone) {
    // Transcluded HTML is now bound to the isolated scope of the directive
    // This allows access to pageData from outside
    elem.append(clone);
  });
}

Answer №2

According to the content found in the AngularJS $compile documentation on transclusion:

Transclusion involves pulling a set of DOM elements from one section of the DOM and inserting them into another section, while still linked to the original AngularJS scope from where they were originally extracted.

This indicates that the transcluded element can only interact with the scope it originated from. If you need to access `pageData`, then you must specify in your directive's scope definition to enable two-way data binding with `pageData`.

DEMO

  .directive('cmsPage', ['CmsPage', 'CmsPagesService', function(CmsPage, CmsPagesService) {
    return {
          restrict: 'E',
          transclude: true,
          template: '<div ng-transclude></div>',
          scope: {
              pageId: '@pageId',
              pageData: '='
          },
          controller: function($scope) {
              $scope.pageData = {};
              CmsPagesService.get($scope.pageId).then(function(result) {
                $scope.pageData = result.data;
              });
              $scope.$watch('pageData', function() {
                  CmsPage.setPage($scope.pageData);
              });
          }
      };
  }]);

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

How can I position an object in Three.js to perfectly align with the left half of the screen, adjusting both its width

When the button is clicked, I want the entire 3D object's width and height to adjust to fit on the left side of the screen, while displaying a div HTML info on the right side. How can I make the object fit on the left side of the screen? Can I use ma ...

What could be the reason behind the Vue property being globally undefined in the component at the time of mounting?

Creating a custom Vue plugin was essential for me, which establishes a global property on Vue in this way: function (Vue, options) { Vue.$detector = new TranslatableStringDetector(); } Utilizing it within a computed property in my component is done l ...

Generating fresh objects to establish a Many-to-Many connection with Sequelize

I am currently utilizing NodeJS, Express, Sequelize, and PostgreSQL in my project. Within my application, I have defined two models: a "User" model and a "Social" model. My goal is to establish a many-to-many relationship where a user can be associated wi ...

Retrieving items from an array based on their class association

My challenge involves handling a list of items obtained using the following code: var searchResultItems = $(resultContainerId + ' li'); Each item in the search results can have different classes. How can I extract all items with a specific clas ...

Using Angular with Firebase to create foreign key relationships and display data using ng

I am currently working with two tables in firebase, namely Departamentos (parent) and Ciudades (child). My challenge is when displaying the cities using ng-repeat, I need to replace the $id of the department with its corresponding name from the Departament ...

What is the best method for storing objects in Firebase in order to easily rearrange them at a later time?

My list looks something like this: {"ids": [ { "id1": { "name": "name1", "lastname": "lastname1" } }, { "id2": { "name": "name2", "lastname": "lastname2" } }, { "id3": { "name": "name3", "l ...

Tips for ensuring my data remains protected when navigating back using the browser's back button

How to Preserve API Call Response Data on Browser Back Button Click with reloadOnSearch Set to false By setting reloadOnSearch to false, I am utilizing $routeUpdate to detect browser back button clicks. Currently, the page gets reloaded upon this event, c ...

Another option could be to either find a different solution or to pause the loop until the

Is it possible to delay the execution of a "for" loop until a specific condition is met? I have a popup (Alert) that appears within the loop, prompting the user for confirmation with options to Agree or Cancel. However, the loop does not pause for the co ...

How to use getServerSideProps in Next.js

In my current scenario, I find myself within a folder in the pages directory, specifically in a file named [id].jsx. My goal is to make getServerSideProps return just the name of the page, for example /page/id123 should return id123. import Link from &a ...

Create an array of various tags using the ngRepeat directive to iterate through a loop

I'm familiar with both ngRepeat and forEach, but what I really need is a combination of the two. Let me explain: In my $scope, I have a list of columns. I can display them using: <th ng-repeat="col in columns">{{ col.label }}</th> This ...

The AngularJS directive is being triggered before the Jquery AJAX request is completed

I am currently facing an issue where the chart in my AngularJS application (using NVD3.org) is loading before the AJAX call completes and data is fetched. How can I ensure that the chart waits for the AJAX call to finish? <script> var dataxx= ...

Experiencing a bug in the production build of my application involving Webpack, React, postCSS, and potentially other JavaScript code not injecting correctly

I've encountered an issue with my webpack.prod.config when building my assets, which may also be related to the JS Babel configuration. While I can successfully get it to work in the development build by inline CSS, the problem arises when attempting ...

unable to navigate to next or previous page in react-bootstrap-table2-paginator

I successfully implemented a table with pagination using react-bootstrap-table2-paginator. On each page number click, it calls an API to fetch the table data. However, I encountered issues with the next page, previous page, and last page buttons not workin ...

Perform a fetch request within a map function or loop

I'm currently working on iterating over an array of objects. Depending on the content of some of those objects, I need to execute a fetch function and wait for the result before updating the new array. Below is my JavaScript code: const allPosts = ...

Database Submission of Newsletter Information

I recently grabbed the following code snippet from a YouTube tutorial (shoutout to pbj746). Everything appears to be functioning correctly except for one crucial issue - the submitted data isn't showing up in the database! I've thoroughly checked ...

What is the best way to switch from http to https in a React application?

When performing audits in Chrome, I encountered a net::ERR_EMPTY_RESPONSE error because Lighthouse was not able to consistently load the requested page. Google developers have recommended configuring my server (possibly node.js) to redirect from http to ht ...

On iOS devices, background images may not appear in the Home Screen after being added

My CSS code looks like this: #thumbnail { background-image: url(bla.jpg), background-size: cover, background-repeat: no-repeat; background-position: 50% 50%; } While it displays fine on iOS Safari and other platforms, the image does not s ...

Can Angular components be used to replace a segment of a string?

Looking to integrate a tag system in Angular similar to Instagram or Twitter. Unsure of the correct approach for this task. Consider a string like: Hello #xyz how are you doing?. I aim to replace #xyz with <tag-component [input]="xyz">&l ...

What is the best way to capture data sent by Express within a functional component?

const Header = (props) => { const [ serverData, setServerData ] = useState({}); useEffect(() => { fetch('http://localhost:4001/api') .then(res => res.json()) .then((data) => { setServerData(data); ...

Issue with React-select: custom Control prevents select components from rendering

Implementing react-select@next and referring to the guide here for custom Control components is not yielding the desired outcome. import TextField from "@material-ui/core/TextField"; import Select from "react-select"; const InputComponent = (props) => ...