How can one adhere to Angular's recommendation of "always using dots with ngModel" while working within isolate scopes?

Currently, I am working on developing an Angular application utilizing Bootstrap.

To reduce the impact of Bootstrap on my HTML code, I have implemented two directives specifically for forms:

form-control.js

module.directive('formControl', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-control.tmpl.html',
    scope: {
      label: '@'
    },
    transclude : true
  };
});

form-control.tmpl.html

<div class="form-group">
  <label class="control-label col-sm-2">
    {{ label }}
  </label>
  <div class="col-sm-10"
       ng-transclude>
  </div>
</div>

In addition, I have developed a set of "extensions" to these directives tailored for various form input fields. For example:

form-input-text.js

module.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      label: '@',
      value: '=ngModel'
    }
  };
});

form-input-text.tmpl.html

<form-control label="{{label}}">
  <input type="text"
         class="form-control"
         ng-model="value">
</form-control>

app.html

<form-input-text label="Name"
                 ng-model="person.name">
</form-input-text>

A challenge arises within this setup due to multiple scopes in use:

appScope = { person : { name : "John" } };
isolateScope = {
  label: "Name",
  value: "John" // bound two-way with appScope.person.name
};
transcludeScope = {
  __proto__: isolateScope,
  label: "Name", // inherited from isolateScope
  value: "John" // inherited from isolateScope
};

When altering the text in the input box, only transcludeScope is affected:

appScope = { person : { name : "John" } };
isolateScope = {
  label: "Name",
  value: "John" // bound two-way with appScope.person.name
};
transcludeScope = {
  __proto__: isolateScope,
  label: "Name", // inherited from isolateScope
  value: "Alice" // overrides value from isolateScope
};

The reason behind this behavior is that the <input> directly links to a property of transcludeScope. Any modifications are made to transcludeScope.value, and do not reflect back to appScope through isolateScope.

I am looking to establish a bidirectional binding between appScope.person.name and a nested property of isolateScope, like isolateScope.model.value.

It would be ideal if I could declare the directive as follows:

form-input-text.js

module.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      model: {
        label: '@',
        value: '=ngModel'
      }
    }
  };
});

This modification would enable the transcluded section to bind to model.value, ensuring changes are visible to isolateScope and subsequently propagated to appScope.

Unfortunately, it appears that Angular does not directly support this particular usage.

If anyone knows of an Angular feature that can accommodate this scenario or offer a workaround, please provide guidance.

Edit:

For the time being, I have opted to integrate the form-control template into the form-input-text template.

form-input-text.tmpl.html

<div class="form-group">
  <label class="control-label col-sm-2">
    {{ label }}
  </label>
  <div class="col-sm-10">
    <input type="text"
           class="form-control"
           ng-model="value">
  </div>
</div>

This approach eliminates the introduction of a child scope via ng-transclude, but results in duplicated markup which defeats the purpose of consolidating everything into one place.

Answer №1

When considering scopes, it's important to integrate with ngModelController rather than focusing solely on transclusion. By incorporating ngModelController "properly," you enable the execution of integrated parsers and formatters, including validation logic, at the appropriate moments. This process is complex due to the presence of two controllers: one in the parent application and one in the directive template. Each controller has two integration pipelines:

  • model value -> view value
  • view value -> model value

The view value of the parent ngModelController serves as the model value for the inner ngModelController, creating the following pipelines:

  • parent model value -> parent view value -> inner model value -> inner view value
  • inner view value -> inner model value -> parent view value -> parent model value

To achieve this integration, follow these steps:

  • Ensure that you include 'ngModel' in the directive definition's 'require' field to access the parent ngModelController.

  • Implement changes from the parent ngModelController to the inner controller using the $render method of the parent controller, utilizing its $viewValue property. This ensures that any functions in the parent $formatters array are executed.

  • User-initiated changes from the inner directive should involve adding a function to the $viewChangeListeners array, which calls $setViewValue on the parent ngModelController. To access this functionality within the linking function scope, utilize named form and input elements. Note that the form is only registered on the directive's scope after the linking function has run, necessitating a watcher to access it.

  • For precautionary measures, ensure that the model in formInputText is enclosed in an object (although this may not be strictly necessary).

  • There is no need to include the model in the scope object of the inner directive.

Combining these steps results in the following implementation:

Your revised directive code goes here...

The corresponding template is structured as follows:

Your revised template code goes here...

This strategy can be observed in action by visiting this link.

Answer №2

If you're looking for a solution tailored to your specific needs, consider using a custom control in this instance. You can create custom <form-input-*> directives as true controls by following the instructions mentioned here. It may require some additional effort, but here's a basic outline of how you can approach it:

form-input-text.js

app.directive('formInputText', function() {
    return {
        restrict: 'E',
        template: '<form-control label="{{label}}"><input type="text" class="form-control" /></form-control>',
        scope: {
            label: '@'
        },
        require: 'ngModel',
        link: function(scope, elem, attrs, ngModel) {
            var input = angular.element(elem[0].querySelectorAll("input")[0]);

            ngModel.$render = function() {
                input.val(ngModel.$viewValue || '');
            };

            input.on('blur keyup change', function() {
                scope.$apply(read);
            });

            function read() {
                ngModel.$setViewValue(input.val());
            }
        }
    };
});

In essence, by requireing the ngModel and implementing its methods as per the guidelines, you can integrate it seamlessly within your custom control. This allows you to leverage various functionalities like custom validators, ng-required, and more.

Check out a functional demo on JSFiddle: http://jsfiddle.net/1n53q59z/

Note that some adjustments might be necessary based on your specific requirements.

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 reason JSON.parse fails to convert a string into a JSON object?

I attempted to convert a string into a JavaScript object using JSON.parse, however, it seems to be unsuccessful. After trying various methods, I did not receive any output in the console.log and no error messages were displayed. JSON.parse(`{'exp&apo ...

Do sibling modules depend on each other's knowledge?

As a beginner in the world of Angularjs, I am intrigued by how the code below functions seamlessly without any issues. My main query pertains to the creation of the "myControllersModule" module without explicitly mentioning "myServicesModule" as one of i ...

Autofill class names in VS Code for Next.js using Emmet

How can I modify Emmet autocomplete to change from className="" to className={styles.}? If it is possible, could you please guide me on how to achieve this? Alternatively, if modifying the autocomplete feature is not an option, is there a way to create a ...

Does Angular 1.3.x have a corresponding .d.ts file available?

Is there a .d.ts file available for Angular 1.3.x to assist in transitioning an app to Typescript 2.0? ...

You can see the JavaScript code directly in the browser

I'm completely puzzled as to why this is happening. How strange that the JavaScript code is visible on the browser, almost appearing like regular text on the web page. This mysterious occurrence seems to be exclusive to Firefox. Despite scouring vario ...

Creating a collapsing drop down menu with CSS

I utilized a code snippet that I found on the following website: Modifications were made to the code as shown below: <div class="col-md-12"> ... </div> However, after rearranging the form tag, the drop-down menu collapse ...

Managing state in React using a nested object structure with React hooks for adding or updating entries

In my React application, I have a state object that represents a book structure consisting of books, chapters, sections, and items: const book = { id: "123", name: "book1", chapters: [ { id: "123", name: "chapter1", sections: [ ...

Send the model to the route to be filled through the query parameter

I'm currently working on a task that involves creating a function to handle app routes. The goal is to pass in an object that will be filled with the fields from the request body. In my code snippet below, I encountered an error mentioning that ' ...

Sorting Object Values with Alternate Order

Is there a way to sort a JSON response object array in a specific order, especially when dealing with non-English characters like Umlauts? object { item: 1, users: [ {name: "A", age: "23"}, {name: "B", age: "24"}, {name: "Ä", age: "27"} ] ...

Looking to grasp the concept of calling inline functions within a React functional component

As a newcomer to React, I recently created a new view within my company. Following the example of many guides, I have utilized inline functions and function components exclusively. One common practice I have adopted is writing onClick events in this manne ...

Create a search preview in JavaScript that emphasizes key information and provides a condensed overview

I am currently working on incorporating a feature that generates a highlighted and condensed preview of a provided text. Below is the code snippet I have so far: <div> Search: <input value='vestibulum' disabled/> </div> < ...

Ways to specify a setter for a current object property in JavaScript

Looking to define a setter for an existing object property in JavaScript ES6? Currently, the value is directly assigned as true, but I'm interested in achieving the same using a setter. Here's a snippet of HTML: <form #Form="ngForm" novalida ...

Migrating to Angular Universal for server-side rendering with an external API server

Thank you for taking the time to read my query. I have developed a project using server-side Node.js and client-side Angular 6. The purpose of the app is to provide information on cryptocurrency prices and details. I am now looking to transition my Angu ...

How to trigger a jQuery function once a v-for loop has completed in VueJS?

Utilizing vue-resource, I successfully retrieve data from my API and incorporate it into my HTML. However, I am encountering an issue where I want to execute a jQuery function after the v-for loop has completed in order for jQuery to recognize elements in ...

WordPress does not recognize the jQuery function

I have encountered an issue with my code where a simple HTML jQuery slider works fine in a standalone file, but when I integrate it into WordPress, it no longer functions properly. The slider is being added to the index.php file by hardcoding the script li ...

display a div positioned relatively next to another div

I'm having trouble displaying a textbox next to another div on my webpage. Instead of appearing next to the div, it is showing up below it. The textbox needs to have an absolute position. You can see the issue in action by checking out this demo. Than ...

Tips for placing multiple images onto one canvas

I am currently working on a web application that allows users to create sketches, define their positions and sizes, and then save them to a database. In another section of the website, I aim to retrieve these images and display them on a single canvas. To ...

What is the best way to split an AJAX response into different variables and effectively retrieve each one of them?

When using AJAX to fetch data from a database and display it in a text box, most examples found online only show how to display the AJAX response in one text box. But what if we need to separate multiple PHP variables retrieved from the AJAX response and d ...

Tips for incorporating a set offset while utilizing the scrollTop() function

I have successfully implemented a code that sets a position:fixed to a div when it scrolls past the top of the screen. The code I used is as follows: var $window = $(window), $stickyEl = $('#the-sticky-div'), elTop = $stickyEl.o ...

Changes made to an array within a watch function do not update the ng-repeat directive

Struggling to comprehend the peculiar behavior of the ng-repeat directive. Let me explain the issue at hand. In the initial scenario, the search box remains empty upon loading completion. When there is no input in the search box, all contacts from the dat ...