Mark Coleman
Mark Coleman

Reputation: 40863

How to create reusable AngularJs directives that copy existing directives

I am in the process of creating a few reusable directives for common controls in our application.

For example we have a snip-it of html for amount text boxes

<div class='amount'>
    <input type='text' ng-model='dollars'/>
</div>

From there I started to create my directive:

app.directive("amount", function(){
    return {
        restrict: "E",
        template: "<div class='amount'><input type='text'/></div>",
        replace: true
    }
});

Which renders the following <html/>

<div class="amount ng-pristine ng-valid" ng-model="dollars">
    <input type="text">
</div>

Now the ng-model is on the <div/> which is not what I want, so I need to create a scope and attach it to the ngModel and things are happy again.

app.directive("amount", function(){
    return {
        restrict: "E",
        scope:{
            ngModel: "="
        },
        template: "<div class='amount'><input type='text' ng-model='ngModel'/></div>",
        replace: true
    }
});

Everything works, but lets say I want to also add in a ngChange directive, does that mean I again need to alter my scope to include ngChange: "="? Like so

app.directive("amount", function(){
    return {
        restrict: "E",
        scope:{
            ngModel: "=",
            ngChange : "="
        },
        template: "<div class='amount'><input type='text' ng-model='ngModel'/></div>",
        replace: true
    }
});

The Question

Do I need to constantly modify the directives scope to include an infinite possible number of other directives I might require? Or is there a way to copy the directives on the <amount/> element not to the <div/> but to the <input/>

E.g.

<amount my-awesome-directive="" ng-disabled="form.$invalid" ng-model="dollarsAndCents" ng-click="aClick()" ng-show="shouldShow()"/>

Turns into

<div class="amount">
    <input my-awesome-directive="" type="text" ng-disabled="form.$invalid" ng-click="aClick()" ng-show="shouldShow()" ng-model="dollarsAndCents"/>
</div>

Can something be done during the pre/post compile to copy them over or am I going about this all wrong?

Update

I was able to get something working via simply looping over all attributes and using the $compile() service. It does work, but is this correct?

app.directive("amount", function ($compile) {
    return {
        restrict: "E",
        template: "<div class='amount'><input type='text' /></div>",
        replace: true,
        compile: function compile(tElement, tAttrs) {
            return function (scope, iElement, iAttrs) {
                var attributes = $(iElement).prop("attributes");
                var $input = $(iElement).find("input");
                $.each(attributes, function () { //loop over all attributes and copy them to the <input/>
                    if (this.name !== "class") {
                        $input.attr(this.name, this.value);
                    }
                });
                $compile($input)(scope);  //compile the input
            };
        }
    };
});

Given the following <html/> if you add any directive to the <amount/> it gets copied down to the <input/>

<div ng-app="app">
        <amount ng-model="dollars" ng-change="changed = 'I Changed!!!'" ng-click="clicked= 'I Clicked It!'" name="amount"></amount>
         <h1>{{dollars}}</h1>
         <h2>{{changed}}</h2>
         <h3>{{clicked}}</h3>
        <input type="button" value="Remove" ng-click="items.splice(items.indexOf(item), 1)"/>
        <hr/>
</div>

updated jsfiddle

Upvotes: 9

Views: 7127

Answers (1)

Dan Kanze
Dan Kanze

Reputation: 18595

Bind a controller and inject $scope for ease.

.controller('Amount', ['$scope', function($scope) {
    $scope.myMoney = '2';
}])

.directive("amount", function(){
    restrict: 'EA',
    replace: true,
    controller: 'Amount',
    template: "<div class='amount'><input type='text' ng-model='myMoney'/></div>",
    //Cleaner to include as URL if the partial is bigger.
    //templateUrl: '/views/amount.html',
    link: function(scope, controller) {}
});

Upvotes: -1

Related Questions