0

I'm getting a weird bug when using ui-router with AngularJS.

I have an Admin area where the admin user can manage users and roles with links constructed by ui-router via ui-sref. The links that are generated should look as follows:

  • To view a list of users: #/admin/users.
  • To view details of a user: #/admin/users/:key.
  • To edit a user: #/admin/users/:key/edit.

The weird thing is, sometimes the :key value doesn't get populated despite being provided with a value. So I end up with urls that look like #/admin/users//edit. This doesn't happen every time, only sometimes, but I have no idea why its happening.

I've included most of the code below. I would have included a plunker, but I wasn't able to reproduce the problem. Sorry to have to throw a load of code into the question.

index.html:

<!doctype html>
<html class="no-js">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Project</title>
        <link rel="stylesheet" href="vendor/sass-bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="styles/main.css">
        <link rel="stylesheet" href="styles/admin.css">
    </head>
    <body ng-app="myApp" id="ng-app">

        <div class="wrapper">
            <div ng-include="'app/navigation/navigation.tpl.html'"></div>

            <div ui-view="" class="view"></div>
        </div>

        <script>
            // This object is injected by the server and consumed by UserProfile (a custom angularjs service)
            window.currentUserProfile = {
                username: 'adminUser',
                firstname: 'First',
                lastname: 'Last',
                role: 'Admin',
                permissions: ['home.view','admin.view']
            };
        </script>

        <script src="vendor/jquery/jquery.js"></script>
        <script src="vendor/angular/angular.js"></script>
        <script src="vendor/angular-ui-router/release/angular-ui-router.js"></script>
        <script src="vendor/sass-bootstrap/dist/js/bootstrap.js"></script>
        <script src="vendor/angular-resource/angular-resource.js"></script>

        <script src="<!-- javascript-files.js -->"></script>
    </body>
</html>

app.js:

'use strict';
angular.module('myApp', [
    'ui.router',
    // ... other modules ...
    'myApp.admin'
])
.run(['$rootScope', '$state', '$stateParams', function($rootScope, $state, $stateParams) {
    $rootScope.$state = $state;
    $rootScope.$stateParams = $stateParams;
}])
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.when('', '/');
    $urlRouterProvider.otherwise('/404');
}]);

app/admin/admin.html:

<div class="container-fluid" id="admin-page">
    <div class="row">
        <div class="col-md-2">
            <div class="well well-sm">
                <ul class="nav nav-pills nav-stacked">
                    <li ng-repeat="item in sections" ui-sref-active="active"><a class="" ui-sref="{{item.state}}">{{item.name}}</a></li>
                </ul>
            </div>
        </div>
        <div ui-view="" class="col-md-10"></div>
    </div>
</div>

app/admin/admin.js:

'use strict';
angular.module('myApp.admin', [
    'myApp.admin.roles',
    'myApp.admin.users'
])
.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.when('/admin', '/admin/roles');
    $stateProvider
    .state('admin', {
        url: '/admin',
        templateUrl: 'app/admin/admin.tpl.html',
        controller: 'AdminCtrl',
        data: {
            requiresPermission: 'admin.view'
        }
    });
}])
.controller('AdminCtrl', ['$scope', 'UserProfile', function ($scope, UserProfile) {
    $scope.sections = [
        {name: 'Roles', state: 'admin.roles.list'},
        {name: 'Users', state: 'admin.users.list'}
    ];
}]);

app/admin/users/users.js:

'use strict';
angular.module('myApp.admin.users', [
    'ngResource'
])
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $stateProvider
    .state('admin.users', {
        abstract: true,
        url: '/users',
        data: {
            requiresPermission: 'admin.view'
        },
        template: '<div ui-view/>'
    })
    .state('admin.users.list', {
        url: '',
        templateUrl: 'app/admin/users/index.tpl.html',
        controller: 'UsersCtrl',
        resolve: {
            users: ['User', function(User) {
                return User.getAll();
            }]
        }
    })
    .state('admin.users.detail', {
        url: '/:key',
        templateUrl: 'app/admin/users/detail.tpl.html',
        controller: 'UserCtrl',
        resolve: {
            user: ['$stateParams', 'User', function($stateParams, User) {
                return User.get({'key':$stateParams.key});
            }]
        }
    })
    .state('admin.users.edit', {
        url: '/:key/edit',
        templateUrl: 'app/admin/users/edit.tpl.html',
        controller: 'UserCtrl',
        resolve: {
            user: ['$stateParams', 'User', function($stateParams, User) {
                return User.get({'key':$stateParams.key});
            }]
        }
    });
}])
.controller('UsersCtrl', ['$scope', 'users', 'User', function ($scope, users, User) {
    $scope.users = users;
}])
.controller('UserCtrl', ['$scope', 'user', function ($scope, user) {
    $scope.user = user;
}])
.factory('User', ['$resource', function ($resource) {
    return $resource('api/users/:key', {key:'@key'}, {
        update: {method:'PUT'},
        getAll: {
            method: 'GET',
            params: {
                key: 'users.json'
            },
            isArray: true
        }
    });
}]);

app/admin/users/index.tpl.html:

<div>
    <h1>Users</h1>
    <div class="table-responsive">
        <table class="table table-bordered table-striped table-hover">
            <thead>
                <tr>
                    <th>Email</th>
                    <th>Name</th>
                    <th>Role</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="user in users" ui-sref="admin.users.detail({key: '{{user.id}}' })">
                    <td>{{user.email}}</td>
                    <td>{{user.firstname}} {{user.lastname}}</td>
                    <td>{{user.role.name}}</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

app/admin/users/detail.tpl.html

<div>
    <div class="row">
        <div class="col-md-12">
            <div class="btn-toolbar" role="toolbar">
                <div class="btn-group">
                    <a ui-sref="admin.users.list" class="btn btn-grey" title="Back to Users"><span class="glyphicon glyphicon-arrow-left"></span></a>
                </div>
                <div class="btn-group">
                    <a ui-sref="admin.users.edit({key: '{{user.id}}' })" class="btn btn-grey" title="Edit User"><span class="glyphicon glyphicon-pencil"></span></a>
                </div>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <h1>{{user.firstname}} {{user.lastname}}</h1>
            <p class="lead">{{user.id}} {{user.email}} - {{user.role.name}}</p>
        </div>
    </div>
</div>

app/admin/users/edit.tpl.html:

<div>
    <div class="row">
        <div class="col-md-12">
            <h1>Edit User</h1>
            <form class="form-horizontal" role="form">
                <div class="form-group">
                    <label for="email" class="col-md-2 control-label">Email</label>
                    <div class="col-md-6">
                        <input type="text" class="form-control" id="email" placeholder="Email" ng-model="user.email">
                    </div>
                </div>
                <div class="form-group">
                    <label for="firstname" class="col-md-2 control-label">First Name</label>
                    <div class="col-md-6">
                        <input type="text" class="form-control" id="firstname" placeholder="First Name" ng-model="user.firstname">
                    </div>
                </div>
                <div class="form-group">
                    <label for="lastname" class="col-md-2 control-label">Last Name</label>
                    <div class="col-md-6">
                        <input type="text" class="form-control" id="lastname" placeholder="Last Name" ng-model="user.lastname">
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-6">
                        <a ui-sref="admin.users.detail({'key': '{{user.id}}' })" class="btn btn-primary">Save</a>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>

Any help would be appreciated.

1
  • I dont know about $stateParams , but I know in config if you want to resolve something using url Params , instead of using $routeParams.yourParam we must use $route.current.params That means in your resolve function , instead of injecting $routeParam , you must inject $route! maybe this is true for $stateParam ? dont know:( I mean maybe in your config instead of $stateParam.YourParam , you must inject $state and use it like : $state.current.params ? Commented Jun 13, 2014 at 15:00

2 Answers 2

1
+50

The one thing that stands out for me is the odd usage of params in your ui-sref directives.

Where you have:

ui-sref="admin.users.detail({key: '{{user.id}}' })"

This should suffice:

ui-sref="admin.users.detail({ key: user.id })"

Could you attempt changing your ui-sref directives to this format and see what results you get? Two way binding shouldn't be needed because ui-sref will take care of compiling the passed in params.

Also as another sidenote; If you make use of track by in your ng-repeat directive, you can start ruling out multiple instances of undefined/null/empty string user id's.

As such: ng-repeat="user in users track by user.id"

I hope that helps to some degree. https://github.com/angular-ui/ui-router/wiki/Quick-Reference#ui-sref

Good luck! : )

Sign up to request clarification or add additional context in comments.

Comments

0

Maybe some hint... I tried to play around with your scenario. And have to say that to achieve url like this #/admin/users//edit using ui-sref like used in the code above:

ui-sref="admin.users.edit({key: user.id})"

there must be either undefined or null or string.Empty '' passed as user.id.

There is a plunker similar to your scenario. These are users in that example

var data = [
    { id : "key1", firstname: "firstName1", ... },
    { id : "key2", firstname: "firstName2", .., },
    { id : ""    , firstname: "firstNameE", ...},
    { }
];

the first two do generate correct/expected url

#/admin/users/key1/edit
#/admin/users/key2/edit

The last two do produce #/admin/users//edit as explained above. So, I would suggest check what values are passed back from your Users resource...

1 Comment

As far as I can tell, I am getting the correct values back from the Users resource. The behaviour I've found is that, for the same User model, sometimes I get #/admin/users/key1/edit and sometimes I get #/admin/users//edit

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.