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

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.

Related

How to make Angular JS use a hash URL to represent an action?

TL;DR;
I've written a program that uses DOM-manipulation and jQuery to respond to the user inputting a comma-separated list of values in a hash-URL and wish to do it in Angular, instead.
The long version
I have been writing a program, on and off, in my spare time, that draws fractal images, such as the famous Mandelbrot fractal. Here's the URL: http://danielsadventure.info/html5fractal/docs/intro.html. I did this as an exercise to flex my HTML5 muscles with features like the canvas element and web workers. The program works great. Here is a rendered image of the "Negabrot" fractal from my program:
Recently, I've been teaching myself Angular, and I decided to rewrite the Javascript using Angular instead of jQuery. Once again, I'm doing this as an exercise.
Angular is, indeed, a very suitable tool because there are lots of forms that the user may use to describe the fractal image to be drawn. I was able to use Angular to bind a model to the forms and get rid of the DOM-manipulation code that I was previously using. Yay! Angular is awesome!
There is another feature of my program that it is not entirely clear how I should convert it to work with Angular. My program is a Single Page Application. There is only one thing it does: draw fractal images. However, I use hash-URLs to keep track of what fractal is being drawn and what configuration is used to draw it. For example, you can follow the URL below to see a zoomed-in section of the Multibrot-5 fractal:
http://danielsadventure.info/html5fractal/index.html#103,0.41000000000000014,1.0299999999999998,0.41999999999999993,1.04,2,1261,true,z%20^%205%20%2B%20c,-2,2,-2,2
As you can see, the URL consists of a list of comma-separated values that describe different aspects of the programs configuration. If you draw something beautiful with it, you can simply send someone else the URL and they can draw the same thing; easy as pie!
In order to accomplish this, I listen for an event that indicates that the hash-URL has changed and respond to it by updating the configuration on the form, once again using old-fashioned DOM-maniputation.
I previously asked on StackOverflow how to respond to hash-URLs, and I was directed to ngRoute. ngRoute looks very useful, but it looks like it is associated primarily with templates and controllers.
In my program, I need not load any additional templates. All I need is to respond to a new hash-URL by updating the configuration and drawing a new fractal. I also want to update the hash-URL with the same when the user manually updates the configuration and draws a new fractal.
In short, what I want to happen is this:
When the user enters a new hash-URL, the program should respond by updating the model that is bound to the inputs so that the form values change.
When the user manually changes the inputs and clicks a button to draw again, the hash-URL should be updated with the new configuration.
With angular ui-router you could do it like this:
angular.module('demoApp', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /fractal
$urlRouterProvider.otherwise("/fractal?minr=-0.29&maxr=-0.27&mini=-0.64");
//
// Now set up the states
$stateProvider
.state('fractal', {
url: '/fractal?minr&maxr&mini',
templateUrl: 'app/partials/fract.html',
controllerAs: 'fract',
controller: function($stateParams) {
console.log($stateParams);
var fract = new Fractal($stateParams);
this.getSettings = fract.getSettings;
}
});
});
In the url property you can specify your params. I've picked just some of your params.
$stateParams service will inject all the params that are passed in the url.
The following is just to show how I've implemented the Fractal class:
function Fractal(settings) {
var that = this;
this.settings = settings;
this.getSettings = function() {
return that.settings;
}
}
And the partial fract.html looks like this (it only outputs the settings):
<h1>Drawing fractal in this state</h1>
<hr/>
{{fract.getSettings()|json}}
In your app you'll probably create a directive for your fractal class because you're doing DOM stuff. I'm just adding everything in the controller of the state to keep the demo simple.
You can add the directive to the fractal.html partial.
Please have a look at the demo below or in this jsfiddle. Please notice that you're not seeing the url parameters in jsfiddle.
In your app they will be present like in the following screenshot:
angular.module('demoApp', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/fractal?minr=-0.29&maxr=-0.27&mini=-0.64");
//
// Now set up the states
$stateProvider
.state('fractal', {
url: '/fractal?minr&maxr&mini',
templateUrl: 'app/partials/fract.html',
controllerAs: 'fract',
controller: function($stateParams) {
console.log($stateParams);
var fract = new Fractal($stateParams);
this.getSettings = fract.getSettings;
}
});
});
// here comes your fractal class
function Fractal(settings) {
var that = this;
this.settings = settings;
this.getSettings = function() {
return that.settings;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<script type="text/ng-template" id="app/partials/fract.html">
<h1>Drawing fractal in this state</h1>
<hr/>
{{fract.getSettings()|json}}
</script>
<div ui-view></div>
<!-- We'll also add some navigation: -->
<a ui-sref="fractal({minr:-0.3, maxr:-0.2, mini: -0.64})">Draw fractal</a>
</div>
ok I am not quite sure what JS code you already have, so I am going to show some snippets you may find helpful!
This is a watch on the URL - so everytime the hash changes this function will be called:
$scope.$on('$locationChangeSuccess', function(event, newState, oldState) {
var values = $location.hash().split(','); // => [103,true,...]
var desc = ['point1', 'boolean']; // I don't know wich values you have..
$scope.values = {}; // values model
angular.forEach(values, function(value, index) {
$scope.values[desc[index]] = value; // {'point1': 103} => nested attribute
});
});
Then you can bind this to a form:
// HTML
<form ng-submit="updateHash()">
<input ng-model="values.point1" />
</form>
// JS
$scope.updateHash = function() {
var updatedValues = [];
angular.forEach($scope.values, function(value) {
updatedValues.push(value);
});
$location.hash(updatedValues); // update URL
};

AngularJS, ui-router & Firebase app not working in Plunker

I just took an app I'm working on and converted it to a Plunk but Angular and/or ui-router is not populating the two views I have in index.html. On my local box the app loads fine but there I have the app modularize. So when I converted it to a Plunk I had to rewire the files together since I can't make modules in Plunker AFAIK. Also, when I load the Plunk in a separate window and open Dev Tools I get no errors so I'm at a loss right now.
Here is link to the Plunk code I made:
http://plnkr.co/edit/2f1RITT6ysZhB5i0UcUw?p=preview
And here is the link to the embedded view (more convenient if you want to use Dev Tools):
http://embed.plnkr.co/2f1RITT6ysZhB5i0UcUw/preview/posts
I should mention that the route has to end in /posts since that it the url of the state named posts. I have no state defined for the root / url. Also the following url failed:
http://embed.plnkr.co/2f1RITT6ysZhB5i0UcUw/posts
Thanks in advance.
I've made few changes. Here is a working plunker
Firstly I upgraded your version to UI-Router 0.2.13 (fixes some issues, simply always use the latest)
The /post is now default
//$urlRouterProvider.otherwise('/');
$urlRouterProvider.otherwise('/posts');
I changed your controller, to not use router params,
// wrong old
/*
app.controller('ProfileCtrl', function ($scope, $routeParams, Profile) {
var uid = $routeParams.userId;
$scope.profile = Profile.get(uid);
Profile.getPosts(uid).then(function(posts) {
$scope.posts = posts;
});
});
*/
// the way with UI-Router
app.controller('ProfileCtrl', function ($scope, $stateParams, Profile) {
var uid = $stateParams.userId;
$scope.profile = Profile.get(uid);
...
JUST to know what is post holding
Also, the passed userId into state contains values like: "simplelogin:82", to observe taht, I added overview of processed post, which is showing info like this:
{
"creator": "3lf",
"creatorUID": "simplelogin:82", // this is passed as userId, is it ok?
"title": "a",
"url": "http://a",
"$id": "-JazOHpnlqdNzxxJG-4r",
"$priority": null
}
Also, this is a fixed way how to call state posts.postview
<!-- wrong -->
<!-- <a ui-sref="posts({postId:post.$id})">comments</a> -->
<!-- correct -->
<a ui-sref="posts.postview({postId:post.$id})">comments</a>
And alos, if the postview should be injected into main area, this should be its defintion
var postView = {
name: 'posts.postview',
parent: posts,
url: '/:postId',
views: {
'navbar#': {
templateUrl: 'nav.tpl.html',
controller: 'NavCtrl'
},
//'#posts.postview': {
'#': {
templateUrl: 'postview.tpl.html',
controller: 'PostViewCtrl'
}
}
};
Check it all here
SUMMARY: Working navigation is among posts - users... the "comments" link is also working, but the target is just loaded ... with many other errors... out of scope here

Angular $location doesn't work

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

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

Render 404 instead of redirecting 404 in angularjs

I'm looking for a good approach to render 404 page instead of redirecting 404 page in angularjs. Many solutions I have found is all about redirecting to another page. It will create a problem that if the user click on browser's back button I will create another redirect to 404 page. So I am looking for a solution that renders 404 page instead.
Thanks for reading, I hope it's understandable for you guys.
Usage of otherwise might be what are you looking for.
angular.module('MyApp', [])
.config(function($routeProvider) {
$routeProvider.
when('/', {templateUrl:'/home.html'}).
// add as many as you want here...
otherwise({templateUrl:'/404.html'}); // Render 404 view
});
Update: After reading more carefully the OP question (sorry, quite early around here), I think I have an alternate solution (actually two):
1) A $scope variable to the main UI of your ng-view that hides the content
This requires ng-show in your view and resolve in your params, then do a $emit to the other controllers in order to tell "Hey, this guy hit a 404, don't display your view"
$routeProvider.otherwise({
controller: 'masterController',
resolve: {
404: function(){
return true;
};
});
angular.module('MyApp', [])
.controller('masterController', function($scope, 404) {
$scope.isOn404 = 404
...
})
// In the view
<div ng-controller="masterController">
<div ng-hide="isOn404">
<!-- Actual content -->
</div>
<div ng-show="isOn404">
<!-- 404 Page -->
</div>
</div>
Now, this requires that you have a master controller that helps you to manage the rest of your UI. Also, you most likely would need to do some coding to handling the rest of the page instead of just using ng-view (e.g. some controllers that show the current header, body, etc).
2) A custom routing system
I actually have done this for a specific project: you have a service that sets up a "BackURL" and "FordwardURL"; each $onRouteChange you store where do you go and where do you come from; if the user is about to hit a 404, you can still render it through my original example, but when the user hits back, catch that through AngularJS and render the actual "Back" page. In this case I'm using a library that helps me with the routing on mobile devices called Lungo and a library that the company I work for uses, the L.A.B (Lungo Angular Bridge).
I'm new to AngularJS, so this may not be an ideal solution, but it works for showing a 404 page, or similar uses such as a login page:
See Working Example
Redirect everything to the same master template:
$routeProvider
.when('/', {
controller: 'homeController',
templateUrl: 'partial.master.html'
})
.when('/cust/:custid', {
controller: 'custController',
templateUrl: 'partial.master.html'
})
master.html template refers to the masterController and has a subpage:
<div ng-controller="masterController">
<h2>{{title}} - Header</h2>
<hr>
<div ng-include="subPage"></div>
<hr>
<h3>{{title}} - Footer</h3>
</div>
masterController has conditional sub-page logic:
controllers.custController = function($scope, $rootScope, $routeParams){
$rootScope.subpage = 'cust';
$scope.cust = getCustomer( $routeParams.custid );
};
controllers.masterController = function($scope, $rootScope) {
switch($rootScope.subpage) {
case 'home':
$scope.subPage = 'partial.home.html';
break;
case 'cust':
if($scope.cust) {
$scope.subPage = 'partial.cust.html';
} else {
$scope.subPage = 'partial.404notfound.html';
}
break;
You could also use ui-router as previously answered here: https://stackoverflow.com/a/23290818/2723184. IMHO, you not only get an awesome library but you get a better solution.

Resources