Error accessing AngularJS factory methods/arrays - javascript

I'm dividing my functions/objects into service and factory methods, and injecting them into my controller patentTab. I had a code for a tab panel which I originally placed within the controller patentTab that worked.
Now I have placed this code in a factory method and for some reason the content isn't loading. Console log shows no errors, and when I click the relative tab the correct URL is loaded, but the content doesn't change. Is there an issue with my array in the factory? If not, what is the reason?
Orginal code
app.controller('patentTab', function($scope, $http){
$scope.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
$http.get('../json/patent-info.json').then(function(response){
$scope.patentData = response.data.patentInfo;
})
$scope.currentTab = 'patent-info.htm';
$scope.onClickTab = function (tab) {
$scope.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
}
$scope.isActiveTab = function(tabUrl) {
return tabUrl == $scope.currentTab;
}
});
New code (with issue)
app.controller('patentCtrl', ['$scope', '$http', 'patentTabFactory', function($scope, $http, patentTabFactory) {
$http.get('http://localhost:8080/Sprint002b/restpatent/').then(function(response) {
$scope.patents = response.data;
});
$scope.loadPatentItem = function(url) {
$scope.patentItem = url;
}
$scope.tabs = patentTabFactory.tabs;
$scope.currentTab = patentTabFactory.currentTab;
$scope.onClickTab = patentTabFactory.onClickTab;
$scope.isActiveTab = patentTabFactory.isActiveTab;
}]);
app.factory('patentTabFactory', function() {
var factory = {};
factory.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
factory.currentTab = 'patent-info.htm';
factory.onClickTab = function (tab) {
factory.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
console.log(tab.url);
}
factory.isActiveTab = function(tabUrl) {
return tabUrl == factory.currentTab; //for styling purposes
}
return factory;
});

You not calling factory.onClickTab() method from your controller.
It should be like :
$scope.onClickTab = function(currentTab) {
patentTabFactory.onClickTab(currentTab);
$scope.currentTab = patentTabFactory.currentTab;
};
and, for isActiveTab, Like :
$scope.isActiveTab = patentTabFactory.isActiveTab(currentTab);

Here is a plunker where I am using a factory. The only changes I have done are:
1. Place the factory file before the app script file.
2. Use a separate declaration for factories and then inject it in the app.
var factories = angular.module('plunker.factory', []);
factories.factory('patentTabFactory', function() {
// Factory bits
};
I have injected the factories in the app.
var app = angular.module('plunker', ['plunker.factory']);
Here is a working plunker for that. PlunkR

Related

Communicating between two controllers in angularjs 1

I'm trying to communicate two controllers.
var main = angular.module('starter', ["ionic", "ngCordova", "starter.services"]);
cart-ctrl.js
main.controller('CartCtrl',
["$scope", "global",
function($scope, global) {
$scope.$on("globalvar", function() {
//alert("from service cart: " + global.cart.items);
console.log("from service cart: " + global.cart.items);
$scope.carts = global.cart.items;
});
}]);
menu-ctrl.js
main.controller('AppCtrl',
["$scope", "$state", "global",
function($scope, $state, global) {
$scope.cart_click = function() {
global.updateCart();
$state.go('app.cart');
}
}]);
services.js
var service = angular.module("starter.services", []);
service.factory("global", ["$rootScope", "database",
function($rootScope, database) {
var service = {
cart: {
items: [],
count: 0
},
broadcastItem: function() {
$rootScope.$broadcast("globalvar");
},
updateCart: function() {
database.select_cart(function(p_cart) {
this.cart.items = p_cart;
alert("service cart: " + JSON.stringify(this.cart.items));
});
this.broadcastItem();
}
};
return service;
}]);
What I wanted to happen is when I click a the tab (which triggeres the cart_click()), the cart list will re-update. However no value is passed into CartCtrl. I wonder what's wrong in this code. service.cart.items has a value when I passed the value from the database.
I think we have 2 options.
You can $scope.cart_click => $rootScope.cart_click.
You can $emit, $broadcast and $on
And see more in https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
I think you should call this.broadcastItem(); inside the callback of your database call. Also context of this inside the callback is not actually of the same service. Update your code as
updateCart: function() {
var self = this;
database.select_cart(function(p_cart) {
self.cart.items = p_cart;
self.broadcastItem();
alert("service cart: " + JSON.stringify(self.cart.items));
});
}

Not able to retrieve data in the other controller using common shared service

I'm creating a shared service called Popup Service so that I can share data between two Controllers i.e BankController and CreateBankController.
I'm able to set the object that I need to share in the PopupService. The popupService is called within BankController (while clicking the Edit link on a data row) to set the object to be shared.
The CreateBankController is then invoked by the popup form, but in the CreateBankcontroller I'm not able to access the shared data from the PopupService, I get an error that objectToEdit variable is not defined.
Please can you advise how I can make the PopupService share data between the two controllers
Can the data in the Popup shared service end up over being overridden by competing user actions and show stale data
WORKING PLUNKER
https://plnkr.co/edit/y8dZNU?p=preview
Retrieving data in CreateBankController
CreateBankController.$inject = ['PopupService'];
function CreateBankController(PopupService) {
var vm = this;
var data = {
bankName: "",
bankAddress: "",
};
debugger;
if (PopupService.getObjectToEdit() !== null) {
data = PopupService.getObjectToEdit();
}
SETTING THE SHARED DATA IN THE BankController
$scope.bankGrid = {
dataSource: queryResult,
columns: [{
dataField: 'orderID',
caption: 'Order ID'
}, {
width: 50,
alignment: 'center',
caption: 'Edit',
cellTemplate: function(container, options) {
$('<a/>').addClass('dx-link')
.text('Edit')
.on('dxclick', function() {
PopupService.addObjecToEdit(options.data);
$scope.showPopup = true;
})
.appendTo(container);
}
shared data service - POPUP SERVICE
(function () {
'use strict';
angular
.module('myApp')
.factory('PopupService', PopupService);
function PopupService() {
var popupInstance = {};
var objectToEdit = {};
var service = {
addObjecToEdit : addObjecToEdit,
getObjectToEdit: getObjectToEdit,
showPopup: showPopup,
hidePopup: hidePopup
};
return service;
//Functions
function addObjecToEdit(pObjectToEdit){
objectToEdit = pObjectToEdit;
}
function getObjectToEdit() {
return objecTtoEdit;
}
function showPopup(){
popupInstance.showPopup();
}
function hidePopup(){
popupInstance.hidePopup();
}
}
}());
You have a typo in the service:
function getObjectToEdit() {
return objecTtoEdit;
}
change to:
function getObjectToEdit() {
return objectToEdit;
}

Calling a method via an array within the same model in which the members are defined

I'm trying to keep all my model functionality in one place. I'd like to be able to call its methods within it:
JS
/**
* This app prints "OK" a number of times, according to which button is pressed
*/
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
$scope.okModel = {
oks: [],
addOne: function(){
this.oks.push(["OK"]);
},
addTwo: function(){
this.addOK();
this.addOK();
},
buttons: [
{name:"addOK", action: this.addOne}, // THIS is where the issue is I think
{name:"add2OKs", action: this.addTwo}
]
};
}]);
HTML
<div ng-controller="MyCtrl">
<!-- print the object holding all the "OK"s -->
oks: {{okModel.oks}}
<!-- show the buttons -->
<button ng-repeat="item in okModel.buttons" ng-click="item.action()">
{{item.name}}
</button>
<!-- print the "OK"s -->
<div ng-repeat="ok in okModel.oks">
{{ok[0]}}
</div>
</div>
I'm not getting an error, but it's not working either. No "OK"s are being added to the model. It seems like the issue may be with the okModel.buttons action property.
Here's a plunker: https://plnkr.co/edit/mDk43yEKSQB37QSmiKJn?p=preview
TL;DR: I realize that the issue is probably with this in buttons, what should I use instead?
Bonus question: I'm new to angular and realize I may be using models incorrectly. If you know of a better way to do models, please let me know.
Since you asked, the "angular" way to do this is to have a service provide the model, rather than defining it in your controller:
var app = angular.module('myApp', []);
app.factory('modelService', function() {
var okModel = {
oks: [],
}
okModel.addOne = function() {
okModel.oks.push(["OK"]);
};
okModel.addTwo = function() {
okModel.addOne();
okModel.addOne();
};
okModel.buttons = [{
name: "addOK",
action: okModel.addOne
},
{
name: "add2OKs",
action: okModel.addTwo
}
];
return okModel;
});
app.controller('MyCtrl', ['$scope', 'modelService', function MyCtrl($scope, modelService) {
$scope.okModel = modelService;
}]);
Here is a plunk.
#reeverd answer seems to be correct. Here is a little more clean up.
$scope.okModel = {
oks: [],
addOne: function(){
oks.push(["OK"]); // you don't have to push an array with the OK text. You can just push in the OK text itself.
},
addMulti: function(cnt){
for (var ii = 0; cnt < ii; ii++) {
// this.addOK(); this may also be your issue addOK function is not defined.
$scope.addOne(); // calls the addOne and pushes OK into the oks array.
}
},
buttons: [
{name:"addOK", action: $scope.addOne}, // THIS is where the issue is I think
{name:"add2OKs", action: $scope.addMulti(2)}
]
};
The problem is that this inside the {name:"addOK", action: this.addOne} in the array is the object itseld, and not the object wrapping the array. In this case you could do something like this:
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
// Here you declare the functions which are going to modify
// $scope.okModel.oks
var addOne = function() {
if ($scope.okModel) {
$scope.okModel.oks.push("OK");
}
},
addTwo = function() {
addOne();
addOne();
};
$scope.okModel = {
oks: [],
addOne: addOne, // Here you assign okModel.addOne to the functions declared above
addTwo: addTwo, // Same here
buttons: [{
name: "addOK",
action: addOne // Same here
}, {
name: "add2OKs",
action: addTwo // Same here
}]
};
}]);
First you declare the functions which will modify $scope.okModel.oks array and then you use that same funcions both in your model methods and in your model buttons.
EDIT: Here you have a working plunker: https://plnkr.co/edit/sKUxjzUyVsoYp3S7zjTb?p=preview
Instead of using this, try using $scope.okModel. The actual object where this refers to is not always what you expect. It depends on how the function is invoked.
EDIT:
You can pull the definition of the functions out of the okModel like this:
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
var addOne = function() {
$scope.okModel.oks.push(["OK"]);
};
var addTwo = function() {
addOne();
addOne();
};
$scope.okModel = {
oks: [],
addOne: addOne,
addTwo: addTwo,
buttons: [{
name: "addOK",
action: addOne
}, {
name: "add2OKs",
action: addTwo
}]
};
}]);

Update ui-router resolved data across different states

I am using ui-router to manage various states of my site. I have used resolve to pass data to header and home controller as displayed in following code. So now I need to update the value of resolved data from HomeController and this change should reflect across to HeaderController too.
var myapp = angular.module('myapp', ["ui.router"]);
myapp.service("DataService", [function() {
var data = { title: 'Some Title' };
this.get = function() {
return data;
};
}]);
myapp.controller("HeaderController", ["data", function(data) {
var vm = this;
vm.title = data.title;
}]);
myapp.controller("HomeController", ["data", function(data) {
var vm = this;
vm.title = data.title;
vm.updateTitle = function() {
// update the resolved data here so the header and home view
// updates with new data.title
data = { title: "Another title" };
// i know i can do vm.title = data.title; to update home view.
// But would be nice to globally update and reflect that change
// on all controllers sharing the same resolved data
};
}]);
myapp.config(function($stateProvider){
$stateProvider
.state({
name: "root",
abstract: true,
views: {
"header": {
templateUrl: "header.html",
controller: 'HeaderController as vm'
}
},
resolve: {
data: ['DataService', function(DataService) {
return DataService.get();
}]
}
})
.state({
name: "root.home",
url: "",
views: {
"content#": {
templateUrl: "home.html",
controller: "HomeController as vm"
}
}
})
});
PS:
Before looking into resolve, I was injecting service directly into the controller so please do not suggest on doing that.
EDIT: Plunkr updated and now works as expected.
Here is link to plunkr
Lesson Learnt:
Angular only watches the object that is assigned to the scope, and keeps separate reference of the objects. I mean:
data = { title: 'some title' };
vm.data = data;
vm.title = data.title;
data.title = 'another title';
{{vm.title}} // some title
/////////////////
data = { title: 'some title' };
vm.data = data;
data.title = 'another title';
{{vm.data.title}} // another title
You should take an advantage of the variable reference, where you should bind your HeaderController data to vm.data = data
Another incorrect thing is data = { title: "Another title" }; which would create an data object with new reference, and the reference of service object will lost. Instead of that you should do data.title = 'Another title';
header.html
{{vm.data.title}}
HeaderController
myapp.controller("HeaderController", ["data", function(data) {
var vm = this;
vm.data = data;
}]);
Update updateTitle method code to below.
vm.updateTitle = function() {
// update the resolved data here so the header updates with new data.title
vm.data.title = "Another title";
};
Demo here
I'd say that rather than playing with actual object reference, you should have setTitle function inside your factory, from updateTitle you will call that setter method which will update title. But in that case you need to again add the service reference on both controller. If its static data then there is no need to pass them by having resolve function. I'd loved to inject the service inside my controllers and then will play with data by its getter & setter.
Preferred Approach Plunkr

Angular page doesn't refresh after data is added or removed

I'm having a recurrent problem with my angular app whereby it doesn't refresh the page after data has been added, edited or removed. So if I add a new item to a list of subjects, the new item doesn't appear on the list unless I navigate away from the page and then come back to it. I've tried using route.reload and then resetting the scope of the subjects list below. I put in an alert to see if it get fired- but the alert appears before the page redirects back to the list of subjects, which is strange as $location.path('/subjects') is two lines before it. Here's my controller:
angular.module('myApp.controllers')
.controller('SubjectEditCtrl', ['$scope', '$routeParams', 'SubjectFactory', 'SubjectsFactory', '$location', '$route',
function ($scope, $routeParams, SubjectFactory, SubjectsFactory, $location, $route) {
// callback for ng-click 'updateSubject':
$scope.updateSubject = function () {
//Performs an update to the server
SubjectFactory.update($scope.subject);
//Redirects to list of all subjects
$location.path('/subjects/');
//Should reset the scope of the subject list
$scope.subjects = SubjectsFactory.query();
//Should reload the page
$route.reload();
//For debugging- the alert appears BEFORE the redirect to list of all subjects happens
alert('route reload happening');
};
SubjectFactory.show({id: $routeParams.subjectId}).$promise.then(function(subject) {
$scope.subject = subject;
}, function(err) {
console.error(err);
});
}]);
Can anyone suggest a solution?
EDIT: Subjects Service
var app = angular.module('myApp.services');
app.factory('SubjectsFactory', function ($resource) {
return $resource('https://myapiurl.com/subjects', {}, {
query: { method: 'GET', isArray: true },
create: { method: 'POST' }
})
});
app.factory('SubjectFactory', function ($resource) {
return $resource('https://myapiurl.com/subjects/:id', {}, {
show: { method: 'GET', isArray: false },
update: { method: 'PATCH', params: {id: '#id'} },
delete: { method: 'DELETE', params: {id: '#id'} }
})
});
Some times you need to apply changes to scope this is done by the following code:
$scope.$apply();
But this can be done only if it's not in "$digest" phase, otherwise it will throw exception. So you need to check first it's not in "$digest" phase then you can apply it. Here is the example of the code I use for safe applying changes:
safeApply: function (scope, callback) {
if (scope.$$phase != '$apply' && scope.$$phase != '$digest' &&
(!scope.$root || (scope.$root.$$phase != '$apply' && scope.$root.$$phase != '$digest'))) {
scope.$apply();
}
if (angular.isFunction(callback)) {
callback();
}
}
I can suggest next way:
You can't get data from database, after adding, you can easly push new added object to $scope.items.
Example:
$scope.add = function (newItem) {
DataService.addItem(newItem).then(function(){
$scope.items.push(newItem);
//or for removing
//$scope.items.splice($scope.items.indexOf(newItem), 1);
});
};
And adjust your factory:
addItem: function (newProject) {
$http.post('Api/Controller/Post').then(function(successResult){
...
}, function (errorResult) {
...
});
}
Item will be added in $scope.items only after success calling of server-side method.
Changing the structure of the requests slightly fixed the problem- so instead of
$scope.updateSubject = function () {
SubjectFactory.update($scope.subject);
$location.path('/subjects/');
$scope.subjects = SubjectsFactory.query();
$route.reload();
};
It is now
$scope.updateSubject = function () {
SubjectFactory.update($scope.subject).$promise.then(function (subject) {
$scope.subject = subject;
$location.path('/subjects/');
$route.reload();
}, function (err) {
console.error(err);
});
};

Categories