35 votes

Liaison TypeScript et Knockout à «ce» problème - une fonction lambda est-elle nécessaire?

J'ai été la création d'un htmlHelper fonction à l'aide de la Machine et KnockoutJS pour éditer une liste de courriels.

La liste des e-mails est un knock-out ObservableArray appelé e-mails, et j'ai un lien en face de chaque élément à supprimer. C'est le fragment HTML:

   <ul data-bind="foreach: emails" >
        <li>
            <a href="#" data-bind="click: $parent.deleteItem">Delete</a>
            &nbsp;<span data-bind="text: $data"></span>
        </li>
    </ul>

Le lien de suppression est lié à $parent.deleteItem c'est une méthode dans le viewmodel:

// remove item
public deleteItem(emailToDelete: string) {
    // remove item from list
    this.emails.remove(emailToDelete);
}

Tout cela fonctionne jusqu'à ce que le deleteItem exécution de la méthode. Le "il" dans cette méthode, lorsqu'elle est appelée, est l'élément du tableau, et non pas le modèle de vue. Ainsi, cette.des e-mails est une référence null et échoue.

Je sais que la Machine prend en charge le Lambda de syntaxe mais je ne trouve pas la bonne façon d'écrire ceci (il y a quelques exemples).

Ou est-il une approche différente que j'ai pu prendre?

49voto

Slawek Points 644

Vous pouvez obtenir une fermeture correcte pour 'ceci' en déclarant le corps de la méthode dans le constructeur de classe

 class VM {
    public deleteItem: (emailToDelete: string) => void;

    constructor() {
        this.deleteItem = (emailToDelete: string) => {
            // 'this' will be pointing to 'this' from constructor
            // no matter from where this method will be called
            this.emails.remove(emailToDelete);
        }
    }        
}
 

MISE À JOUR:

Il semble que depuis Typescript ver 0.9.1, vous pouvez obtenir le même résultat en utilisant les initialiseurs de champ lambda:

 class VM {
    public deleteItem = (emailToDelete: string) => {
        this.emails.remove(emailToDelete);
    }        
}
 

26voto

RPJ Points 111

Gants les gens! Liez simplement $ parent comme ceci:

 <a href="#" data-bind="click: $parent.deleteItem.bind($parent)">Delete</a>
 

6voto

Markus Jarderot Points 33893
 declare class Email { }
declare class ObservableArray {
    remove(any): void;
}

class MyViewModel {
    public emails : ObservableArray;

    constructor() {
        Rebind(this);
    }

    public deleteItem(emailToDelete: Email) {
        this.emails.remove(emailToDelete);
    }
}

function Rebind(obj : any)
{
    var prototype = <Object>obj.constructor.prototype;
    for (var name in prototype) {
        if (!obj.hasOwnProperty(name)
                && typeof prototype[name] === "function") {
            var method = <Function>prototype[name];
            obj[name] = method.bind(obj);
        }
    }
}
 

Vous voudrez peut-être un polyfill pour Function.bind() :

 // Polyfill for Function.bind(). Slightly modified version of
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (typeof Function.prototype.bind !== "function") {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = <any[]> Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {},
            fBound = function() {
                return fToBind.apply(this instanceof fNOP && oThis ? this: oThis, aArgs.concat());
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}
 

6voto

Lucas Lorentz Points 31

Ma solution finale est une classe de base, qui relie tous les prototypes des fonctions à lui-même sur le constructeur. Un peu comme Markus Jarderot de la solution.

class BaseClass {
    constructor() {
        for (var i in this) {
            if (!this.hasOwnProperty(i) && typeof (this[i]) === 'function' && i != 'constructor') {
                this[i] = this[i].bind(this);
            }
        }
    }
}

Avantages:

  • Toutes les sous-classes sont obligés d'appeler super constructeur, ce qui est le comportement que je voulais.
  • Lorsque le relier code est exécuté, il y a seulement prototype des fonctions dans l'objet (les variables sont ajoutées plus tard).
  • Il permet d'éviter la création de grandes fonctions sur chaque objet. Seul un petit proxy fonction est créée par un objet lorsque vous appeler bind.
  • Une meilleure organisation de la classe code en ne mettant pas les fonctions sur le constructeur.
  • Toute fonction peut être utilisé comme un rappel, vous n'avez pas besoin de modifier le code quand une fonction est appelée à partir d'un événement.
  • Vous n'avez pas le risque de la liaison des fonctions deux fois.
  • Il est préférable de lier la fonction qu'une seule fois, au lieu de le faire dans la vue à chaque fois que la souris/de liaison d'événement est exécuté.

PS:
Vous avez encore besoin de le lier polyfill.
Je suis en utilisant typesript 0.9.5

0voto

Xavier Poinas Points 8534

Bien que je préfère la solution de Markus, voici ce que j'ai utilisé auparavant pour contourner ce problème:

 public fixThis(_this, func) {
    return function () {
        return _this[func].apply(_this, arguments);
    };
}

<a href="#" data-bind="click: fixThis($parent, 'deleteItem')">Delete</a>
 

Notez que des arguments supplémentaires peuvent être transmis à la méthode en les ajoutant après le nom de la méthode:

 fixThis($parent, 'deleteItem', arg1, arg2);
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X