I have an application built with Angularjs using Angularjs Translate library - https://angular-translate.github.io/, and I have set it up with UI router because the current website has already been indexed by google with language versions like www.domain.com/en/us/ and I need to keep the same URL structure. The website has over 30 languages but it is a SPA (Single Page Application).
Below is my Javascript for the app configuration:
app.run(['$rootScope', '$translate', '$state', function ($rootScope, $translate, $state) {
$rootScope.onChangeValue = function (e) {
$rootScope.$broadcast("changeValue", e);
};
$rootScope.$on('$stateChangeSuccess', onStateChangeSuccess);
function onStateChangeSuccess(event, toState, toParams) {
var current = $translate.use();
if (!current || current !== toParams.lang)
$translate.use(toParams.lang);
}
}]);
app.config(['$translateProvider', function ($translateProvider) {
$translateProvider.useStaticFilesLoader({
prefix: '../languages/locale-',
suffix: '.json'
});
$translateProvider.fallbackLanguage('en');
$translateProvider.preferredLanguage('en');
$translateProvider.useSanitizeValueStrategy('escape');
$translateProvider.useCookieStorage();
}]);
app.config(['$stateProvider', '$locationProvider', function ($stateProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('');
$stateProvider.state('catalog', {
url: '/{lang}',
controller: "SudokuController",
params: {
lang: {
value: function ($translate) {
return $translate.use();
}
}
}
});
}]);
and in the controller I have these functions:
app.controller("SudokuController", ['$scope', 'Chronicle', '$timeout', '$cookies', '$translate', '$state', '$stateParams', function ($scope, Chronicle, $timeout, $cookies, $translate, $state, $stateParams) {
$scope.params = $stateParams;
$scope.changeLanguage = function (newLang) {
var params = angular.extend($stateParams, { lang: newLang });
$translate.use(newLang).then(function () {
$state.transitionTo($state.current, params, {
reload: true, inherit: false, notify: true
});
});
}
}]);
Here is the HTML that is generating the nav with multiple languages:
<div class="language-dropdown">
<ul class="country-list" ng-controller="navigationLanguages">
<li class="country" ng-repeat="language in languagesMenu">
<div class="country-flag" style="background-image: url('images/mk.png');"></div>
<a ng-bind="language.Language" ng-click="changeLanguage(language.Code)"></a>
</li>
</ul>
</div>
So far I have achieved to add to the URL the chosen language, for instance, if I click on English I get www.domain.com/en, if I click on Spanish I get www.domain.com/es. But when I go directly to the www.domain.com/en I get Error 404 - Object not found!
How can I make so when the URL is inserted along with the language
after the / to access the website with that particular language
instead of showing Error 404.
How to make the languages states like www.domain.com/en/us instead of
www.domain.com/en (so it goes first the language code and then the
country code)?
Is it possible for these URLs/ languages to be crawlable by Google?
What does your server code look like? I'm guessing it's only serving up the index page on "/" so if you refresh your page on any route that isn't the root it won't serve anything up and you'll get not found. Basically for single page apps the routing should be handled completely client side because that is the piece that has the context about your app to know what to do. So always have your server serve up the index page no matter which route, that will let ui router take over and do its magic
To add /en/us update your state config. Also, you don't really need to define params unless you need a default value
$stateProvider.state('catalog', {
url: '/{lang}/{region}',
controller: "SudokuController"
});
Google's crawlers might be able to crawl a single page app like yours but most crawlers can't - so if you care about SEO you'll need to do some extra stuff. You have a couple options
Pay for a service like https://prerender.io/ You have to set up a little bit of code on your server but for the most part they take care of making your site crawlable. I've used them in the past and recommend them. How does it work? Basically when a crawler visits your site instead of serving them up the single page app, prerender will serve them a static version of your site that has been pre-rendered that the crawler can understand.
The second option is quite a bit of work. I've also done this (but on the newer version of angular) and don't recommend it, lots of headaches. Its called server side rendering. Basically instead of having another service create a prerendered version of your page, your server does it on the fly. The newer version of angular has some built in functionality for this, not sure about angularjs. I recommend googling more about prerendering and server side rendering there's more than I can say to do it justice here.
Related
I want to display an HTML file when a user has logged in. Right now we have a slideshow that is presented to the user after logging in, but after it, I want to display another view.
I jumped on this project although I'm new to Angular and Ionic, I assume this is how we render out the slideshow:
In our controller, we have an IntroCtrl which operates the logic behind the slideshow. So, do I need to create another controller to display my view afterwards?
.controller('IntroCtrl', function ($scope, $state, $localStorage, $ionicSlideBoxDelegate, $ionicScrollDelegate, $stateParams) {
$scope.$on('$ionicView.enter', function (e) {
$ionicScrollDelegate.resize()
$ionicSlideBoxDelegate.slide(0, 1)
})
$scope.email = $stateParams.email
$scope.startApp = function () {
$localStorage.hasViewedSlideBox
$state.transitionTo('tab.dash')
}
$scope.next = function () {
$ionicSlideBoxDelegate.next();
}
})
I found this in our app.js and this file contains $stateProvider, $urlRouterProvider, $ionicConfigProvider.
At the bottom I found a lot of these:
.state('login', {
url: '/login',
cache: false,
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
Therefore I assume I should create a new one containing the templateUrl and controller of my view? I read online that these .state has to do with Angular routing, but I can be wrong.
However, I would be really happy if anyone could guide me here.
Like I said, I want to display a new view after the slideshow.
Thanks!
I suggest create a new html and design your "afterLogin" html and copy your state(to which you will be redirected after login), comment it out and change the templateUrl to your new html file.
That should do all what you want.
You don't need your IntroCrl in the new file because it will only run a slideshow which is not present.
But you should insert
$scope.startApp = function () {
$localStorage.hasViewedSlideBox
$state.transitionTo('tab.dash')
}
into your new controller.
If you don't have a route to which you are redirected after login, you have to provide more code, so we can dive more deep into it
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
I would like to know if my app would be crawlable if I do not use href on my link but only a data-ng-click function. For example, will the page2/index.html will be visit by the google bot if I code it the way below, and if not, what should I put in the href so it is?
The HTML
go page 2
The routes
app.config(function ($routeProvider) {
$routeProvider
.when('/page2',
{
templateUrl: 'views/app/page2/index.html'
})
});
And the GO() function
app.run(function ($rootScope, $location) {
$rootScope.go = function (route) {
$location.path(route);
}
});
Thanks guys
No, it will not be indexed. You should at least create a sitemap.xml.
angular app (and every spa app) is never crawlable because content are injected into DOM by javascript and not by server side processes
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!
I am slightly puzzled on how to use angularjs to build a blog-like web site.
If you think of an ordinary blog,,, and say,, I am building it using php and mysql.. what I don't understand is how do I make angular get an article based on id..
I can load data for a list of all articles.. I can load data for a single page (from a static json),, I understand how to send http requests,, but how do I access
mysite.com/page?id=1 or
mysite.com/page?id=2 or
mysite.com/page?id=3
Obviously, I want to load the same template for each separate blog post.. but I have not yet seen a single example that explains this simply.
If I have three posts in my database with id 1,2,3 how is angular meant to generate links to each individual article? I understand that I can load data to assemble urls but what urls? I suppose I am confused with routing.
Could you recommend a SIMPLE and understandable example of this? Could you suggest how to think about this?
Thanks!
In this short explanation I will use examples based on official tutorial.
Propably somwhere in your application you created controllers module with code close to that:
var blogControllers = angular.module('blogControllers', []);
// other controllers etc.
blogControllers.controller('BlogPostCtrl', ['$scope',
// more and more of controller code
We will be back here in a moment :) If you have controllers module, you can create a route linked to the specific controller:
var blogApp = angular.module('blogApp', [
'ngRoute',
'blogControllers'
]);
blogApp .config(['$routeProvider',
function($routeProvider) {
$routeProvider.
// other routes
when('/post/:postId', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl'
}).
// other...
}]);
Well but what we can do with this :postId parameter? Lets go back to our controller:
blogControllers.controller('BlogPostCtrl', ['$scope', '$routeParams', 'BlogPost',
function($scope, $routeParams, BlogPost) {
$scope.post = BlogPost.get({postId: $routeParams.postId});
}]);
As you see, we are passing $routeParams here and then our BlogPost resource (it will be explained). Simplifying (again), in $routeParams you have all the params that you put in the $routeProvider for exact route (so :postId in our example). We got the id, now the magic happens ;)
First you need to add services and/or factories to your app (and look, we are using ngResource):
var blogServices = angular.module('blogServices ', ['ngResource']);
blogServices.factory('BlogPost', ['$resource',
function($resource){
return $resource('action/to/get/:postId.json', {}, {
query: {method:'GET', params: { postId: 'all' }, isArray:true}
});
}]);
Now you know what is our BlogPost in controller :) As you see default value for postId is "all" and yes, our api should retrieve all posts for postId = "all". Of course you can do this in your own way and separate this to two factories, one for details and one for list/index.
How to print all of the blog names? Quite simple. Add list routing without any special params. You already know how to do this so let's skip it and continue with our list controller:
blogControllers.controller('BlogListCtrl', ['$scope', 'BlogPost',
function($scope, BlogPost) {
$scope.blogPosts = BlogPost.query(); // no params, so we are taking all posts
}]);
Voila! All posts in our $scope variable! Thanks to this, they are accessible in the template/view :) Now we just need to iterate those posts in our view, for example:
<ul class="blog-posts">
<li ng-repeat="blogPost in blogPosts">
{{blogPost.title}}
</li>
</ul>
And this is it:) I hope you will find AngularJS quite easy now!
Cheers!
I think what you want to use for this is $routeParams. Have a look at this video from egghead.io which explains how to use it:
http://egghead.io/lessons/angularjs-routeparams
It's a good practice to use hash navigation. So your routing should look like this
mysite.com/blog/#!/id=1
mysite.com/blog/#!/id=2