AngularJS + ui router and page refresh - javascript

I am having this strange issue with only one of my views. The problem is that whenever I reload it by refreshing (Cmd + R / F5) the browser the whole app doesn't work until I change the route (manually or by clicking a link). The view resolves some data before it renders and I can verify that there are no request to the server whatsoever. Basically when I refresh I see all my scripts are loaded but my controller is never called (had a console.log in there) and I never get the view rendered.
Here is the state
$stateProvider.state( 'purchases', {
url: '/purchases',
views: {
"main": {
controller: 'PurchasesCtrl',
templateUrl: 'purchases/purchases.tpl.html'
}
},
resolve: {
purchases: function(PurchasesService, $rootScope) {
return PurchasesService.getPurchasesForUser($rootScope.user._id);
}
},
data:{ pageTitle: 'Purchases' }
});
$stateProvider.state( 'purchases_buy', {
url: '/purchases/buy/{itemId:[0-9a-fA-F]{24}}',
views: {
"main": {
controller: 'PurchasesBuyCtrl',
templateUrl: 'purchases/purchases_buy.tpl.html'
}
},
resolve:{
item: function(ItemsService, $stateParams){
return ItemsService.getItem($stateParams.itemId);
}
},
data:{ pageTitle: 'Buy' }
});
And here is the controller:
.controller( 'PurchasesCtrl', ['$scope', 'PurchasesService', '$stateParams', 'purchases', function ( $scope, PurchasesService, $stateParams, purchases ) {
$scope.purchases = purchases.data;
console.log("loaded");
$scope.predicates = ['firstName', 'lastName', 'birthDate', 'balance', 'email'];
$scope.selectedPredicate = $scope.predicates[0];
}])
.controller( 'PurchasesViewCtrl', ['$scope', 'PurchasesService', '$stateParams', 'purchase', function ( $scope, PurchasesService, $stateParams, purchase ) {
$scope.purchase = purchase.data;
}])
I have no idea why it keeps doing this but it is annoying and I would like to know what is the problem.

This is not an issue with UI-Router, but rather than server related issue wherein when you refresh your browser it doesn't point back to the index.html
So you'll need to host your page in an Apache environment and create a redirection such as creating an .htaccess file
I have this as an issue before, you can trace back my ticket from Github
The difference only is that I use Grunt to serve my app then I created an .htaccess along with my index.html
here's an .htaccess gist that I used for solving this issue. link

Related

Is it possible to pass param from state to state with angularJs?

I wish to pass a param from one store-state to the display product info in products-state:
My app - storeApp
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('store', {
url: '/store',
templateUrl: 'store/store',
controller: 'storeCtrl'
})
.state('products', {
url: '/products/:productSku',
templateUrl: 'store/product',
controller: 'productCtrl',
resolve: {
productResource: 'productFactory',
_product: function(productResource, $stateParams){
return productResource.getProduct($stateParams.productSku);
}
}
Store.jade
a(href='/products/{{product.sku}}')
Product controller
.controller("productCtrl", function ($rootScope, $http, $stateParams, productFactory, storeFactory) {
//.controller('productCtrl', ['_product', function ($scope, $rootScope, storeFactory, _product) {
console.log($stateParams.productSku);
Product Factory
function getProduct(sku) {
return $http.get('http://localhost:3000/api/products/' + sku );
}
Since I am using MEAN Stack, node has the router attached to express:
Server.js
const storeController = require('./controllers/store');
server.get('/store/product', passportConfig.isAuthenticated, storeController.getProductPage);
Store.js
exports.getProductPage = (req, res) => {
res.render('store/product', {
title: 'PP',
angularApp: 'storeApp'
})
}
I tried returning _product but I get Unknown provider: _productProvider <- _product <- productCtrl
I tried using ui-sref - a(ui-sref="products({productSku:'{{product.sku}}'})") in store.jade to send param from store_State to products_State & finally got an object back from API.
Now the issue is that node will not return the view.
Basically what I am trying to achieve is:
Node serving client views, all store views - store/ product/ cart are attached to angular app served through Server.js, Clicking store product will redirect to product page after resolve product info from api.
I am getting product info but not getting product view.
I looked it up but all solutions did not work....maybe my bad :-(
How can I go about this?
UPDATE-1: this is whats happening:
UPDATE-2:
When I pass the control to angular, I have express routing the menu, and angular stateProvider routing/ connecting views to controllers.
Main view that loads is the store itself:
app.js - store route
$stateProvider
.state('store', {
url: '/store',
templateUrl: 'store/store',
controller: 'storeCtrl'
})
server.js (express)
server.get('/store', passportConfig.isAuthenticated, storeController.getStorePage);
store.js
exports.getStorePage = (req, res) => {
res.render('store/store', {
title: 'S--tore',
angularApp: 'storeApp'
});
}
store.ctr.js
angular.module("storeApp")
.controller("storeCtrl", function($rootScope, $http, storeFactory) {
var products;
storeFactory.getProducts().then(function(_products) {
products = _products.data;
$rootScope.products = products;
});
That loads just fine!
But when I try to send the param productSku from store view to product view and have the resolve send product params back to product view that where it stops working, it's either I get the view OR i get the params.
I tried different ways of resolve, they all result the same - view OR product params.
app.js - product route
.state('products', {
url: '/products/:productSku',
templateUrl: 'store/product',
controller: 'productCtrl',
resolve: {
_product: function ($stateParams, $state, $http) {
return $http.get('http://localhost:3000/api/products/' + $stateParams.productSku );
//return productResource.getProduct($stateParams.productSku)
}
}
})
If I remove the resolve and send a(href='/products/{{product.sku}}') from store.jade I get the template in the route, chrome console error I get is `Error: $injector:unpr Unknown Provider _product <- productCtrl
product.ctr.js
.controller('productCtrl', ['_product', function ($rootScope, $http, $stateParams, productFactory, storeFactory, _product) {
If I send a(ui-sref="products({productSku: product.sku })") with resolve I get product params (shown in WebStorem snapshot above) NO view.
angular will not load jade templates, You will need an html template, The jade template is loaded by express. You might like to try using ui-view like this:
Store.jade
div(ui-view)
a(href='/products/{{product.sku}}')
Which should make angular look for the unnamed view when loading the route.
Your templateUrl's don't look to be pointing to files, perhaps you're missing the file extension?
Make sure you return a $promise in resolve as ui-router waits until they are resolved before rendering the view.
I'd recommend having named views with corresponding config in route too:
.state('store', {
url: '/store',
views: {
'#': {
templateUrl: 'store/store.html',
controller: 'storeCtrl'
}
}
})
.state('products', {
url: '/products/:productSku',
templateUrl: 'store/product',
controller: 'productCtrl',
resolve: {
_product: function ($stateParams, $state, $http) {
return $http.get('http://localhost:3000/api/products/' + $stateParams.productSku ).$promise;
}
}
})
See the docs here: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
This is the solution I found:
store.jade
a(href='/products/{{product.sku}}' ng-click='sku(product.sku)')
stroe.ctr.js
$rootScope.sku = function(value){
storeFactory.singleProduct.productSku = value;
storeFactory.singleProduct.saveSku();
}
store.fac.js
var singleProduct = {
productSku : '',
saveSku: function() {
sessionStorage.productSku = angular.toJson(singleProduct.productSku);
},
getSku: function() {
singleProduct.productSku = angular.fromJson(sessionStorage.productSku);
return singleProduct.productSku;
}
}
product.ctr.js
var sp = storeFactory.singleProduct.getSku();
productFactory.getProduct(sp).then(function (product) {
$rootScope.product = product.data;
});
product.fac.js
function getProduct(sku) {
return $http.get('http://localhost:3000/api/products/' + sku );
}
Basically I am storing productSku to sessionStorage and getting it back from there when product.jade view loads...
Thanx to all who tried...
You can pass it as $state.data in your controller.
toState = $state.get("some-state");
toState.data.foo = $scope.foo;
$state.go(toState);
You have not included dependancy of _products in storeCtrl. When you call that service you get an error.
Correct code is:
angular.module("storeApp")
.controller("storeCtrl", function($rootScope, $http, storeFactory, _products) {
var products;
storeFactory.getProducts().then(function(_products) {
products = _products.data;
$rootScope.products = products;
});
You can use console.log("something here" + _products.data); to see in your browser console

Pass Json data to another route template in angular js

After login I want to pass the user details to dashboard?How it possible in angular js?
Login.js
mySchoolApp.controller('loginController', ['$scope', '$http', function($scope, $http) {
this.loginForm = function() {
let encodedString = 'uname=' +this.username +'&pwrd=' +this.password;
sessionStorage.user = encodedString;
console.log(sessionStorage.user)
window.location.href = 'dashboard.html';
}
}]);
In console I'm getting the value.
How to get the user details in dashboard.html page?
You should use ng-route to achieve this.Angular isn't designed to work like this
Here is sample
$stateProvider
.state('app', {
abstract: true,
url: "",
template: '<ui-view/>'
})
.state('app.home', {
url: "/",
templateUrl: "partials/main_page.html",
resolve: {
skipIfLoggedIn: skipIfLoggedIn
}
}).state('app.dashboard', {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: 'DashboardCtrl',
activePage:'dashboard',
resolve: {
loginRequired: loginRequired
}
You can store it in a localstorage.So you can use angular-local-storage Angular module for that.
How to set :
myApp.controller('MainCtrl', function($scope, localStorageService) {
//...
function submit(key, val) {
return localStorageService.set(key, val);
}
//...
});
How to Get :
myApp.controller('MainCtrl', function($scope, localStorageService) {
//...
function getItem(key) {
return localStorageService.get(key);
}
//...
});
You should use router module ui-router or ng-router in order to use angualrjs logic in that sense but then your pages are going to be loaded via ajax and regular session http authentication can not be applied.
If that's the case then use angular service provider and let me know to edit my answer.
If you'd like to keep data across pages and not using database or server.
Then what is left as options are: sessionStorage and localStorage.
The localStorage keeps data permanently until browser cache deletes it while the other one obviously for the session.
sessionStorage.setItem('myCat', 'Tom');
If you want to keep js collection like object or array first stringify it:
var user = {pass:'moo', name: 'boo'};
sessionStorage.setItem('userDetais', JSON.stringify(user));

AngularJS app making multiple request to the backend

I have an Angular app which makes some calls (POST and GET for now) to a backend service (powered by node.js with a REST interface). While developing the app itself I noticed it makes two requests to the backend each time a button is pressed or a page is loaded. Curiously everything works but each time I press some button the backend gets two requests. I am not using any fancy package only ngRoute, ngResource and routeStyles for css partials. Anybody has an idea of what could be the reason why the app behaves like that?
I actually found another question similar to this one but the OP there was using express aside of Angular and there is no answer...
EDIT added some code.
in app.js:
'use strict';
var cacheBustSuffix = Date.now();
angular.module('myApp', ['myApp.controllers', 'myApp.services', 'myApp.filters', 'ngRoute', 'ngResource', 'routeStyles'])
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$locationProvider
.html5Mode({enabled: true,
requireBase: false})
.hashPrefix('!');
$routeProvider
.when('/', {redirectTo: '/myApp'})
.when('/myApp', {
templateUrl: '/partials/home.html?cache-bust=' + cacheBustSuffix,
controller: 'ctrlHome'
})
.when('/myApp/search', {
templateUrl: '/partials/search.html?cache-bust=' + cacheBustSuffix,
controller: 'ctrlSearch'
})
.when('/myApp/list/', {
templateUrl: '/partials/list.html?cache-bust=' + cacheBustSuffix,
controller: 'ctrlList'
})
// a bunch of other redirections
.otherwise({
templateUrl: '/partials/404.html?cache-bust=' + cacheBustSuffix,
controller: 'ctrl404'});
}]);
from services.js:
'use strict';
var app = angular.module('myApp.services', ['ngResource']).
factory('List', function ($resource) {
return $resource(WSROOT + '/search', {}, {get: {method: 'GET', isArray: false}});
});
from controllers.js, one controller that makes multiple requests
var controllers = angular.module('myApp.controllers', []);
var ctrlList = controllers.controller('ctrlList', function ($scope, $window, List) {
$window.document.title = 'myApp - List';
List.get({}, function (data) {
// $scope.res is an array of objects
$scope.res = data.response;
$scope.nitems = data.response.length;
});
});
ctrlList.$inject = ['$scope', 'List'];
And the network call when loading the index+home and navigating to some other page. As you can see, it first loads the index page, the scripts and styles listed there (not shown), then the home where I have a controller similar to the one above and suddenly two wild request to my web server:
Can we see your HTML files? I had this problem a while back. My solution was that by declaring a controller in the routing, and in the pages, a double post was created as each controller was loaded twice.
//Home
.state('tab.home', {
url: '/home',
views: {
'tab-home': {
templateUrl: 'templates/tab-home.html',
controller: 'HomeCtrl' // <-- This goes away
}
}
})

AngularJS + Routing + Resolve

I'm getting this error:
Error: Error: [$injector:unpr] http://errors.angularjs.org/1.3.7/$injector/unpr?p0=HttpResponseProvider%20%3C-%20HttpResponse%20%3C-%20DealerLeads
Injector Unknown provider
Here's my router (ui.router):
$stateProvider
.state('main', {
url: "/main",
templateUrl: "views/main.html",
data: { pageTitle: 'Main Page' }
})
.state('leads', {
url: "/leads",
templateUrl: "views/leads.html",
data: { pageTitle: 'Dealer Leads' },
controller: 'DealerLeads',
resolve: DealerLeads.resolve
})
Here's my Controller:
function DealerLeads($scope, HttpResponse) {
alert(JSON.stringify(HttpResponse));
}
Here's my resolve:
DealerLeads.resolve = {
HttpResponse: function ($http) {
...
}
}
The data is getting to the controller, I see it in the alert. However, after the controller is done, during the rendering of the view (I think), the issue seems to be happening.
The final rendered view has two controllers: One main controller in the body tag, and the second controller 'DealerLeads' inside of that. I've tried removing the main controller, and the issue is still present.
What am I doing wrong? Is there any more code that is necessary to understand/resolve the issue?
When you use route resolve argument as dependency injection in the controller bound to the route, you cannot use that controller with ng-controller directive because the service provider with the name HttpResponse does not exist. It is a dynamic dependency that is injected by the router when it instantiates the controller to be bound in its respective partial view.
Just remove the ng-controller="DealerLeads" from the view and make sure that view is part of the html rendered by the state leads # templateUrl: "views/leads.html",. Router will bind it to the the template for you resolving the dynamic dependency HttpResponse. If you want to use controllerAs you can specify that in the router itself as:-
controller: 'DealerLeads',
controllerAs: 'leads' //Not sure if this is supported by ui router yet
or
controller: 'DealerLeads as leads',
Also when you do:
.state('leads', {
url: "/leads",
templateUrl: "views/leads.html",
data: { pageTitle: 'Dealer Leads' },
controller: 'DealerLeads',
resolve: DealerLeads.resolve
})
make sure that DealerLeads is accessible at the place where the route is defined. It would be a better practice to move the route definition to its own controller file so that they are all in one place. And whenever possible especially in a partial view of a route it is better to get rid of ng-controller starting the directive and instead use route to instantiate and bind the controller for that template. It gives more re-usability in terms of the view as a whole not being tightly coupled with a controller name and instead only with its contract. So i would not worry about removing ng-controller directive where router can instantiate the controller.
I'm not completely understand you question, and also not an expert as #PSL in angular.
If you just want to pass some data into the controller, maybe below code can help you.
I copied a piece of code from the project:
.state('masthead.loadTests.test',{
url: '/loadTests/:id',
templateUrl: 'load-tests/templates/load-test-entity.tpl.html',
controller: 'loadTestEntityCtrl',
data: { pageTitle: 'loadTests',
csh: '1005'
},
resolve: {
// Get test entity data before enter to the page (need to know running state)
LoadTestEntityData: [
'$stateParams',
'$state',
'LoadTestEntity',
'LoggerService',
'$rootScope',
function ($stateParams, $state, LoadTestEntity, LoggerService, $rootScope) {
// Get general data
return LoadTestEntity.get({id: $stateParams.id},function () {
},
// Fail
function () {
// When error navigate to homepage
LoggerService.error('error during test initiation');
$state.go('masthead.loadTests.list', {TENANTID: $rootScope.session.tenantId});
}).$promise;
}
]
}
})
Here the LoadTestEntityData is the data we injected into the controller,
LoadTestEntity and LoggerService are services needed for building the data.
.factory('LoadTestEntity', ['$resource', function ($resource) {
return $resource(
'/api/xxx/:id',
{id: '#id'},
{
create: {method: 'POST'},
update: {method: 'PUT'}
}
);
}])
Hope it helps!

AngularJS, resolve data before showing view

This subject has been already asked but I couldn't figure out what to do in my case.
Using AngularJS 1.0.5:
Before showing the view "login", I want to get some data and delay the view rendering while the data isn't loaded from an AJAX request.
Here is the main code. Is it the good way?
angular.module('tfc', ['tfc.config', 'tfc.services', 'tfc.controllers']).config([
'$routeProvider', '$locationProvider', '$httpProvider',
function($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/login', {
templateUrl: 'views/login.html',
controller: "RouteController",
resolve: {
data: function(DataResolver) {
return DataResolver();
}
}
});
}
]);
module_services = angular.module("tfc.services", []);
module_services.factory("DataResolver", [
"$route", function($route) {
console.log("init");
return function() {
// Tabletop is a lib to get data from google spreadsheets
// basically this is an ajax request
return Tabletop.init({
key: "xxxxx",
callback: function(data, tabletop) {
console.log("[Debug][DataResolver] Data received!");
return data;
}
});
};
}
]);
The point of AngularJS is that you can load up the templates and everything and then wait for the data to load, it's meant to be asynchronous.
Your view should be using ng-hide, ng-show to check the scope of the controller so that when the data in the scope is updated, the view will display. You can also display a spinner so that the user doesn't feel like the website has crashed.
Answering the question, the way you are loading data explicitly before the view is rendered seems right. Remember that it may not give the best experience as there will be some time to resolve that, maybe giving an impression that your app stopped for some moments.
See an example from John Pappa's blog to load some data before the route is resolved using angular's default router:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
You basically use the resolve parameters on the route, so that routeProvider waits for all promises to be resolved before instantiating the controller. See the docs for extra info.

Categories