Undefined Variables in AngularJS Directive Scopes

Check out this JSFiddle I created

https://jsfiddle.net/9Ltyru6a/3/

I set up a controller and a directive in the fiddle to trigger a callback when a value is changed. Instead of using the ng-change directive in Angular, I wanted to create an event similar to the standard onchange event that triggers when the field is blurred.

Controller:

var Controllers;
    (function (Controllers) {
    var MyCtrl = (function () {
        function MyCtrl($scope) {
            $scope.vm = this;
        }

        MyCtrl.prototype.callback = function (newValue) {
            alert(newValue);
        };

        return MyCtrl;
    })();
    Controllers.MyCtrl = MyCtrl;
})(Controllers || (Controllers = {}));

Directive:

var Directives;
(function (Directives) {
    function OnChange() {
        var directive = {};
        directive.restrict = "A";
        directive.scope = {
            onchange: '&'
        };
        directive.link = function (scope, elm) {
            scope.$watch('onChange', function (nVal) {
                elm.val(nVal);
            });
            elm.bind('blur', function () {
                var currentValue = elm.val();
                scope.$apply(function () {
                    scope.onchange({ newValue: currentValue });
                });
            });
        };
        return directive;
    }
    Directives.OnChange = OnChange;
})(Directives || (Directives = {}));

HTML:

<body ng-app="app" style="overflow: hidden;">
    <div ng-controller="MyCtrl">
        <button ng-click="vm.callback('Works')">Test</button>
        <input onchange="vm.callback(newValue)"></input>
    </div>
</body>

The button works fine, indicating that the controller is functioning properly. However, I encounter a "vm is undefined" error every time I change the value in the input field and move focus away.

Thank you for your assistance!

Answer №1

To start, ensure you are using proper controllerAs notation and avoid using $scope.vm = this;:

ng-controller="MyCtrl as vm"

Additionally, do not mix custom directive with the native onchange event handler as this may cause an undefined error. Instead, name your directive something like onChange and utilize the on-change attribute.

The accurate code should appear as follows:

var app = angular.module("app", []);

var Directives;
(function (Directives) {
    function OnChange() {
        var directive = {};
        directive.restrict = "A";
        directive.scope = {
            onChange: '&'
        };
        directive.link = function (scope, elm) {
            elm.bind('blur', function () {
                var currentValue = elm.val();
                scope.$apply(function () {
                    scope.onChange({
                        newValue: currentValue
                    });
                });
            });
        };
        return directive;
    }
    Directives.onChange = OnChange;
})(Directives || (Directives = {}));

app.directive("onChange", Directives.onChange);


var Controllers;
(function (Controllers) {
    var MyCtrl = (function () {
        function MyCtrl($scope) {

        }

        MyCtrl.prototype.callback = function (newValue) {
            alert(newValue);
        };

        return MyCtrl;
    })();
    Controllers.MyCtrl = MyCtrl;
})(Controllers || (Controllers = {}));

app.controller("MyCtrl", ["$scope", function ($scope) {
    return new Controllers.MyCtrl($scope);
}]);

See a demo here: https://jsfiddle.net/9Ltyru6a/5/

Answer №2

If you want your controller value to update only on blur instead of on every keypress, Angular provides the ngModelOptions for this purpose. Here's an example:

<input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" />

You can even include a debounce or a button to clear the value...

<form name="userForm">
  <input type="text" name="userName" 
         ng-model="user.name" ng-model-options="{ debounce: 1000 }" />

  <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
</form>

In these scenarios, if you add an ng-change, it will only trigger on blur or after the debounce.

You can also create directives that utilize $validators or $asyncValidators from the ngModelController

Here's an example from the Angular Developer Guide:

app.directive('username', function($q, $timeout) {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
    var usernames = ['Jim', 'John', 'Jill', 'Jackie'];

      ctrl.$asyncValidators.username = function(modelValue, viewValue) {

        if (ctrl.$isEmpty(modelValue)) {
          // consider empty model valid
          return $q.when();
        }

        var def = $q.defer();

        $timeout(function() {
          // Mock a delayed response
          if (usernames.indexOf(modelValue) === -1) {
            // The username is available
            def.resolve();
          } else {
            def.reject();
          }

        }, 2000);

        return def.promise;
      };
    }
  };
});

and the HTML:

<div>
    Username:
    <input type="text" ng-model="name" name="name" username />{{name}}<br />
    <span ng-show="form.name.$pending.username">Checking if this name is available...</span>
    <span ng-show="form.name.$error.username">This username is already taken!</span>
</div>

You can also use ng-model-options to ensure this only triggers once.

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

Whenever I am building a React application, I encounter a bug that states: "node:fs:1380 const result = binding.mkdir()"

Whenever I try to enter the command: create-react-app my-app --template typescript I keep encountering this error message: node:fs:1380 const result = binding.mkdir( ^ Error: EPERM: operation not permitted, mkdir 'D:\ ...

Modifying Parent Component Layout Using Vue.js

I am currently in the process of converting a UI element into a component for the purpose of reuse. This particular element consists of two buttons that toggle the visibility of two DOM elements within the parent app. The state of these buttons is stored i ...

Use JavaScript to add a div element to the page ten times every time the button is clicked

Here is the code I have written: $(document).ready(function () { $('<button class="btn more">View More</button>') .appendTo(".listing-item-container") .click(function() { $(this).closest(". ...

"Switch the forward slashes with hyphens when working with AngularJS

Would anyone know how to replace all instances of "/" with "-" in a date string like "09/29/2017" using AngularJS? $scope.newString = $scope.date.replace(/\//g, "-"); Currently, the above code generates a date like "09-29/2017" instead of replacing ...

Utilizing ES6 Map type in TypeScript for Angular 2 Response Data Transfer Object Definition

Is it possible to utilize the es6 Map type in an HTTP Response DTO? Let's consider an Angular 2 request: public loadFoos(): Observable<FoosWrapper> { return this.http.get("/api/foo") .map(res => res.json()); } Now, take a loo ...

Troubleshooting ASP.net core AntiForgeryToken problem: model not receiving bound data

Currently, I have a functioning project built on a booking system in asp.net core that integrates a google Timeline chart. The interactive element involves a modal popup, which displays a partial view for data input when the chart is clicked. Utilizing Jav ...

What is the best way to require users to click one of the toggle buttons in a form?

Is it possible to require the user to click one of the toggle buttons in a React form? I want to display an error if the user tries to submit the form without selecting a button. Even though I tried using "required" in the form, it didn't work as expe ...

Tips for transferring a request response to another request:

In my ExpressJS application, I am utilizing the request-promise module to send two requests. The catch is that I need the response data from the first request to be transferred to the second request. Here is an illustration of what I am aiming for: const ...

Connect button with entry field

I want to create a connection between an input element and a button, where the button triggers the input and the input remains hidden. Here is a solution that I came across: <a href="javascript:void(0)" id="files" href=""> <button id="uploadDe ...

Steps for adding hover color to a div with linear gradient border

My current challenge involves adding a hover color to a specific div, but I'm facing obstacles due to the gradient background that was implemented to achieve border-radius functionality. This task is being carried out within a React Component using c ...

Discover the most recent date of a document by analyzing two distinct fields: the month and the year

In my mongoDB database, the documents have the following structure: {username:String, paymentYear:Int, paymentMonth:Int} I am trying to retrieve the latest document for a specific username, which would be the one with the date closest to the current Date ...

AngularJS - real-time validation using the $valid property

I have implemented AngularJS to handle form validation and submission. The submit button is configured as follows: <button type="submit" ng-disabled="btn" ng-click="submit(settings.$valid)" class="btn btn-primary"> Submit </button> The butt ...

Looking for some guidance on grasping the concept of strict mode in React and determining what actions can be considered side effects

The other day, I came across a strange bug in React and here is a simplified version of it. let count = 0; export default function App() { const [countState, setCountState] = useState(count); const [countState2, setCountState2] = useState(count); con ...

javascript making a button using an object

When trying to create a button from a JavaScript object, I am following this approach: for (buttonName in buttons){ var htmlbutton = '<button type="button" onclick="'+buttons[buttonName]()+'">'+buttonName+'< ...

Utilizing Google Places Autocomplete to tailor search outcomes

I'm currently working on customizing the output of my Google Places autocomplete code. Specifically, I want to change the displayed result when a user selects an address from the list. For example, one of the results is: '45 Alexandra Road Holi ...

Steps for creating a TypeScript project for exporting purposes

Forgive me for my lack of experience in the js ecosystem. Transitioning from static languages to typescript has been a positive change, though I still find myself struggling to grasp the packaging/module system, especially when coupled with typescript defi ...

execute a rigorous compilation during the ng build angular process

I am currently working on a project using angular-cli and I have configured my package.json with the following scripts: "scripts": { "ng": "ng", "build": "ng build --base-href /test/", "prod": "ng build --prod --base-href /test/" } According to the ...

Is the value of the object in the array already present in another array of objects?

Plunker - http://plnkr.co/edit/jXdwOQR2YLnIWv8j02Yp In my angular project, I am working on a view that displays a list of users in the main container (array-A) and a sidebar with selected users (array-B). The first array (A) contains all users: [{ $$has ...

Is there a way to configure Cordova to utilize Yarn JS instead of NPM when installing plugins?

Updated Question: When adding plugins to my Cordova project, I currently use the command cordova plugin add x, which I believe utilizes npm in the background. Is there a way to switch out npm for Yarn within Cordova? This change would greatly impact cach ...

Toggle the sidebars to slide out and expand the content division using JavaScript

I am looking to achieve a smooth sliding out effect for my sidebars while expanding the width of the middle content div. I want this action to be toggled back to its original state with another click. Here's what I've attempted so far: $(' ...