I've created an Angular service which serves as a simple mechanism to handle success/warning/error/info alerts to the user in a common place throughout my app (code below). These alerts are bound to an Angular-UI alert element, listing all the alerts. My controller handles the plumbing.
So my question is how can I cause every controller in my app to call $alert.clear() upon the controller's destruction? I believe I can do this the hard way by calling something like this from every single controller:
$scope.$on('$destroy', function(){
$alerts.clear();
});
However, I don't really want that boilerplate stuff sprinkled everywhere. I'd like to be able to control that behavior common to ALL controllers in my app once and forget about it.
Thanks in advance for any gentle nudge or violent thwack in the right direction!
HTML snippet
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{alert.msg}}</alert>
service.alert.js
app.factory('$alert', function() {
var alerts = [];
var clearAlerts = function() {
alerts = [];
};
var closeAlert = function(index, clearOthers) {
alerts.splice(index, 1);
};
var createAlert = function(type, message, clearOthers) {
if (clearOthers)
alerts = [];
alerts.push({type: type, msg: message});
};
var alertSuccess = function(message, clearOthers) {
clearOthers = clearOthers || true;
createAlert('success', message, clearOthers);
};
var alertInfo = function(message, clearOthers) {
clearOthers = clearOthers || true;
createAlert('info', message, clearOthers);
};
var alertWarning = function(message,clearOthers) {
clearOthers = clearOthers || true;
createAlert('warning', message, clearOthers);
};
var alertDanger = function(message, clearOthers) {
clearOthers = clearOthers || true;
createAlert('danger', message, clearOthers);
};
return {
$alerts: function() { return alerts; },
$success: function(message, clearOthers) { return alertSuccess(message, clearOthers); },
$info: function(message, clearOthers) { return alertInfo(message, clearOthers); },
$warning: function(message, clearOthers) { return alertWarning(message, clearOthers); },
$danger: function(message, clearOthers) { return alertDanger(message, clearOthers); },
$clear: function() { return clearAlerts(); },
$close: function(index) { return closeAlert(index); }
};
});
You can inherit a controller in all of your controllers.
It still involves you making all your controllers children of a parent controller but other than that it will work flawlessly.
The controller inheritence is done like this:
app.controller('ParentCtrl', function ($scope) {
"use strict";
$scope.$on("$destroy", function (event, val) {
alert("Controller Destoryed");
});
});
app.controller('ChildCtrl', ['$scope', '$controller', function ($scope, $controller) {
"use strict";
$controller('ParentCtrl', {$scope: $scope});
}]);
Here is a plunkr demo (notice that a $destory event is broadcast on the $scope so this example works exactly as if a $destroy event was broadcast)
This is may helps you
clearAlerts: function() {
for(var x in this.alerts) {
delete this.alerts[x];
}
}
Please take a look at this Demo
Related
i can't find a solution to this, basicly everytime i do a login, i want to store the user that i get from the node end point in the service, after that in my main Controller i should get the name of the user, but that never happen, dunno why
here is the code:
app.controller('MainCtrl', function ($scope, $state,$location,$http,user) {
$scope.user = {
nome: user.getProperty()
};
$scope.showRegister = function () {
$state.go('register');
}
$scope.showLogin = function () {
$state.go('login');
}
});
app.controller('loginController', function ($scope, $http, $state,user) {
$scope.login = function () {
var data = {};
data.password = $scope.loja.password;
data.email = $scope.loja.email;
$http.post('http://localhost:8080/login/',data)
.success(function (data) {
console.log(data);
user.setProperty(data.nome);
$state.go('home');
})
.error(function (statusText) {
console.log("failed");
});
}
});
user service
app.service('user', function () {
var property = {};
return {
getProperty: function () {
return property.nome;
},
setProperty: function (value) {
property.nome = value;
}
};
});
You could just watch your service for changes by adding this code to your MainCtrl:
$scope.$watch(function () { return user.getProperty();}, updateProp, true);
function updateProp(newValue, oldValue) {
$scope.user = {
nome: newValue
};
}
updateProp gets executed everytime the value of user.getProperty() changes.
Your main issue is with your MainCtrl . In the initial execution of MainCtrl there is no value set into your service so its get blank. MainCtrl executes before setting the value in the service.
$scope.user = {
nome: user.getProperty()
};
this code should be executed after setting the value in the service but it executes in the initialization of controller.
You can get the reference from the fiddle below.
http://jsfiddle.net/ADukg/9799/
this is my controller:
angular
.module('studentsApp')
.controller('StudentsController', StudentsController);
function StudentsController($scope, StudentsFactory) {
$scope.students = [];
$scope.specificStudent= {};
var getStudents = function() {
StudentsFactory.getStudents().then(function(response) {
if($scope.students.length > 0){
$scope.students = [];
}
$scope.students.push(response.data);
});
};
}
This is my factory:
angular.module('studentsApp')
.factory('StudentsFactory', function($http) {
var base_url = 'http://localhost:3000';
var studentsURI = '/students';
var studentURI = '/student';
var config = {
headers: {
'Content-Type': 'application/json'
}
};
return {
getStudents: function() {
return $http.get(base_url + studentsURI);
}
};
});
And here is how I'm trying to unit test the controller:
describe('Controller: Students', function() {
var StudentsController, scope, StudentsFactory;
beforeEach(function() {
module('studentsApp');
inject(function($rootScope, $controller, $httpBackend, $injector) {
scope = $rootScope.$new();
httpBackend = $injector.get('$httpBackend');
StudentsFactory = $injector.get('StudentsFactory');
StudentsController = $controller('StudentsController', {
$scope : scope,
'StudentsFactory' : StudentsFactory
});
students = [{
name: 'Pedro',
age: 10
}, {
name: 'João',
age: 11
}, {
name: 'Thiago',
age: 9
}];
spyOn(StudentsFactory, 'getStudents').and.returnValue(students);
});
});
it('Should get all students', function() {
scope.students = [];
StudentsController.getStudents();
$scope.$apply();
expect(scope.students.length).toBe(3);
});
});
The problem is when I run the test, the following message is displayed:
undefined is not a constructor (evaluating
'StudentsController.getStudents()')
I looked at the whole internet trying to find a tutorial that can help me on that, but I didn't find anything, could someone help me here?
It's link to the fact that the function getStudent() is private (declared by var). Thus your test can't access it. You have to attach it to the $scope or this to be able to test it.
I generally use this in controller:
var $this = this;
$this.getStudents = function() {
...
};
There's no StudentsController.getStudents method. It should be
this.getStudents = function () { ... };
Mocked StudentsFactory.getStudents returns a plain object, while it is expected to return a promise.
$controller shouldn't be provided with real StudentsFactory service as local dependency (it is already provided with it by default):
var mockedStudentsFactory = {
getStudents: jasmine.createSpy().and.returnValue($q.resolve(students))
};
StudentsController = $controller('StudentsController', {
$scope : scope,
StudentsFactory : mockedStudentsFactory
});
I'm trying to figure out how to mock an angular provider for a unit test. In the following snippet I have a 'translate' provider that is used to determine which language will be displayed in a view by default. I'd like to inject a different version of this provider into my tests to ensure my app displays the right thing based on a provider's settings. What I'm doing right now clearly doesn't seem to be working. Thanks in advance for your help.
By the way, if you're wondering why a provider was used instead of something else like a service or a simple value, this is a contrived example that distills a problem I'm having in a larger application. I need to inject something into an application's config method, which means I need to mock a provider.
var app = angular.module('app', []);
app.config(function($provide) {
$provide.provider('translate', function() {
return {
$get: function() {
return {
language: 'en'
};
}
};
});
});
app.controller('ctl', function($scope, translate) {
if (translate.language === 'en') {
$scope.greeting = "Welcome to the application.";
} else {
$scope.greeting = "Velkommen til appen.";
}
});
// ---SPECS-------------------------
describe('App', function() {
beforeEach(angular.mock.module('app'));
describe('by default', function() {
beforeEach(angular.mock.inject(
function(_$compile_, _$rootScope_) {
const viewHtml = $('#view');
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.isOn = false;
elm = $(viewHtml);
$compile(elm)($rootScope);
$rootScope.$digest();
}));
it('shows English', function() {
expect(elm.text()).toMatch(/Welcome/);
});
});
describe('without English specified', function() {
beforeEach(angular.mock.module('app', function ($provide) {
$provide.provider('translate', function () {
return {
$get: function () {
return { preferredLanguage: 'no' };
}
};
});
}));
beforeEach(angular.mock.inject(
function(_$compile_, _$rootScope_) {
const viewHtml = $('#view');
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.isOn = false;
elm = $(viewHtml);
$compile(elm)($rootScope);
$rootScope.$digest();
}));
it('shows Norwegian', function() {
expect(elm.text()).toMatch(/Velkommen/);
});
});
});
// --- Runner -------------------------
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
<link href="http://jasmine.github.io/1.3/lib/jasmine.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="http://code.angularjs.org/1.4.9/angular.js"></script>
<script src="http://code.angularjs.org/1.4.9/angular-mocks.js"></script>
<script src="http://jasmine.github.io/1.3/lib/jasmine.js"></script>
<script src="http://jasmine.github.io/1.3/lib/jasmine-html.js"></script>
<div ng-app="app">
<div id="view" ng-controller="ctl">{{greeting}}</div>
</div>
you can do it like this: -
beforeEach(module('app', function ($provide) {
$provide.provider('translate', function() {
return {
$get: function() {
return {
language: 'fr'
};
}
};
});
}));
you can also put the above code in a util method, that will take the language code as a parameter, so you don't spread above code everywhere.
I 'm developing a simple ionic app, and part of the app requires you to press two buttons at once. I've built this logic like so:
<!--yT stands for yourThumb, pT stands for partnersThumb -->
<a class="icon ion-qr-scanner lg-txt" on-hold="Global.thumbHoldManager('yT',true)" on-release="Global.thumbHoldManager('yT',false, true)"></a>
<a class="icon ion-qr-scanner lg-txt" on-hold="Global.thumbHoldManager('pT',true)" on-release="Global.thumbHoldManager('pT',false, true)"></a>
I have a method on my controller which handles this event using a service I 've created
var globalCtrl = function (clickHandler, $timeout) {
var self = this;
this.clickHandler = clickHandler;
this.timeout = $timeout;
this.readyState = clickHandler.ready;
this.showInstruction = false;
clickHandler.watchForReady();
};
globalCtrl.prototype.thumbHoldManager = function(which, what, up) {
this.clickHandler.setClickState(which, what);
var self = this;
if (up) {
this.clickHandler.stopWatching();
}
if (!this.readyState) {
this.instruction = "Hold both thumbs in place to scan"
if (!this.showInstruction) {
this.showInstruction = true;
self.timeout(function() {
self.showInstruction = false;
}, 5000)
}
}
};
globalCtrl.$inject = ['clickHandler', '$timeout'];
The service clickHandler exposes an api to a private object whose job it is to track when a button is pressed, and when both buttons are pressed to navigate to a new url.
.factory('clickHandler', [
'$interval',
'$rootScope',
'$location',
function($interval, $rootScope, $location) {
// Service logic
// ...
var clickState = {
yT: false,
pT: false,
ready: false,
watching: false,
watcher: false
};
// Public API here
return {
setClickState: function(which, what) {
clickState[which] = what;
},
getClickState: function(which) {
return clickState[which]
},
getReadyState: function() {
return ((clickState.yT) && (clickState.pT));
},
watchForReady: function() {
var self = this;
clickState.watching = $interval(function() {
clickState.ready = self.getReadyState();
},50);
clickState.watcher = $rootScope.$watch(function() {
return clickState.ready
}, function redirect(newValue) {
if (newValue) {
self.stopWatching();
$location.path('/scan');
}
})
},
stopWatching: function() {
if (clickState.watching) {
$interval.cancel(clickState.watching);
clickState.watcher();
clickState.watching = false;
clickState.watcher = false;
}
}
};
}
])
I don't get any errors with this code, everything works as it should, the watcher gets registered on the hold event and unregistered on the release event. But no matter what I do, I cannot seem to get my phone to detect a press on both buttons. It's always one or the other and I don't know why. I can't test this in the browser or the emulator since multi-touch is not supported and I don't have a multi-touch trackpad if it were.
Here's how I implemented my own directive and service to do this:
.factory('clickHandler', ['$interval', '$rootScope', '$location', '$document', function ($interval, $rootScope, $location, $document) {
// Service logic
// ...
$document = $document[0];
var
touchStart,
touchEnd;
touchStart = ('ontouchstart' in $document.documentElement) ? 'touchstart' : 'mousedown';
touchEnd = ('ontouchend' in $document.documentElement) ? 'touchend' : 'mouseup';
var clickState = {
yT: false,
pT: false,
ready: false,
watching: false,
watcher: false,
startEvent: touchStart,
endEvent: touchEnd
};
// Public API here
return {
setClickState: function (which, what) {
clickState[which] = what;
},
getClickState: function (which) {
return clickState[which]
},
getReadyState: function () {
return ( (clickState.yT) && (clickState.pT) );
},
watchForReady: function () {
var self = this;
//prevent multiple redundant watchers
if (clickState.watching) {
return;
}
clickState.watching = $interval(function () {
clickState.ready = self.getReadyState();
}, 50);
clickState.watcher = $rootScope.$watch(function () {
return clickState.ready
}, function redirect(newValue) {
if (newValue) {
self.stopWatching();
$location.path('/scan');
}
})
},
stopWatching: function () {
if (clickState.watching) {
$interval.cancel(clickState.watching);
clickState.watcher();
clickState.watching = false;
clickState.watcher = false;
}
},
getTouchEvents: function () {
return {
start: clickState.startEvent,
end: clickState.endEvent
}
}
};
}])
.directive('simultaneousTouch', ['clickHandler', '$document', function (clickHandler) {
return {
restrict: 'A',
link: function (scope, elem, attr) {
var touchEvents = clickHandler.getTouchEvents();
elem.on(touchEvents.start, function () {
clickHandler.watchForReady();
clickHandler.setClickState(attr.simultaneousTouch, true);
});
elem.on(touchEvents.end, function () {
clickHandler.stopWatching();
clickHandler.setClickState(attr.simultaneousTouch, false);
})
}
}
}]);
Crossposting stankugo's answer from the ionic forums for the sake of reference. The simple solution below is entirely his idea, I've just done a little cleanup.
angular.module('xxxx').directive('multitouch', function () {
return function(scope, element, attr) {
element.on('touchstart', function() {
scope.$apply(function() {
scope.$eval(attr.multitouch);
});
});
};
});
Use like:
<div multitouch="handler()"></div>
I'm having problems creating a variable and using it in a MEAN package. I'm basing it off of the "articles" package that comes as an example. Everything I see is the same in the client-side controller, but I'm not sure why I'm catching the error when I try to start my app (with grunt) on the "books" package but not the "articles" package.
I have not implemented all the controllers that articles has yet, that may be an issue?
When I start the app with grunt, I get this error on : 'book' is defined but never used MEAN stack controller
I believe the error is in the controller, but if you need to see other files please let me know.
books.js
//client-side controller
'use strict';
angular.module('mean.books').controller('BooksController', ['$scope', 'Global', 'Books',
function($scope, Global, Books) {
$scope.global = Global;
$scope.package = {
name: 'books'
};
$scope.hasAuthorization = function(book) {
if (!book || !book.user) return false;
return $scope.global.isAdmin || book.user._id === $scope.global.user._id;
};
$scope.create = function(isValid) {
if (isValid) {
var book = new Books({
title: this.title,
author: this.author,
description: this.description,
seller: this.seller
});
/* Not sure if we need this location thing
book.$save(function(response) {
$location.path('books/' + response._id);
});
*/
this.title = '';
this.content = '';
this.description = '';
this.seller = ''; // or this.user implement
} else {
$scope.submitted = true;
}
};
}
]);
articles.js //this is the example that I'm basing it from
'use strict';
angular.module('mean.articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Global', 'Articles',
function($scope, $stateParams, $location, Global, Articles) {
$scope.global = Global;
$scope.hasAuthorization = function(article) {
if (!article || !article.user) return false;
return $scope.global.isAdmin || article.user._id === $scope.global.user._id;
};
$scope.create = function(isValid) {
if (isValid) {
var article = new Articles({
title: this.title,
content: this.content
});
article.$save(function(response) {
$location.path('articles/' + response._id);
});
this.title = '';
this.content = '';
} else {
$scope.submitted = true;
}
};
$scope.remove = function(article) {
if (article) {
article.$remove(function(response) {
for (var i in $scope.articles) {
if ($scope.articles[i] === article) {
$scope.articles.splice(i, 1);
}
}
$location.path('articles');
});
} else {
$scope.article.$remove(function(response) {
$location.path('articles');
});
}
};
$scope.update = function(isValid) {
if (isValid) {
var article = $scope.article;
if (!article.updated) {
article.updated = [];
}
article.updated.push(new Date().getTime());
article.$update(function() {
$location.path('articles/' + article._id);
});
} else {
$scope.submitted = true;
}
};
$scope.find = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
$scope.findOne = function() {
Articles.get({
articleId: $stateParams.articleId
}, function(article) {
$scope.article = article;
});
};
}
]);
In $scope.create function you defined book
var book = new Books({
and never use it. That's reason you get warning. If you want to skip jshint warnings in development use grunt -f or allow unused variables in your grunt configuration (or .jshintrc if you use it)