$rootscope object model from service not (always) available in every controller - angularjs

In my app definition I have:
var myVtModule = angular.module('vtApp', ['myAppDev','mongoAPI']);
myVtModule.run(function($rootScope, $location, Shop){
$rootScope.shopData = {};
Shop.getShop(function(response){
$rootScope.shopData = response;
});
})
Shop is a service retrieving data from server, it works. The problem is that in the controller I don't always have access to shopData, sometimes is empty, sometimes is working normally.
function SupportCtrl($rootScope, $scope) {
console.log ($rootScope.shopData);
}
Why is not updating when it receives the response from the service? I can't put the Shop.getShop in the controller as I need it everywhere...

My suggestion would be to use controller inheritance and a service so that your child controllers can always have access to the shop. Here is a demo:
http://beta.plnkr.co/edit/DtjwLMi3jHCdzThEJDNn?p=preview
Code:
angular.module('myApp', ['myApp.services']);
function MainCtrl($scope, ShopService) {
$scope.shop = ShopService.getShop();
}
function ChildCtrl($scope) {
}
angular.module('myApp.services', []).
factory('ShopService', function ($rootScope) {
var shop = {
storename: 'your store'
};
var service = {
getShop: function() {
return shop;
}
}
return service;
});
HTML that sets up the child controller relationship:
<html ng-app="myApp" >
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css">
<script>document.write("<base href=\"" + document.location + "\" />");</script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
The store is {{shop.storename}}
<div ng-controller="ChildCtrl">
The store name is: {{shop.storename}}
</div>
</body>
</html>
You will notice that the shop is available to the child because the parent scope retrieved it. I set up the service so you can make it testable/etc. Besides the inheritance you could just as easily inject the ShopService into each controller and have them make the call, but if you have a lot of controllers that could become tedious.
Some more info on scope inheritance:
can i inherit a parent controller's variables?
http://docs.angularjs.org/guide/dev_guide.mvc.understanding_controller

If you need the results in your controller, you should skip myVtModule.run and do something like this instead:
function SupportCtrl($scope, Shop) {
Shop.getShop(function(response){
console.log (response);
//do something, put response in $scope or $scope.$rootScope etc...
});
}

Your ng-controller scopes all prototypically inherit from rootScope (eventually), so they all have access to any objects you define on rootScope. Since retrieving data from a server is an asynchronous operation, your controllers may execute before the server data is returned.
You can use $watch in your controllers to determine when the data is actually available. However, if you just want your views to update when the data becomes available, you don't have to use $watch. Angular should automatically update your views when the data becomes available. E.g.,
function SupportCtrl($rootScope, $scope) {
$scope.shopData = $rootScope.shopData;
}
Then in your view:
<div ng-controller="SupportCtrl">shopData = {{shopData}}</div>
Fiddle - I simulated a server delay using $timeout. Note that in the fiddle I also use angular.copy() to update the reference the controller has, rather than assigning $rootScope.shopData to a new/different array:
app.run(function($rootScope, $timeout){
$rootScope.shopData = [];
$timeout(function() {
angular.copy([{d1: 'data1', d2: 'data2'}], $rootScope.shopData);
}, 2000);
});

Related

Pass value from one controller to another in angular js1

I am using $rootScope for passing value from one controller to another but i did not get the value in second controller(cart).
x.controller("first",function($scope,$http,$rootScope){
$scope.cart1=function(x,y){
$rootScope.img=x;
$rootScope.login=y;
}
});
x.controller("cart",function($scope,$rootScope){
$scope.cart2=$rootScope.img;
console.log($scope.cart2)
});
$rootScope does not get the value assigned if you simply define in controller, you need to transfer the data from controller1 to controller2 based on some event
DEMO
var x =angular.module('app', [])
x.controller("first",function($scope,$rootScope){
$scope.cart1=function(x,y){
console.log(x);
$rootScope.img=x;
$rootScope.login=y;
}
});
x.controller("cart",function($scope,$rootScope){
$scope.getData=function(){
$scope.cart2=$rootScope.img;
console.log($scope.cart2)
};
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.4.3" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body >
<div ng-controller="first">
<button ng-click="cart1('test','test2')">transfer</button>
</div>
<div ng-controller="cart">
<button ng-click="getData()">getStoredValue</button>
{{cart2}}
</div>
</body>
</html>
NOTE
Using $rootScope is not a recommended way to transfer variable across controllers, instead try using services.
why not using $broadcast, considering your controller structure $scope.$parent is enough, in oter case inject $rootScope from where you are firing the $broadcast
var x =angular.module('app', [])
x.controller("first",function($scope){
$scope.cart1=function(x,y){
$scope.$parent.$broadcast('value changed', {x: X, y: y});
}
});
x.controller("cart",function($scope){
$scope.$on('value changed', function(data){
$scope.login = x;
$scope.cart2 = y;
});
});
Now your values are set in scope, use it whenever needed (in case of binding it will automatically reflect in UI)
(function() {
"use strict";
angular
.module('app')
.factory("cartFactory", cartFactory);
function cartFactory($http) {
return {
login: '',
cart2:''
};
}})();
This is your factory
you can use multiple controller like this...
var x =angular.module('app', [])
x.controller("first",function($scope,cartFactory){
cartFactory.login=x;
cartFactory.cartFactory=y;
}
});
x.controller("cart",function($scope,cartFactory){
$scope.login = cartFactory.login;
$scope.cart2 = cartFactory.cart2;
});
This is very simple to pass data one controller to another multiple controller.

Angularjs TypeError: is not a function issue

I have a select that calls a controller in a directive, that in turn calls a function from a service when the user selects a value in the dropdown list. For some reason I'm getting this error:
TypeError: myService.getMessage is not a function
So I created a plunker to pull out the basic functionality and I was able to duplicate the error using a controller to call a basic service, but I'm still not solving it yet.
Here is the code:
HTML Code:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.2.x" src="https://code.angularjs.org/1.2.22/angular.js" data-semver="1.2.22"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<select ng-options="option for option in listOfOptions"
ng-model="selectedItem"
ng-change="selectedItemChanged()">
</select>
<p>This is the selected Item from the model: <b>{{selectedItem}}</b></p>
<p>This is the result of the ng-change function: <b>{{calculatedValue}}</b></p>
</body>
Script Code:
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, myService) {
$scope.listOfOptions = ['One', 'Two', 'Three'];
$scope.selectedItemChanged = function(){
$scope.calculatedValue = 'You selected number ' + $scope.selectedItem;
//Call the service.
myService.getMessage();
}
});
app.service('myService', function(){
function getMessage(){
alert("You are in myService!");
}
});
I've seen lots of different, much more complicated code for this type of error, but I'm not understanding what is causing this? Any ideas as to the proper way to do this?
What I'm trying to do is to use a function like myService.mySearch(param1) from a controller or directive.
wrong service code, should be:
app.service('myService', function() {
this.getMessage = function() {
alert("You are in myService!");
}
});
.service() is a function which takes a name and a function that defines the service.It acts as a constructor function.We can inject and use that particular service in other components : controllers, directives and filters.
Correct Syntax :
app.service('myService', function(){
this.getMessage = function(){
alert("You are in myService!");
}
});
Main thing is that service is a constructor function. Hence, we can work with this keyword. In background, this code calls Object.create() with the service constructor function, when it gets instantiated.
In addition to Fetra R 's answer,
Factory is mostly preferable in all cases. It can be used when you have constructor function which needs to be instantiated in different controllers. Service is a kind of Singleton Object. The Object return from Service will be same for all controller.
So as it is like a singleton object, you have to declare it as The Fetra R's Answer, or You may write a factory.
app.factory('myService', function() {
return {
getMessage: function() {
alert("You are in myService!");
}
}
});
and use it in the same manner.

View is not updated after the $http async call when using controllerAs syntax

I am learning angularjs and I am trying to use the controllerAs syntax as I am from Java background and this would make more sense to me but I am having trouble understanding the digest loop.
I am trying to do a http call and update the variable in the controller.When I am using $scope in controller the view is updated after the data is received but when I am using the controllerAs syntax the view is not updated.
Codepen with $scope Syntax
http://codepen.io/eternal15/pen/BzANEw?editors=1111
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.2/angular.min.js"></script>
</head>
<body ng-app="Test" ng-controller="testCtrl">
{{output}}
<button ng-click="onClick()">Test</button>
</body>
</html>
//JS FILE
angular.module("Test", []).controller('testCtrl', ['$scope','$http', function($scope, $http){
$scope.output = "Loading";
$scope.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
$scope.output = "worked!!";
console.log($scope.output);
})
}
}]);
Codepen with controllerAs Syntax (View not updated)
http://codepen.io/eternal15/pen/yJKoaZ?editors=1011
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.2/angular.min.js"></script>
</head>
<body ng-app="Test" ng-controller="testCtrl as test">
{{test.output}}
<button ng-click="test.onClick()">Test</button>
</body>
</html>
//JS File
angular.module("Test", []).controller('testCtrl', ['$http', function($http){
this.output = "Loading";
this.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
this.output = "worked!!";
console.log(this.output);
})
}
}]);
I have read about the controllerAs syntax and I think it would add the object (test in the example above) under scope and thus the variables are accessible using (test) object.
So the digest loop runs after $http call because the view is updated in the first example using $scope. Since the digest loop is executed the object test in the second example should also be updated right?
Also i tried to inject $scope and do $scope.$apply() and that also didn't work and it gave me this error
Error: [$rootScope:inprog] http://errors.angularjs.org/1.5.2/$rootScope/inprog?p0=%24digest
I would like to know what I am doing wrong. Although i could go back to using the $scope format, I would like to know if I am doing something wrong or should I add other statements to watch the variables and update the values manually.
Thanks in advance
this has a different meaning inside function. Assign this to a variable and use it. Try:
angular.module("Test", []).controller('testCtrl', ['$http', function($http){
var vm = this;
vm.output = "Loading";
vm.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
vm.output = "worked!!";
console.log(vm.output);
})
}
}]);
This is because of javascripts closures. When defining a new function you're creating a new scope, hence the keyword this has a new meaning for each new scope.
To solve this, define the controllers scope at the top of your controller. Common names used are either vmor $ctrl.
Your controller would then look somehting like this:
angular.module("Test", []).controller('testCtrl', ['$http', function( $http){
var $ctrl = this;
$ctrl.output = "Loading";
$ctrl.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
$ctrl.output = "worked!!";
//$scope.$apply();
})
}
}]);

AngularJS: Binding JavaScript Received in XHR to the View

Working on a reporting application where reports are generated (in HTML) from a BIRT Report Engine object. The report data comes as a JSON string is recieved from XHR. The JSON string contains a combination of HTML and javascript (a function call, specifically). Once received, the report data is stuffed into a for display in the view. The view is put together using AngularJS.
I did some research and found that binding the HTML/javascript to the view in Angular requires the use of $compile. Using that as a basis, i put together some code that will include data and execute code bound from a string defined explicitly in the $scope. But - unless i'm going overlooking something after staring at the same stuff for a couple hours, the approach i'm using does not work with $scope data defined by XHR. Here's a plunkr to show the general idea implemented. Any suggestions would be greatly appreciated.
The HTML
<!DOCTYPE html>
<html data-ng-app="example">
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.11.0/ui-bootstrap-tpls.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-sanitize.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="controller" >
<h1>Static Content</h1>
<p><button href="javascript: void(null)" role="link" ng-click="loadSubreport('recv_po_detail.rptdesign', 'static')">PO000007</button></p>
<h1>HTML from static string</h1>
<div compile="snippet"></div>
<h1>HTML from server string</h1>
<div compile="html.body"></div>
<hr />
<button ng-click="alert()">Show XHR Data</button>
</body>
</html>
The Javascript
var app = angular.module('example', []);
app.controller('controller', ['$scope', '$compile', '$http', function ($scope, $compile, $http){
$scope.snippet="<p><button href=\"javascript: void(null)\" ng-click=\"loadSubreport('recv_po_detail.rptdesign', 'compiled')\">PO000007</button></p>";
$http.get('data.json')
.success(function (data) {
$scope.html = data;
});
$scope.loadSubreport = function (filename, source) {
alert("Called from " + source);
};
$scope.alert = function () {{
alert($scope.html.body);
}}
}]);
app.directive('compile', ['$compile', function ($compile) {
"use strict";
return function (scope, element, attrs) {
var ensureCompileRunsOnce = scope.$watch(
function (scope) {
return scope.$eval(attrs.compile);
},
function (value) {
element.html(value);
$compile(element.contents())(scope);
ensureCompileRunsOnce();
}
);
};
}]);
Your watch goes off right at the start, when html.body still is undefined.
Then you run ensureCompileRunsOnce() and unwatch the scope. So the proper report, once loaded, never gets compiled.
I uncommented the line ensureCompileRunsOnce() and get a nice view of the report.
DEMO

Why doesn't ng-hide/show work if expression is a scope variable modified on broadcast event

I have the following code:
script.js:
var app = angular.module('TestApp',[]);
app.factory('testfactory', function($rootScope, $window){
var factory = {};
factory.auth = function() {
//This is only to make call to auth() async.
//Actual code is using google-api call.
setTimeout(auth, 2000);
}
$window.auth = function(){
$rootScope.$broadcast('loggedin');
}
return factory;
});
app.controller('MainCtrl', function($scope, testfactory) {
$scope.status = {
loggedIn: false
}
$scope.test = testfactory;
$scope.$on('loggedin', function(){
$scope.status.loggedIn = true;
alert('loggedIn');
});
});
index.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body ng-app="TestApp">
<div ng-controller="MainCtrl">
<button ng-click="test.auth()" ng-hide="status.loggedIn">Auth</button>
</div>
<script data-require="angular.js#1.2.19" data-semver="1.2.19" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
This should hide the button "Auth" on clicking, however it does not. It works only when its clicked the second time. What's happening here? I'm modifying the scope variable inside angular's broadcasted event, so binding should work. What am I missing here?
Edit: I know wrapping the code within $scope.$apply works, but my question is why isn't it happening automatically, since I'm not modifying the variable from outside the scope.
Here's a plunker for this code - http://plnkr.co/edit/Ov568VDWCKarFHQjgbvG
Answer: This discussion on google groups says $broadcast doesn't trigger auto-apply. So, if $broadcast is called from outside of angular-world, $apply must be applied manually.
Because Angular use $digest (documentation, and why/where to use) to keep the binding between the $scope and the interface. Try to force the $digest:
$scope.$on('loggedin', function(){
$scope.status.loggedIn = true;
$scope.digest();
alert('loggedIn');
});
or
$scope.$on('loggedin', function(){
$scope.apply(function(){
$scope.status.loggedIn = true;
});
alert('loggedIn');
});
Edit:
why isn't it happening automatically ?
The setTimeout function runs outside the angular scope, therefore angular has no idea that you might change something.
You could also solve the problem with the #pixelbits solution. The $timeout service is just a wrapper around javascript's setTimeout witch executes within the angular scope.
http://plnkr.co/edit/o3XtMLpCquHcU2j2z4v9?p=preview
please add $scope.$apply();
app.controller('MainCtrl', function($scope, testfactory) {
$scope.status = {
loggedIn: false
}
$scope.test = testfactory;
$scope.$on('loggedin', function()
{
$scope.status.loggedIn = true;
$scope.$apply();
alert('loggedIn');
});
});
Use $timeout instead of setTimeout. $timeout will trigger a $digest cycle, but setTimeout will not:
$timeout(auth,2000);

Resources