ui-router nested view doesn't get loaded properly - javascript

Hey I have a simple app which I have created.
The app has the following views
1.Settings - Main View.
2.Profile - Sub view, child of Settings View.
3.Account - Sub View, child of Settings View.
I want the application to start from profile page, therefore I wrote the following code:
$urlRouterProvider.otherwise('/profile');
But apparently noting is happen, I got a blank screen and can't see the view properly(settings.profile).
I have added my code to a plunker to make things more convenient and accessible for other developers which can help me solve my problem.
https://plnkr.co/edit/RFP5dUNV876cy8vRDuvL?p=preview
Main main code is like this:
/**
* Module
*
* Description
*/
var app = angular.module('app', ['ui.router'])
app.controller('mainCtrl', ['$scope', function($scope){
$scope.title="Index Page";
}]);
app.controller('ProfileController', ['$scope', function($scope){
$scope.title="Profile Page";
}]);
app.controller('AccountController', ['$scope', function($scope){
$scope.title="Account Page";
}]);
app.controller('SettingsController', ['$scope', function($scope){
$scope.title="Settings Page";
}]);
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('settings', {
url: '/settings',
templateUrl: 'settings.html',
controller: 'SettingsController'
})
.state('settings.profile', {
url: '/profile',
templateUrl: 'profile.html',
controller: 'ProfileController'
})
.state('settings.account', {
url: '/account',
templateUrl: 'account.html',
controller: 'AccountController'
});
$urlRouterProvider.otherwise('/profile');
});

Change the .otherwise function to:
$urlRouterProvider.otherwise('/settings/profile');
Since the other routes are nested within settings state, you can only access the nested views via the <ui-view> in settings.html. The urls are also nested within each other if url parameter is defined in both, so /settings/profile is required.
Updated Plnkr

Related

Routing issue with angular directives

I'm trying to setup one of my first angular projects and am having trouble getting to grips with the routing.
On page load I see the initial template that has been set by the preferencesDirective, which is great.
When I click the "Change Template" button I want it to change to another template but nothing happens. If I set the template url's in the $routeProvider to something invalid then I see a 404 error in the debugger which tells me something must be working but nothing happens when the template url is valid.. How do I get it to change correctly?
Thanks.
<div id="PreferencesApp" class="" ng-app="clientPreferencesModule">
<preferences-directive factory-settings="clientPreferences"></preferences-directive>
Change Template
</div>
<script>
var app = angular.module("clientPreferencesModule", ["ngResource", "ngRoute"]);
//var app = angular.module("clientPreferencesModule", ["ngRoute"]);
app.config(function ($routeProvider) {
$routeProvider.when("/", { controller: "clientPreferencesController", templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html' });
$routeProvider.when("/Preferences/:id", { controller: "clientPreferencesController", templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html' });
$routeProvider.when("/Preferences", { controller: "clientPreferencesController", templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html' });
$routeProvider.when("/Details", { controller: "clientPreferencesController", templateUrl: '/AngularTemplates/ClientPreferences/DetailsTemplate.html' });
});
app.controller('clientPreferencesController', clientPreferencesController);
clientPreferencesController.$inject = ["$scope", "$resource", "$rootScope", "$http", "$route", "$location"];
function clientPreferencesController($scope, $resource, $rootScope, $http, $route, $location) {
this.model = #Html.Raw(JsonConvert.SerializeObject(Model));
$scope.location = $location.path();
}
app.directive('preferencesDirective', preferencesDirective);
function preferencesDirective() {
return {
restrict: 'EA',
scope:
{
factorySettings: '='
},
controller: 'clientPreferencesController',
controllerAs: 'pc',
bindToController: true,
templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html'
}
}
</script>
For routing to work you've to create different views along with its associated controller & then have directive inside that view. And also you'll need ng-view directive in index.html in which all the routes view going to be loaded. And also preferencesDirective should only contain the reusable unique functionality, & the complete app view, so that you can have it different views with different data sets alongside different view components.
So, your template can be:
<div id="PreferencesApp" class="" ng-app="clientPreferencesModule">
Change Template
<div ng-view></div>
</div>
Now for different routes you can have each different controllers or if you want to handle it in one controller the have only one, but different from directive's controller, so it can be,
app.config(function ($routeProvider) {
$routeProvider.when("/", { controller: "viewController", templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html' });
$routeProvider.when("/Preferences/:id", { controller: "viewController", templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html' });
$routeProvider.when("/Preferences", { controller: "viewController", templateUrl: '/AngularTemplates/ClientPreferences/PreferencesTemplate.html' });
$routeProvider.when("/Details", { controller: "viewController", templateUrl: '/AngularTemplates/ClientPreferences/DetailsTemplate.html' });
});
Have preferencesDirective in all these templates. (This will now potentially change the directive's template but you can have changing dom of each view in views's templates & keep directive's template constant)
Now in viewController by making use of $routeParams you can check the current route & send different data to preferencesDirective's controller.
Now if you must want to change directives template conditionally then make use of ng-include inside directive's template.
function preferencesDirective() {
return {
restrict: 'EA',
scope:
{
factorySettings: '=',
templateSrc: '='
},
controller: 'clientPreferencesController',
controllerAs: 'pc',
bindToController: true,
templateUrl: '<ng-include src="pc.template()"></ng-include>'
}
}
function clientPreferencesController($scope, $resource, $rootScope, $http, $route, $location) {
this.model = #Html.Raw(JsonConvert.SerializeObject(Model));
$scope.location = $location.path();
$scope.template = function(){
if($scope.templateSrc) {
return '/AngularTemplates/ClientPreferences/'+ $scope.templateSrc + '.html';
}
}
}
Here share that templateSrc from viewController based on current route.

AngularJS not loading templates in Codeigniter

I'm trying to use Angular with my Codeigniter project. The problem is that it doesn't load the proper template file through angular router.
In the root instead of showing templateUrl: 'index.php/welcome/home', it loads the index.php/welcome. It loads the index page inside the controller instead of the ones I have specified.
Below is my code:
JS
var app = angular.module('MyCtrl', ['ngRoute', "ui.bootstrap.modal"]);
app.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'index.php/welcome/home',
controller: 'mainController'
})
.when('/user', {
templateUrl: 'index.php/user/account',
controller: 'userController'
})
});
app.controller('mainController', function($scope) {
$scope.message = 'main con';
});
app.controller('userController', function($scope) {
$scope.message = 'user con!';
});
Welcome controller in Codeigniter:
public function home(){
$this->load->view('home');
}
Can someone tell me why this is not working and what I have done wrong?

Access url params in master controller

My application has two controllers. I have a pageCtrlwhich I use to handle my navigation and sidebar. I also have a calendarCtrl for handling the data on my page. This controller is configured as shown below:
$stateProvider
.state('calendar', {
url: '/calendar/:category/:competition/:team',
controller: 'calendarCtrl',
templateUrl: 'app/modules/calendar/calendarView.html',
})
To make my navigation work I also need access to the :category/:competition/:team-params in my pageCtrl. Can I configure this using the same way? Something like:
$stateProvider
.state("page", {
abstract: true,
controller: 'pageCtrl',
// params: :category/:competition/:team
})
Edit: Using $stateParams in the calendarCtrl works fine. I just can't figure out how I can make sure my pageCtrl also can get read the url.
Since you're using ui.router, inject $stateParams in your controller(s) and then you can access those values like so:
controller.js
function($stateParams){
$stateParams.category
$stateParams.competition
$stateParams.team
My suggestion would be - use more views - the UI-Router built feature.
Multiple Named Views
There is a working plunker
Let's have the 'Parent' state which has this template:
This blue is the parent template. Orange are child views
<!-- HERE is one named view target -->
<div ui-view="title">This is a title filled by child having access to param</div>
...
<!-- HERE is other view target un-named -->
<div ui-view></div>
And its state is very simple. The interesting is the child state, which is taking care about both views:
.state('parent', {
abstract: true,
url: "/parent",
templateUrl: 'tpl.parent.html',
})
.state('parent.child', {
url: "/child/:id",
views : {
'': {
templateUrl: 'tpl.child.html',
},
'title': {
templateUrl: 'tpl.title.html',
controller: 'TitleCtrl',
},
}
})
So, we do have a target for "some other view" title or side bar. Check it here
And we can even place some default implementation there inside of our "non-abstract" parent state.
There is extended plunker with non abstract parent state definition:
.state('parent', {
url: "/parent",
views : {
'': {
templateUrl: 'tpl.parent.html',
},
'title#parent': {
template: 'the parent own TITLE',
},
}
})
Check it here
There is a way, how to grant access to latest/up-to-date $stateParams - including current state and its child(ren) as well. (working example here)
It is surprisingly easy:
.run(['$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}])
And that's it. (check similar answer and some discussion here)
With this approach, we will even in parent $scopes have updated reference to the latest $stateParams. While in our own, we will still receive just our own part
.controller('ParentCtrl', ['$scope', '$stateParams', function ($scope, $stateParams) {
$scope.currentStateParams = $stateParams;
}])
The above is valid for states like these:
.state('parent', {
url: "/parent?area",
templateUrl: 'tpl.html',
controller: 'ParentCtrl',
})
.state('parent.child', {
url: "/child/:id",
templateUrl: 'tpl.html',
controller: 'ChildCtrl',
})
Working example to play here.
But I still would say, that this is a bit ... against the UI-Router. I would prefer this answer. Because in that case, each view (while injected into some parent area) is really aware about $stateParams, which belongs to that state. What we are doing here is introduction of some observer pattern (we should watch changes if we want to react in parent) and that would later bring more issues then profit

How to set ng-show on a specific element through different views/controllers?

Plunker: http://plnkr.co/edit/0efF1Av4lhZFGamxKzaO?p=preview
Below is my header, there is an ng-show="cornerLogo" which I only want to be set true on the about, login and register views, but false the home view.
<body id="body_id"
ng-app="myApp"
ng-controller="HomeCtrl">
<header>
<section ng-show="cornerLogo">
<h1>Logo</h1>
</section>
<nav id="main_nav">
<ul>
<li><a ui-sref="about">About</a></li>
<li><a ui-sref="login">Sign In</a></li>
<li><a ui-sref="register">Create Account</a></li>
</ul>
</nav>
</header>
<ui-view></ui-view>
So it works in my HomeCtrl because that is the main controller on the page.
var app = angular.module('app-home', [])
.controller('HomeCtrl', ['$scope', function($scope) {
$scope.cornerLogo = false;
}]);
However when I switch to the about, login or register views I lose that $scope
Is there a way somehow to have a global var set somewhere in my stateProvider for ui-router? Otherwise, how would you go about this issue?
var app = angular.module('bitAge',
['ui.router',
'app-header',
'app-home',
'app-about',
'app-login',
'app-register'])
.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: '_views/home.html',
controller: 'HomeCtrl',
})
.state('about', {
url: '/about',
templateUrl: '_views/about.html',
controller: 'AboutCtrl'
})
.state('login', {
url: '/login',
templateUrl: '_views/login.html',
controller: 'LoginCtrl'
})
.state('register', {
url: '/register',
templateUrl: '_views/register.html',
controller: 'RegisterCtrl'
});
// default view:
$urlRouterProvider.otherwise('/home');
}]);
Apart from my comments in the question, to fix your issue you can take this approach.
You have HomeCtrl specified as bound controller in the state registration of home partial. So instead create a main controller for your application. So that you keep the responsibilities separated out. Inject $state and expose a method called hideLogo and use $state.is to determine the logic to show/hide the logo.
i.e:
var app = angular.module('app-home')
.controller('MainCtrl', ['$scope', '$state', function($scope, $state) {
$scope.hideLogo = function(){
return $state.is('home');
}
}]);
In the index html use MainCtrl as your main controller for the app.
<body ng-app="myApp" ng-controller="MainCtrl">
<header>
<section
ng-hide="hideLogo()">
<h1>Corner Logo</h1>
</section>
Plnkr
If you want to use $state directly on the view you would need to inject it in MainCtrland set $state on the $scope object so that you can use it directly. Though i highly recommend not to use this technique, you should not expose state in the scope object and ultimately in the view. Just expose only what is needed in the viewmodel.
i.e in the MainCtrl :
var app = angular.module('app-home')
.controller('MainCtrl', ['$scope', '$state', function($scope, $state) {
$scope.$state= $state;
}]);
and just use it on the view as:
<section
ng-hide="$state.is('home')">
You can check your current state and depends on that, show or not your logo.
<section ng-show="$state.includes('home')">
<h1>Logo</h1>
</section>
Also, your anchor elements to navigate, should be like this <a ui-sref="about">About</a> and so on, because if you use normal href attribute, angular wont change state.
Also, you need to inject $state in your main module and then you can use $state module
var app = angular.module('myApp',
['ui.router',
'app-home',
'app-about']).run(function ($state,$rootScope) {
$rootScope.$state = $state;
})
UPDATE:
Here is the punklr with the answer

AngularJS dynamic routing

I currently have an AngularJS application with routing built in.
It works and everything is ok.
My app.js file looks like this:
angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', { templateUrl: '/pages/home.html', controller: HomeController });
$routeProvider.when('/about', { templateUrl: '/pages/about.html', controller: AboutController });
$routeProvider.when('/privacy', { templateUrl: '/pages/privacy.html', controller: AboutController });
$routeProvider.when('/terms', { templateUrl: '/pages/terms.html', controller: AboutController });
$routeProvider.otherwise({ redirectTo: '/' });
}]);
My app has a CMS built in where you can copy and add new html files within the /pages directory.
I would like to still go through the routing provider though even for the new dynamically added files.
In an ideal world the routing pattern would be:
$routeProvider.when('/pagename', { templateUrl: '/pages/pagename.html', controller: CMSController });
So if my new page name was "contact.html" I would like angular to pick up "/contact" and redirect to "/pages/contact.html".
Is this even possible?! and if so how?!
Update
I now have this in my routing config:
$routeProvider.when('/page/:name', { templateUrl: '/pages/home.html', controller: CMSController })
and in my CMSController:
function CMSController($scope, $route, $routeParams) {
$route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
alert($route.current.templateUrl);
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];
This sets the current templateUrl to the right value.
However I would now like to change the ng-view with the new templateUrl value. How is this accomplished?
angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/page/:name*', {
templateUrl: function(urlattr){
return '/pages/' + urlattr.name + '.html';
},
controller: 'CMSController'
});
}
]);
Adding * let you work with multiple levels of directories dynamically.
Example: /page/cars/selling/list will be catch on this provider
From the docs (1.3.0):
"If templateUrl is a function, it will be called with the following
parameters:
{Array.} - route parameters extracted from the current
$location.path() by applying the current route"
Also
when(path, route) : Method
path can contain named groups starting with a colon and ending with a star: e.g.:name*. All characters are eagerly stored in $routeParams under the given name when the route matches.
Ok solved it.
Added the solution to GitHub - http://gregorypratt.github.com/AngularDynamicRouting
In my app.js routing config:
$routeProvider.when('/pages/:name', {
templateUrl: '/pages/home.html',
controller: CMSController
});
Then in my CMS controller:
function CMSController($scope, $route, $routeParams) {
$route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
$.get($route.current.templateUrl, function (data) {
$scope.$apply(function () {
$('#views').html($compile(data)($scope));
});
});
...
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];
With #views being my <div id="views" ng-view></div>
So now it works with standard routing and dynamic routing.
To test it I copied about.html called it portfolio.html, changed some of it's contents and entered /#/pages/portfolio into my browser and hey presto portfolio.html was displayed....
Updated
Added $apply and $compile to the html so that dynamic content can be injected.
I think the easiest way to do such thing is to resolve the routes later, you could ask the routes via json, for example. Check out that I make a factory out of the $routeProvider during config phase, via $provide, so I can keep using the $routeProvider object in the run phase, and even in controllers.
'use strict';
angular.module('myapp', []).config(function($provide, $routeProvider) {
$provide.factory('$routeProvider', function () {
return $routeProvider;
});
}).run(function($routeProvider, $http) {
$routeProvider.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
}).otherwise({
redirectTo: '/'
});
$http.get('/dynamic-routes.json').success(function(data) {
$routeProvider.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
});
// you might need to call $route.reload() if the route changed
$route.reload();
});
});
In the $routeProvider URI patters, you can specify variable parameters, like so: $routeProvider.when('/page/:pageNumber' ... , and access it in your controller via $routeParams.
There is a good example at the end of the $route page: http://docs.angularjs.org/api/ng.$route
EDIT (for the edited question):
The routing system is unfortunately very limited - there is a lot of discussion on this topic, and some solutions have been proposed, namely via creating multiple named views, etc.. But right now, the ngView directive serves only ONE view per route, on a one-to-one basis. You can go about this in multiple ways - the simpler one would be to use the view's template as a loader, with a <ng-include src="myTemplateUrl"></ng-include> tag in it ($scope.myTemplateUrl would be created in the controller).
I use a more complex (but cleaner, for larger and more complicated problems) solution, basically skipping the $route service altogether, that is detailed here:
http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm
Not sure why this works but dynamic (or wildcard if you prefer) routes are possible in angular 1.2.0-rc.2...
http://code.angularjs.org/1.2.0-rc.2/angular.min.js
http://code.angularjs.org/1.2.0-rc.2/angular-route.min.js
angular.module('yadda', [
'ngRoute'
]).
config(function ($routeProvider, $locationProvider) {
$routeProvider.
when('/:a', {
template: '<div ng-include="templateUrl">Loading...</div>',
controller: 'DynamicController'
}).
controller('DynamicController', function ($scope, $routeParams) {
console.log($routeParams);
$scope.templateUrl = 'partials/' + $routeParams.a;
}).
example.com/foo -> loads "foo" partial
example.com/bar-> loads "bar" partial
No need for any adjustments in the ng-view. The '/:a' case is the only variable I have found that will acheive this.. '/:foo' does not work unless your partials are all foo1, foo2, etc... '/:a' works with any partial name.
All values fire the dynamic controller - so there is no "otherwise" but, I think it is what you're looking for in a dynamic or wildcard routing scenario..
As of AngularJS 1.1.3, you can now do exactly what you want using the new catch-all parameter.
https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5
From the commit:
This allows routeProvider to accept parameters that matches
substrings even when they contain slashes if they are prefixed
with an asterisk instead of a colon.
For example, routes like edit/color/:color/largecode/*largecode
will match with something like this
http://appdomain.com/edit/color/brown/largecode/code/with/slashs.
I have tested it out myself (using 1.1.5) and it works great. Just keep in mind that each new URL will reload your controller, so to keep any kind of state, you may need to use a custom service.
Here is another solution that works good.
(function() {
'use strict';
angular.module('cms').config(route);
route.$inject = ['$routeProvider'];
function route($routeProvider) {
$routeProvider
.when('/:section', {
templateUrl: buildPath
})
.when('/:section/:page', {
templateUrl: buildPath
})
.when('/:section/:page/:task', {
templateUrl: buildPath
});
}
function buildPath(path) {
var layout = 'layout';
angular.forEach(path, function(value) {
value = value.charAt(0).toUpperCase() + value.substring(1);
layout += value;
});
layout += '.tpl';
return 'client/app/layouts/' + layout;
}
})();

Categories