14

I use KnockoutJS in my projects, but want to learn AngularJS as it has a lot of tasty features that Knockout doesn't have. So I'm interested in rewriting some of my code using Angular. But I don't understand how to do some simple things that I use in Knockout. For example, Knockout has a feature of computed observables. It's cool! I've already found that I can use a simple function instead. But Knockout provides "write" function to a computed observables, like:

var first_name = ko.observable('John'),
    last_name = ko.observable('Smith'),
    full_name = ko.computed({
        read: function(){
            return first_name() + ' ' + last_name();
        },
        write: function(new_value){
            var matches = new_value.match(/^(\w+)\s+(\w+)/);

            first_name(matches[1]);
            last_name(matches[2]);
        }
    });

This code on JSFiddle: http://jsfiddle.net/Girafa/QNebV/1/

This code allows me to update the "first_name" and "last_name" observables when I change the "full_name" one. How it can be done using AngularJS? A function with an argument being checked for existence? Something like this?

first_name = 'John';
last_name = 'Smith';
full_name = function(value){
    if (typeof value != 'undefined')
    {
        // do the same as in the Knockout's write function
    }
    else
    {
        // do the same as in the Knockout's read function
    }
}

What is the best practice?

Girafa
  • 3,370
  • 3
  • 18
  • 33
  • possible duplicate of [KO.Computed equivalent in Angular / Breeze Initializer](http://stackoverflow.com/questions/18222111/ko-computed-equivalent-in-angular-breeze-initializer) – PW Kad Oct 22 '13 at 19:48
  • 1
    I don't think this is a duplicate. He's asking about how to implement a "setter", which the other question doesn't cover. – Ken Smith Oct 22 '13 at 20:23

2 Answers2

12

I've found such a solution: http://jsfiddle.net/Girafa/V8BNc/

Instead of using angular $watch method, we set native javascript getter and setter of the fullName property:

Object.defineProperty($scope, 'fullName', {
    get: function(){
        #...
    },
    set: function(newValue){
        #...
    }
})

Think this is more convenient as I don't need to make any special watchers in the code. But I don't know about browser support of this solution.

Girafa
  • 3,370
  • 3
  • 18
  • 33
  • This is great. No need for $watch - yay! – Jarnal Nov 18 '13 at 19:18
  • 3
    Just in case anyone is worried, [browser support is IE9+](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) – Zequez Jun 06 '14 at 20:00
  • Can you post a full example please? I need a computed observable in a template, can't figure out how this can be used to access the other things in the current scope? Point of a computed observable is to calculate something based off some other value - how do you pass values unto these functions? – pilavdzice Sep 30 '14 at 23:37
  • These functions are written inside the controller, so all variables inside controller function scope are available in these functions. – Girafa Oct 01 '14 at 09:32
  • Another option is to create a simple function in your controller and then call it in your view. – Girafa Oct 01 '14 at 09:38
  • And yet another one is to register $watch or $watchCollection to the properties your computed value depends on, and in the callback function assign computed value to another property of your scope. Then you will be able to bind to that property in your view. This approach is useful if you have multiple places to paste the computed value in the view, so function will be executed only once but not each time angular encounters it in the view. – Girafa Oct 01 '14 at 09:38
0

Sorry about that. It's true, this is simpler in knockout because a function is called whereas a property is used in angular. This is the way I could resolve it, but I would like to know if there is a better way.

I fixed this time Plunker

app.controller('Ctrl', function($scope) {
    $scope.firstName = 'John';
    $scope.lastName  = 'Smith';

    $scope.getFullName = function() {
        $scope.fullName = $scope.firstName + ' ' + $scope.lastName;
        return $scope.fullName;
    }

    $scope.$watch('fullName', function(newValue, oldValue) {
        var names = newValue.split(' ');
        $scope.firstName = names[0];
        $scope.lastName  = names[1];  
    });
});
yeraycaballero
  • 1,603
  • 3
  • 19
  • 29
  • This works only in one direction: if I change the fullName value. But if I have inputs for all the three scope properties and change "firstName" one, for example, the "fullName" doesn't update. So should I make a function to put first and last names together in one string, and then use $watch for every scope property used here to set the "fullName" value again? – Girafa Oct 23 '13 at 06:00
  • 1
    In the second example when I change first or last name, the fullName property is not being updated. It disappoints me. Such a simple thing made in knockout becomes so hard to make in Angular. – Girafa Oct 23 '13 at 13:17
  • I wrote it again to avoid confusion. In knockout is simpler because a function is called and it can be used as a getter or setter. In angular a property is changed directly, there is no need to call a function, so I'm afraid it's need a setter property an a getter one. I'm not saying this is the only way. – yeraycaballero Oct 23 '13 at 16:14
  • 1
    I've found possibly a better solution. Look at my own answer below. – Girafa Oct 23 '13 at 17:56