So I am basically tranferring data between 2 pages in angular using $route service and not using url params.
I get the data sent to on the second page by the first page but when I refresh the second page data is lost!
How do I solve this,I cant use url params as data will have many text fields and also will be having an array.
I am already using service to pass data but service data doesnt persist after refresh.
I dont want to make database as Its not practical I just want that data to stay on client machine.
You can use localStorage to persist the data:
Plunker
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.1.5" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<script src="https://rawgithub.com/gsklee/ngStorage/master/ngStorage.js"></script>
<script>
angular.module('app', [
'ngStorage'
]).
controller('Ctrl', function(
$scope,
$localStorage
){
$scope.$storage = $localStorage.$default({
x: 42,
y: 1
});
});
</script>
</head>
<body ng-controller="Ctrl">
<button ng-click="$storage.x = $storage.x + 1">{{$storage.x}}</button> + <button ng-click="$storage.y = $storage.y + 1">{{$storage.y}}</button> = {{$storage.x + $storage.y}}
</body>
</html>
Services are always the best way to store data between controllers. A service persists data through the applications life cycle, therefore we can create a simple one like this:
DataCacheService
angular.module('dataPassingDemo').service('DataCacheService', DataCacheService);
function DataCacheService () {
var self = this,
_cache = {};
self.get = get;
self.set = set;
function get (key) {
if (angular.isDefined(key) && angular.isDefined(_cache[key])) {
return _cache[key];
}
return false;
}
function set (key, value) {
if (angular.isDefined(key)) {
_cache[key] = value;
}
}
Then, given 2 controllers:
Controller 1
angular.module('dataPassingDemo').controller('Controller1', Controller1);
function Controller1 (DataCacheService) {
var vm = this;
vm.setName = setName;
function setName (name) {
DataCacheService.set('name', name);
}
}
Controller 2
angular.module('dataPassingDemo').controller('Controller2', Controller2);
function Controller2 (DataCacheService) {
var vm = this;
vm.name = DataCacheService.get('name');
}
You can see that we are able to pass data between the 2 controllers seamlessly.
You can get more information about services and factories from this article.
Related
I have a datatables.js table set up in an angular 1.2 project. I want a value from an $http.get(...) call to be what is displayed in one of the cells of each row of the table.
I know that $http returns a promise, but I can't figure out how to change the code so that the value of the resolved promise is what is returned by the render function so that the data & not the promise is what is displayed in the table.
UPDATED: Do I need to pre-fetch the data before the table is created? <-- This is the answer! See the selected answer for implementation. You can't make the call for every row within the table using something like Angulars $http because there is no opportunity to return the resolved promises data within the render function.
I'm not looking for a hack unless it is necessary. I would like to solve this with a known pattern.
Here is a fiddle of what I'm trying to do: jsfiddle example
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div ng-app="salutationApp">
<div ng-controller="salutationReportController">
<table id="salutationTable" />
<button type="button" ng-click="init()">
Create Table
</button>
</div>
</div>
<script src="https://cdn.datatables.net/t/dt/jq-2.2.0,dt-1.10.11/datatables.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.29/angular.js"></script>
<script src="app.js"></script>
</body>
</html>
var salutationApp = angular.module("salutationApp", []);
salutationApp.controller("salutationReportController", ["$http", "$scope", function ($http, $scope) {
var getSalutationLink = function (data) {
var url = 'http://jsonplaceholder.typicode.com/posts/1';
return $http({
method: 'GET',
url: url,
params: data
});
};
var init = function () {
var salutationDataSource = [
{
salutationDataField: "hello!"
},
{
salutationDataField: "good morning!"
},
{
salutationDataField: "greeetings!"
}
];
$("#salutationTable").DataTable({
"bDestroy": true,
data: salutationDataSource,
columns: [{
title: "Salutation",
data: "salutationDataField",
render: function (cellData, type, fullRowData, meta) {
//I want this to return a some data from the server
var retVal = getSalutationLink({
salutation: cellData,
});
return retVal; // I know this is a promise... but I need to return the value of the resolved promise. I want the data from the web server to be the return value. This is where I'm stuck.
}
}]
});
};
$scope.init = function () {
init();
};
}]);
Thanks!
I don't think your ng-controller would even work? You don't set it to a global variable name, it has to be declared within angular.
html
<div ng-controller="SalutationReportController">
<div id="salutationTable"></div>
</div>
js
//this shouldn't work
var SalutationReportController = function() { /* blah */ }
//this would work
angular
.module('app')
.controller('SalutationReportController', ['$http', '$scope', function() {
$http.get('/api/foobar').then(function(res) {
//now the data you got from the promise is publicly available to your child directive
$scope.tabledata = res.data;
});
}])
Somewhere in the code for your directive you'll need to add the correct scope properties and have the directive inherit the property according to its api.
Have you looked in to Angular-datatables?
http://l-lin.github.io/angular-datatables/#/withPromise
angular.module('showcase.withPromise', ['datatables', 'ngResource']).controller('WithPromiseCtrl', WithPromiseCtrl);
function WithPromiseCtrl(DTOptionsBuilder, DTColumnBuilder, $resource) {
var vm = this;
vm.dtOptions = DTOptionsBuilder.fromFnPromise(function() {
return $resource('data.json').query().$promise;
}).withPaginationType('full_numbers');
vm.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('firstName').withTitle('First name'),
DTColumnBuilder.newColumn('lastName').withTitle('Last name').notVisible()
];
}
(simple plunkr demo here)
SUMMARY:
There is a leak using ng-repeat after the 2nd wave iterating over an 'array' of custom objects like this :
<div ng-repeat="d_sampleObject in mySampleObjects">
{{d_sampleObject.description}}
</div>
Memory profile reveals an extra 'd_sampleObject' left over and not de-referenced. More details (via a controller and an injected service) below. A simple demonstration also in the provided plunkr link. Any thoughts and help greatly appreciated in advance!
NOTE: 'mySampleObjects' is an array of the following instances:
ml.MySampleObject = function (id) {
this.id = id;
this.description = 'this is object #:' + ' '+id;
}
DETAILS:
I have a custom object model that reflects the business domain objects that we utilize in our AngularJS app. I have found that when I pass an instance of one of my custom objects to ng-repeat, a reference is kept to it (I think by Angular) and memory is not freed. This happens on the second 'wave' (click on 'refresh') of the ng-repeat as it iterates, again, over its array of objects. This leak is exposed in my Profile tests (in Chrome) . Here is a short example in plunkr. Click on 'refresh' button once (or more) to see the extra 'd_sampleObject' object instance that is leaked (in Chrome Profile Inspection). Note, the 'd_sampleObject' name is only used when passed to ng-repeat. I have included screenshots of the extra object instance ('d_sampleObject') that is being leaked further below. Why is there a leak and how can it be avoided?
(Note, I have found if I don't iterate over my object collection (JS array) thru an object but rather thru a primitive index ('integer'), there is no leak. The leak seems to only happen when I use an object reference as a result of ng-repeat iterations)
SIMPLE HTML:
<!DOCTYPE html>
<html ng-app="memoryleak">
<head>
<meta charset="utf-8" />
<title>Memory Leak Test</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.13/angular.min.js" data-semver="1.3.13"></script>
<script src="app.js"></script>
<script src="dataservice.js"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-repeat="d_sampleObject in mySampleObjects">
{{d_sampleObject.description}}
</div>
<br>
<button ng-click="redo()">Number of refreshes: {{numRedos}}!</button>
</body>
</html>
SIMPLE APP.JS
(function(ml) {
var app = angular.module('memoryleak',['servicemodule']);
app.controller('MainCtrl', ['$scope', 'dataservice', function($scope, dataservice) {
$scope.redo = function () {
$scope.numRedos++;
$scope.mySampleObjects = dataservice.myObjectCollection;
dataservice.redo();
}
$scope.redo();
}]);
}(window.MEMLEAK = window.MEMLEAK || {}));
SIMPLE dataservice.js
(function(ml) {
'use strict';
var serviceModule = angular.module('servicemodule',[]);
serviceModule.factory('dataservice', ['$rootScope', '$http',
function ($rootScope, $http) {
this.myObjectCollection = [];
this.redo = function () {
this.numRedos++;
// that.myObjectCollection = [];
this.myObjectCollection.length = 0;
for (var i = 0; i < 10; i++) {
var sampleObject = new ml.MySampleObject(i);
that.myObjectCollection.push(sampleObject);
}
sampleObject=null;
}
ml.MySampleObject = function (id) {
this.id = id;
this.description = 'this is object #:' + ' '+id;
}
return this; //return the entire service to make methods accessible to dependents
}]);
}(window.MEMLEAK = window.MEMLEAK || {}));
SCREENSHOT 1: (FIRST PAGE LOAD--there are 10 'mySampleObjects' created)
SCREENSHOT 2: (CLICKED ON REFRESH--there is an 11th mySampleObject created/leaked with a reference to the instance name of 'd_sampleObject' passed to ng-repeat.)
There is acknowledgement by the AngularJS folks that this is indeed a bug in the framework. A fix and pull request has been posted.
I have also asked what the timeframe is for a formal fix.
I have the Controller
function loginController($scope, $http, $cookieStore, $location) {
var token = $cookieStore.get('token');
var conId = $cookieStore.get('Cont_Id');
var exId = $cookieStore.get('ex_Id');
$scope.log_me = function() {
$scope.login_me = [];
var login_un = $scope.uservals;
var login_pwd = $scope.passvals;
var logs_me = "api call here";
$http.get(logs_me)
.success(function(response) {
$cookieStore.put('token', response.token);
$cookieStore.put('ex_Id', response.ExId);
$cookieStore.put('Cont_Id', response.contactId);
$cookieStore.put('email', response.email);
$cookieStore.put('name', response.name);
$scope.log_sess = response;
$scope.sess_id= response.ss_id;
alert($scope.sess_id);
if (response.status == "failure, invalid username or password") {
$('.login_error').show();
$('.login_error').html('Invalid username or password');
$('.login_error').delay(4000).fadeOut();
$('.loading').hide();
} else {
$location.path('/dashboard');
}
});
}
}
I have used the above controller in my login page and it is working fine. Now i want to use the same controller in another template and retrieve the value "$scope.sess_id"
My Template is
<div class="page" >
<style>
#report_loader object {
width: 100%;
min-height: 700px;
width:100%;
height:100%;
}
</style>
<div class="loading"> </div>
<section class="panel panel-default" data-ng-controller="loginController">
<div class="panel-body" style=" position: relative;">
<div id="report_loader" style="min-height:600px;">
{{sess_id}}
<script type="text/javascript">
$("#report_loader").html('<object data="https://sampleurl/contact/reports/members/sorted_list.html?ss_id=' + sess_id+' />');
</script>
</div>
</div>
</section>
</div>
I am unable to retrieve the value {{sess_id}} here. What should be done so that i can bring this value in my template
You're routing the user to the "dashboard" route upon successful log in. Even though it might feel like you're using the same "loginController" for both login and dashboard, it will be an entirely new instance of both the controller and $scope. Which is why the {{sess_id}} is not displaying on the dashboard template.
If you're following an MVC-like pattern of AngularJS, ideally you want to be creating a new controller for your dashboard template. See explanation: https://docs.angularjs.org/guide/controller#using-controllers-correctly
So, I would create a DashboardCtrl and share the sess_id between the two. There are plenty of examples out there of how to share data between controllers:
You can use a factory: Share data between AngularJS controllers
You can use $rootScope: How do I use $rootScope in Angular to store variables?
Hope it helps.
I would use the rootScope approach, but an easier way to do that is to simply create a 'global' variable.
In your main controller (not your login controller), define a global scope variable like this:
$scope.global = {};
Then in your login controller, modify your session id to use the global variable:
$scope.global.sess_id= response.ss_id;
alert($scope.global.sess_id);
Then in your html:
<div id="report_loader" style="min-height:600px;">
{{global.sess_id}}
It's simple and works like champ.
I would create a service :
services.sessionService = function(){
var sessionID = null;
this.setSessionID = function(id){
sessionID = id;
}
this.getSessionID = function(){
return sessionID;
}
}
then in your controller :
$scope.sess_id= response.ss_id;
alert($scope.sess_id);
sessionService.setSessionID( $scope.sess_id );
and in your dashboard controller :
$scope.sess_id = sessionService.getSessionID();
Approaches
Your question's answer has many approach. They are:
Using value or service, you can call it wherever your controllers need them.
Using $rootScope, this is very common and easy to use. Just define your $rootScope inside your main controller or whatever controller that called first and then you can call it from other controllers like any $scope behavior.
Using $controller service or usually called controller inheritance. Define this in controller function's parameter, then type $controller('ControllerNameThatIWantToInheritance', {$scope:$scope});
Maybe any other approach can be use to it. Each of them have strength and weakness.
Examples:
using value
.value('MyValue', {
key: null
})
.controller('MyCtrl', function ($scope, MyValue) {
$scope.myValue = MyValue;
})
you can modified MyValue from service too
using $rootScope
.controller('FirstCtrl', function ($scope, $rootScope) {
$rootScope.key = 'Hello world!';
})
.controller('SecondCtrl', function ($scope, $rootScope) {
console.log($rootScope.key);
})
will print 'Hello World', you can also use it in view <div>{{key}}</div>
using $controller
.controller('FirstCtrl', function ($scope) {
$scope.key = 'Hello world!';
})
.controller('SecondCtrl', function ($scope, $controller) {
$controller('FirstCtrl', {$scope:$scope});
})
Second controller will have $scope like first controller.
Conclusion
In your problem, you can split your controller for convenient. But if you dont' want to, try to define $scope.sess_id first. It will tell the Angular that your sess_id is a defined model, and angular will watch them (if you not define it first, it will be 'undefined' and will be ignored).
function loginController($scope, $http, $cookieStore, $location) {
var token = $cookieStore.get('token');
var conId = $cookieStore.get('Cont_Id');
var exId = $cookieStore.get('ex_Id');
$scope.sess_id = null //<- add this
$scope.log_me = function() {
$scope.login_me = [];
var login_un = $scope.uservals;
var login_pwd = $scope.passvals;
var logs_me = "api call here";
$http.get(logs_me)
.success(function(response) {
$cookieStore.put('token', response.token);
$cookieStore.put('ex_Id', response.ExId);
$cookieStore.put('Cont_Id', response.contactId);
$cookieStore.put('email', response.email);
$cookieStore.put('name', response.name);
$scope.log_sess = response;
$scope.sess_id= response.ss_id;
alert($scope.sess_id);
if (response.status == "failure, invalid username or password") {
$('.login_error').show();
$('.login_error').html('Invalid username or password');
$('.login_error').delay(4000).fadeOut();
$('.loading').hide();
} else {
$location.path('/dashboard');
}
});
}
}
I've built an app with firebase that can login a user and attain their id, but I can't figure out how to incorporate this with a user making a submission of a string.
See Code pen here: http://codepen.io/chriscruz/pen/OPPeLg
HTML Below:
<html ng-app="fluttrApp">
<head>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<script src="https://cdn.firebase.com/js/client/2.0.2/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/0.9.0/angularfire.min.js"></script>
</head>
<body ng-controller="fluttrCtrl">
<button ng-click="auth.$authWithOAuthPopup('google')">Login with Google</button>
<li>Welcome, {{user.google.displayName }}</li>
<button ng-click="auth.$unauth()">Logout with Google</button>
<input ng-submit= "UpdateFirebaseWithString()" ng-model="string" ></input>
Javascript Below:
<script>
var app = angular.module("fluttrApp", ["firebase"]);
app.factory("Auth", ["$firebaseAuth", function($firebaseAuth) {
var ref = new Firebase("https://crowdfluttr.firebaseio.com/");
return $firebaseAuth(ref);
}]);
app.controller("fluttrCtrl", ["$scope", "Auth", function($scope, Auth) {
$scope.auth = Auth;
$scope.user = $scope.auth.$getAuth();
$scope.UpdateFirebaseWithString = function () {
url = "https://crowdfluttr.firebaseio.com/ideas"
var ref = new Firebase(url);
var sync = $firebaseAuth(ref);
$scope.ideas = sync.$asArray();
$scope.ideas.$add({
idea: $scope.string,
userId:$scope.user.google.id,
});
};
}])
</script>
</body>
</html>
Also assuming, the above dependencies, the below works to submit an idea, but the question still remains in how to associate this with a user. See codepen here on this: http://codepen.io/chriscruz/pen/raaENR
<body ng-controller="fluttrCtrl">
<form ng-submit="addIdea()">
<input ng-model="title">
</form>
<script>
var app = angular.module("fluttrApp", ["firebase"]);
app.controller("fluttrCtrl", function($scope, $firebase) {
var ref = new Firebase("https://crowdfluttr.firebaseio.com/ideas");
var sync = $firebase(ref);
$scope.ideas = sync.$asArray();
$scope.addIdea = function() {
$scope.ideas.$add(
{
"title": $scope.title,
}
);
$scope.title = '';
};
});
</script>
</body>
There a couple of things tripping you up.
Differences between $firebaseand $firebaseAuth
AngularFire 0.9 is made up of two primary bindings: $firebaseAuth and $firebase. The $firebaseAuth binding is for all things authentication. The $firebase binding is for synchronizing your data from Firebase as either an object or an array.
Inside of UpdateFirebaseWithString you are calling $asArray() on $firebaseAuth. This method belongs on a $firebase binding.
When to call $asArray()
When you call $asArray inside of the UpdateFirebaseWithString function you will create the binding and sync the array each time the function is called. Rather than do that you should create it outside of the function so it's only created one item.
Even better than that, you can abstract creation of the binding and the $asArray function into a factory.
Plunker Demo
app.factory("Ideas", ["$firebase", "Ref", function($firebase, Ref) {
var childRef = Ref.child('ideas');
return $firebase(childRef).$asArray();
}]);
Get the user before the controller invokes
You have the right idea by getting the user from $getAuth. This is a synchronous method, the app will block until the user is returned. Right now you'll need to get the user in each controller. You can make your life easier, by retrieving the user in the app's run function. Inside of the run function we can inject $rootScope and the custom Auth factory and attach the user to $rootScope. This way the user will available to all controllers (unless you override $scope.user inside of your controller).
app.run(["$rootScope", "Auth", function($rootScope, Auth) {
$rootScope.user = Auth.$getAuth();
}]);
This is a decent approach, but as mentioned before $scope.users can be overridden. An even better way would be to resolve to user from the route. There's a great section in AngularFire guide about this.
Associating a user with their data
Now that we have the user before the controller invokes, we can easily associate their id with their input.
app.controller("fluttrCtrl", ["$scope", "Ideas", function($scope, Ideas) {
$scope.ideas = Ideas;
$scope.idea = "";
$scope.UpdateFirebaseWithString = function () {
$scope.ideas.$add({
idea: $scope.idea,
userId: $scope.user.google.id,
}).then(function(ref) {
clearIdea();
});
};
function clearIdea() {
$scope.idea = "";
}
}]);
So I have a bootstrap list:
<div class="ajax_company_list" ng-app="app">
<div class='list-group' ng-controller="PolicyController as policyCtrl">
<a href="#" class='list-group-item' ng-repeat="company in policyCtrl.companies">{{company.primary_name}}
</a>
<div id="loadingIcon" class='list-group-item'>
Loading...
</div>
</div>
</div>
Here is my Angular Javascript:
var app = angular.module('app', []);
app.controller('PolicyController', ['$scope', 'CompanyService', function($scope, CompanyService) {
$scope.companies = [
{
policy_number: 12345,
primary_name: "test"
}
];
$scope.getCompanies = function() {
CompanyService.fetchCompanies()
.success(function(data) {
$scope.companies = data.companies;
})
}
}]);
app.factory('CompanyService', ['$http', function($http) {
return {
fetchCompanies: function() {
return $http.get('http://spoonerinc:8886//json/glmod_Spooner-Inc?pagenum=1');
}
}
}]);
I basically have 2 questions. If I set $scope.companies equal to an array of objects, it does not show up but if I change $scope.companies to this.companies, it starts working again. Why is this?
2nd question, I can see the service call running in my net tab and can console.log the data and it reads fine. But it is not updating my actual list at all and I'm not sure what I'm doing wrong.
I am fairly new to Angular so if there is any advice on how I can do my code better, please let me know.
Thanks!
Because you are using the "Controller As" syntax, which effectively publishes the entire controller object to the scope.
What happens under the hood looks something like this:
function myCtrl($scope){
$scope['someAlias'] = this;
}
If you are going to use the controller as syntax, it's best to use a more object based approach instead of pushing things onto the $scope
Either on the prototype:
function myCtrl(companiesService){
this.companiesService = companiesService;
this.init();
}
myCtrl.prototype = {
init:function(){
var _this = this;
_this.companiesService.get()
.then(function(result){
_this.companies = result.data;
});
}
};
Or as closure style object:
function myCtrl(comapniesService){
var ctrl = {};
function init(){
companiesService.get()
.then(function(result){
ctrl.companies = result.data;
});
}
return ctrl;
}
For your second question, I think your problem is here:
ng-repeat="company in policyCtrl.companies"
You don't need to specify the controller as a prefix, since you've already declared it with ng-controller. It should be:
ng-repeat="company in companies"
And ng-controller to be:
ng-controller="PolicyController"
My guess is that the first problem will go away once you correct this.