I'm trying to create resusable alert service, which I would call anywhere in my application with just:
alertService.showAlert('warning', 'something went wrong!');
For example after ajax call to backend api.
Right now I'm using a factory and a directive, but It seems I'm doing something wrong, because the directive does not update after a call to showAlert method. Right now I have something like this:
var srv = angular.module('srv', []);
srv. factory('alertService', ['$timeout', function($timeout){
var alertService = this;
alertService.alertNeeded = false;
alertService.alertClass = '';
alertService.alertMessage = '';
alertService.setAlertNeeded = function(){
alertService.alertNeeded = true
};
alertService.setAlertClass = function(type){
if(type === 'warning')
alertService.alertClass = 'alert-warning';
if(type === 'success')
alertService.alertClass = 'alert-success';
if(type === 'info')
alertService.alertClass = 'alert-info';
if(type === 'danger')
alertService.alertClass = 'alert-danger';
};
alertService.setAlertMessage = function(message){
alertService.alertMessage = message;
};
return {
showAlert: function(class, msg){
alertService.setAlertNeeded();
alertService.setAlertClass(class);
alertService.setAlertMessage(msg);
}
};
}]).
directive('myAlerts', ['alertService', function(alertService){
return {
restrict: 'A',
template: '<div ng-class="alertClass" ng-show="alertNeeded">{{alertMessage}}</div>',
link: function(scope){
scope.alertNeeded = alertService.alertNeeded;
scope.alertMessage = alertService.alertMessage;
scope.alertClass = alertService.alertClass;
}
}
}]).
controller('alertShowingController', ['$scope', 'alertService', function($scope, alertService){
alertService.showAlert('warning', 'Warning alert!!!')
}]);
My code doesn't look exactly the same, but I just wanted to show what I'm trying to do: I want to call alertService.showAlert(...) from another controller in another module (which depends on srv module) and this way update the variables in myAlerts directive to show the proper alert.
The thing is after call to showAlert method The values are set, but within the directive code I'm getting alertService.alertNeeded as undefined.
I'm completely new to AngularJs, so maybe I'm getting something wrong, but I spent whole evening to make it work and I still have no idea what is the proper solution for this.
Please help!
Here is a pattern that I used once before
var srv = angular.module('srv', []);
srv.factory('alertService', ['$timeout', function($timeout){
var alertListeners = [];
this.register = function (listener) {
alertListeners.push(listener);
};
this.notifyAll = function (data) {
for (// each listener in array) {
var listenerObject = alertListeners[i];
try { // do not allow exceptions in individual listeners to corrupt other listener processing
listenerObject.notify(data);
} catch(e) {
console.log(e);
}
}
};
}]).
directive('myAlerts', ['alertService', function(alertService){
var alertDirectiveObserver = function($scope, alertService) {
this.notify = function(data) {
/*
* TO DO - use data to show alert
*/
};
alertService.register(this);
};
return {
restrict: 'A',
template: '<div ng-class="alertClass" ng-show="alertNeeded">{{alertMessage}}</div>',
controller: ['$scope', 'alertService', alertDirectiveObserver],
link: function(scope){
}
}
}]).
controller('alertShowingController', ['$scope', 'alertService', function($scope, alertService){
alertService.notifyAll({'warning', 'Warning alert!!!'})
]);
Of course you should also cleanup by registering a function to delete objects on scope destroy.
eg
element.on('$destroy', function() {
alertService.unregister(// some listener id);
});
Your code has two different meanings for alertService. Inside the factory definition, it refers to the factory itself. Everywhere else, it refers to the object returned by the factory. The easiest way to move forward would be to add a few missing methods to the object returned by the factory:
return {
showAlert: function(cssClass, msg){
alertService.setAlertNeeded();
alertService.setAlertClass(cssClass);
alertService.setAlertMessage(msg);
},
alertClass: function() { return alertService.alertClass; },
alertMessage: function() { return alertService.alertMessage; },
alertNeeded: function() { return alertService.alertNeeded; }
};
Then, change your directive's template so that it calls these functions on each digest cycle:
directive('myAlerts', ['alertService', function(alertService){
return {
restrict: 'A',
template: '<div ng-class="alertClass()"' +
' ng-show="alertNeeded()">' +
' {{alertMessage()}}' +
'</div>',
link: function(scope){
scope.alertNeeded = alertService.alertNeeded;
scope.alertMessage = alertService.alertMessage;
scope.alertClass = alertService.alertClass;
}
}
}])
Then you should see your warning message. Try it in a fiddle.
Related
I have a really serious problem, I'm updating, editing, deleting data, and the two-way data binding is not working.
This is one of my controllers:
'use strict';
var EventController = function($timeout, $scope, $state, EventModel) {
this.$timeout = $timeout;
this.$scope = $scope;
this.$state = $state;
this.EventModel = EventModel;
/**
* When the page is requested, retrieve all the data.
*
*/
this.retrieve();
};
EventController.prototype = {
create: function(event) {
var that = this;
this.EventModel.Model.insert(event)
.then(function() {
that.refresh();
});
},
retrieve: function() {
var that = this;
this.EventModel.Model.find()
.then(function(result) {
that.$scope.events = result;
});
},
one: function(id) {
var that = this;
this.EventModel.Model.one(id)
.then(function(result) {
that.$scope.event = result;
});
},
update: function(id, event, state) {
if (state !== undefined) {
event.is_active = state;
}
var that = this;
this.EventModel.Model.update(id, event)
.then(function() {
that.refresh();
});
},
delete: function(id) {
var check = $('[data-controller-input]:checked');
var that = this;
$.each(check, function() {
var target = $(this);
var id = target.prop('id');
that.EventModel.Model.remove(id)
.then(function() {
that.refresh();
});
});
},
clear: function() {
this.$scope.event = angular.copy(this.$scope.initial);
},
refresh: function() {
this.$state.go(this.$state.current, {}, {reload: true});
}
};
angular
.module('adminApp')
.controller('EventController',
[
'$timeout',
'$scope',
'$state',
'EventModel',
EventController
]
);
In the create, update and delete methods I need to update the HTML without refreshing the page, I already tried using, $scope.apply, $scope.digest, $timeout after the result came, but not happens in the HTML.
If I try $scope.apply and $scope.digest the error will be:
Prevent error $digest already in progress when calling $scope.$apply()
So I was trying to wrap the $scope.$apply or $digest with the $timeout, same result, nothing happens.
Thanks.
First of all, your refresh method will never update your controller.it will simply fail just because this.$state.current won't be able to resolve any url ,template or controller.
And this is the main reason you are not able to see updated data ,just check your console you might be getting Error: Cannot transition to abstract state '[object Object]' error.
Update : I have create a plnkr.as i don't have access to event model code i simply removed it and try to create the same scenario.
http://plnkr.co/edit/RsI3TgKwcjGEXcTMKoQR?p=preview
see if this can help you
I am not sure, but try using the following function which checks the current phase before executing your function. It may solve the issue.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Usage:
$scope.safeApply(function() {
//Your lines
});
Error: ngRepeat:dupes Duplicate Key in Repeater
http://plnkr.co/edit/hZtIXkPM7dhpf4P7rd6W?p=preview
I have an array which ng-repeats a list of tags on the page. Next I have an ng-click which sends the tag data into the scope of another controller whois job it is to display those selected tags in another list.
It's easier to see the code in action in the plnkr above, but the basics are:
the first tags Array is in the cnt controller
when you click on a tag, it gets stored in the TagDetailsFactory service
I then broadcast an event to the view controller to then call the getTagDetails function in TagDetailsFactory to retrieve the saved tags and store them into the viewTags array in the view controller.
This is where I'm getting the ngDupes error :(
However, the array in cnt is named $scope.tags = [];
and the array in view is $scope.viewTags = [];
// Code goes here
angular.module('app', [])
.directive('tagDetails', function() {
return {
restrict: "E",
link: function($scope, el, attrs) {
// console.debug($scope, attrs);
},
scope:{
tag:'=ngModel'
},
template: '<div ng-show="tag.showDetails">{{tag.details}}</div>'
};
})
.controller('cnt', ['$scope',
'$rootScope',
'TagDetailsFactory',
function($scope,
$rootScope,
TagDetailsFactory) {
$scope.tags = [];
for(var i = 0; i < 100; i++) {
$scope.tags.push(
{ name: 'Foo Bar ' + i, details: 'Details' + i }
);
}
$scope.showTagDetails = function(t) {
t.showDetails = true;
}
$scope.leaveTag = function(t) {
t.showDetails = false;
}
$scope.sendTag = function(t) {
TagDetailsFactory.saveTagDetails(t);
$rootScope.$broadcast('updateView');
}
}])
.factory('TagDetailsFactory', function() {
var savedTags = [];
var saveTagDetails = function(tag) {
savedTags.push(tag);
}
var getTagDetails = function() {
return savedTags;
}
return {
saveTagDetails : saveTagDetails,
getTagDetails : getTagDetails
};
})
.controller('view', ['$scope',
'$rootScope',
'TagDetailsFactory',
function($scope,
$rootScope,
TagDetailsFactory) {
$scope.viewTags = [];
$scope.$on('updateView', function() {
console.log('updateView');
var tags = TagDetailsFactory.getTagDetails();
console.log(tags);
$scope.viewTags.push(tags);
});
// $scope.showTagDetails = function(t) {
// t.showDetails = true;
// }
// $scope.leaveTag = function(t) {
// t.showDetails = false;
// }
}]);
ng-repeat does not allow duplicate items (otherwise how can it keep track of them, if you wanted to updated something, for example?).
"
In order to deal with this problem, you can add track by $index to your ng-repeat value:
ng-repeat="data in dataset track by $index"
You can have it track by other values in your data also, but it needs to be unique.
Your Plunker
In AngularJS I know I can attach a listener to a variable like this:
$scope.$watch("variableName", listenerFunc);
I'm wondering whether it's possible to query a variable to find out which functions are already listening to it.
Specifically, I'd like to do something like the following
if( listenerIsNotAlreadyAssigned("variableName",listenerFunc) ){
$scope.$watch("variableName", listenerFunc); // assign it
}
Is there a way to implement the code above in Angular? If so, how?
Here's one method that might be considered hacky.. Demo here (click).
app.controller('myCtrl', function($scope) {
function checkWatchString(prop) {
var found = false;
angular.forEach($scope.$$watchers, function(item, i) {
if (item.exp === prop) { found = true; }
});
return found;
}
$scope.$watch('foo', function() {
});
console.log(checkWatchString('bar')); //false
console.log(checkWatchString('foo')); //true;
});
If you store a reference to the creation of a watch, you get a function reference that cancels the watch. You could take the obvious approach and track this manually and also be keeping the cancel function for each watch available.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.foo = '123';
var reg = {};
if (!reg.foo) {
reg.foo = $scope.$watch('foo', function() {
console.log('foo changed');
});
}
});
I made an demo using a service for this: http://jsbin.com/AbAwObE/4/edit
Watch service
app.factory('watchService', function() {
var watchService = function(prop, unset) {
if (!watchService[prop]) {
return false;
}
if (unset) {
watchService[prop]();
delete watchService[prop];
}
return true;
};
return watchService;
});
Add $watch
watchService[prop] = $scope.$watch(prop, function() {
++$scope.changeCount;
});
Check $watch
if (watchService(prop)) {
Remove $watch
watchService(prop, true);
var myappWebApp = angular.module('myappWebApp', ['ui.bootstrap']);
//factory
myappWebApp.factory('wired', function () {
this.currOp = false;
return {
currOp1 : this.currOp
}
});
// controller
myappWebApp.controller('wiredCtrl',
function ($scope, $http, wired) {
//data
$scope.currOp = wired.currOp;//why is this undefined?
$scope.currOpInText = wired.currOpInText();
$scope.altOpInText = null;
$scope.boxA = null;
....
How should my scope.currOp always automatically have the same value as wired.currOp?
If you are copying just a bool, you can't. Booleans are copied by value, so you don't get reference-based updates.
You might consider using an object with a boolean:
myappWebApp.factory('wired', function () {
this.state = { curOp: false };
return {
state: this.state
}
});
Then, when you reference it in your scope, you can do this:
myappWebApp.controller('wiredCtrl',
function ($scope, $http, wired) {
$scope.opState = wired.state;
});
And now when curOp changes, the controller will have the change. You can watch for changes:
$scope.$watch("opState.curOp", function(newVal, oldVal) {
// Handle changes in curOp
});
Or you can bind to it:
CurOp: {{state.currOp}}
Note: You asked: "why is this undefined?" The answer is because your service is exposing currOp1 but you are referencing currOp
I have managed to return data from Web API that can be displayed using Angular. But now I need to be able to filter that data. I have created a directive that passes the parameter that I want to filter by, but I cannot find any info on what syntax I would use to do the filtering. Here is my service :
var fixtureService = angular.module("fixtureService", ["ngResource"]).factory("Fixture", function ($resource, $rootScope) {
fixtureService.addFilter = function (seasonNo) {
alert(seasonNo);
//do the filtering here?
};
return $resource(
"/api/fixture/:Id",
{ Id: "#Id" },
{ "update": { method: "PUT" } }
);
});
Any help would be much appreciated!
EDIT : Here is my directive :
app.directive("season", function () {
return {
restrict: 'E',
controller: SeasonCtrl,
template: '<select name="Seasons" ng-model="selectedSeason" ng-options="season.SeasonNo for season in seasons" ng-change="handleChange(season)">\
<option value=""> --Valitse-- </option>\
</select>',
link: function (scope, elem, attrs, ctrl) {
scope.handleChange = function () {
if (scope.selectedSeason != null) {
fixtureService.addFilter(scope.selectedSeason.SeasonNo);
} else {
fixtureService.clearFilter();
}
};
}
};
});
Since you want to use it outside of just display purposes and it seems you want to filter it at the service level, you can use a resource along with a function to process the data. This example uses only angular (you could use something like lodash or underscore too). Basically I'm saving the resource in the service for use and creating some functions for the service that I will be calling and filtering the data. I've also added a regular filter on an ngrepeat.
http://plnkr.co/edit/JLeejQb9kLyzsFPI2o9o?p=preview
app.controller('MainCtrl', function($scope, Fixture) {
$scope.locations = Fixture.getFilteredData('Austin');
$scope.unfiltered = Fixture.getData();
});
angular.module("fixtureService", ["ngResource"]).factory("Fixture", function ($resource, $rootScope, $q, $filter) {
var resource = $resource("data.json");
var originallist =[];
var service = {
getData:function() {
var d = $q.defer();
resource.get(function(data) {
originallist = data.data.locationlist;
d.resolve(originallist);
});
return d.promise;
},
cityFilter:function(list, filtered) {
return $filter('filter')(list, {city:filtered});
},
getFilteredData: function(filtered) {
var self = this;
var d = $q.defer();
var list = []; // going to use for the filter
// you could check to see if we already have an originallist, just depends on if you want to cache that
self.getData().then(function(data){
list = self.cityFilter(originallist, filtered)
d.resolve(list);
});
return d.promise;
}
};
return service;
});
<body ng-controller="MainCtrl">
<h2>Filtered</h2>
<div ng-repeat='location in locations'>{{location.locationname}} is in {{location.city}}</div>
<h2>Unfiltered</h2>
<div ng-repeat='location in unfiltered'>{{location.locationname}} is in {{location.city}}</div>
<h2>Filtered with an ngRepeat Filter</h2>
<div ng-repeat='location in unfiltered | filter:{city:"Austin"}'>{{location.locationname}} is in {{location.city}}</div>
</body>