angular watch object not in scope - javascript

I have a service in which values can change from outside Angular:
angularApp.service('WebSocketService', function() {
var serviceAlarms = [];
var iteration = 0;
this.renderMessages = function(alarms, socket) {
if (! angular.equals(serviceAlarms, alarms)) {
serviceAlarms = alarms;
iteration++;
}
};
this.getAlarms = function () {
return serviceAlarms;
};
this.iteration = function () {
return iteration;
};
this.socket = initSocketIO(this);
});
The initSocketIO function makes callbacks to this services renderMessages() function and serviceAlarms variable gets changed on a steady basis.
Now i am trying to watch for changes in this service like so:
controllers.controller('overviewController', ['$scope', 'WebSocketService', function ($scope, WebSocketService) {
$scope.$watch(
function () {
return WebSocketService.iteration();
},
function(newValue, oldValue) {
$scope.alarms = WebSocketService.getAlarms();
},
true
);
}]);
to no avail. The second function provided to $watch never gets executed except on controller initialization.
I have tried with and without true as third parameter.

You should use $rootScope.$watch (not $scope.$watch)

I ended up using the solution below since $watch didn't work as excpected.
I refactored the solution to use $rootScope in combination with:
angularApp.run(['$rootScope', function($rootScope){
$rootScope.socket = {};
$rootScope.socket.alarms = [];
$rootScope.socket.faults = [];
$rootScope.socket.renderErrors = function(faults, socket) {
var faultArray = [];
angular.forEach(faults, function(error) {
error.value ? faultArray.push(error) : null;
});
if (! angular.equals($rootScope.socket.faults, faultArray)) {
$rootScope.socket.faults = faultArray;
$rootScope.apply();
}
};
$rootScope.socket.renderMessages = function(alarms, socket) {
if (! angular.equals($rootScope.socket.alarms, alarms)) {
$rootScope.socket.alarms = alarms;
$rootScope.$apply();
}
};
$rootScope.socket.socket = initSocketIO($rootScope.socket);
}]);
Now i have my socket-updated-model in all scopes to use freely in controllers and views.
Controller example:
$scope.acknowledgeAlarm = function(alarm) {
$scope.socket.socket.emit('acknowledgeAlarm', {
hash:alarm.icon.hash,
id:alarm.id
});
};
View example:
<div ng-repeat="alarm in socket.alarms">
{{alarm.name}} {{alarm.icon.progress}}
</div>

Related

Getting injector error even if the factory is returning the object

Its been like 6-7 months now with Angular and I have acquired not much but a relevant knowledge. But from past one day I am stuck on this error as shown below:
Error: [$injector:undef]
http://errors.angularjs.org/1.6.5/$injector/undef?p0=DataService
Stack trace: K/<#http://localhost:64965/Scripts/angular.min.js:7:76
Below is my service code:
(function () {
var DataService = function ($http) {
var allItems = function () {
return $http.get("./data/myData.json").then(function (serviceResp) {
return serviceResp.data;
});
};
};
app.factory("DataService", ["$http", DataService]);
}());
Below is my controller code:
(function () {
var ItemController = function ($scope, DataService) {
var allItems = function (data) {
$scope.collection = data;
};
};
app.controller("ItemController", ["$scope", "DataService", ItemController])
}());
The factory is returning the object but still I am getting the above error. I tried cleaning the cache several times and restarted the app many times.
The way you wrote your service is wrong.
It should be written in the following way:
var app = angular.module('ServiceExample',[]);
var serviceExampleController =
app.controller('ServiceExampleController', ServiceExampleController);
var serviceExample = app.service('NameOfTheService', NameOfTheService);
ServiceExampleController.$inject = ['NameOfTheService'] //protects from minification of js files
function ServiceExampleController(NameOfTheService){
serviceExampleController = this;
serviceExampleController.data = NameOfTheService.getSomeData();
}
function NameOfTheService(){
nameOfTheService = this;
nameOfTheService.data = "Some Data";
nameOfTheService.getSomeData = function(){
return nameOfTheService.data;
}
}
Then in the HTML you can use it like:
<div ng-controller = "ServiceExampleController as serviceExample">
{{serviceExample.data}}
</div>
If you are looking to use factory read this post.
Factory must return a value/obj. You are not returning anything. I have not tested, but it should work.
(function () {
var DataService = function ($http) {
var allItems = function () {
return $http.get("./data/myData.json").then(function (serviceResp) {
return serviceResp.data;
});
};
return allItems ;
};
app.factory("DataService", ["$http", DataService]);
}());

How can a link function and controller function share knowledge in an angular directive?

I have an angular directive that shows payment history. By default, it shows the last 6 payments using the vm.numberOfPaymentsToDisplay variable. If you click view more, it adds 10. Now, when a user clicks on another section, there's a listener in the link function that is supposed to reset the number to 6, however vm is undefined.
Here's the code:
angular.module('nui.settings2.account')
.directive('paymentHistory', function(){
function PaymentHistoryController(paymentHistoryService, $filter, $window, $translate){
const filter = $filter('formatCurrency');
var vm = this;
vm.payments = paymentHistoryService.get();
vm.numberOfPaymentsToDisplay = 6;
vm.getLastPayment = getLastPayment;
vm.viewMorePayments = viewMorePayments;
vm.title = $translate.instant('NUI.SETTINGS.PAYMENT_HISTORY');
function getLastPayment(){
const lastTransaction = paymentHistoryService.getLastPayment();
return lastPaymentInfo = "amount (date)";
}
function viewMorePayments(){
vm.numberOfPaymentsToDisplay = vm.numberOfPaymentsToDisplay + 10;
return true;
}
}
function link(scope, element, attrs, [expander, paymentHistory]){
const containerEl = element.children();
expander.registerContentContainer(containerEl);
scope.$on(expander.COLLAPSE_EVENT, () => vm.numberOfPaymentsToDisplay = 6);
scope.$on("$destroy", () => scope.$emit(expander.CONTAINER_DEREGISTER_EVENT));
paymentHistory.cancel = () => expander.collapse();
}
return {
restrict: 'E',
templateUrl: 'nui/settings2/account/billing/payment-history.directive.html',
controller: PaymentHistoryController,
link: link,
require: ['^^settingExpander', 'paymentHistory'],
controllerAs: 'PaymentHistoryCtrl',
bindToController: true
};
});
How can I set vm.numberOfPaymentsToDisplay = 6 in the link function even though this knowledge is only known by the controller?
You can add a method to your PaymentHistoryController like setNumberOfPaymentsToDisplay as you inject your PaymentHistoryController into your link function you can call the method like this:
paymentHistory.setNumberOfPaymentsToDisplay(6);
Controller code:
function PaymentHistoryController(paymentHistoryService, $filter, $window, $translate){
const filter = $filter('formatCurrency');
var vm = this;
vm.payments = paymentHistoryService.get();
vm.numberOfPaymentsToDisplay = 6;
vm.getLastPayment = getLastPayment;
vm.viewMorePayments = viewMorePayments;
vm.setNumberOfPaymentsToDisplay = setNumberOfPaymentsToDisplay;
vm.title = $translate.instant('NUI.SETTINGS.PAYMENT_HISTORY');
function getLastPayment(){
const lastTransaction = paymentHistoryService.getLastPayment();
return lastPaymentInfo = "amount (date)";
}
function viewMorePayments(){
vm.numberOfPaymentsToDisplay = vm.numberOfPaymentsToDisplay + 10;
return true;
}
function setNumberOfPaymentsToDisplay(amount) {
vm.numberOfPaymentsToDisplay = amount;
}
}
link code:
function link(scope, element, attrs, [expander, paymentHistory]){
const containerEl = element.children();
expander.registerContentContainer(containerEl);
scope.$on(expander.COLLAPSE_EVENT, () => paymentHistory.setNumberOfPaymentsToDisplay(6));
scope.$on("$destroy", () => scope.$emit(expander.CONTAINER_DEREGISTER_EVENT));
paymentHistory.cancel = () => expander.collapse();
}
Actually you have more options.
the general approach to share data between components is to use a service that is a singleton , a single instance cached and injected by angular everytime you use it inside your component. another valid solution is to emit events.
Service:
.service('MyService', function(){
var data;
this.setData = function(newData){
data = newData;
}
this.getData = function(){
return data;
}
})
Events:
$rootScope.$broadcast('my.evt', data); //down in the scope chain, visible to any scope
$rootScope.$emit('my.evt', data); //up in the scope chain since is the rootscope only visible to rootScope
$scope.$emit //up in the scope chain
$scope.$broacast //down in the scope chain
to listen for events:
$rootScope.$on('my.evt', function(evt, data){ //do something }
or
$scope.$on('my.evt', function(evt, data){ //do something }
However in this case you're using the link function to modify your business logic and this is not the the conventional approach, usually the link is used only to handle dom events and to modify the dom. so my personal advice is to refactor your code and put the whole business logic inside the controller

Checking whether a function is called when another function is triggered

I am new to AngularJS and Jasmine. Given the following controller, how do I test whether the allPanelsRetrieved() function is called when the $scope.getPanels is triggered?
angular.
module('panelList').
controller('PanelListController', ['Panel', 'PanelSelection', '$scope', '$location', '$uibModal', '$rootScope',
function PanelListController(PanelSelection, $scope, $location, $uibModal, $rootScope) {
$scope.maxAbv = 2;
$scope.minAbv = 12;
this.allPanelsRetrieved = (index, before, filterParams) => {
.....
};
$scope.getPanels = () => {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
$scope.currentPagePanels = this.allPanelsRetrieved (1,[], filterParams);
};
}]).
component('panelList', {
templateUrl: '/components/panel-list/panel-list.template.html',
controller:'PanelListController',
});
Assuming you want allPanelsRetrived to be called, then simply use a boolean.
var bool = false
this.allPanelsRetrieved = (index, before, filterParams) => {
.....
bool=true;
};
$scope.getPanels = () => {
if (bool) {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
$scope.currentPagePanels = this.allPanelsRetrieved (1,[], filterParams);
} else {
// allPanelsRetrieved was not called
}
};
I can see that allPanelsRetrieved seems to be a private(local) method and used inside that controller.
You need not test private(local) methods execution.
If you still want to check if the method is triggered or not you can use jasmine's toHaveBeenCalled() method
execept(myMethod).toHaveBeenCalled();
passes when method is called.

How to run the service on page load

I have the function inside service in angular module but how do I run the service on page load. I am not using any controller here
Code:
var app = angular.module("myTmoApppdl", [])
.service('populatePageObjsService', function () {
var self = this;
//digitalData is a global object
self.populatePage = function (digitalData) {
var dataPageObject = document.getElementById('metaJson').getAttribute("data-pageObject");
var jsonPageObject = JSON.parse(dataPageObject);
for (var key in jsonPageObject.page.pageInfo) {
var value = jsonPageObject.page.pageInfo[key];
if (digitalData.page.pageInfo.hasOwnProperty(key)) {
digitalData.page.pageInfo[key] = value;
}
}
console.log("Page data populated successfully: ", digitalData.page.pageInfo);
}();
});
I tried using the () after function but it didn't execute the function
Update:
var app = angular.module("myTmoApppdl", []).run(function () {
app.service('populatePageObjsService', function () {
var self = this;
self.populatePage = function (digitalData) {
var dataPageObject = document.getElementById('metaJson').getAttribute("data-pageObject");
var jsonPageObject = JSON.parse(dataPageObject);
for (var key in jsonPageObject.page.pageInfo) {
var value = jsonPageObject.page.pageInfo[key];
if (digitalData.page.pageInfo.hasOwnProperty(key)) {
digitalData.page.pageInfo[key] = value;
}
}
console.log("Page data populated successfully: ", digitalData.page.pageInfo);
};
});
});
You can call your service method in .run() block
var app = angular.module("myTmoApppdl", []);
app.run(function(populatePageObjsService){ // inject your service here
//use your service here
});
app.service('populatePageObjsService', function() {
// your service code here
});
With run module
Simple example
var app = angular.module("myTmoApppdl", [])
.run(function(populatePageObjsService) {
populatePageObjsService.init();
})
.service('populatePageObjsService', function() {
var self = this;
self.init = function() {
alert("run");
};
});
Plunker
Since an AngularJS service is singleton, you can be sure that your function will only be executed once, but you have to bear in mind that it is also lazily instantiated - Angular only instantiates a service when an application component depends on it - source.
With this in mind, if a service is somehow self-reliable (for example, fetching the user's country via an API call - since this can be executed only once at the start of the app and it's unlikely for the data to change), besides the usual service bindings, you can also put logic in there.
I've edited your code to showcase this approach.
angular
.module("myTmoApppdl", [])
// populatePageObjsService needs to be injected somewhere otherwise it won't get instantiated
.service('populatePageObjsService', function() {
//////////////
// Bindings //
//////////////
var self = this;
//digitalData is a global object
self.populatePage = function(digitalData) {
var dataPageObject = document.getElementById('metaJson').getAttribute("data-pageObject");
var jsonPageObject = JSON.parse(dataPageObject);
for (var key in jsonPageObject.page.pageInfo) {
var value = jsonPageObject.page.pageInfo[key];
if (digitalData.page.pageInfo.hasOwnProperty(key)) {
digitalData.page.pageInfo[key] = value;
}
}
console.log("Page data populated successfully: ", digitalData.page.pageInfo);
};
///////////////
// Run Block //
///////////////
// Calling the desired function.
self.populatePage(digitalData);
});
angular.element(document).ready(function () {
// your code here
});

Deletion of elements in array with $timeout does not work properly

The directive notification should delete "itself" after 5 seconds. However some elements get missed and some get deleted more than once. Identifier property is unique for each notification. Thanks for help.
Factory
angular.module('AdS').factory('notificationFactory', function () {
var notificationFactory = {};
notificationFactory.notifications = [];
notificationFactory.identifier =0;
notificationFactory.add = function(note){
if(typeof note!=='undefined'){
notificationFactory.identifier++;
note.identifier = notificationFactory.identifier;
notificationFactory.notifications.push(note);
}
}
notificationFactory.delete = function (note) {
if(typeof note!=='undefined'){
for(var i =0;i<notificationFactory.notifications.length;i++){
if(notificationFactory.notifications[i].identifier==note.identifier){
notificationFactory.notifications.splice(i,1);
}
}
}
return "";
}
notificationFactory.getNotifications = function () {
return notificationFactory.notifications;
}
return notificationFactory;
});
Directive
angular.module('AdS').directive('siteNotification', [
'$timeout',
function ($timeout) {
return {
restric: "E",
templateUrl: "/Templates/htmlBits/notification.html",
scope: {
note:"=",
center:"="
},
link: function (scope, element, attrs) {
$timeout(function () {
scope.center.delete(scope.note);
}, 5000);
scope.delete=function(note){
scope.center.delete(note);
}
}
};
}
]);
html
<site-notification ng-repeat="not in notificationCenter.notifications track by $index" center=notificationCenter note=not ></site-notification>
Rather than using an array for notificationFactory.notifications, you could use an object, with the unique identifier pointing to your note like so:
notificationFactory.notifications = {};
notificationFactory.add = function(note) {
if (typeof note!=='undefined') {
notificationFactory.identifier++;
notificationFactory.notifications[notificationFactory.identifier] = note;
}
}
notificationFactory.delete = function (note) {
if(typeof note!=='undefined'){
notificationFactory.notifications[notificationFactory.identifier] = null;
}
return "";
}
Also, in your directive, you seem to be injecting notificationFactory via the html. This is very unusual, the typical way to inject your factory is as follows:
angular.module('AdS').directive('siteNotification', [
'$timeout',
'notificationFactory',
function ($timeout, notificationFactory) {
...
}
The only reason I can see to do it differently way is if you want to be able to inject a different type of factory into your directive.

Categories

Resources