Una direttiva angolare può passare argomenti a funzioni in espressioni specificate negli attributi della direttiva?


160

Ho una direttiva di forma che utilizza un callbackattributo specificato con un ambito isolato:

scope: { callback: '&' }

Si trova all'interno di un ng-repeatmodo quindi l'espressione che passo include idl'oggetto dell'oggetto come argomento della funzione di callback:

<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>

Quando ho finito con la direttiva, chiama $scope.callback()dalla sua funzione controller. Nella maggior parte dei casi questo va bene, ed è tutto ciò che voglio fare, ma a volte vorrei aggiungere un altro argomento all'interno di directivese stesso.

Esiste un'espressione angolare che consentirebbe questo $scope.callback(arg2):, risultando callbackessere chiamato con arguments = [item.id, arg2]?

In caso contrario, qual è il modo migliore per farlo?

Ho scoperto che funziona:

<directive 
  ng-repeat = "item in stuff" 
  callback = "callback" 
  callback-arg="item.id"/>

Con

scope { callback: '=', callbackArg: '=' }

e la direttiva che chiama

$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );

Ma non penso che sia particolarmente pulito e implichi l'inserimento di materiale extra nell'ambito dell'isolato.

C'è un modo migliore?

Parco giochi Plunker qui (avere la console aperta).


L'attributo che nomina "callback =" inganna. È davvero una valutazione del callback, non un callback stesso.
Dmitri Zaitsev,

@DmitriZaitsev è un'espressione angolare di callback che valuterà una funzione JavaScript. Penso che sia abbastanza ovvio che non è una funzione JavaScript in sé. È solo una preferenza, ma preferirei non dover aggiungere tutti i miei attributi a "-expression". Ciò è coerente con l' ngAPI, ad esempio ng-click="someFunction()"è un'espressione che valuta l'esecuzione di una funzione.
Ed Hinchliffe

Non ho mai visto un'espressione angolare chiamata "callback". È sempre una funzione che passi per essere chiamata, da cui il nome. Nel tuo esempio usi persino una funzione chiamata "callback" per rendere le cose ancora più confuse.
Dmitri Zaitsev,

Non sono sicuro che tu sia confuso o lo sono. Nel mio esempio $scope.callbackè impostato dall'attributo callback="someFunction"e dalla scope: { callback: '=' }proprietà dell'oggetto definizione direttiva. $scope.callback è una funzione da chiamare in un secondo momento. Il valore dell'attributo effettivo è ovviamente una stringa, come sempre nel caso dell'HTML.
Ed Hinchliffe,

Nominate sia l'attributo che la stessa funzione - "callback". Questa è la ricetta per la confusione. Facile da evitare davvero.
Dmitri Zaitsev,

Risposte:


215

Se dichiari il tuo callback come menzionato da @ lex82 come

callback = "callback(item.id, arg2)"

È possibile chiamare il metodo di callback nell'ambito della direttiva con la mappa degli oggetti e farebbe correttamente l'associazione. Piace

scope.callback({arg2:"some value"});

senza richiedere $ parse. Guarda il mio violino (registro console) http://jsfiddle.net/k7czc/2/

Aggiornare : c'è un piccolo esempio di questo nella documentazione :

& o & attr: fornisce un modo per eseguire un'espressione nel contesto dell'ambito padre. Se non viene specificato alcun nome attr, si presume che il nome dell'attributo sia uguale al nome locale. Definizione data e widget dell'ambito: {localFn: '& myAttr'}, quindi isola la proprietà dell'ambito localFn punterà a un wrapper di funzione per l'espressione count = count + value. Spesso è desiderabile passare i dati dall'ambito isolato tramite un'espressione e all'ambito padre, ciò può essere fatto passando una mappa di nomi e valori di variabili locali nel wrapper di espressioni fn. Ad esempio, se l'espressione è incremento (importo), allora possiamo specificare il valore importo chiamando localFn come localFn ({importo: 22}).


4
Molto bella! Questo è documentato ovunque?
ach

12
Non credo sia una buona soluzione perché nella definizione della direttiva, a volte non si sa quale sia il parametro da passare.
OMGPOP

Questa è una buona soluzione e grazie per quello, ma credo che la risposta abbia bisogno di un po 'di marea. Chi è lex82 e che cosa ha menzionato?
Wtower,

Approccio interessante Sebbene cosa succede quando si desidera consentire il passaggio di qualsiasi funzione con QUALSIASI parametro (o multiplo)? Non sai nulla della funzione né dei suoi parametri e devi eseguirla su qualche evento all'interno della direttiva. Come procedere? Ad esempio, su una direttiva potresti avere onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis

58

Nulla di sbagliato con le altre risposte, ma utilizzo la seguente tecnica per passare le funzioni in un attributo direttiva.

Lascia la parentesi quando includi la direttiva nel tuo html:

<my-directive callback="someFunction" />

Quindi "scartare" la funzione nel collegamento o controller della direttiva. ecco un esempio:

app.directive("myDirective", function() {

    return {
        restrict: "E",
        scope: {
            callback: "&"                              
        },
        template: "<div ng-click='callback(data)'></div>", // call function this way...
        link: function(scope, element, attrs) {
            // unwrap the function
            scope.callback = scope.callback(); 

            scope.data = "data from somewhere";

            element.bind("click",function() {
                scope.$apply(function() {
                    callback(data);                        // ...or this way
                });
            });
        }
    }
}]);    

Il passaggio "da scartare" consente di chiamare la funzione utilizzando una sintassi più naturale. Assicura inoltre che la direttiva funzioni correttamente anche se nidificata in altre direttive che potrebbero passare la funzione. Se non hai effettuato il wrapping, se hai uno scenario come questo:

<outer-directive callback="someFunction" >
    <middle-directive callback="callback" >
        <inner-directive callback="callback" />
    </middle-directive>
</outer-directive>

Quindi finiresti con qualcosa del genere nella tua direttiva interna:

callback()()()(data); 

Che fallirebbe in altri scenari di nidificazione.

Ho adattato questa tecnica da un eccellente articolo di Dan Wahlin a http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

Ho aggiunto il passaggio da scartare per rendere più naturale la chiamata alla funzione e per risolvere il problema di nidificazione che avevo riscontrato in un progetto.


2
Un approccio gradevole ma non sono in grado di utilizzare il thispuntatore all'interno del metodo di callback, perché utilizza l'ambito della direttiva. Sto usando Typescript e il mio callback è simile al seguente:public validateFirstName(firstName: string, fieldName: string): ng.IPromise<boolean> { var deferred = this.mQService.defer<boolean>(); ... .then(() => deferred.resolve(true)) .catch((msg) => { deferred.reject(false); }); return deferred.promise; }
ndee,

1
Avviso: se si dispone di direttive nidificate e si desidera propagare la richiamata verso l'alto, è necessario decomprimere in ciascuna direttiva, non solo quella che attiva la richiamata.
Episodex,

44

Nella direttiva ( myDirective):

...
directive.scope = {  
    boundFunction: '&',
    model: '=',
};
...
return directive;

Nel modello di direttiva:

<div 
data-ng-repeat="item in model"  
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>

Nella fonte:

<my-directive 
model='myData' 
bound-function='myFunction(param)'>
</my-directive>

...dove myFunction è definito nel controller.

Si noti che paramnel modello di direttiva si lega ordinatamente a paramnel sorgente ed è impostato su item.


Per chiamare dall'interno della linkproprietà di una direttiva ("dentro" di essa), usa un approccio molto simile:

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;

Pur avendo In source: bound-function = 'myFunction (obj1.param, obj2.param)'> quindi come procedere?
Ankit Pandey,

15

Sì, esiste un modo migliore: è possibile utilizzare il servizio $ parse nella direttiva per valutare un'espressione nel contesto dell'ambito padre, associando determinati identificatori nell'espressione a valori visibili solo all'interno della direttiva:

$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });

Aggiungi questa riga alla funzione di collegamento della direttiva in cui puoi accedere agli attributi della direttiva.

L'attributo callback può quindi essere impostato come callback = "callback(item.id, arg2)"perché arg2 è associato a yourSecondArgument dal servizio $ parse all'interno della direttiva. Direttive come ng-clickconsentono di accedere all'evento clic tramite$event identificatore all'interno dell'espressione passata alla direttiva usando esattamente questo meccanismo.

Si noti che non è necessario creare callbackun membro del proprio ambito isolato con questa soluzione.


3
L'uso scope.$parentrende la direttiva "permeabile" - "conosce" troppo del mondo esterno, cosa che un componente incapsulato ben progettato non dovrebbe.
Dmitri Zaitsev il

3
Bene, sa che ha un ambito genitore ma non accede a un campo particolare nell'ambito quindi penso che questo sia tollerabile.
lex82,

0

Per me il seguito ha funzionato:

in direttiva dichiararlo così:

.directive('myDirective', function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            myFunction: '=',
        },
        templateUrl: 'myDirective.html'
    };
})  

Nel modello di direttiva utilizzarlo nel modo seguente:

<select ng-change="myFunction(selectedAmount)">

E poi quando usi la direttiva, passa la funzione in questo modo:

<data-my-directive
    data-my-function="setSelectedAmount">
</data-my-directive>

Si passa la funzione dalla sua dichiarazione e viene chiamata dalla direttiva e i parametri vengono popolati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.