Angular $location doesn't work - angularjs

I created a plunkr for this code and it can be viewed here:
The problem is very simple. I am trying to create a master/details scenario. So there are two templates: listings and details. In the listing controller there is a methods redirects to the detials route. This method works well as i verified it with the debugger (via breaking point).
$scope.goToDetails = function(propItem) {
//$rootScope.currentProperty = propItem;
$location.path('/details/');
}
The 'details' path (see blow) calls the 'detailsController', which is currently (for testing purposes) defined as:
var detailsController = function($scope, $http, $routeParams, $rootScope) {
var dosomething = "do";
};
I verified with the debugger that the execution indeed reaches the "dosomething" command and that the route changes in the browser to "details". However, and HERE is the problem, when I continue with the debugger, angular changes the route back to the default route. I went over the definitions but nothing that i did seems wrong.Any ideas?
Here is how I defined the routes:
app.config(function($routeProvider) {
$routeProvider
.when('/main/', routes.main)
.when('/details/', routes.details)
.otherwise({
redirectTo: '/main'
});
});
var routes = {
main: {
templateUrl: 'PropertiesResults.html',
controller: 'listingController'
},
details: {
templateUrl: 'property-detail.html',
controller: 'detailsController'
},
}

Replace:
<a href="#" ng-click="goToDetails(property)" ...
With:
<a href="" ng-click="goToDetails(property)" ...
Or it will go to your otherwise route.

Change your link to be
Read More
A few things to consider:
Links should work just like in a regular html page
If you want to execute code on a new page, look into putting that code on the route, or in a controller in the new view.
If you want to conditionally enable or disable the link, think about disabling the link with something like <a ng-disabled="expression"... this might not work out of the box but you could add a custom directive.
If you still need to run that code in a controller method, consider using a <button type="button" class="link"... and style it to look like a link, e.g. display: inline; border: 0; background: transparent;"
Happy coding

Related

$location.path or $location.url doesn't trigger the ngRoute controller

I have a route defines as follows:
$routeProvider.
when('/projects/', {
controller: 'ProjectCtrl',
controllerAs: 'project_ctrl',
templateUrl: '/static/app/partials/project.html'
}).
After the login finishes I need the user to land on this link, hence in my controller I am using this:
vm.login = function(form) {
if (form.$valid) {
loginService.login(vm.loginFormData.username, vm.loginFormData.password);
loginService.setUpUser()
$location.url("/projects");
}
}
But unfortunately the controller associated with this view is not triggered, that is ProjectCtrl is not triggered. However when I click on the navigation link which uses in the dom, it works fine. Can someone please guide me here, may I am missing something conceptual.
Hence the larger question is how do I redirect a user in the controller using some APIs which also complies with ngRoute based controllers.
Try removing the last / in url so it matches $location.url("/projects");
$routeProvider.
when('/projects', {

Angular UI Router, Sub views get unset after a change of state

I debated a while on this but I got a Plunk that reproduce it.
I have a state "Contact" that get loaded by default. with $state.transitionTo
Inside that state I have some views, they all get loaded and everything work.
If I click to change the state to "Home" by default or by "ui-sref" and in the "Home" state/template I have ui-sref="contacts". When we click back to set the state to contacts it should work, but all the sub views are now not being called properly.
It seems that when ui-sref call the state this one behave differently that when it is loaded by default.
Why $state.transitionTo(''); seems to work differently than ui-sref.
<script>
var myapp = angular.module('myapp', ["ui.router"])
myapp.config(function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /
$urlRouterProvider.otherwise("/")
$stateProvider
.state('home', {
templateUrl: 'home.html',
controller: function($scope){
}
})
.state('contacts', {
templateUrl: 'contacts.html',
controller: function($scope){
}
})
.state('contacts.list', {
views:{
"":{
template: '<h1>Contact.List Working wi no Data defined.</h1>'
},
"stateSubView":{
template: '<h2>StateSubView Working</h2>'
},
"absolute#":{
template: '<h2>Absolute item</h2>'
}
}
});
});
myapp.controller('MainCtrl', function ($state) {
$state.transitionTo('contacts.list');
})
Q2:
Why is the Absolute tag that is under contact work when I add the view in the Index, but is not working when it is inside the contact.html file. Absolute reference work only with the Index and not if called everywhere?
"absolute#":{
template: '<h2>Absolute item</h2>'
}
I saw that in index.html you have an empty ui-view tag. What do you expect to go there? I think you can not do this. The router just doesn't know with which state (home or contacts) it should replace. Apparently it picks the second one (contacts). I'd suggest to put url: '/' in the home state and you'll see the difference.
This is for sure one issue.
Other than that:
You can't simply access views from contacts.list in contacts afaik.
The empty ui-view work as a wild card and can be use to switch across multiple route even if we have nested element. But if we have a nested view contact.list it can only be access if we put the whole path in ui-sref="contacts.list" because the list child of contact cannot be access only by using ui-sref="contacts"

DotJem AngularJS routing: Error: Could not locate 'pagename' under '$root'

Whenever I start the site it works fine, but all the links are broken. They work, I can click on them and it directs me to the right URL but no content relative to that specific page shows up. But if I were to copy and paste that URL into a different browser window, itd redirect me back home as if that URL didnt exist...
Heres my apps JS file:
var app = angular.module("myApp", ['dotjem.routing']);
app.config(function($routeProvider, $stateProvider){
$routeProvider.otherwise({redirectTo: '/home'});
$stateProvider.state('home', {route: '/home', views:{"main":{template:"template/guide.html"}}});
$stateProvider.state(['$register', '$http', function(reg, $http){
return $http.get("api/getpages.php").success(function(data){
for(element in data){
reg(data[element].pagename, {route: data[element].path, view:{"main":{template:data[element].templateUrl}}});
}
});
}]);
});
Im getting this error when I try and click on pages after refreshing on a page that I clicked ona link to previously, then all the links on the menu bar go dead:
Error: Could not locate 'mybuilds' under '$root'.
at Error ()
at a.lookup (http://url.co.uk/angular/myblog/scripts/angular-routing.min.js:8:23732)
at a.resolve (http://url.co.uk/angular/myblog/scripts/angular-routing.min.js:8:23975)
at Object.J.url (http://url.co.uk/angular/myblog/scripts/angular-routing.min.js:8:18670)
at f (http://url.co.uk/angular/myblog/scripts/angular-routing.min.js:8:29260)
at i (http://url.co.uk/angular/myblog/scripts/angular-routing.min.js:8:29537)
at link (http://url.co.uk/angular/myblog/scripts/angular-routing.min.js:8:29694)
at nodeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.js:6230:13)
at compositeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.js:5640:15)
at compositeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.js:5643:13)
<a sref="'{{link.path}}'" class="ng-binding">
Is how my link works.
A JSON sample if it helps:
[{"id":1,"displayName":"Tour","pagename":"home","templateUrl":"templates\/tourview.html","path":"\/home"}]
I have also tried putting in the links manually but still no joy.
<div jem-view="main"></div>
Is my view. The home page works perfectly.
One of the things i can easily spot that seems incorrect is your use of sref, as you point it towards the route rather than the state, sref is meant to make it easier to manage states and their URLS in that you can link directly to them rather than having to duplicate the route in multiple places.
So rather than what you did, you should just say <a sref="link.pagename"> (it's a regular binding, we use '' around it when we make static links, just like ng-include) assuming you have a service or something where you store those links and they are just the json object you got from the server.
Working example: http://plnkr.co/edit/M9Ey6VnJn7wHLqNdHmwh?p=preview
To look at the use of sref, we can have a look at how it's done in that sample:
app.service('menu', function() {
var self = this;
self.register = function(title, state) {
self.items.push({
title: title, state: state
});
}
self.items = [];
});
As I said, you probably have some sort of menu service, in the sample I called this menu, that isn't the best name, but it serves it's purpose in the demo. This is where we register links.
Note that we CAN actually access the whole internal "state" tree, BUT that is not an official API. (it's exposed for testing). So instead, we simply manage it outside. This will make it possible to categorize items as well etc. as we might not want every single state to figure on the top bar.
app.config(['$stateProvider',
function(sp) {
sp.state('home', {
route: '/',
views: {
"main": {
template: "template/guide.html"
}
}
});
sp.state(['$register', '$http', 'menu',
function(register, http, menu) {
// This is just a workaround, home is a static state, but we can't access the
// menu service in the provider so we just register it here. There is 2 "correct"
// aproaches to this problem in my mind:
// - User a menu provider which can be used along static state registration.
// - Just write static references in the HTML.
//
// What I did here was just easier for this sample.
menu.register('HOME', 'home');
return http.get("routes.json").success(function(data) {
angular.forEach(data, function(state){
// Register the "state link" with the menu service.
// All we need here is it't title and the state name.
menu.register(state.displayName, state.pagename);
// Register the actual state. I would have prefered different names here
// but thats preference.
register(state.pagename, {
route: state.path,
views: { main: { template: state.templateUrl } }
});
});
});
}
]);
}
]);
Won't elaborate much more on this that the comments does.
app.controller('siteController', ['$scope', '$state', 'menu',
function($scope, $state, menu) {
//Some ugly bindup just so we can demonstrate that the latebound registration works.
this.menu = menu;
this.state = $state;
}
]);
I like to have a global site controller in my angular apps, in this example we just use it to expose the menu service and state service directly, that is not a recommended approach, but its a quick approach for a demo.
<body ng-controller="siteController as site">
...
<ul class="nav navbar-nav">
<li ng-repeat="page in site.menu.items"
ng-class="{ active: site.state.isActive(page.state) }">
<a sref="page.state">{{ page.title }}</a>
</li>
</ul>
And finally an example of how we build the menu.

If my url contains route parameters, then hash links reroute in angular

If I have a one level route, then the hash links work as expected with no rerouting. However I have some urls that are country/kh and if I try using hash tags such as country/kh#projects, the page reroutes, which is very annoying.
So, if im on page countries and click the link #developing, then the page will scroll to #developing without rerouting, which is desired. If I'm on page country/kh and I click #projects, the page will reroute, then scroll to #projects; I don't want the rerouting to occur.
The issue only occurs for links of the nature page1/parameter#anchor, not for simple pageA#anchor.
It is very difficult to answer your question without any code samples or a plunker. I implemented a plunker code ( http://plnkr.co/edit/oflB21?p=preview ) to try to reproduce this issue but as you can see I could not reproduce the issue. i.e. You can easily navigate back and forth between two different sections of the page, e.g. between #/Country/Italy#Section-4 and #/Country/Italy#Section-1, without any page load or reroute. Please check out my working example at the following plunker. This most probably is happening to you due to a missing hash bang or forward slash or details like that.
HTML snippet for the home page:
<ul>
<li>Go to /Country</li>
<li>Go to /Country/US</li>
<li>Go to /Country/Italy#Section-4</li>
<li>Go to /Country/Canada#Section-8</li>
</ul>
HTML snippet for the country page:
<div id="Section-1" class="section pink">
Section 1
<div ng-if="country">
<a ng-href="#/Country/{{country}}#Section-8">Go to /Country/{{country}}#Section-8</a>
</div>
<div ng-if="!country">
<a ng-href="#/Country#Section-8">Go to /Country#Section-8</a>
</div>
</div>
All the JavaScript code:
var app = angular.module("app", ["ngRoute"]);
app.config(["$routeProvider", "$locationProvider",
function ($routeProvider, $locationProvider) {
$routeProvider
.when("/", {
templateUrl: "./home-page.html",
caseInsensitiveMatch: true,
})
.when("/Home", {
templateUrl: "./home-page.html",
caseInsensitiveMatch: true,
})
.when("/Country", {
templateUrl: "./country-page.html",
caseInsensitiveMatch: true,
})
.when("/Country/:country", {
templateUrl: "./country-page.html",
caseInsensitiveMatch: true,
})
}]);
countryController.$inject = ["$scope", "$routeParams", "$location", "$anchorScroll"];
function countryController($scope, $routeParams, $location, $anchorScroll) {
$scope.country = $routeParams.country;
if (!!$location.$$hash) {
$location.hash($location.$$hash);
$anchorScroll();
}
}
Alright, I believe the main issue is that Angular handles routing with hashes (sometimes). What you need to do is use the $anchorScroll service. So your JS would look something like:
function ScrollCtrl($scope, $location, $anchorScroll) {
$scope.gotoBottom = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
};
}
And then your HTML could be:
<div id="scrollArea" ng-controller="ScrollCtrl">
<a ng-click="gotoBottom()">Go to bottom</a>
<a id="bottom"></a> You're at the bottom!
</div>
http://plnkr.co/edit/De6bBrkHpojgAbEvHszu?p=preview - this is a plunkr (not mine) that demonstrates using $anchorScroll if you need to see it in action
There's a dead-simply solution to your problem...
Instead of doing:
go
Just do
go
I suspect the reason for the unexpected behavior is a bug/feature of whatever routing solution you are using (ie the built-in angular router, or ui-router or whatever). ui-router has a way to disable re-routing when going to the same route...
I think I had the same problem that you are having.
This is how I did it with my github page, http://ngmap.github.io.
Th site, http://ngmap.github.io, has many pages and each page has lots of anchors, all anchors are coded naturally.
Without the following code of http://ngmap.github.io/javascripts/app.js, when you click an anchor in your page;
it sets $location.path to /anchor. i.el http://url.com/#anchor
and it sets $location.hash to ``.
This behaviour will prevent the page from scrolling down to the hash because simply there is no hash in the url.
By simply adding $location.hash and scrolling down to that anchor, all should work.
/**
* when the location is changed, scroll the page to the proper element.
* by changing location hash from '' to 'hash', so that it can be used as $anchorScroll
*/
$rootScope.$on('$locationChangeSuccess', function(newRoute, oldRoute) {
$location.hash($location.path().replace(/^\//,""));
$anchorScroll();
});
With the above code,
$location.path remains the same, /anchor
$location.hash now becomes anchor
The only thing you may not like is, the url. It looks little dirty, but I did not mind.
i.e. http:/ngmap.github.io/basics.html#/map-geolocation#map-geolocation
Hope it helps

AngularJS: Multiple views with routing without losing scope

I'm trying to implement a classic list/details UI. When clicking an item in the list, I want to display an edit form for that item while still displaying the list. I'm trying to work around Angular's 1-view-per-page limitation and decided to do it by having all URLs routed to the same controller/view. (Perhaps this is the root of my problem and I'm open to alternatives.)
Routing:
$routeProvider
.when('/list', { templateUrl: '/Partials/Users.html', controller: UserController })
.when('/edit/:UserId', { templateUrl: '/Partials/Users.html', controller: UserController })
.otherwise({ redirectTo: '/list' });
The view (/Partials/Users.html):
<!-- List of users -->
<div ng-repeat="user in Users">
Edit {{ user.Name }}
</div>
<!-- Edit form -->
<div>
{{ SelectedUser.Name }}
</div>
Controller:
function UserController($scope, $routeParams) {
// the model for the list
$scope.Users = GetUserListFromService();
// the model for the edit form
if ($routeParams.UserId != null)
$scope.SelectedUser = GetUserFromService($routeParams.UserId);
}
Problems:
When clicking an edit link, the controller is reinstantiated with a new scope, so I have to re-init the Users list. (In a more complex example I could have input from the user stored bound to the model and this would also get lost.) I'd prefer to persist the scope from the previous route.
I'd prefer to use a separate controller (or, as many other Angular developers have complained, the ability to have multiple displayed views!) but that leads to the same issue of losing scope.
Try using ui-router: http://github.com/angular-ui/ui-router.
They have nested views and easier state management than angular default routing :-)
Multiple views are not supported in core AngularJS. You can use this library for this purpose which supports any amount of nested views on the page, where each level is configured independently with its own controller and template:
http://angular-route-segment.com
It is much simpler to use than ui-router. Sample config may look like this:
$routeSegmentProvider.
when('/section1', 's1.home').
when('/section1/prefs', 's1.prefs').
when('/section1/:id', 's1.itemInfo.overview').
when('/section1/:id/edit', 's1.itemInfo.edit').
when('/section2', 's2').
segment('s1', {
templateUrl: 'templates/section1.html',
controller: MainCtrl}).
within().
segment('home', {
templateUrl: 'templates/section1/home.html'}).
segment('itemInfo', {
templateUrl: 'templates/section1/item.html',
controller: Section1ItemCtrl,
dependencies: ['id']}).
within().
segment('overview', {
templateUrl: 'templates/section1/item/overview.html'}).
segment('edit', {
templateUrl: 'templates/section1/item/edit.html'}).
up().
segment('prefs', {
templateUrl: 'templates/section1/prefs.html'}).
up().
segment('s2', {
templateUrl: 'templates/section2.html',
controller: MainCtrl});
I've found Angular Multi View to be a godsend for this scenario. It lets you preserve scope as the route changes and lets multiple controllers share the same route without nesting your views.
I recommend Angular Multi View if you have more than 2 views on your page. Otherwise, when using ui-router, nesting multiple views gets messy really fast.
I came up with the same problem and I personnaly don't like plugins when they aren't absolutely unavoidable. I just moved singleton part to a service.
In my case there are :id[/:mode] routes and I want to react different way if user changes just mode or id too. Thus, I have to know previous id.
So, there is a service with activate method which updates its state. And the scope is reinitialized every time with the following code.
module.controller('MyController', ['$scope', '$routeParams', 'navigator', function($scope, $routeParams, navigator) {
var id = null;
var mode = null;
if (typeof($routeParams.id)!='undefined')
{
id = $routeParams.id;
}
if (typeof($routeParams.mode)!='undefined')
{
mode = $routeParams.mode;
}
navigator.activate(id, mode);
$scope.items = navigator.items;
$scope.activeItem = navigator.activeItem;
$scope.modes = navigator.modes;
$scope.activeMode = navigator.activeMode;
}]);
In activate method I can compare id to the singleton's activeItem.id and react differently.

Resources