Assess transcluded content prior to template compilation in an AngularJS directive

I am currently developing a custom "collapseText" directive using AngularJS. The purpose of this directive is to display a specified maximum number of characters and provide a "Read More" option to expand the text if it exceeds the limit.

My goal is for this directive to support transcluding text, including expressions.

Here is an example of how I envision using the directive:

<collapse-text max-length="10">This text will be collapsed</collapse-text>
<collapse-text max-length="10">{{foo}}</collapse-text>

The template structure I have in place is as follows:

<span>{{lessText}}</span>
<span ng-if="overflow">
    <span ng-if="!readMore" style="cursor:pointer" ng-click="toggleReadMore()">...(read more)</span>
    <span ng-if="readMore">{{moreText}}</span>
</span>

Here is a snippet of my directive implementation:

'use strict'
angular.module('myModule')

.directive('collapseText', function($window){
    return{
        restrict: 'E',
        scope: true,
        transclude: true,
        controller : function($scope){
            $scope.toggleReadMore = function(){
                $scope.readMore = true;
            }
        },
        link: function(scope, element, attrs, ctrl, transclude){
            scope.maxLength = attrs.maxLength;
/* 1. Evaluate transcluded element */
/* 2. Check transcluded element's length */
/* 3. Set lessText, moreText, readMore and overflow */
/* 4. Evaluate this directive's template */
            console.log(transclude(scope.$parent, function(compiled){
                scope.lessText = compiled.text().substring(0, scope.maxLength);
                scope.moreText = compiled.text().substring(0, scope.maxLength); 
                scope.readMore = false;
                scope.overflow = scope.moreText ? true : false;
                return compiled.text();
            }).text());
        },
        templateUrl: "templates/collapse-text-template.html"
    }
});

I have encountered two specific issues while implementing this directive:

  1. SOLVED: The ng-if statements do not update after changes to the overflow and readMore variables, resulting in certain text fields not displaying.
    • To resolve this, I adjusted the ng-if conditions to use "overflow === true", "readMore === false", and "readMore === true". However, the main issue regarding transcluded text evaluation remains unresolved.
  2. PENDING: The expression {{foo}} prints as "{{foo}}" instead of the actual content of foo.

I appreciate any guidance on resolving these challenges. Thank you for your assistance!

Answer №1

When working within the directive's link() function, it is essential to ensure that {{foo}} is evaluated before being utilized. One way to achieve this is by scheduling a new task in the browser's event loop using $timeout(). Although not necessarily the most elegant solution, it gets the job done.

Below is your code enhanced with $timeout() and some minor refinements:

<div ng-app="myModule" ng-controller="MyController">
    <collapse-text max-length="10">This text will be collapsed</collapse-text>
    <collapse-text max-length="10">{{foo}}</collapse-text>
</div>

template.html

<span ng-if="!readMore">{{lessText}}</span>
<span ng-if="overflow">
    <span ng-if="!readMore && overflow" style="cursor: pointer;" ng-click="toggleReadMore()">...(read more)</span>
    <span ng-if="readMore">{{moreText}}</span>
</span>

Script

angular.module('myModule', []).controller('MyController', function($scope){
    $scope.foo = 'This text will also be collapsed';
});

angular.module('myModule').directive('collapseText', function($timeout){
    return {
        restrict: 'E',
        scope: true,
        transclude: true,
        controller: function($scope){
            $scope.toggleReadMore = function(){
                $scope.readMore = true;
            };
        },
        link: function(scope, element, attrs, ctrl, transclude){
            var maxLength = +attrs.maxLength;
            var compiled = transclude(scope.$parent);

            $timeout(function(){
                scope.lessText = compiled.text().substring(0, maxLength);
                scope.moreText = compiled.text();
                scope.readMore = false;
                scope.overflow = scope.moreText.length > maxLength;
            });
        },
        templateUrl: "template.html"
    }
});


It is important to note that this implementation does not automatically react to changes in $scope.foo (i.e., updates will not trigger re-rendering of the directive). If real-time updates are required, consider passing the content to the directive as an attribute and implementing a watcher instead of relying on transclusion. Here's an example:

angular.module('myModule').directive('collapseText', function(){
    return {
        restrict: 'E',
        scope: {
            myContent: '=',
            // ...
        },
        link: function(scope){
            scope.$watch('myContent', function(newValue){
                if (newValue !== undefined) {
                    doSomethingWith(newValue);
                }
            });
        },
        templateUrl: "template.html"
    }
});

Answer №2

Following the suggestion from Aletheios, I discovered a method that involves implementing a watch on the transcluded text and creating a manual transclude function to store the raw input in the local scope. The implementation includes displaying the raw input in the "lessText" field, which will be processed later by the parent for transclusion. This transclusion triggers the watch in the collapseText directive.

While I anticipate some potential issues due to my relative newness to AngularJS:

  • If transcluded text longer than what is displayed by "maxLength" requires further processing, will it function properly? For example:

    <collapse-text max-length="10" {{myLongVariableName.anotherLongVariableName}}</collapse-text>
    
  • The current code does not permit any markup within the text. For instance:

    <collapse-text max-length="20"><b>I am bold</b></collapse-text>
    
  • Even if it allowed markup, there's a risk of the markup being disrupted when divided into lessText and moreText sections.

The Resolution

Preliminary tests indicate seamless performance with plaintext and AngularJS expressions.

use.html

An important point to consider is that this code is intended for use within another directive, hence {{parent.foo}} should also remain functional.

<div ng-app="myModule" ng-controller="MyController">
    <collapse-text max-length="10">This text will be collapsed</collapse-text>
    <collapse-text max-length="10">{{foo}}</collapse-text>
</div>

template.html

<span ng-if="readMore === false">{{lessText}}</span>
<span ng-if="overflow === true">
    <span ng-if="readMore === false" style="cursor:pointer" ng-click="toggleReadMore()">...(read more)</span>
    <span ng-if="readMore === true">{{moreText}}</span>
</span>

Script

angular.module("myModule")
.controller('MyController', function($scope){
    $scope.foo = 'This text will also be collapsed';
})
.directive('collapseText', function($window){
    return{
        restrict: 'E',
        scope: true,
        transclude: true,
        controller : function($scope){
            $scope.readMore = false;
            $scope.toggleReadMore = function(){
                $scope.readMore = true;
            }

            $scope.$watch(
                            function(){
                                return $scope.transcluded;
                            },
                            function(newValue){
                                if($scope.transcluded){
                                    $scope.lessText = $scope.transcluded.text().substring(0, $scope.maxLength);
                                    $scope.moreText = $scope.transcluded.text();
                                    $scope.readMore = false || $scope.readMore; //If it was arleady true, do not collapse it again.
                                    $scope.overflow = $scope.moreText ? true : false;
                                }
                            }
                        );
        },
        link: function(scope, element, attrs, ctrl, transclude){
            scope.maxLength = attrs.maxLength;
            transclude(scope.$parent, function(transcluded){
                scope.transcluded = transcluded;
            });
        },
        templateUrl: "template.html"
    }
});

Your feedback on the provided code and any recommended "best practices" for similar implementations would be greatly valued.

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

React-querybuilder experiencing issues with validator functionality

While utilizing the react-querybuilder, I have encountered an issue with field validation not functioning correctly. Upon reviewing this StackBlitz, it appears that when clicking on Rule and checking all fields, there are no errors present. export const fi ...

Verify a unique cross-field rule for comparing two time strings, customized by Vee

I am currently facing an issue with validating two time strings in my buefy form using vue. The goal is to ensure that the second time input does not exceed a one-hour difference from the first time input. Both time fields have granularity down to millisec ...

Adding a disabled internal Style node to the HTML5 DOM: A simple guide

According to this, the style tag can be turned off using the disabled attribute. I attempted the following: <head> <style>body { color: black; }</style> <style disabled>body {color: red; }</style> </head> <bo ...

Is it possible to install non-root dependencies in node_modules using NPM?

The repository @superflycss/component-navbox contains the following list of dependencies: "devDependencies": { "@superflycss/component-body": "^1.0.1", "@superflycss/component-display": "^1.0.2", "@superflycss/component-header" ...

Form in HTML with Automatic Multiplication Functionality in pure JavaScript

I'm struggling with integrating a simplified HTML form with JavaScript to dynamically adjust and multiply the entered amount by 100 before sending it via the GET method to a specific endpoint. Below is the HTML form: <body> <form method= ...

What are the steps to inject the npm package called lodash as a dependency injection in AngularJS for the angular-google-maps module with the help

I have set up my angular application using npm as the package manager and Browserify to manage libraries. The specific package I am using is angular-google-maps from http://angular-ui.github.io/angular-google-maps. An error message I encountered is: Refe ...

Using xignite api to retrieve stock data

I've been encountering difficulties trying to make this JavaScript script function properly. Every time I attempt to run it, I receive an error message stating: "XMLHttpRequest cannot load" "No 'Access-Control-Allow-Origin' header is presen ...

PHP: Communicating Data with JavaScript

What if my PHP script requires some time to complete its operations? How can I keep the client updated on the progress of the operation, such as during a file download where the estimated time and data size need to be communicated? In PHP, calculating all ...

What strategies can be utilized to manage a sizable data set?

I'm currently tasked with downloading a large dataset from my company's database and analyzing it in Excel. To streamline this process, I am looking to automate it using ExcelOnline. I found a helpful guide at this link provided by Microsoft Powe ...

Is there a way to invoke a different function within a class from a callback function in an HTTP request?

Having an issue with my HTTP GET request function in the "CheckPrice" class. When trying to call another function within the class callback, it's showing as undefined. Any suggestions? const got = require("got") class PriceCheck { constructor() { ...

React Context Matters: Troubles Unleashed

I've been facing some difficulties with passing a value from one file to another. The problem seems to be related to implementing context, but I can't seem to figure out where I went wrong! import React from 'react' const Mycontext = ...

Deleting a document by ObjectID in MongoDB with Node and Express without using Mongoose: A step-by-step guide

As a newcomer to backend development, I am currently using Node/Express/MongoDB with an EJS template for the frontend. I am in the process of creating a simple todo list app to practice CRUD operations without relying on Mongoose but solely native MongoDB. ...

Vue and Jexcel: maximizing efficiency with event handling and dynamic calculations

I am trying to utilize the vue wrapper for jexcel and want to trigger the undo function from the toolbar computed field. However, I am facing difficulties accessing the spreadsheet instance as it throws an error saying 'this.undo is undefined.' ...

Creating a triangle number pattern in JavaScript with a loop

Hi there, I'm currently facing an issue. I am trying to generate a triangular number pattern like the one shown below: Output: 1223334444333221 =22333444433322= ===3334444333=== ======4444====== I attempted to write a program for this, however, ...

The issue of `Console.log` displaying as undefined arises after subscribing to a provider in Ionic3

In the process of implementing the Spotify api into my Ionic 3 app, I encountered an issue where the data retrieved appears as undefined when attempting to log it. Let me share some code and delve deeper into the problem. Here is the function getData() tha ...

Looking to add a form within another form using AJAX? Just keep in mind that the initial form should also be inserted using AJAX

I have incorporated a form using AJAX on a Php page. Now, I am trying to add another form within that existing form which is also created using AJAX, but unfortunately, it is not functioning as expected. This serves as the parent page. <div class=" ...

HTML textarea fails to recognize inline styles

My journey with JHipster began recently when I embarked on my first project. Two entities were created, one of which resembles a blog. This particular blog entity has a content attribute that interacts with a textarea. In my blogs.html file, I allowed the ...

Struggling to transfer information between two controllers

There seems to be an issue with transferring information from Controller 1 to Controller 2. Despite having a service that manages data, it is not functioning properly. The specific error message indicates that Controller 1's dataService.getData is not ...

What is the best way to pass a variable within a jQuery event map?

Consider the below jQuery .on() event map: $('header').on({ mouseenter: function(e) {}, mouseleave: function(e) {}, }, 'li'); Is there a way to share a common var $this = $(this) variable between the mouseenter and mouseleave even ...

Attempting to single out various entities within a JSON array through the use of radio buttons

I am currently developing a website to showcase sports teams' schedules. Each team has its own JSON file containing relevant information that I aim to display upon selecting the team from a radio button. For instance, let's consider the example ...