I have a list of icons on a webpage generated from an object array. When the user clicks on an icon, the corresponding object from the array is passed to a function in a factory which saves the name of the object selected, then $state.go is called to change routes. On the new route a controller is loaded which loads the same factory and tries to access the name of the saved object. The problem is that about 7 times out of 10, it works perfectly, and the other 3 times is gives a "Unable to get property 'name' of undefined or null reference" type error.
Here is the controller passing the selected value to the factory:
platformHome.controller('PlatformHome', ['$scope', 'appManager', '$state', function ($scope, appManager, $state) {
var SF = appManager.state.SF;
var SO = appManager.state.SO;
$scope.productLineSelected = function (product) {
setProductLine(product);
};
function setProductLine(product) {
SF.setProduct(product);
$state.go('metricDashboard');
}
}]);
Here is the factory:
applicationManager.factory('appStateManager', ['$rootScope', '$sessionStorage', '$state', function ($rootScope, $sessionStorage, $state) {
// STATE OBJECT CLASSES
//
var stateClasses = {};
stateClasses.ProductLine = function (name) {
this.name = name;
this.dashboard = {
mode: 'reporting', //reporting, analysis
modeView: 'canvas', //canvas, data
index: {
report: 0,
userReport: 0,
canvas: 0,
group: 0,
element: 0,
filter: 0,
}
};
this.reports = [];
this.canvases = [new stateClasses.Canvas];
};
// STATE DATA FUNCTIONS
//
var stateFunctions = {};
stateFunctions.setProduct = function (product) {
session.StateObject.productLine.current = product.Code;
session.StateObject[product.Code] = (typeof session.StateObject[product.Code] === 'undefined') ? new stateClasses.ProductLine(product.Name) : session.StateObject[product.Code];
};
// STUCTURE
//
var stateScope = $rootScope.$new(true);
var session = $sessionStorage;
session.StateObject = (typeof session.StateObject === 'undefined') ? new stateClasses.StateObject : session.StateObject;
stateScope.SO = session.StateObject;
stateScope.SF = stateFunctions;
return stateScope;
}]);
Here is the controller trying to access the name:
metricDashboard.controller('MetricDashboard', ['$scope', 'appManager', function ($scope, appManager) {
var SF = appManager.state.SF;
var SO = appManager.state.SO;
DSO = SO[SO.productLine.current];
$scope.name = DSO.name;
}]);
I suspect that the issue is related to the order in which things are happening, however, I cannot figure out why it works 7 times out of 10.
When I do get the error, I have been able to determine that the line SO.productLine.current in the second controller has a value of none, meaning it doesn't seem to have been updated from the scope of the controller, however, at the same time, I'm also using console.log(JSON.stringify()) inside the factory, and the factory does indeed show a proper value instead of none.
I've also tried using $timeout on $state.go, and also tried passing it as a callback, however neither of those prevent the issue. Again 7 times out of 10, the code runs fine and the name property is value, but sometimes its not.
I was able to correct the problem with a few steps. The idea is to remove the assignment of the DSO variable from the MetricDashboard controller and move that functionality of assignment into the factory, then simply reference the newly assigned variable back in the controller.
Here are the changes:
In Factory
...
// STATE DATA FUNCTIONS
//
var stateFunctions = {};
stateFunctions.setProduct = function (product) {
session.StateObject.productLine.current = product.Code;
session.StateObject[product.Code] = (typeof session.StateObject[product.Code] === 'undefined') ? new stateClasses.ProductLine(product.Name) : session.StateObject[product.Code];
//new functionality for assignment
session.DynamicStateObject = session.StateObject[product.Code];
stateScope.DSO = session.DynamicStateObject;
};
// STUCTURE
//
var stateScope = $rootScope.$new(true);
var session = $sessionStorage;
session.StateObject = (typeof session.StateObject === 'undefined') ? new stateClasses.StateObject : session.StateObject;
//new structure to persist assignment beyond page refresh
session.DynamicStateObject = (typeof session.DynamicStateObject === 'undefined') ? {} : session.DynamicStateObject;
//new reference
stateScope.DSO = session.DynamicStateObject;
stateScope.SO = session.StateObject;
stateScope.SF = stateFunctions;
return stateScope;
In Controller
var SF = appManager.state.SF;
var SO = appManager.state.SO;
//Removed assignmnet
//DSO = SO[SO.productLine.current];
//Added reference
var DSO = appManager.state.DSO;
$scope.name = DSO.name;
While I have not yet tested the new code extensively, I have not been able reproduce the error.
Related
I use MVC razor to render the model below.
#Html.HiddenFor(u => u.FormNo, new { #ng_model = "formData.formno" })
When source code is viewed, the html input has a value.
<input id="FormNum" name="FormNum" ng-model="formData.formno" type="text" value="154S00017">
but angular returns this as a blank value in the controller.
var url;
var controller = 'Inbox';
var action = 'Get_RCTS_FormHistory';
var RCTS = angular.module('RCTS', []);
RCTS.controller('historyController', function ($scope, $http) {
$scope.formData = {};
$scope.formData.formno = '';
url = "/" + controller + "/" + action + "?FormNo=" + $scope.formData.formno;
alert(url)
$http.get(url).success(function (data) {
$scope.logs = data;
});
});
And when debugged in Visual studio.
I'm fairly new to angular and this really irritates me. I tried adding a document.ready() function there on the controller but it also returned an error.
Thanks
I resolved this by assigning the value from another html input.
$scope.formData.formno = $('#FormNo').val();
and ultimately resolved it by using
#Html.HiddenFor(u => u.FormNo, new { #ng_model = "formData.formno",#ng_init=#Model.FormNo })
from Razor view.
You are not updating the value of scope variable
$scope.formData.formno = 'cakefun';
I have discovered a perplexing circumstance in which the $scope.watch is not triggering as I would expect. I am hoping some of you AngularJS buffs out there can help shed some light on why Angular is behaving so peculiarly with $scope.watch and Service objects. As a note this was discovered while I was messing with the Ionic framework.
Below is an example of what I am running, and it works as expected (to be used to maintain state information during the session.)
TL;DR:
Watch doesn't fire on the controller when I update currentUser with a new userObject (currentUser = new userObject();) in my service. It does fire if I instead update each individual attribute of the object.
currentUser.name = 'Foo';
currentUser.email = 'foo#bar.com';
I am seeking guidance on why so I can better understand.
Code
Service (Working)
angular.module('app.services')
.service( 'UserService', function() {
var userObject = function(){
return {
id: null,
username: null,
email: null,
fullName: null,
modified: null
}
};
var currentUser = new userObject();
var createUser = function(id, username, email, fullName, modified ){
var newUserObject = new userObject();
newUserObject.id = id;
newUserObject.username = username;
newUserObject.email = email;
newUserObject.fullName = fullName;
newUserObject.modified = (modified) ? modified : new Date();
return newUserObject;
};
var setCurrentUser = function(userObj){
console.log('First');
console.dir(currentUser);
setUserId(userObj.id);
setUsername(userObj.username);
setEmail(userObj.email);
setFullName(userObj.fullName);
setModifiedDate(userObj.modified);
console.log('Second');
console.dir(currentUser);
return currentUser;
};
});
Controller
angular.module('app.controllers')
.controller('DashboardCtrl', function ($scope, UserService) {
var dashboard = this;
dashboard.user = UserService.currentUser;
$scope.$watch('UserService.currentUser', function(newVal, oldVal) {
if (newVal !== oldVal ){
dashboard.user = newVal;
}
});
var createdUser = UserService.createUser(
1,
'user1',
'asdf#asdf.com',
'Test User',
new Date()
);
UserService.setCurrentUser(createdUser);
});
View
<div ng-controller="DashboardCtrl as dashboard">
<span bind="dashboard.user.fullName">Loading</span>
</div>
Result
WAHOO! Loading is replaced on init with '' (null) and immediately after it is updated by the watch with 'Test User' (faster than the human eye unless debugging)
Breaking Scenario
The code above works great. However I wanted to try and reduce code by reducing repetition of effort by creating a new UserObject and then setting that object as 'Current User'. The change I made was as follows (view and controller were unchanged):
Service (Broken)
angular.module('app.services')
.service( 'UserService', function() {
var userObject = function(){
return {
id: null,
username: null,
email: null,
fullName: null,
modified: null
}
};
var currentUser = new userObject();
var createUser = function(id, username, email, fullName, modified ){
var newUserObject = new userObject();
newUserObject.id = id;
newUserObject.username = username;
newUserObject.email = email;
newUserObject.fullName = fullName;
newUserObject.modified = (modified) ? modified : new Date();
return newUserObject;
};
var setCurrentUser = function(userObj){
console.log('First');
console.dir(currentUser);
// Set original 'currentUser' with a new UserObject passed by controller
currentUser = userObj;
console.log('Second');
console.dir(currentUser);
return currentUser;
};
});
Result
Loading is replaced with '' on init (null) and $scope.watch never triggers in this scenario. My expectation is that the watch should be doing a deep watch on the object, and when I replace the object with a new one it should trigger that the object changed and trigger the watch.
The only thing I can figure, is that when I replace the currentUser object with a new object is that the delegates for $watch are also lost on that object. Does anyone have insight on how I can tell?
Phew.
When you give a string to the $watch it checks that variable inside angular function, As you don't have UserService.currentUser inside your scope then that watcher function will never get fired.
For making your watch working, you need to use function instead of string, then that function will return an expression. By adding it in watcher function, so that it will get evaluated on each digest cycle & will perform dirty checking. If value gets change it will fire the watcher function
Code
$scope.$watch(function(){
return UserService.currentUser;
}, function(newVal, oldVal) {
if (newVal !== oldVal ){
dashboard.user = newVal;
}
});
I try to make a word combinator for keywords. After combining them I want to load dynamically google search requests into an iframe. Unfortunately I have some problems with $sce-service.
Errormessage:
TypeError: Cannot read property 'trustAsUrl' of undefined
Same error for $sce.trustAsResourceUrl(...);
My plunkr:
http://plnkr.co/edit/N2nzBVElPtegaPYUCXlz
Important part:
Controller.js
var keywordAppControllers = angular.module('keywordAppControllers', []);
keywordAppControllers.controller('KeywordCtrl', ['$scope','$sce',
function ($scope) {
$scope.myData = {};
$scope.myData.previewUrl="";
$scope.serpPreview = function(keyword, $sce, $scope) {
previewUrl="https://www.google.de/search?q=" + encodeURIComponent(keyword);
// previewUrl="https://www.google.de/search?q=" + keyword;
console.log(previewUrl);
trustedUrl = $sce.trustAsUrl('https://www.google.de/');
console.log(trustedUrl);
// $scope.myData.previewUrl = $sce.trustAsUrl('www.google.de/');
// $scope.myData.previewUrl = $sce.trustAsUrl('https://www.google.de/');
// $scope.myData.previewUrl = $sce.trustAsUrl('https://google.de/');
// $scope.myData.previewUrl = $sce.trustAsUrl('//google.de/');
};
// create a blank object to hold our form information
// $scope will allow this to pass between controller and view
$scope.formData = {};
}
]);
App.js:
var keywordApp = angular.module('keywordApp', [
'keywordAppControllers'
]).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads.
'self',
// Allow loading from our assets domain. Notice the difference between * and **.
'https://google.de/*',
'http://google.de/*',
'https://google.de/**',
'http://google.de/**'
]);
});
Ok I have two modules which depend upon each other both modules have services, directives, ctrl's etc, now my question is how do i get values assigned in the nested function of the second module's service in the controller of the first service, I have added the dependencies to the first controller but i can't see to get at the nested functions variables to then manipulate them in the ctrl of the first module here's the code(considerably cut down):
angular.module("mainapp", [
"dateSheet",
"bookingApp"
]).controller("AppCtrl", [
"$scope",
"$attrs",
"Booking",
function (scope, source, attributes, AppDataLoader, booking, Booking) {
//HERE I NEED TO BE ABLE TO DO SOMETHING LIKE THIS
var getdaiyrate = function(){
var dumpDailyRates = scope.Booking.getalldates.getrates.dailyPrice
console.log(dumpDailyRates);
}
}
]);
angular.module("bookingApp", ["bookingApp.services",]);
angular.module("bookingApp.services").service("Booking", [
function(){
function getRate(source, dateSheet, dateSheetCtrl, expect, $$childTail, appData) {
var dateValue = $("Date", source).text() || "";
if (!dateValue) {
return null;
}
var dailyPrice = $("DailyPrice", source).text() || "";
var weeklyPrice = $("WeeklyPrice", source).text() || "";
var monthlyPrice = $("MonthlyPrice", source).text() || "";
var isAvailable = $("IsAvailable", source).text() === "1";
var minimumStay = Number($("MinimumStay", source).text());
if (isNaN(minimumStay)) {
minimumStay = DEFAULT_MINIMUM_STAY;
}
return {
date: new Date(dateValue),
dailyPrice: dailyPrice,
weeklyPrice: weeklyPrice,
monthlyPrice: monthlyPrice,
reserved: !isAvailable,
minimumStay: minimumStay
};
}
return {
getalldates: function(source, $scope){
return getRate(source, scope);
}
};
}
]);
The above doesn't work what am i doing wrong....
Could someone please send me in the direction of a decent tutorial that deals with a end to end app using various modules and dependencies??
Chris
You need to inject the service module into the module that you want to use it in. So the first line becomes
angular.module("mainapp", ["dateSheet","bookingApp","bookingApp.services"])
Also i don't see the creation of bookingApp.services so this may also be required
angular.module("bookingApp.services",[]);
and the invocation would be something like this
var dumpDailyRates = Booking.getalldates(sourceParameter, $scope);
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.