$rootScope in AngularJS stays UNDEFINED in Controller despite injection? - angularjs

It's a small ionic / angularJS project for a mobile app.
I am trying to read a JSON file in the same folder as index.html, then assigning it to a $rootScope variable in the RUN segment and then retrieving the $rootScope variable value in a controller. But it's simply not working despite service injections and all.
This is my JSON file:
{"docs":[{"Name":"Alfreds Futterkiste","City":"Berlin","Country":"Germany"},
{"Name":"Ana Trujillin Moreo","City":"México D.F.","Country":"Mexico"}
]
}
This is my app.js / script.js:
angular.module('starter', ['ionic'])
.run(function($ionicPlatform, $rootScope, $http) {
$ionicPlatform.ready(function() {
$rootScope.myDocs = $http.get("/customers.json")
.success(function (response) {
return response.docs;
});
});
})
.controller('MyCtrl', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.myDocs = $rootScope.myDocs;
//$scope.myDocs = [{'Name': 'a1'},{'Name': 'b1'},{'Name': 'c1'}];
$scope.myStr = JSON.stringify($rootScope.myDocs);
}])
This is the relevant portion from my index.html:
<body ng-app="starter">
<ion-pane>
<ion-header-bar class="bar-stable">
<h1 class="title">Ionic Blank Starter</h1>
</ion-header-bar>
<ion-content ng-controller="MyCtrl">
<ion-list>
<ion-item ng-repeat="x in myDocs">
{{x.Name}}
</ion-item>
</ion-list>
</ion-content>
</ion-pane>
</body>
I have seen other similar examples here but this is still not working and I been banging my head against the wall to achieve a very simple thing.
Fast help is much appreciated.
Here is the Plunker: http://plnkr.co/edit/zx9ANUzCYVvosZFiPSGY?p=info

You're having a timing issue because of ionicPlatform.ready and you aren't using the $http promise correctly.
This is one way to do it:
.run(function($rootScope, $http) {
// $http returns a promise, not the value
$rootScope.docPromise = $http.get("/customers.json")
.then(function(response) {
return response.docs;
});
})
.controller('MyCtrl', ['$scope', '$rootScope', function($scope, $rootScope) {
// you get the value within a .then function on the promise
$rootScope.docPromise.then(function(result) {
$scope.myDocs = result;
})
}])
It is unlikely that you need to make this http call within ionicPlatform.ready. If you don't need to, then it is only delaying the call for no benefit. If for some reason you do need to make the call within the ready function, there are numerous ways to solve this. Here is one example:
// inject $q service
var deferred = $q.defer();
$rootScope.docPromise = deferred.promise;
$ionicPlatform.ready(function() {
$http.get(etc).then(function(response) {
deferred.resolve(response.docs);
});
});
// use $rootScope.docPromise as in the previous example

You might be assuming that the return value from the success method on $http.get() will be assigned to myDocs property on $rootScope but it doesn't work that way. $http.get() is an Asynchronous operation and the success callback will be invoked once the response is available. Also the return value from your success callback is not assigned because it happens sometime in the future.
You need to update your ready() like below.
$ionicPlatform.ready(function() {
/* get will return a promise, cache it in on rootScope for use in controller*/
$rootScope.httpPromise = $http.get("/customers.json");
});
In your Controller
.controller('MyCtrl', ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.httpPromise.then(function(response){
$scope.myDocs = response.data.docs;
});
}]);

Related

AJAX call using Angular Seed

Edit: James P. led me to determine that the issue appears to be with CORS and not necessarily with any of the Angular code. Please read comments below.
I am very new to AngularJS and JS altogether, so I'm sure the answer to this is something simple that I have overlooked so thank you in advance.
I am using Angular Seed and I have created an API that is verified working (as in, I go to my URL:3000/getstuff and it displays queries from my mongodb just fine).
That API returns a JSON format response from a mongodb with 3 key/pairs including id. I am able to view it in browser just fine.
So in Angular seed, very basic view1/view1.js I modified to as such:
'use strict';
angular.module('myApp.view1', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {
templateUrl: 'view1/view1.html',
controller: 'View1Ctrl'
});
}])
.controller('View1Ctrl', ['$scope', '$http', function($scope, $http) {
//$scope.test = "test";
//the above works when I bind in view1.html
$http.get('http://x.x.x.x:3000/getstuff').
success(function(response) {
$scope.information = response;
});
}]);
And it is not working as I thought it might. So when I try to bind this response in view1.html with a simple {{information}} it's blank. This code did not break the app either, it still works and I am able to display {{test}} if I uncomment it.
Anyhow, any help would be very much appreciated. And for the record, I have been reading up on this for days and days before posting this. I am just a novice is all.
You need to use angular.copy to copy the data to your $scope variable:
$http.get('http://x.x.x.x:3000/getstuff')
.then(function (response) {
// Success
angular.copy(response.data, $scope.information);
}, function () {
// Failure
});
Then in your view:
<div ng-controller="MainController">
<div ng-repeat="item in information">
<p>{{item.id}}</p>
<p>{{item.type}}</p>
<p>{{item.text}}</p>
</div>
</div>
Where id ,type and text are properties of each item in the Array.
EDIT: Just for completeness the following is a working example, with above html snippet.
(function () {
"use strict";
angular.module("app", []);
angular.module("app").controller("MainController", mainController);
function mainController($scope, $http) {
$scope.information = [];
$http.get("http://api.scb.se/OV0104/v1/doris/en/ssd")
.then(function (response) {
// Success
angular.copy(response.data, $scope.information);
}, function () {
// Failure
});
}
})();

Angularjs - Calling only one of many subcontrollers/ multiple controllers

I have an index page wherein I define two controllers. I want to call one main controller always (should be rendered always) and the other is called only for specific sub URL calls. Should I make one nested within another, or I can keep them independent of each other? I don't have access to change routes or anything, only the controller.
Right now when I use the template (HTML) mentioned, it calls/renders both controllers, even though url is say /index
Only for /index/subPage, I want both controllers to be rendering.
/index
/index/subPage
HTML:
<div ng-controller="MainCtl" ng-init=initMain()>
<p> Within ctller2 {{results}} </p>
</div>
<div ng-controller="Ctller2"> <!-- should not be displayed unless /subPage/mainUrl is rendering -->
<p> Within ctller2 {{results}} </p>
</div>
JS:
app.controller('MainCtl', ['$scope', '$http', '$location', function ($scope, $http, $location) {
$http.get('xx/mainUrl').then(function(data) {
$scope.results = someDataJSON;
console.log(someDataJSON);
});
$scope.initMain = function() {
$scope.initMethods();
}
}]);
app.controller('Ctller2', ['$scope', '$http', '$location', function ($scope, $http, $location) {
// This controller gets initialized/rendered/called even when xx/mainUrl is called, and when xx/subPage/mainUrl is called too..
$http.get('xx/subPage/mainUrl').then(function(data) {
$scope.results = someDataJSON;
console.log(someDataJSON);
})
$http.get('xx/subPage').then(function(data) {
$scope.results = data.data;
console.log(data);
})
angular.element(document).ready(function () {
alert('Hello from SubCtl, moving over from main controller to here');
});
}]);
What am I doing wrong? I'm new to Angular.js
You can conditionally initiate a controller using ng-if. So you could try something like this:
<body ng-controller="MainCtrl">
<div ng-controller="ctrl1">{{hello}}</div>
<div ng-controller="ctrl2" ng-if="showCtrl2">{{hello}}</div>
</body>
and then set the value of the variable in a parent controller by checking the current url using $location.path()
var app = angular.module('plunker', []);
app.config(function($locationProvider){
$locationProvider.html5Mode(true);
});
app.controller('MainCtrl', function($scope, $location) {
$scope.showCtrl2 = ($location.path() === 'my path');
});
app.controller('ctrl1', function($scope){
$scope.hello = 'ctrl1 says hello';
});
app.controller('ctrl2', function($scope){
$scope.hello = 'ctrl2 says hello';
});
But it's a bit hacky and for a larger project a more robust solution would require using something like ui.router.

AngularJS: TypeError: Cannot read property 'get' of undefined

I am trying to use a service inside my angular controller, however I am getting the error in the TypeError: Cannot read property 'get' of undefined.
This is how my app.js script look like:
angular
.module('ToDoApp', ['ngAnimate', 'todoService'])
.controller('ToDoController', ['$scope', function($scope, $http, Todo) {
Todo.get()
.success(function(data) {
$scope.tasks = data;
});
// ...
}]);
It doesn't know about the get propery. This is how Todo looks like:
angular.module('todoService', [])
.factory('Todo', function($http) {
return {
get: function() {
return $http.get('/todos');
},
// ...
}
});
And I'm loading the scripts in this order:
<script src="{{ asset('/scripts/services/todoService.js') }}"></script>
<script src="{{ asset('/scripts/app.js') }}"></script>
The error sounds to me like the controller isn't getting the service object correctly, like this dependency is missing. But I already loaded the dependency with the module.
What am I missing?
Deleted my previous comment, figured out what you had wrong. You're using the array notation to fix minification, but you're only listing $scope, you need to list the others as well, meaning you need to add the $http and the ToDo:
angular
.module('ToDoApp', ['ngAnimate', 'todoService'])
.controller('ToDoController', ['$scope', '$http', 'ToDo', function($scope, $http, Todo) {
Todo.get()
.success(function(data) {
$scope.tasks = data;
});
// ...
}]);

Angular factory returning a promise

When my app starts I load some settings from a server. Most of my controllers need this before anything useful can be done. I want to simplify the controller's code as much as possible. My attempt, which doesn't work, is something like this:
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {
var deferred = $q.defer();
$http.get('/api/public/settings/get').success(function(data) {
$rootScope.settings = data;
deferred.resolve();
});
return deferred.promise;
}]);
app.controller('SomeCtrl', ['$rootScope', 'settings', function($rootScope, settings) {
// Here I want settings to be available
}]);
I would like to avoid having a lot of settings.then(function() ...) everywhere.
Any ideas on how to solve this in a nice way?
$http itself return promise you don't need to bind it inside the $q this is not a good practice and considered as Anti Pattern.
Use:-
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http) {
return $http.get('/api/public/settings/get')
}]);
app.controller('SomeCtrl', ['settings',$scope, function(settings,$scope) {
settings.then(function(result){
$scope.settings=result.data;
});
}]);
Your way can be done as :-
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {
var deferred = $q.defer();
$http.get('/api/public/settings/get').success(function(data) {
deferred.resolve(data);
});
return deferred.promise;
}]);
app.controller('SomeCtrl', ['$scope', 'settings', function($scope, settings) {
settings.then(function(data){
$scope.settings=data;
})
}]);
Don't overload $rootScope if you wanted it you need to use $watch for the changes in $rootScope(Not recommended).
Somewhere you would need to "wait".
The only built-in way in Angular to completely absolve the controller from having to wait on its own for async data to be loaded is to instantiate a controller with $routeProvider's route's resolve property (or the alternative $stateProvider of ui.router). This will run controller only when all the promises are resolved, and the resolved data would be injected.
So, ng-route alternative - plunker:
$routeProvider.when("/", {
controller: "SomeCtrl",
templateUrl: "someTemplate.html",
resolve: {
settings: function(settingsSvc){
return settingsSvc.load(); // I renamed the loading function for clarity
}
});
Then, in SomeCtrl you can add settings as an injectable dependency:
.controller("SomeCtrl", function($scope, settings){
if (settings.foo) $scope.bar = "foo is on";
})
This will "wait" to load someTemplate in <div ng-view></div> until settings is resolved.
The settingsSvc should cache the promise so that it won't need to redo the HTTP request. Note, that as mentioned in another answer, there is no need for $q.defer when the API you are using (like $http) already returns a promise:
.factory("settingsSvc", function($http){
var svc = {settings: {}};
var promise = $http.get('/api/public/settings/get').success(function(data){
svc.settings = data; // optionally set the settings data here
});
svc.load = function(){
return promise;
}
return svc;
});
Another approach, if you don't like the ngRoute way, could be to have the settings service broadcast on $rootScope an event when settings were loaded, and controllers could react to it and do whatever. But that seems "heavier" than .then.
I guess the third way - plunker - would be to have an app-level controller "enabling" the rest of the app only when all the dependencies have preloaded:
.controller("AppCtrl", function($q, settingsSvc, someOtherService){
$scope.loaded = false;
$q.all([settingsSvc.load, someOtherService.prefetch]).then(function(){
$scope.loaded = true;
});
});
And in the View, toggle ng-if with loaded:
<body ng-controller="AppCtrl">
<div ng-if="!loaded">loading app...</div>
<div ng-if="loaded">
<div ng-controller="MainCtrl"></div>
<div ng-controller="MenuCtrl"></div>
</div>
</body>
Fo ui-router this is easily done with having an application root state with at least this minimum definition
$stateProvider
.state('app', {
abstract: true,
template: '<div ui-view></div>'
resolve: {
settings: function($http){
return $http.get('/api/public/settings/get')
.then(function(response) {return response.data});
}
}
})
After this you can make all application states inherit from this root state and
All controllers will be executed only after settings are loaded
All controllers will gain access to settings resolved value as possible injectable.
As mentioned above resolve also works for the original ng-route but since it does not support nesting the approach is not as useful as for ui-router.
You can manually bootstrap your application after settings are loaded.
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");
var $rootScope = initInjector.get("$rootScope");
$http.get('/api/public/settings/get').success(function(data) {
$rootScope.settings = data;
angular.element(document).ready(function () {
angular.bootstrap(document, ["app"]);
});
});
In this case your whole application will run only after the settings are loaded.
See Angular bootstrap documentation for details

Scope not updating in the view after promise is resolved

Even though there is a similar question for this Data is not getting updated in the view after promise is resolved but I am already using this approach and the view is not being updated.
I have a factory:
'use strict';
myApp
.factory('Factory1', [ '$http','$q','$location', '$rootScope', 'Service', function($http, $q, $location, $rootScope, Service){
return {
checkSomething: function(data){
var deferred = $q.defer(); //init promise
Service.checkSomething(data,function(response){
// This is a response from a get request from a service
deferred.resolve(response);
});
return deferred.promise;
}
};
}]);
I have a controller:
'use strict';
myApp
.controller('MyCtrl', ['$rootScope', '$scope', '$location','Service', 'Factory1' function($rootScope, $scope, $location, Service, Factory1) {
if(Service.someCheck() !== undefined)
{
// Setting the variable when view is loaded for the first time, but this shouldn't effect anything
$scope.stringToDisplay = "Loaded";
}
$scope.clickMe = function(){
Factory1.chechSomething($scope.inputData).then(function(response){
$scope.stringToDisplay = response.someData; // The data here is actually being loaded!
});
};
}]);
And the view:
<div class="app " ng-controller="MyCtrl">
{{stringToDisplay}}
<button class="button" ng-click="clickMe()">Update display</button>
</div>
But the data is not being updated in the view when I click on a button "Update display". Why?
Even though the $scope is being loaded with the data
EDIT:
Hm, it seems that I am getting a error when I try $scope.$apply() and it says:
[$rootScope:inprog] $digest already in progress
This might be a digest cycle issue. You could try:
...
$scope.stringToDisplay = response.someData;
$scope.$apply();
...
For the sake of completeness, here is a nice wrap-up of $scope.$apply.
Edit: I tried to reproduce the error in this fiddle, but cannot seem to find any issues. It works flawlessly without $scope.$apply. I use setTimeout in order to simulate asynchronous operations, which should not trigger a digest cycle by itself.
Try this instead of your function
$scope.clickMe =$scope.$apply(function(){
Factory1.chechSomething($scope.inputData).then(function(response){
$scope.stringToDisplay = response.someData; // The data here is actually being loaded!
});
});

Resources