UI Notifications with angular js - angularjs

I have to implement some standard notification UI with angular js. My approach is the following (simplified):
<div ng-controller="MainCtrl">
<div>{{message}}</div>
<div ng-controller="PageCtrl">
<div ng-click="showMessage()"></div>
</div>
</div>
And with the page controller being:
module.controller("PageCtrl", function($scope){
counter = 1
$scope.showMessage = function(){
$scope.$parent.message = "new message #" + counter++;
};
});
This works fine. But I really don't like the fact that I need to call $scope.$parent.
Because if I am in another nested controller, I will have $scope.$parent.$parent, and this becomes quickly a nightmare to understand.
Is there another way to create this kind of global UI notification with angular?

Use events to send messages from one component to another. That way the components don't need to be related at all.
Send an event from one component:
app.controller('DivCtrl', function($scope, $rootScope) {
$scope.doSend = function(){
$rootScope.$broadcast('divButton:clicked', 'hello world via event');
}
});
and create a listener anywhere you like, e.g. in another component:
app.controller('MainCtrl', function($scope, $rootScope) {
$scope.$on('divButton:clicked', function(event, message){
alert(message);
})
});
I've created a working example for you at http://plnkr.co/edit/ywnwWXQtkKOCYNeMf0FJ?p=preview
You can also check the AngularJS docs about scopes to read more about the actual syntax.
This provides you with a clean and fast solution in just a few lines of code.
Regards,
Jurgen

You should check this:
An AngularJS component for easily creating notifications. Can also use HTML5 notifications.
https://github.com/phxdatasec/angular-notifications

After looking at this: What's the correct way to communicate between controllers in AngularJS? and then that: https://gist.github.com/floatingmonkey/3384419
I decided to use pubsub, here is my implementation:
Coffeescript:
module.factory "PubSub", ->
cache = {}
subscribe = (topic, callback) ->
cache[topic] = [] unless cache[topic]
cache[topic].push callback
[ topic, callback ]
unsubscribe = (topic, callback) ->
if cache[topic]
callbackCount = cache[topic].length
while callbackCount--
if cache[topic][callbackCount] is callback
cache[topic].splice callbackCount, 1
null
publish = (topic) ->
event = cache[topic]
if event and event.length>0
callbackCount = event.length
while callbackCount--
if event[callbackCount]
res = event[callbackCount].apply {}, Array.prototype.slice.call(arguments, 1)
# some pubsub enhancement: we can get notified when everything
# has been published by registering to topic+"_done"
publish topic+"_done"
res
subscribe: subscribe
unsubscribe: unsubscribe
publish: publish
Javascript:
module.factory("PubSub", function() {
var cache, publish, subscribe, unsubscribe;
cache = {};
subscribe = function(topic, callback) {
if (!cache[topic]) {
cache[topic] = [];
}
cache[topic].push(callback);
return [topic, callback];
};
unsubscribe = function(topic, callback) {
var callbackCount;
if (cache[topic]) {
callbackCount = cache[topic].length;
while (callbackCount--) {
if (cache[topic][callbackCount] === callback) {
cache[topic].splice(callbackCount, 1);
}
}
}
return null;
};
publish = function(topic) {
var callbackCount, event, res;
event = cache[topic];
if (event && event.length > 0) {
callbackCount = event.length;
while (callbackCount--) {
if (event[callbackCount]) {
res = event[callbackCount].apply({}, Array.prototype.slice.call(arguments, 1));
}
}
publish(topic + "_done");
return res;
}
};
return {
subscribe: subscribe,
unsubscribe: unsubscribe,
publish: publish
};
});

My suggestion is don't create a one on your own. Use existing models like toastr or something like below.
http://beletsky.net/ng-notifications-bar/

As suggested above, try to use external notifications library. There're a big variety of them:
http://alertifyjs.com/
https://notifyjs.com/
https://www.npmjs.com/package/awesome-notifications
http://codeseven.github.io/toastr/demo.html

Related

Implementing notification alerts in angularjs

I was wondering how an error alert would be implemented using angularjs.
Required functionality:
An alertQueue consists of all the alerts to be displayed to the user. These alerts are deleted from the queue after a span of 3 seconds. The user himself can close the alert by clicking the close button.
This AlertService must be the core service. Alerts are rendered in the view as <alert-list></alert-list>i.e using a component alertList.
Should be able to update alerts from other controllers like: AlertService.alert("my alert").
so far what I have done?
angular.
module('core').
factory('AlertService', [function() {
var alertQueue = [];
var addAlert = function(message, type){
message = {message: message, type: type};
alertQueue.push(message)
};
var deleteAlert = function(alert){
alertQueue.splice(alertQueue.indexOf(alert), 1);
};
return{
warning: function(msg){
addAlert(msg, "warning");
},
success: function(msg){
addAlert(msg, "success");
},
removeAlert: function(alert){
deleteAlert(alert);
},
getAlerts: function(){
return alertQueue;
}
}
}]);
angular.
module('alertApp').
component('alertList', {
templateUrl: '/static/js/app/aurora-alert/aurora-alert.template.html',
controller: ['$routeParams','$scope', 'Aurora',
function AlertController($routeParams, $scope, AlertService) {
var self = this;
self.alertQueue = AlertService.alertQueue;
self.alert = function(){
var message = arguments[0];
AlertService.warning(message);
};
self.removeAlert = function(alert) {
AlertService.removeAlert(alert);
};
}
]
});
I know that I'm doing something wrong in the above code and in its logic. I said above that I require the <alert-list></alert-list> component. So the alertService is injected as a dependency into alertController. But how am I going to raise the alert from other controllers? I know we can use $scope.$broadcast but that doesn't feel right.
Please explain how to achieve this? No third party libraries are to be used.
I think you are going about it only slightly incorrectly. Your alert-list should be responsible only for displaying and removing alerts, not for creating them. Leave the creation of alerts to your controllers
So for example, if you run into an error with an ApiSerivce:
DemoCtrl(AlertService, ApiService) {
ApiService.submitForm({some:data}).then(function() {
//something successfull happened
}).catch(function(error) {
AlertService.warning("Something bad happened calling the API serivce");
});
}
Then you can change your AlertService to broadcast an event when a new alert is created that the alert-list can listen to:
factory('AlertService', ["$rootScope", function($rootScope) {
var alertQueue = [];
var addAlert = function(message, type){
message = {message: message, type: type};
alertQueue.push(message)
$rootScope.$broadcast("new-alert"); //notify the list that there are new alerts
};
This is how you would listen to it in your alert-list:
$scope.$on("new-alert", function() {
self.alertQueue = AlertService.alertQueue;
});
This way, as soon as an alert is created, the alert-list is instantly updated with the latest queue of alerts.
You would probably want to do the same thing for alert deletion.

How to refresh the view when angular $http promise call wrapped in rxjs Observable

I have a project that uses angular's $http service to load data from a remote location. I want to use rxjs Observables so the call in my service looks like this:
userInfo() : Rx.Observable<IUserInfo> {
var url : string = someUrl + this._accessToken;
return Rx.Observable.fromPromise<IUserInfo>( this.$http.get<IUserInfo>( url ) );
}
and this is subscribed to by my controller like this:
getUserInfo() : void {
this._googleService.userInfo().subscribe(
( result ) => { this.handleUserInfo( result ) },
( fault : string ) => this.handleError( fault )
)
}
private handleUserInfo( result : IHttpPromiseCallbackArg<IUserInfo> ) : void {
console.log( "User info received at " + new Date() );
this._name = result.data.given_name + " " + result.data.family_name;
this._email = result.data.email;
this._profilePicUrl = result.data.picture;
}
the problem is that despite the name, email and profile pic being updated these changes are not visible. As soon as anything else triggers an angular $apply the changes appear but because of the Observable these changes in the controller happen after the angular digest loop that is triggered by the $http call.
This does work correctly if my service just returns a promise to the controller.
How do I update my view in this case? I do not want to manually have to wire up each observable to trigger a digest cycle. I want all Observables to trigger a digest cycle when they receive a new value or error.
We can use the ScopeScheduler from rx.angular.js for this. We only have to create a new one where we create our angular module and pass the $rootScope to it:
const module : ng.IModule = angular.module( 'moduleName', [] );
module.run( ["$rootScope", ( $rootScope ) => {
new Rx.ScopeScheduler( $rootScope );
}]);
That's all you have to do. Now all Rx.Observables trigger an $apply when they get a new value.
For some reason the ScopeScheduler was deleted when the rx.angular.js library was upgraded to rxjs version 4. We have to use rx.angular.js version 0.0.14 to use the ScopeScheduler.
I do not know what the suggested solution to this is in version 4.
A project using this fix can be viewed here:
https://github.com/Roaders/Typescript-OAuth-SPA/tree/observable_apply_issues
I couldn't get the Rx.ScopeScheduler method to work, so I just overwrote the rx observable subscribe method itself instead, and wrapped the callbacks in $rootScope.$apply :)
module.run(['$rootScope', 'rx', function ($rootScope, rx) {
rx.Observable.prototype.subscribe = function (n, e, c) {
if(typeof n === 'object') {
return this._subscribe(n);
}
var onNext = function(){};
if(n) {
onNext = function(value) {
if($rootScope.$$phase) {
n(value);
}
else {
$rootScope.$apply(function(){ n(value); });
}
};
}
var onError = function(err) { throw err; };
if(e) {
onError = function(error) {
if($rootScope.$$phase) {
e(error);
}
else {
$rootScope.$apply(function(){ e(error); });
}
};
}
var onCompleted = function(){};
if(c) {
onCompleted = function() {
if($rootScope.$$phase) {
c();
}
else {
$rootScope.$apply(function(){ c(); });
}
};
}
return this._subscribe(
new rx.AnonymousObserver(onNext, onError, onCompleted)
);
};
}]);

Atmosphere and Angular JS how to

I'm an atmosphere & Angular newbie and I'm really struggling to find an answer to this! Maybe I'm asking the wrong question.
I am setting up notifications using Atmosphere. I can open the websocket and watch the updates happen if I post the API URL directly into my browser.
In Angular I have an ng-repeat loop, which I would like to run as each new update adds a new object to the websocket.
<li ng-repeat="notification in notifications track by $index">
I am using angular watch to check for updates, but it doesn't pick up the new objects being added to the array. Here is my code:
// notification alerts
$scope.notifications = [];
notificationsService.notificationAlerts().then(function success(response) {
var jsonStringArray = response.data.split('|');
$scope.notifications = $.map(jsonStringArray, function(n, i){
if (n !== ""){
return JSON.parse(n);
}
});
console.log('Connect', response);
});
$scope.$watch('notifications', function(newVal, oldVal){
console.log('Watch', $scope.notifications);
}, true);
Hopefully I've made myself clear, let me know if I need to elaborate, or if I'm asking the wrong question. Thanks!
OK, I managed to solve this, for anyone stumbling across it later. Here is the final JS:
// add number of notifications to ".notifications-number"
function updateNumberOfNotifications(){
var numberOfNotifications = $("ul.notifications-list li").not(".nocount").length;
if (numberOfNotifications < 1) {
$(".notifications-number, .notifications-list").addClass("hidden");
} else {
$(".notifications-number").html(numberOfNotifications);
$(".notifications-number, .notifications-list").removeClass("hidden");
}
}
// notification alert variables
$scope.notifications = [];
var socket = atmosphere;
var subSocket;
// subscribe
function subscribe() {
var request = {
url : "/service/notifier",
transport: 'long-polling'
};
request.onMessage = function (response) {
//console.log('response', response);
var jsonStringArray = response.responseBody.split('|');
// console.log('json string array', jsonStringArray);
$.each(jsonStringArray, function(index, elem){
if (elem != ""){
$scope.notifications.push(JSON.parse(elem));
console.log("object", JSON.parse(elem));
}
});
//$scope.notifications.push($scope.newNotification);
$scope.$apply();
updateNumberOfNotifications();
// console.log('$scope.notifications', $scope.notifications);
};
subSocket = socket.subscribe(request);
}
function unsubscribe(){
socket.unsubscribe();
}
// subscribe on load and update notifications
updateNumberOfNotifications();
subscribe();

angularjs binding/scope issue for select list?

OK switching my code to angularjs and the angular 'way', not sure what I am doing wrong.
A select list is not getting updated when the model changes unless I call $apply, and I find myself calling apply a lot.
index.html has this:
<div id='rightcol' data-ng-include="'partials/rightSidebar.html'"
data-ng-controller="rightSidebarController">
</div>
and rightSidebar.html has this:
<select id='srcList' size='10'
data-ng-model="data.source"
data-ng-click='srcOnclick()'
data-ng-options="s.title for s in data.srcList | filter:{title:data.srcFilter} | orderBy:'title'"></select>
rightSidebarController.js has this:
$scope.data = {};
$scope.data.srcList = dataProvider.getSourceList();
$scope.data.source = dataProvider.getSource();
dataProvider is a service that makes an asynchronous database call (IndexedDB) to populate srcList, which is what gets returned in dataProvider.getSource().
Is it the asynchronous database call that forces me to call $apply, or should the controller be ignorant of that?
Is there a 'better' way to do this?
Edited to add service code.
Another controller calls dataProvider.refreshSourceList:
myDB.refreshSourceList = function() {
myDB.getRecords("source", function(recs) {
myDB.srcList = recs;
$rootScope.$broadcast('SrcListRefresh');
});
};
myDB.srcList is the field being bound by $scope.data.srcList = dataProvider.getSourceList();
myDB.getRecords:
myDB.getRecords = function(storeName, callback) {
var db = myDB.db;
var recList = [];
var trans = db.transaction([storeName], 'readonly');
var store = trans.objectStore(storeName);
var cursorRequest = store.openCursor();
cursorRequest.onerror = myDB.onerror;
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
if (cursor === false || cursor === undefined) {
if (callback !== undefined) {
$rootScope.$apply(function() {
callback(recList);
});
}
} else if (cursor.value !== null) {
recList.push(cursor.value);
cursor.continue();
}
};
cursorRequest.onerror = myDB.onerror;
};
Anything you do async needs to be wrapped in $scope.$apply(). This is because angular works in a similar fashion to a game loop, however instead of constantly running, it knows to end the loop when an action is taken, and $scope.$digest() is called.
If you are using IndexedDB, I would recommend creating an angular wrapper for it, like so:
(forgive my IndexedDB code, I'm not experience with it)
angular.module('app',[])
.factory('appdb', function($rootScope){
var db = indexedDB.open('appdb', 3);
return {
get : function(table, query, callback) {
var req = db.transaction([table])
.objectStore(table)
.get(query);
req.onsuccess(function(){
$rootScope.$apply(function(){
callback(req.result);
});
});
}
};
});
This way you can be sure that any data retrieve and set on a controller scope inside of callback will have $scope.$digest() called afterward.

How to check internet connection in AngularJs

This is how I would check internet connection in vanilla javascript:
setInterval(function(){
if(navigator.onLine){
$("body").html("Connected.");
}else{
$("body").html("Not connected.");
}
},1000);
I have angular controllers and modules in my project. Where should I put the code above? It should be executed in global context and not be assigned to a certain controller. Are there some kind of global controllers maybe?
First of all, I advise you to listen to online/offline events.
You can do it this way in AnguarJS:
var app = module('yourApp', []);
app.run(function($window, $rootScope) {
$rootScope.online = navigator.onLine;
$window.addEventListener("offline", function() {
$rootScope.$apply(function() {
$rootScope.online = false;
});
}, false);
$window.addEventListener("online", function() {
$rootScope.$apply(function() {
$rootScope.online = true;
});
}, false);
});
NOTE: I am wrapping changing of root scope's variable in $apply method to notify Angular that something was changed.
After that you can:
In controlller:
$scope.$watch('online', function(newStatus) { ... });
In HTML markup:
<div ng-show="online">You're online</div>
<div ng-hide="online">You're offline</div>
Here is a working Plunker: http://plnkr.co/edit/Q3LkiI7Cj4RWBNRLEJUA?p=preview
Other solution could be to broadcast online/offline event. But in this case you need to initialize current status upon loading and then subscribe to event.
It's definitely not as nice, but you could just try an AJAX request to your web server; it'll either succeed or time out.
Also, the HubSpot/offline project looks really good.
Your options:
addEventListener on the window, document, or document.body.
setting the .ononline or .onoffline properties on document or
document.body to a JavaScript Function object.
specifying ononline="..." or onoffline="..." attributes on the tag in
the HTML markup
I will demonstrate the easiest.
In you controller
document.body.onoffline = function() {
alert('You are offline now');
$scope.connection = 'offline'
}
document.body.ononline = function() {
alert('You are online again');
$scope.connection = 'online'
}
Check $scope.connection variable before you try to send requests around.
For Angular 2+ you can use ng-speed-test:
Just install:
npm install ng-speed-test --save
Inject into your module:
import { SpeedTestModule } from 'ng-speed-test';
#NgModule({
...
imports: [
SpeedTestModule,
...
],
...
})
export class AppModule {}
Use service to get speed:
import {SpeedTestService} from 'ng-speed-test';
#Injectable()
export class TechCheckService {
constructor(
private speedTestService:SpeedTestService
) {
this.speedTestService.getMbps().subscribe(
(speed) => {
console.log('Your speed is ' + speed);
}
);
}
}

Resources