I wrote an AngularJS directive, but I'm pretty new about it, and I don't know if I done in the "Angular way"...
Here is my plunker with the code: http://plnkr.co/edit/X1tOk4z8f6dCK3mfB7HP?p=preview
html:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<meta charset=utf-8 />
<title>Directive Test</title>
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<button id="button1" ng-click="dummyClickFoo()" wait-button="foo"><i></i> Foo</button>
<button id="button2" ng-click="dummyClickBar()" wait-button="bar"><i></i> Bar</button>
</body>
</html>
js:
app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.dummyClickFoo = function() {
$scope.startSpinner('foo');
setTimeout(function() {
$scope.stopSpinner('foo');
}, 3000);
};
$scope.dummyClickBar = function() {
$scope.startSpinner('bar');
setTimeout(function() {
$scope.stopSpinner('bar');
}, 3000);
};
});
app.directive('waitButton', function() {
return {
restrict: 'A',
controller: ['$scope', '$element', function($scope, $element) {
$scope.startSpinner = function(id) {
var el = angular.element(document.querySelector('[wait-button="'+id+'"]'));
el.children('i').text('searching...');
};
$scope.stopSpinner = function(id) {
var el = angular.element(document.querySelector('[wait-button="'+id+'"]'));
el.children('i').empty();
};
}]
};
});
I find that the document.querySelector('[wait-button="'+id+'"]') part, it's a bit "nasty"... (or not?); otherwise I don't know a better way to re-use the same directive different times in the same controller.
Can someone suggest me a better code?
Thank you.
I'd advocate using the link function for this type of thing:
link: function($scope, elem, attrs){ /* do something w. elem */ }
It's not very angular-ish to be accessing your element in the controller. That's the whole point of the link & compile functions of the directive object....
... but in rare cases this is justified. The injected $element in your controller references the same thing your angular.element(document.querySelector('[wait-button="'+id+'"]')) code is doing. You only need to use $element at this point. But may I recommend a more angular approach altogether?
The other issue is how you're basically communicating your intent from the directive to the main controller and back to the directive. Your use case is a little different than most in that you have an async nature.
I have made an example that leverages an isolate scope and callback parameters. In most real-world scenarios you'll be dealing with promises for async callbacks. As such, I used the .finally logic from the promises to execute the callback that communicates back to the directive that whatever async logic has wrapped up.
Things to keep in mind in my examples:
link function versus a custom directive controller.$scope paradigm.plunker - http://plnkr.co/edit/0AvlCQW5qqkpYKl2WpB3?p=preview
main controller
.controller 'MainCtrl', class MainCtrl
@$inject = [
'$scope'
'$interval'
]
constructor: ($scope, @$interval)->
@viewData = 'Skynet 2.0'
@isLoading = false
callbackExample: ($callbackFunc)->
@loadRqst()
.finally -> $callbackFunc?()
loadRqst: ->
@isLoading = 1
# this returns a promise which gets processed in the example functions
@$interval =>
console.log @isLoading++
, 250, 10
.finally =>
@isLoading = false
implementing ui
<button callback-btn="vc.callbackExample($callbackFunc)">
Callback Example<i> - I'm loading & I'm #1</i>
</button>
<button callback-btn="vc.callbackExample($callbackFunc)">
Callback Example<i> - Look I can load too, I'm #2</i>
</button>
css
[callback-btn] i{
display: none;
}
[callback-btn].loading i{
display: initial;
}
.directive 'callbackBtn', ($parse)->
dir =
restrict: 'A'
scope: { callbackBtn: '&' }
link: ($scope, elem, attrs)->
onCallback = ->
console.log 'on callback'
elem.removeClass 'loading'
elem.on 'click', ->
elem.addClass 'loading'
$scope.$apply ->
$scope.callbackBtn({$callbackFunc: onCallback})
The whole idea of directive is to facilitate separation of concerns.
When you use directive, all it's functionality should be taken care of by the directive itself.
Right now, you have part of the directive's logic in your MainCtrl which should be there ideally in your directive.
Also, in your directive, there is a controller function. Ideally, the controller function should only handle data passing between the views models etc.
For DOM manilulations, something that you are doing, it should be implemented in a link function.
So, the angular way of doing the same thing would be as follows.
Your Html
<body ng-controller="MainCtrl">
<button id="button1" wait-button="foo"><i></i> Foo</button>
<button id="button2" wait-button="bar"><i></i> Bar</button>
<!-- NOTICE: No ng-click handlers here -->
</body>
Your MainCtrl
app.controller('MainCtrl', function($scope) {
// No code required here. This will be handled in Directive's link function.
});
Your waitButton directive.
app.directive('waitButton', function() {
return {
restrict: 'A',
controller: ['$scope', '$element', function($scope, $element) {
//Again, it's not controller's job to handle DOM manipulations. So code required here.
}],
link: function($scope, $element){
var waitButtons = angular.element(document.querySelectorAll('[wait-button]'));// NOTE: I've used querySelectorAll instead of querySelector. This will make this function generic.
waitButtons.on('click', function(){
var $this = angular.element(this);
startSpinner($this);
setTimeout(function(){
stopSpinner($this);
},3000);
});
function startSpinner(el) {
el.children('i').text('searching...');
}
function stopSpinner(el) {
el.children('i').empty();
}
}
};
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With