Setting up and testing UI Router - javascript

I'm trying to use ncy-angular-breadcrumb, which uses ui routes. I have a module set up to house the ui routes:
(function () {
var app = angular.module('configurator.uiroutes', ['ui.router', 'ncy-angular-breadcrumb']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('home', {
url: '/',
ncyBreadcrumb: {
label: 'Series'
}
});
}]);
})();
I have this test to see if the route works:
describe('Configurator UI Routes', function () {
beforeEach(module('configurator.uiroutes'));
beforeEach(module('ui.router'));
var $state, $rootscope, state = '/';
// Inject and assign the $state and $rootscope services.
beforeEach(inject(function (_$state_, _$rootScope_) {
$state = _$state_;
$rootscope = _$rootScope_;
}));
//Test whether the url is correct
it('should activate the state', function () {
$state.go(state);
$rootscope.$digest();
expect($state.current.name).toBe('home');
});
});
Which produces this error:
Error: Could not resolve '/' from state ''
I'm just not getting the UI Router and either how it works, or what it's supposed to do. I just want to grab parts of the url and pass that info to ncyBreadcrumb, but I can't even get the base URL to work.
Any pointers or help would be awesome!
Edit: I've gotten the test to pass by replacing '/' with 'home' in the test. My bigger question is: is there a way to test that when the URL '/' is hit, that the state.current.name becomes 'home'. In my use of the app, this doesn't appear to be happening, and I'm hoping that the unit test can help tell me why.
This is a single page application, if that's relevant.

Error is enough self explanatory, as you asked ui-router to redirect on / state using $state.go
Basically $state.go which asks for state name rather than url.
var $state, $rootscope, state = 'home'; //<--changed from `/` to `home`

Related

Routes loaded from an api - controller never called

I'm trying to load routes into my angularJS app by running an ajax call and setting up routes on my RouteProvidor. The following is my code to do so.
var app = angular.module('app', ['ngRoute']);
var appRouteProvider;
app.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
appRouteProvider = $routeProvider;
appRouteProvider.when('/', {
templateUrl : '../../app/templates/home.html',
controller : 'IndexController'
});
}]).run(function($http, $route){
url = siteApiRoot+'api/routes';
$http.post(url, jQuery.param([]), {
method: "POST",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
).success(function(data){
for(i in data){
appRouteProvider.when(data[i].route, {
templateUrl : data[i].template,
controller : data[i].controller
});
}
console.log($route.routes);
});
});
The console.log outputs a correct set of routes to the console which seems to indicate that the routes have been correctly assigned. But if I were to open any url that should be handled by the route. Nothing happens. The basic assets load i.e. navigation bar and footer which are constant throughout but the controller for the route is never called. What am I missing here guys.
-- UPDATE
Tehcnically I have routes that follow the following patterns:
List of entries:
/<city>/<category>
/<city>/<subdistrict>-<category>
/<city>/<entry-slug>
I'm not sure how well to define the above - basically the first two routes would invoke one controller and one view while the third woudl invoke another. However I'm stuck with how to define this kind of routing in AngularJS provided that etc are all slugs in a database. Pretty much left with hardcoding an array of routes but that also doesn't work as it seems.
Plus I also have other pages that are static - eg /about /site/contact - a bit lost on routing here.
You can't change the router configuration after initialisation, but you can use a parameterized route to handle everything.
You can fetch the routing data in an external service, and find the appropiate entry for the current parameters with whatever lookup logic you need. I assume the point of this is to have different templates and controllers for these routes.
The template you can solve with a simple ng-include, but you'll have to manually instantiate the controller. Look into $injector instead of the $controller call here for more details on this one, as you'll probably need full dependency injection for them. The RouteController here just passes its own scope to the created controller (which at this point really is just like any generic service), which is already attached to the container.html by the router. Note that the ng-include creates a child scope, so you have to be careful if you want to assign new variables on the scope in templates.
(If this is a problem, you can manually fetch, build and attach the template too: take a look into $templateRequest, $templateCache and $compile services. (You will have to create a directive to attach it to the DOM))
Here is the barebones sample code:
var app = angular.module('app', ['ngRoute']);
app.service("getRouteConfig", function($http) {
var routeRequest = $http.post(url, jQuery.param([]), {
method: "POST",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
return function(params) {
return routeRequest.then(function(routes) {
// find route entry in backend data for current city, category/slug
return routes[params.city][params.slug];
});
}
})
app.controller("RouteController", function(route, $scope, $controller) {
$controller(route.controller, {$scope: $scope});
})
app.config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl : '../../app/templates/home.html',
controller : 'IndexController'
});
$routeProvider.when('/:city/:url', {
templateUrl : '../../app/templates/container.html',
controller : 'RouteController',
resolve: {
route: function(getRouteConfig, $routeParams) {
return getRouteConfig($routeParams);
}
}
});
});
container.html:
<ng-include src='$resolve.route.template'>
Angular config functions are for setting up configuration, which is used for initialising services. This means that trying to alter the config from the run() function will result in nothing happening, as the config has already been utilised.
One possible option is to provide the config from the server inside the actual js file sent to the client. Otherwise there is no easy way to alter config using $http.
There is more discussion here: use $http inside custom provider in app config, angular.js

AngularJS ui-router default state for states without a URL

We have a couple of states for one page, so the states don't have any URL assigned to them. Is there a way to specify a default state for URL-less states? Maybe similar to $urlRouterProvider.otherwise('/');.
I tried going to a state in Angular's run statement or within the controller but that causes a transition superseded error, which is probably due to the fact that $state hasn't been initialized.
Below is a short example. I would like to start directly in stateA.
angular
.module('myModule')
.config([
'$stateProvider',
function($stateProvider) {
$stateProvider
.state('stateA', {
controller: function () {
// Do some magic related to state A
}
})
.state('stateB', {
controller: function () {
// Do some magic related to state B
}
});
}
])
.controller(['$state', '$timeout', function($state, $timeout){
// My global controller
// To set a default state I could do:
// $timout(function () {
// $state.go('stateA');
// }, 0);
// But that doesn't feel right to me.
}]);
Update:
Thanks to this answer I figured out that I have to wrap $state.go into a $timeout, to avoid the transition superseded error.
This will call a custom rule that goes to a default state without tampering with a URL.
$urlRouterProvider.otherwise(function($injector) {
var $state = $injector.get('$state');
$state.go('stateA');
});`

Completely Lost on AngularJS Authentication Implementation

I am not new to programming, but I am new to AngularJS. I am working with my first AngularJS site and am looking to add an authentication & authorization layer to my app. I am trying to follow this article which seems to be very straight forward: https://medium.com/#mattlanham/authentication-with-angularjs-4e927af3a15f
However, I find that too many AngularJS articles only give snippets without actually giving guidance on where to actually implement the code! Very, very frustrating . . .
I have a:
app.js in my app root directory
controller.js for a page that I want to put behind an authorization layer
I also have the corresponding REST services to handle the user authentication
Can someone please help me distinguish what items from the article should go in app.js (if any???) and which ones should go in my individual page?
My app.js has looked like:
var app = angular.module('appname', [$stateProvider]);
app.config(function($stateProvider, $urlRouterProvider){
alert("auth fired");
$stateProvider
.state("account", {
url: "/account.html",
templateUrl: "/account.html",
controller: "HistoryCtrl",
authenticate: true
})
.state("login", {
url: "/login.html",
templateUrl: "/login.html",
controller: "LoginCtrl",
authenticate: false
});
// Send to login if the URL was not found
$urlRouterProvider.otherwise("/login.html");
});
And the page I want to secure looked like:
var app = angular.module('appname', []);
app.controller('HistoryCtl', function($scope, $http) {
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.headers.common = {};
$httpProvider.defaults.headers.post = {};
$httpProvider.defaults.headers.put = {};
$httpProvider.defaults.headers.patch = {};
}]);
$http({
method: "post",
url: "http://serviceURL/v1/account",
headers:{'Content-Type': 'application/json'},
data: {"txtToken":"myusertoken"}
}).success(function (response) { $scope.names = response.entries;});
});
app.run(function ($rootScope, $state, AuthService) {
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
if (toState.authenticate && !AuthService.isAuthenticated()){
// User isn’t authenticated
$state.transitionTo("login");
event.preventDefault();
}
});
});
But none of these seemed to fire and there were no errors in the console. How would one go about implementing the snippets from the article? Can someone please break it out according to the file in which each snippet should go? Do I need to declare app.js as a source file in my HTML or is using the app name in the HTML tag sufficient?
Welcome to Angular! Can you verify that you have an ng-app='appname' directive in your HTML? You'll need this to "bootstrap" the entire application. You'll also need to declare ui.router as a dependency, which you'll need in order for this to work.
I work at Stormpath and we have a guide which shows you how to build an Angular application from scratch, including authorization (using our module). You can find it here: Stormpath's AngularJS Guide
Hope this helps!

Unit Testing UI Router Nested States

I'm trying to unit test ui.router nested states and am getting the following error:
Error: Could not resolve 'client_view.notes' from state ''
I know it's something to do with how I'm setting up the states & scopes before the test. Can someone fill me in?
Routing:
.state('client_view', {
url: '/client/{id}',
templateUrl: 'static/templates/client.tpl.html',
controller: 'ClientController'
})
.state('client_view.notes', {
url: '/notes',
templateUrl: 'static/templates/client_notes.tpl.html',
controller: 'ClientNotesController',
parent: 'client_view'
})
Test:
$scope = $rootScope.$new();
client_controller = $controller('ClientController', {
$scope: $scope,
$state: $state
});
$rootScope.$apply(function () {
$state.go('client_view.notes');
});
expect($scope.active('notes')).toEqual(true);
$scope.active is a method I wrote that is present in the ClientController. The test will pass if set up properly.
Thanks!
I have been waiting to cross this bridge of route testing myself. So I dont know exactly how to handle this situation but I can try to speculate.
As the spec doesnt have access to the whole picture of from state, maybe you can use
$state.current
to set the current state before you apply the $state.go - but this is just speculation - as the error is saying i dont know where I came from.
Error: Could not resolve 'client_view.notes' from state ''
state config api

Angular controller not initialized in jasmine test

I ran into issues writing jasmine tests for an AngularJS application using angular ui-router. My services and app get initialized properly in the test, but the controllers do not start up properly. I've taken the application in question out of the equation and reduced the problem to a simple one controller example that exhibits the same behavior. Here's the actual test code:
describe('Test', function() {
var async = new AsyncSpec(this);
var scope = {};
beforeEach(angular.mock.module('TestApp'));
beforeEach(angular.mock.inject(function($rootScope, $state, $templateCache) {
scope.$rootScope = $rootScope;
scope.$state = $state;
$templateCache.put('start.html', '<div class="start"></div>');
}));
async.it('Test that TestCtrl is initialized', function(done) {
scope.$rootScope.status = { done: false };
scope.$rootScope.$on('$stateChangeSuccess', function(event, state, params) {
expect(scope.$rootScope.status.done).toBe(true);
done();
});
scope.$state.transitionTo('start', {}, { notify: true });
scope.$rootScope.$apply();
});
});
Here's the complete runnable test
The application gets initialized correctly, the ui router is able to transition the application to the correct state, but the controller does not get initialized. I need the router to initialize the controllers as the router passes critical configuration to them. I want to avoid duplicating that configuration in the tests.
I must be missing something, but what? I appreciate any and all input, thanks!
You need to use the $controller service to instantiate your controller in your tests and pass it your scope. For example...
ctrl = $controller('TestCtrl', {$scope: scope});
Notice that I also moved the declaration of $rootScope.done to the TestCtrl to prevent an error about $rootScope.done being undefined. Here's the fiddle...
http://jsfiddle.net/C8QtB/3/

Categories

Resources