I'm using Twitter Bootstrap for UI in my webapp. Particulary its Alert component. I want to write a simple angular service to wrap Bootstrap's Alert to have a possibility of informing users from any peace of angular code. Like this:
Informer.inform("message", "ERROR"); // will result in alerting with `alert-error` class
Informer.inform("message", "INFO"); // will result in alerting with `alert-info` class
My idea is to to append the template to the end of the <body>
:
<div class="alert {{alertClass}} fade in informer" id="informer">
<button type="button" class="close" data-dismiss="alert">×</button>
<div class="valignCenterWrapper">
<div class="valignCenter" id="informerMessage">
{{message}}
</div>
</div>
</div>
Something like this:
grfx.factory("Informer", function() {
return {
inform : function(message, type) {
// Here be dragons. How can I compile/append the template.
$("#inform").alert();
}
};
});
The only thing I want to know: how do I write this with angular, not with jQuery? Is the code above good for start? Folks in the internets say that I should only use directives for DOM manipulation. But I do not understand it: I do not have any existing markup to apply directive on it. Alerts will be appended to the page as a result of some compupations/user interactions. Which services ($compile
, $parse
, $document
) should I use to compile temlate and append it somewhere to the body?
EDIT: Is it also possible to get angularjs service outside of controller. Just in regular JS code so I can write getServiece("Informer").inform("", "")
?
EDIT 2: Ok, what I have now:
grfx.factory("Informer", function($compile, $rootScope) {
return {
inform : function(message, type) {
var scope = $rootScope.$new();
scope.message = message;
scope.type = type;
$(document.body).append($compile("<div class='alert {{type}} fade in informer' id='informer'><button type='button' class='close' data-dismiss='alert'>×</button><div class='valignCenterWrapper'><div class='valignCenter' id='informerMessage'>{{message}}</div></div></div>")(scope));
}
};
});
With this code I am able to use injected service from controllers. But there is an issue when I try to call service outside angular code:
angular.element(document).injector().get("Informer").inform("Message", "alert-error");
This shows popup with {{message}}
e.g. it does not compile template correctly.
In AngularJS we should be focusing on model manipulation and your Informer
service is no exception - it should only hold model and shouldn't be concerned with DOM manipulation. The rule of thumb where DOM manipulation = directive is a very good one and if you follow it it will save you a lot of headaches.
Back to your problem at hand, the solution is to have a service focused on model manipulation and a directive to display this model. Let's start with the service:
app.factory('Informer', function(){
var messages = [];
var Informer = {};
Informer.inform = function(msg, type) {
messages.push({
msg: msg,
type: type
});
};
Informer.allInfos = function() {
return messages;
};
Informer.remove = function(info) {
messages.splice(messages.indexOf(info), 1);
};
return Informer;
});
When this service is ready you can easily use it in a controller (or even inside other services!):
app.controller('MainCtrl', function($scope, Informer) {
Informer.inform("error message", "error");
Informer.inform("info message", "info");
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
});
And finally, to render alerts you can use bootstrap's markup directly, or write a very simple directive that encapsulates it. Here I', using the alert directive from http://angular-ui.github.com/bootstrap/
<body ng-controller="MainCtrl">
<alert ng-repeat="alert in allInfos()" type="alert.type" close="remove(alert)">{{alert.msg}}</alert>
</body>
Of course you don't need to use directives from this repo, you can create your own or use raw markup if needed.
Here is a plunker demonstrating a working example: http://plnkr.co/edit/VxAcjHFhxXODFB5iAfyX?p=preview
To sum up:
I would also advice removing jQuery from a project while learning AngularJS. This way you will quicker get into AngularJS-zen state!
Angular way, I believe, is to manipulate dom by hand as few as possible. And even if you have to manipulate it - do it only within a directive.
So, the way to go, unlike jQuery, is to have a model and use bindings to change your dom.
For that reason, the way I would choose - it to have some InformerController which is binded somewhere in your layout template, and iterate over an array of current informs, and draw them directly.
app.controller('InformersController', function($scope, InformerService) {
$scope.informs = InformerService.get();
$scope.close = function (index) {
InformerService.close(index)
}
});
And in your template:
<div ng-controller="InformersController">
<div ng-repeat="inform in informs">
<div class="alert {{inform.alertClass}} fade in informer">
<button type="button" ng-click="close($index)" class="close">×</button>
<div class="valignCenterWrapper">
<div class="valignCenter">
{{inform.message}}
</div>
</div>
</div>
</div>
</div>
If you need to show some alert from any place, use it injecting InformerService
to your controller, and adding data using it.
app.service('InformerService', function () {
var informs = [];
this.get = function () {
return informs;
};
this.inform = function (message, type) {
informs.push({
alertClass: 'alert-' + type,
message: message
});
}
this.close = function (index) {
informs.splice(index, 1);
}
});
For example:
app.controller('SomeController', function($scope, InformerService) {
$scope.doError = function (msg, type) {
InformerService.inform(msg, type);
};
});
And in your template:
<div class="well" ng-controller="SomeController">
<button class="btn btn-danger" ng-click="doError('Hello', 'error')">Error</button>
<button class="btn btn-info" ng-click="doError('Hello', 'info')">Info</button>
</div>
You can see it all together at this fiddle: http://jsfiddle.net/zc3YH/6/
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