How to handle nested services and promises using resolve and $routeChangeError - javascript

That's more like a research I did while I was playing with AngularJS and I would like to share as I think some people might find this useful.
Sometimes you need to fetch some data from several services before you instantiate the controller and render the view.
You could also have a situation when a particular service is waiting for a response from another service - kinda of nested service structure.
On top of that you want to make sure that if any of these services fails you will handle the error accordingly.

The module myApp has to services called myFirstService and mySecondService.
If you make any of the services fail by rejecting it:
defer.reject("second Service Failed");
The $routeChangeError event is fired and a message is displayed to the user in the console.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myApp</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"> </script>
<script>
var myApp = angular.module('myApp', []);
myApp.config(function($routeProvider){
$routeProvider
.when('/',
{
controller: 'ViewCtrl',
templateUrl: 'view/app.html',
resolve: {
loadData: function(myFirstService){
return myFirstService.start();
}
}
})
});
var appCtrl = myApp.controller('AppCtrl', function($scope, $rootScope){
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
console.log('Some service has failed: ', rejection);
});
});
var viewCtrl = myApp.controller('ViewCtrl', function($scope, $route){
$scope.feedback = {
message: 'All the services are working!'
}
});
myApp.factory('myFirstService', ['$q', '$timeout','mySecondService', function($q, $timeout, mySecondService) {
var defer = $q.defer();
return {
start: function() {
$timeout(function(){
defer.resolve('FIRST Service \'myFirstService\' Failed');
}, 2000);
return mySecondService.start().then(function(){
return defer.promise
});
}
}
}]);
myApp.factory('mySecondService', ['$q', '$timeout', function($q, $timeout) {
var defer = $q.defer();
return {
start: function() {
$timeout(function(){
defer.resolve("second Service Failed");
}, 2000);
return defer.promise;
}
}
}]);
</script>
</head>
<body ng-app="myApp" ng-controller="AppCtrl">
<script id="view/app.html" type="text/ng-template">
<h1>{{ feedback.message }}</h1>
</script>
<div ng-view></div>
</body>
</html>

Related

cannot pass parameters via angular service between 2 html file

I have 2 html file and I want to pass parameters via angular service between them.
these are the files I have:
index.html
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body>
<script type="text/javascript" src="services.js"></script>
<div ng-app="myApp" ng-controller="myCtrl2">
</div>
enter here
<script>
var app=angular.module("myApp");
app.controller("myCtrl2", ['$scope','$location', 'myService',
function($scope, $location, myService) {
myService.set("world");
}]);
</script>
</body>
</html>
enter2.html
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body>
<script type="text/javascript" src="services.js"></script>
<div ng-app="myApp" ng-controller="myCtrl3">
hello {{x}}
</div><script type="text/javascript">
var app=angular.module("myApp");
app.controller("myCtrl3", ['$scope','$location', 'myService',
function($scope, $location, myService) {
$scope.x=myService.get();
}]);
</script>
</body>
</html>
services.js
var app=angular.module("myApp", []);
app.factory('myService', function() {
var savedData = {}
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
why can't I get "hello world" in enter2.html, but instead get "hello" (x is not found by service)...?
When you go from index.html to enter2.html the whole page loads from scratch. For the data that you are expecting to stay in the browser, you might need to use advanced angular concepts such as loading just a part of the page using ng-view.
If that's something you have already overruled, saving the data in the service somewhere (may be the browser session) before unloading (window.onunload event) the page and then loading it back from there when the service loads (window.onload event) could also work.
Here is a working example based on your code.
I kept your index.html and added ui-view to have a single page application. The app uses 'ui.router'.
In the myCtrl2 I saved the data in the service, and call it back from myCtrl3:
.controller('myCtrl2', ['$scope', 'myService', function($scope, myService) {
console.log('myCtrl2');
myService.set('world');
}])
.controller('myCtrl3', ['myService', function(myService) {
console.log('myCtrl3');
var vm = this;
vm.x = myService.get();
}])
To keep things simple, I have one Javascript file:
angular.module('myApp', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'index.html',
controller: 'myCtrl2',
controllerAs: 'vm'
})
.state('enter2', {
url: '/enter2',
templateUrl: 'enter2.html',
controller: 'myCtrl3',
controllerAs: 'vm'
});
})
.factory('myService', function() {
var savedData = {}
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
})
.controller('myCtrl2', ['$scope', 'myService', function($scope, myService) {
console.log('myCtrl2');
myService.set('world');
}])
.controller('myCtrl3', ['myService', function(myService) {
console.log('myCtrl3');
var vm = this;
vm.x = myService.get();
}])
I also uses the var vm=this and ControllerAs as often recommended to avoid $scope issues.
index.html looks like below... pleaes note the ui-sref instead of href:
<div ui-view="">
<a ui-sref="enter2">Enter here</a>
</div>
enter2.html is now just the div part and your content:
<div>
Hello {{ vm.x }}
</div>
Let us know if that helps.
Additional info:
AngularJS Routing Using UI-Router
AngularJS's Controller As and the vm Variable
Sounds like you need to use a controller for your view page
https://docs.angularjs.org/guide/controller

Problems with uploading a file and parsing with AngularJS

Trying to build a simple application that allows a user to upload a file, and upon clicking the 'add' button, It parses the file and displays the result within the browser.
I am using IntelliJ to generate the AngularJS application stub, and modifying it accordingly.
My attempt is below:
view1.html
<!DOCTYPE html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>My HTML File</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="../app.css">
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/ng-file-upload/ng-file-upload-shim.js"></script> <!-- for no html5 browsers support -->
<script src="bower_components/ng-file-upload/ng-file-upload.js"></script>
<!--<script src="view1.js"></script>-->
</head>
<body>
<div ng-controller="View1Ctrl">
<input type="file" id="file" name="file"/>
<br/>
<button ng-click="add()">Add</button>
<p>{{data}}</p>
</div>
</body>
</html>
view1.js
'use strict';
angular.module('myApp.view1', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {
templateUrl: 'view1/view1.html',
controller: 'View1Ctrl'
});
}])
.controller('View1Ctrl', ['$scope', function ($scope) {
$scope.data = 'none';
$scope.add = function() {
var f = document.getElementById('file').files[0],
r = new FileReader();
r.onloadend = function(e) {
$scope.data = e.target.result;
}
r.readAsArrayBuffer(f);
}
}]);
view1_test.js
'use strict';
describe('myApp.view1 module', function() {
beforeEach(module('myApp.view1'));
describe('view1 controller', function(){
it('should ....', inject(function($controller, $rootScope) {
//spec body
// var view1Ctrl = $controller('View1Ctrl');
var $scope = $rootScope.$new(),
ctrl = $controller('View1Ctrl', {
$scope: $scope
// $User: {}
});
expect(ctrl).toBeDefined();
}));
});
});
app.js
'use strict';
// Declare app level module which depends on views, and components
angular.module('myApp', [
'ngRoute',
'myApp.view1',
'myApp.view2',
'myApp.version'
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.otherwise({redirectTo: '/view1'});
}]);
I am not sure where I could potentially be going wrong? I viewed quite a few questions to this and tried multiple different approaches but I cannot get this to work despite all of my tests passing.
The issue was around my view1.js file. I found the Papa Parse library extremely useful.
Here is my solution used from the open source Papa Parse community:
Papa.parse(fileInput[0], {
complete: function(results) {
console.log("Complete!", results.data);
$.each(results.data, function(i, el) {
var row = $("<tr/>");
row.append($("<td/>").text(i));
$.each(el, function(j, cell) {
if (cell !== "")
row.append($("<td/>").text(cell));
});
$("#results tbody").append(row);
});
}
});

How can I get multiple Angular JS routes to share a single controller instance?

I have an angular JS app which has an intro page and a 'working' page..
A simplified version of my html looks like:
(function () {
'use strict';
var MyController = function (_, $rootScope, $scope, $location, $anchorScroll, $timeout, gettextCatalog, service) {
var self = this;
console.log("test");
//More code which invokes http requests
};
angular.module('my.controller', [ 'gettext', 'lodash', 'ngRoute', 'ngSanitize', 'ngAnimate', 'my.service' ]).config(
function ($routeProvider) {
$routeProvider.when('/', {
'templateUrl': 'modules/home.html',
'reloadOnSearch': false
}).when('/chatting', {
'templateUrl': 'modules/working.html',
'reloadOnSearch': false
});
}).controller('MyController', MyController);
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<html id="ng-app" ng-app="app" ng-controller="MyController as myCtrl">
<head>
<link rel="stylesheet" href="styles/main.css">
<title>What's in Theaters</title>
</head>
<body id="app-body" class="dialog-body">
<div style="height: 100%; width: 100%" ng-view></div>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-gettext/dist/angular-gettext.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="app.js"></script>
<script src="modules/my-controller.js"></script>
</body>
</html>
The problem I have is that when I move from one route to another i.e. from "/" to "/#working" the MyController is re-initialized and the http requests which were already completed are now discarded.. How do I share the same instance of the controller across both routes?
Each view bound to a controller creates it's own instance of that controller. Controller's are only shared by view inheritance, that is, a child view will have access to the same instance of the parent's controller.
However that doesn't seem to be what you are trying to do. It sounds like what you want to do is persist the state of some data (acquired via http requests) between controller instances. In that case, what you'd likely want to do is move the request logic into a service (or factory) and then inject that service into the controller. Since services are singletons in angular, your requests should now only be performed once.
Example:
var MyController = function (_, $rootScope, $scope, $location, $anchorScroll, $timeout, gettextCatalog, service, yourHttpService) {
var self = this;
console.log("test");
// get data from your http service here... perhaps $scope.data = yourHttpService.getData(...).then(...);
};
.factory('yourHttpService', function($http, $q) {
var dataCache = ...; // create a local memory db to store the data
return {
getData: function(...) {
var deferred = $q.defer();
// check to see if the data is already cached, if so resolve the promise with the cached data
// if data is not cached, perform the $http request to fetch the data, then cache it and resolve your promise
return deferred.promise;
};
};
});
You can always pass your controller while defining your routes
$routeProvider.when('/', {
'templateUrl': 'modules/home.html',
'controller' : MyController,
'reloadOnSearch': false
}).when('/chatting', {
'templateUrl': 'modules/working.html',
'controller' : MyController,
'reloadOnSearch': false
});

Angular: 'modulerr', "Failed to instantiate module

I'm trying to learn Angular and hitting a brick wall trying to add routes to my application.
I keep getting presented this error
'modulerr', "Failed to instantiate module
From other stackoverflow questions i gather its not from loading ngRoute correctly but angular-route.js is loaded in the head, and ngRoute is declared in my module construct so i'm a bit confused
My index file is as followed
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.js"></script>
<script src="https://cdn.firebase.com/js/client/2.2.4/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.2/angularfire.min.js"></script>
<script src="js/app.js"></script>
<script src="js/animations.js"></script>
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
<script src="js/services.js"></script>
</head>
<body ng-app="pgpChatApp">
<div class="view-container">
<div ng-view="" class="view-frame"></div>
</div>
</body>
</html>
My app file
'use strict';
var pgpChatApp = angular.module('pgpChatApp', [
'ngRoute',
'firebase',
'pgpChatAnimations',
'pgpChatControllers',
'pgpChatFilters',
'pgpChatServices'
]);
pgpChatApp.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/home", {
// the rest is the same for ui-router and ngRoute...
controller: "userAuth",
templateUrl: "partials/home.html"
}).when("/account", {
// the rest is the same for ui-router and ngRoute...
controller: "AccountCtrl",
templateUrl: "partials/msg_room.html",
resolve: {
// controller will not be loaded until $requireAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth", function (Auth) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
return Auth.$requireAuth();
}]
}
});
}]);
My controller file
var pgpChatAppControllers = angular.module('pgpChatAppControllers', []);
pgpChatAppControllers.controller("userAuth", ["$scope", "$routeParams", "Auth",
function ($scope, $routeParams, Auth) {
$scope.createUser = function () {
$scope.message = null;
$scope.error = null;
Auth.$createUser({
email: $scope.email,
password: $scope.password
}).then(function (userData) {
$scope.message = "User created with uid: " + userData.uid;
}).catch(function (error) {
$scope.error = error;
});
};
$scope.removeUser = function () {
$scope.message = null;
$scope.error = null;
Auth.$removeUser({
email: $scope.email,
password: $scope.password
}).then(function () {
$scope.message = "User removed";
}).catch(function (error) {
$scope.error = error;
});
};
}]);
Has anyone got an idea what the fix is?
Thanks in advanced
[EDIT]
Full exception message
http://errors.angularjs.org/1.3.15/$injector/modulerr?p0=pgpChatAnimations&p1=Error: [$injector:nomod] http://errors.angularjs.org/1.3.15/$injector/nomod?p0=pgpChatAnimations
at Error (native)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:6:417
at https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:21:412
at a (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:21:53)
at w.bootstrap (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:21:296)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:35:116
at r (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:7:302)
at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:34:399)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:35:63
at r (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js:7:302)"
The url for angular-route is not working. Try using:
http://code.angularjs.org/1.2.0-rc.3/angular-route.js
I guess i my module names still weren't matching the defined modules in app.js
This diff shows the fix
https://github.com/jkirkby91/angular-firebase-chat/commit/35be74592197d16435adb322f0e24963108ed97a

Angular translate not working between 2 controllers

I would like to use i18n and i10n in my Angular app.
I read that Angular-translate can help with this, however, it doesn't work for me.
In my index.html:
<!DOCTYPE html>
<html ng-app="eApp">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="../common/css/bootstrap.css" />
<link rel="stylesheet" type="text/css" href="../common/css/style.css" />
<title></title>
</head>
<body ng-controller="AppCtrl">
<div id="container" ng-view></div>
<!--- Libs Js files --->
<script type="text/javascript" src="../vendor/angularjs/angular.min.js"></script>
<script type="text/javascript" src="../vendor/angularjs/angular-route.min.js"></script>
<script type="text/javascript" src="../vendor/angularjs/angular-translate.min.js"></script>
</body>
</html>
In my eApp.js:
var eApp = angular.module('elbitApp', ['ngRoute', 'ui.bootstrap', 'config', 'pascalprecht.translate']);
// configure our routes
eApp.config(["$routeProvider",
function ($routeProvider) {
$routeProvider
// route for the home page
.when('/c', {
templateUrl: 'c/partials/c.html',
controller: 'CController'
})
// route for the about page
.when('/de', {
templateUrl: 'd/partials/dE.html',
controller: 'DEController',
resolve: {
data: function (DDataService) {
return DDataService.loadData().then(function (response) {
return response.data;
});
}
}
})
// route for the contact page
.when('/di', {
templateUrl: 'd/partials/di.html',
controller: 'DIController',
resolve: {
data: function (DDataService) {
return DDataService.loadData().then(function (response) {
return response.data;
});
}
}
}).otherwise({
redirectTo: '/di'
});
}]).config(["$httpProvider",
function ($httpProvider) {
// Configure the $httpProvider to parse date with date transformer
$httpProvider.defaults.transformResponse.push(function (responseData) {
convertDateStringsToDates(responseData);
return responseData;
});
}]).config(["$translateProvider",
function ($translateProvider) {
$translateProvider.translations('en', {
"TITLE": 'Hello',
"FOO": 'This is a paragraph.',
"BUTTON_LANG_EN": 'english',
"BUTTON_LANG_DE": 'german'
});
$translateProvider.translations('de', {
"TITLE": 'Hallo',
"FOO": 'Dies ist ein Paragraph.',
"BUTTON_LANG_EN": 'englisch',
"BUTTON_LANG_DE": 'deutsch'
});
$translateProvider.preferredLanguage('en');
}]);
// main controller that catches resolving issues of the other controllers
eApp.controller('AppCtrl', function ($rootScope) {
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
alert("Cant resolve the request of the controller "); //TODO: add URL + additional data.
})
});
In this file I defined my app and added the $translateProvider and two dictionaries.
Afterwards I got to my deController.js:
eApp.controller('DispatcherEventsController', ['$scope', '$route', '$translate',
function($scope, $route, $translate){
var data = $route.current.locals.data;
$scope.title = $translate.instant("FOO");
$scope.switchLanguage = function(languageKey){
$translate.use(languageKey);
};
}]);
In de.html I added a h1 tag with FOO and in a click I would like to change to German:
<h1>{{title |translate}}</h1>
<h1 translate="{{title}}"></h1>
<button type="button" id="searchButton" class="btn btn-default" ng-click="switchLanguage('de')">German</button>
I don't get any problem, but nothing happens. I expected that the English title will be converted to German title.
What can I do to make this work?
It works well for me. Here is a jsFiddle DEMO.
In this case, you want to bind $scope.title with translation key "FOO".
You should change the value of $scope.title dynamically in the switchLanguage function. Then view will be updated accordingly.
//default value
$scope.title = $translate.instant("HEADLINE");
$scope.switchLanguage = function(key){
$translate.use(key);
$scope.title = $translate.instant("HEADLINE");
}
In my opinion, maybe use translation key is a better way than scope data binding. You don't have to maitain the value of key manually.
<h1>{{'FOO' | translate}}</h1>
According to the error msg you provided, maybe you could check if there is any typo syntax in your controller.
Should be
$translate.use(languageKey)
Not
$translate.uses(languageKey)
Though not directly related to your question - you must remember to set the preferred language in the translations.js file, or whatever its name is that you define your key-value translations. Otherwise it will just default to whatever the key is.
...
GREETING: 'Hello World!',
...
$translateProvider.preferredLanguage('en');
Without this line, when doing this:
<h2>{{'GREETING'|translate}}</h2>
Would appear as just GREETING instead of the 'Hello World.'

Categories