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!
Related
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.
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'm trying to use ncy-angular-breadcrumb, which uses ui routes. I have a module set up to house the ui routes:
(function () {
var app = angular.module('configurator.uiroutes', ['ui.router', 'ncy-angular-breadcrumb']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('home', {
url: '/',
ncyBreadcrumb: {
label: 'Series'
}
});
}]);
})();
I have this test to see if the route works:
describe('Configurator UI Routes', function () {
beforeEach(module('configurator.uiroutes'));
beforeEach(module('ui.router'));
var $state, $rootscope, state = '/';
// Inject and assign the $state and $rootscope services.
beforeEach(inject(function (_$state_, _$rootScope_) {
$state = _$state_;
$rootscope = _$rootScope_;
}));
//Test whether the url is correct
it('should activate the state', function () {
$state.go(state);
$rootscope.$digest();
expect($state.current.name).toBe('home');
});
});
Which produces this error:
Error: Could not resolve '/' from state ''
I'm just not getting the UI Router and either how it works, or what it's supposed to do. I just want to grab parts of the url and pass that info to ncyBreadcrumb, but I can't even get the base URL to work.
Any pointers or help would be awesome!
Edit: I've gotten the test to pass by replacing '/' with 'home' in the test. My bigger question is: is there a way to test that when the URL '/' is hit, that the state.current.name becomes 'home'. In my use of the app, this doesn't appear to be happening, and I'm hoping that the unit test can help tell me why.
This is a single page application, if that's relevant.
Error is enough self explanatory, as you asked ui-router to redirect on / state using $state.go
Basically $state.go which asks for state name rather than url.
var $state, $rootscope, state = 'home'; //<--changed from `/` to `home`
Currently I have a local server running with my main page at localhost
I ideally want the setup where the url has a key that I send off to my database to get data and the main page loads that data
For example, someone enters the url of localhost/4962 then 4962 gets sent off to the database, while localhost loads. I am using angularJS so I do have a way of getting the url by doing...
var module = angular.module('app', []).run(function($rootScope, $location) {
$rootScope.location = $location;
});
Then location.absUrl() gets me the complete URL.
But obviously there is no page at localhost/4962, so i just get a 404.
How do i ignore the numbers in the url? Also how do I get just the numbers, I would be willing to do localhost/#/4962 if a hash would simplify matters.
EDIT:
Below are some useful answers. Here is what I went with -
var app = angular.module("app", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when('/:message',
{
template: " ",
controller: "AppCtrl"
}).otherwise({redirectTo: '/'});
});
app.controller("AppCtrl", function($scope, $routeParams) {
$scope.model = {
message: $routeParams.message
}
//debugging
console.log($scope.model.message)
});
I needed to have
<ng-view></ng-view>
in the index.html.
Angularjs using $routeProvider without ng-view has a way of avoiding the addition of ng-view. Will try that next.
Do it like this:
angular.module('app', ['ngRoute']).config(function($routeProvider, $locationProvider){
$routeProvider.when('/:number?', {
controller: 'homeCtrl',
templateUrl: '/home.html'
});
$locationProvider.html5Mode(true);
}).run(function(){
...
});
In this case, number is optional route paramerer (because it has ? at the end). Then you can get number from controller:
angular.module('app').controller('homeCtrl', function($routeParams){
var number = $routeParams.number;
});
Don't forget to include angular-route.js file ;)
You can use the $location.path()-Function, should return just the path (without hashtag).