0

I would like to know what is the best way to keep the Display string value and the actual internal value of a directive consistent.

In the code below, I have a time input, where user can input a string representing the time ("00:00"), the directive calculate the actual value and set it accordingly every time user change the string value.

However, as the actual value is exposed and can be changed by other component, I need some way to make the string value consistent with the actual value.

$watch is one solution but I then need to disable the watch when in onChange() method. So, I wonder if there is a better way of doing this.

[EDIT]: The reason I am using ng-blur is due to the validation in case the string value is not a valid format, it is reverted back to the actual value.

Cheers,

Jsfiddle: https://jsfiddle.net/minhnq013/v5rax0vk/3/

angular.module('myapp', [])
    .directive('timeInput', function () {
    return {
        restrict: 'E',
        template: '<input type="text" ng-model="ctrl.strValue" ng-blur="ctrl.onChange()"/><span ng-bind="ctrl.value"></span>',
        scope: {
            'value': '='
        },
        controller: Controller,
        controllerAs: 'ctrl',
        bindToController: true
    };

    function Controller() {
        var ctrl = this;
        ctrl.value = ctrl.value || 0;
        ctrl.strValue = "00:00";

        /**
         *  Called when the UI determine user input value has change,
         *  Set the internal value to be same as user input text value.
         **/
        function onChange() {
            var hour = ctrl.strValue.substr(0, 2);
            var minute = ctrl.strValue.substr(3);
            var dateObj = new Date(ctrl.value);
            var regex = new RegExp("^\\d{2}:\\d{2}$");
            // If format is not valid, revert to last value
            if (!ctrl.strValue.match(regex)) {
                ctrl.strValue = ("0"+dateObj.getUTCHours()).slice(-2) + ":" + ("0"+dateObj.getUTCMinutes()).slice(-2);
                return;
            }

            dateObj.setUTCHours(hour);
            dateObj.setUTCMinutes(minute);
            ctrl.value = dateObj.getTime();
        }
        ctrl.onChange = onChange;

        function filterText() {

        }
    }
});
MikeNQ
  • 653
  • 2
  • 15
  • 29

3 Answers3

0

I would use a filter. If you are using this code in multiple locations, then you can use a directive which uses the filter.

The filter would be responsible for displaying formatted internal data. This would eliminate the need to store two objects for essentially one value.

Also, using a filter would eliminate the need for onChange events.

For more information on filters, please see: https://scotch.io/tutorials/building-custom-angularjs-filters

Raphael Rafatpanah
  • 19,082
  • 25
  • 92
  • 158
  • Your suggestion makes me think of a two-way filter. Since I do need to allow user changing the text value which also change the actual value as well. Calling it a display text value wasn't very smart of me. – MikeNQ Aug 13 '15 at 16:58
  • @MikeNQ, what are the current internal and display formats exactly? – Raphael Rafatpanah Aug 13 '15 at 17:09
  • It is as in the jsfiddle, String format is "hh:mm" to represent time. While the actual value is the millisecond integer value in UTC. – MikeNQ Aug 13 '15 at 17:12
  • Is there a reason for not using standard timestamps? – Raphael Rafatpanah Aug 13 '15 at 17:15
  • There is some complication issue on the server-side that they do not have the resource to take care of timezone stuff. Also, add the library to front-end side to handle time takes a lot of emails, so I need this as temporary solution. But regardless, as the value is integer, it should do little harm to how the time input work. – MikeNQ Aug 13 '15 at 17:21
  • One way to solve this would be to get a timestamp from midnight today, and calculate the time as the different between user time and midnight. Then you only need to store the difference. – Raphael Rafatpanah Aug 13 '15 at 17:27
0

There's a lot of different ways to structure this, but I would suggest that rather than using a watch, you try to keep it event driven. I'm partly basing this more on your code comment than your question Called when the UI determine user input value has change, but it sounds like you might just be thinking of the wrong event for what you're trying to achieve. What about changing ng-blur to something like ng-change, so that you function runs on every change to keep your logic in sync. You can easily pair this with model options for some pretty awesome functionality, for instance if you want to slow it down to half-second updates as: ng-model-options="{ debounce: { 'default': 500 } }"

template: '<input type="text" ng-model="ctrl.strValue" ng-change="ctrl.onChange()"/><span ng-bind="ctrl.value"></span>',
wesww
  • 2,863
  • 18
  • 16
  • this also works well with what you described in this line: "directive calculate the actual value and set it accordingly every time user change the string value" – wesww Aug 13 '15 at 16:58
  • My bad for not including the reason for using ng-change. There is also a validate mechanism that revert the string value to the actual value if the string format is not valid. But this is best checked after user un-focus the field. – MikeNQ Aug 13 '15 at 17:07
0

After looking around more, I found this post and seems to be the right solution for the problem.

Creating a new directive restrict to attribute and act as a filter for the text input value.

http://stackoverflow.com/questions/15343068/using-angularjs-filter-in-input-element

MikeNQ
  • 653
  • 2
  • 15
  • 29