Building a unique directive in AngularJS that functions similarly to ng-required

I've dedicated around 4 hours to this task already.
My goal is to create a custom directive similar to ng-required that handles input validation.
Below is the code for ng-required:

var requiredDirective = function() {
  return {
    restrict: 'A',
    require: '?ngModel',
    link: function(scope, elm, attr, ctrl) {
      if (!ctrl) return;
      attr.required = true; // ensuring truthiness for non-input elements

      ctrl.$validators.required = function(modelValue, viewValue) {
        return !attr.required || !ctrl.$isEmpty(viewValue);
      };

      attr.$observe('required', function() {
        ctrl.$validate();
      });
    }
  };
};

When the required attribute value changes, validation will be triggered again. For example,

<input 
ng-model="sor.seal"
ng-model-options="{ updateOn : 'default blur' }"
class="form-control" 
ng-required="sor.sealUwatType=='SEAL'" 
type="text"
placeholder="Enter a SEAL/UWAT ID"
id="seal" 
name="seal" 
/>

How does it insert the required="required" attribute when the ng-required expression is true?
And how does it remove the attribute when the ng-required expression is false?

Answer №1

Angular's framework automatically creates an internal ng-attribute Alias directive for all boolean attribute equivalents like

ng-required, ng-checked, ng-disabled
, and more.

Source

var BOOLEAN_ATTR = {};
forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
  BOOLEAN_ATTR[lowercase(value)] = value;
});

The generic link function registered on these attributes performs a toggling action.

Source

forEach(BOOLEAN_ATTR, function(propName, attrName) {
  // binding to multiple is not supported
  if (propName == "multiple") return;

  function defaultLinkFn(scope, element, attr) {
    scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
      attr.$set(attrName, !!value);
    });
  }

  var normalized = directiveNormalize('ng-' + attrName);
  var linkFn = defaultLinkFn;

  if (propName === 'checked') {
    linkFn = function(scope, element, attr) {
      // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
      if (attr.ngModel !== attr[normalized]) {
        defaultLinkFn(scope, element, attr);
      }
    };
  }

  ngAttributeAliasDirectives[normalized] = function() {
    return {
      restrict: 'A',
      priority: 100,
      link: linkFn
    };
  };
});

Essentially, ng-required adds the attribute required, which is internally registered as a directive that handles validation for ng-model.

You can confirm this by examining the contents of the ngRequired directive, which includes two configurations - one for setting/resetting attributes and another for validation with ng-model and validators.

.config(function($provide) {
  $provide.decorator('ngRequiredDirective', function($delegate) {
    console.log($delegate); //Check the console on the configuration you will see array of 2 configurations
    return $delegate;
  })
});

This concept is similar to creating multiple configurations for the same directive selector:

angular.module('app', []).directive('myDir', function() {
  return {
    restrict: 'A',
    link: function() {
      console.log("A");
    }
  }
}).directive('myDir', function() {
  return {
    restrict: 'A',
    link: function() {
      console.log("B");
    }
  }
}).config(function($provide) {
  $provide.decorator('myDirDirective', function($delegate) {
    console.log($delegate); //Check the console on the configuration you will see array of 2 configurations
    return $delegate;
  })
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app="app">

  <div my-dir></div>
</div>

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

Leveraging the NextAuth hooks, employ the useSession() function within the getServerSideProps

I'm currently working on retrieving data from my server based on the user who is logged in. I am utilizing Next-Auth and usually, I can easily obtain the session by calling: const { data: session } = useSession(); In a functional component, this work ...

The property 'createDocumentFragment' is not defined and cannot be read in JavaScript code

I'm working on loading data from my database using ajax, but I'm facing an issue with the this method not functioning as expected. Below is a snippet of my source code: $(".cancel-btn").click(function() { var cancelArea = $('.cancel&apos ...

Is there a way to retrieve the original value of the substr value?

I successfully retrieved the correct value using the substr() method, but I am facing difficulty in getting back the original value. Can someone please guide me on how to achieve this? For example: "AAAAAA" -> AA... but I am unable to revert from AA.. ...

Retrieve a specific progress bar by its unique identifier and use AngularJS to dynamically update its value

How can I update the value of a specific progress bar in AngularJS based on its id? I am looking for a solution to this issue. Below are the progress bars that I have: <progressbar value="0" id="seekbar1"></progressbar> <progressbar value= ...

Creating an interactive dropdown menu in react.js

For a project I'm working on, the user can input data that is then sent to a MongoDB instance. There's also a search bar where users can retrieve the data and select a specific ID to view all corresponding document information in text fields. ht ...

Encountered an Internal Server Error (500) while attempting to create a new user through localhost:8800/api/auth/register

I'm currently working on setting up a website with registration, login, and logout functionalities using react and mysql. At the moment, I am facing some issues specifically in the registration process. When attempting to create a new user, I encounte ...

Learn how to simultaneously play two audio files using the same identifier in JavaScript

I'm facing an issue with two audio files that have a progress bar and both have the same ID: <audio id="player" src="<?=base_url('mp3/'.$rec->file)?>"></audio> </p> ...

center the view on the specified element

Utilizing ReactJs, I am developing a horizontal timeline that showcases dates. For instance, there is a list of 100 elements, each containing a date and text content. The dates are displayed horizontally using flex-direction="row" and overflowX ...

"Techniques for incorporating a screen in Angular application (Switching between Edit and View modes) upon user interaction

We are currently working on a screen that requires the following development: This screen will have the following features: When clicking on a button, the fields should become editable. In the image provided, there is some repeated data, but in our case, ...

Tips for Increasing Version Number with Gulp Task

Looking to update the version number in a JavaScript file (myConstantsFile.js) with a new string. Currently, the version number is "01.11.15" and appears like this in myConstantsFile.js along with other constants: .constant('productVersion', &ap ...

Manipulating values in JavaScript using an onclick event in PHP

I am looking to remove the "http" from the URL part of an input link before sending the data. This is my input code that looks like this when clicked: <input style="outline: none;" type="button" onclick="formatText ('link:url');" class="btn ...

Managing a two-dimensional array in AngularJS: tips and tricks

I have a JSON source that provides me with a double array: // Function and module code omitted .. $scope.texts = [ ['Small sheep and ham.'], ['Ducks go moo.', 'Helicopters and racecars go bang!'] ]; My goal is to display ...

Enhancing Typography in Material UI with Custom Breakpoints in React CustomThemes

Currently, I am utilizing material UI and React to develop a single-page application (SPA). However, I have encountered an issue with ensuring that my pages are responsive for smaller screen sizes. To address this, I have been manually adding fontSize: { x ...

MEAN stack application that has an input field with a dynamic attribute which saves as undefined

How come the value for a dynamic attribute is showing up as "undefined" in my form? I initially thought it was related to a hidden input, but the issue persists even when the input is not hidden. When trying to save a new event, I want the user to be able ...

Implementing a substantial update in MVC with the help of Slickgrid

Working on optimizing an MVC application that uses SlickGrid for grid edits. The grid can vary from 500 to 25,000 rows with around 40 columns. While the grid functions well, I'm facing challenges when it comes to posting changed data to my controller ...

What is the best way to link assets within an Angular custom element (Web Components)?

After successfully creating a web component and referencing an image from my asset folder, everything was running smoothly on my local environment. However, when I published my custom element to Firebase hosting, I encountered some issues. When trying to ...

Default value for the href property in NextJS Link is provided

Is there a default href value for Next/Link that can be used, similar to the way it is done in plain HTML like this: <a href='#' ></a> I attempted to do this with Link, but it resulted in the page reloading. Leaving it empty caused a ...

Browser unable to interpret HTML tag stored in ModelMap as HTML content

I am working on a spring controller with the code snippet below : @RequestMapping(value="/getMessage.htm", method=RequestMethod.POST) protected String uploadFile(ModelMap model){ //... other codes model.addAttribute("theMessage", "Hello world ...

Utilize Typescript to aim for the most recent edition of EcmaScript

Are you looking to configure your typescript build to compile to the most recent version or the most current stable release of EcmaScript? For example, using this command: tsc --target <get latest version> Alternatively, in a tsconfig file: { & ...

Adjust the dimensions of an image post-creation

let displayedImage = Ti.UI.createImageView({ image : somefile.png, height:'100', width:'100' }); The code above shows how I created the image. But now, I am wondering how I can resize the image after its initial creation. ...