controller methods matching ui-router states - angularjs

In my stateProvider, I am trying to following a more RESTful approach but given the controllers in Angularjs can contain all sorts of functions, I want to encapsulate the different routes to an appropriate part of the controller (or if you have a better way, please advise)
My current state:
.state('main.align.movements.new',{
permissions: '<LOGGED_IN>',
url: '/new',
views: {
'#main': {
templateUrl: 'app/main/align/movements/new.html',
// controller: function($scope){
// $scope.newMovement = new Movement()
// $scope.save = function(){
// $scope.newMovement.save()
// }
// $scope.cancel = function(){
// }
// }
controller: 'MovementsCtrl.hello'
}
}
});
As you can see, I commented out some of the code I was going to write as my controller was just about to get pretty large too quickly. Yet, I do depend on the MovementsCtrl for /index as it's large enough as it is. I was wondering if there is a way to have within the MovementsCtrl a way to encapsulate everything related to /new and /edit, etc. without having a huge Ctrl of code (for example, at /new I want to initiate an instance of Movement right away but don't want that initiation to persist even at /edit and I don't want to get ugly and do $stateParams to check the path given I am doing that for $stateProvider anyways )
Thoughts?

Related

Adding a controller to my mean.js app seems to automatically fire an api call?

Having trouble tracking this down. I am adding a workflow to user signup in a meanjs app, such that an admin has to invite a user in order for them to be allowed to signup.
For some reason, whenever the invitation form loads, an API call is attempted to /api/users/invitation which I did not (as far as I know) ask for, and it doesn't make sense to have one as there's no data it needs. I assume there's something being auto-wired for me somewhere, but since this 404 causes the page to fail, I need to kill it if I can.
Here's my controller:
(function () {
'use strict';
angular
.module('users.admin')
.controller('InvitationController', InvitationController);
InvitationController.$inject = ['$scope', '$state', '$window', 'Authentication'];
function InvitationController($scope, $state, $window, Authentication) {
var vm = this;
vm.invitation = {};
vm.sendMail = sendMail;
vm.authentication = Authentication;
function sendMail(isValid) {
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.invitationForm');
return false;
}
var invitation = vm.invitation;
// TODO: send to the server
console.log(invitation);
}
}
}());
Here's the stateprovider fragment that's relevant:
.state('admin.user-invite',{
url: '/users/invitation',
templateUrl: 'modules/users/client/views/admin/invite-user.client.view.html',
controller: 'InvitationController',
controllerAs: 'vm',
data: {
pageTitle: 'Invite a User'
}
})
Any idea where else to look? This is my first app using the MEAN.js framework, though I've used angular quite a bit in the past.
OK, took me an embarrassingly long time to realize this, but it turns out the URL matching was the actual problem at hand.
I should have included my full set of client-side routes in the question, as what was happening was that /users/invitation was actually the last route declared. Turns out, /users/:userId was matching against that, and so the router was interpreting invitations as a userId, and the matching route had a resolver function which was in turn trying to call my server-side users api.
Now the part that is still baking my noodle (though it's less important in that my current problem is solved) is why would the route matcher do that, but still render the template that I'd assigned to the /users/invitation route? The fact that it was doing that certainly made the debugging longer, as the matched route has a very different template assigned to it than my intended route.
Can you examine network requests in developer tools. It could be a request to '/users/invitation', but the '/api' part is getting prefixed on the server side code (prefixed to router??).

Get Adobe DTM to work with page load rules in Angular

Basically, I'm trying to find a way to efficiently fire tags using Adobe DTM in Angular without having to change the page. We have an exceptionally long elevation cycle so having to add _satelitte.track() to a page to get a new tag to fire would severly limit the benefit of using DTM. I'm primarily concerned this would be necessary in the case that I'd want to only fire tags in certain situations so I wouldn't be able to use the already existing rule in place.
I believe that _satellite.pageBottom() is what tells the satellite object that a page load has occured. I'm attempting to call _satellite.pageBottom() to mimic a page load any time the route changes.
.config(function($routeProvider, $httpProvider){
$routeProvider.when('/one', {
templateUrl: 'view1.html',
controller: 'Data1Controller'
}).when('/two', {
templateUrl: 'view2.html',
controller: 'Data2Controller'
}).when('/three', {
templateUrl: 'view1.html',
controller: 'Data3Controller'
}).when('/four', {
templateUrl: 'view2.html',
controller: 'Data4Controller'
}).when('/five', {
templateUrl: 'form.html',
controller: 'FormController'
}).otherwise({
controller: "Default",
templateUrl: 'default.html'
});
$httpProvider.interceptors.push('CacheBusterInterceptor');
}).run(function($rootScope){
$rootScope.$on('$routeChangeStart', function(){
_satellite.pageBottom();
});
});
This has worked well for me in other situations where I want to mock a page load but it's not working with the _satellite.pageBottom() call and I am at a loss how I might get DTM to work in Angular without using the direct call rules. Any advice on getting either this way to work or another work around to having to make changes to the code on the page?
Thanks,
Mike
I've used a generic _satellite.track('pageLoad') direct call rule to work around single page app challenges. You don't have to change the page code that way. As long as the variables you need are accessible by DTM, you can run all your logic in the tag manager.
Edit for dynamic direct call rule:
$rootScope.$on('$routeChangeSuccess',function(e, curr, pre){
_satellite.track(current.$$route.originalPath);
})
or if you want to add a pagename in your $routeProvider config:
$routeProvider.when('/',{pageName:'Fantastic Home Page',controller:...,templateUrl:...}
$rootScope.$on('$routeChangeSuccess',function(e, curr, pre){
_satellite.track(current.pageName || 'whoops no pageName');
})
Adobe DTM allows for firing rules based on popstate or hashtag change. The best approach is to setup your rules based on these events and if your app supports deep linking then also setup page load rules based on the deep link URL's.

Multi-steps register form in Angularjs

I'm building an app in which I've got a registration flow divided in 3 parts, Personal Info, Profile picture, and so on, each one in its invidual view.
So, which way do you think is the best way to persist data between views so in the final step I can show the whole data to the user and ask for confirmation.
Local Storage? $cacheFactory? I've really have no idea how to do it, any advice will be usefull!
Thanks in advance
cookie or local storage is available,but I think Angular way is recommended.
$rootScope: you can storage user data in $rootScope simply use
$rootScope.userData = {
'userName' : 'first',
'userId' : 'second'
};
to storage and read data in new view, remember to inject $rootScope into your controller
$broadcast: angular provide event $broadcast and $emit, so you can use $on to watch event and get your user data.and this is a demo http://jsfiddle.net/whitewolf/5JBA7/15/
route resolve? : if you send user data in every step to database,
you can use resolve in route config to load user data by $http.get().
add a service
app.factory("messageService", function($q){
return {
getMessage: function(){
return $q.when("Hello World!");
}};
});
in route config
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}}
})
in your controller
app.controller("newsController", function (message) {
$scope.message = message;
});
You are looking for an angular wizard
if you are using ui-router, you can easily do this with nested states
have a look
http://scotch.io/tutorials/javascript/angularjs-multi-step-form-using-ui-router
one form ( main state )
separate templates (nested states)
Note: specify controller for main state only and not for nested states
It will depend on the requirement.
If there is a requirement that the user can complete two steps and
come back later to complete the third step, then better go with local
persistence.
But if it's really not the case, you need not use any persistence
mechanism. Only thing is you need to properly map the models. And on the final submit, you can persist the complete input data.

Passing additional parameters using $routeProvider

In my AngularJS app I have one template and one controller that I can reach via 2 different url:
$routeProvider.when('/site/contents/:obj/:parent/:isnew?', {
templateUrl: '/partials/site/data.html',
resolve: {
loggedin: checkLoggedin
}
});
$routeProvider.when('/site/contents/new-obj', {
templateUrl: '/partials/site/data.html',
resolve: {
loggedin: checkLoggedin
}
});
The second route should statically set a parameter isnew=1, as I would do using a RewriteRule in apache, but looking at the documentation https://docs.angularjs.org/api/ngRoute/provider/$routeProvider it seems impossible to do.
Anyone had encountered the same question?
You can't really set default route parameters in Angular, if that is what you are trying to do.
The best idea would probably be to check in your controller if $routeParams.isnew is set and, if not, default it to 1.
Angular routes are supposed to be fairly simple rather than being heavily pattern based like RewriteEngine in apache, so the logic should be in your controller.
Your controller could look like so:
function($routeParams) {
$scope.isNew = ($routeParams.isnew || 1);
}
In your case, you can probably even get away with having no isnew param. Use the first route for modifying existing objects and the second for creating them (so the existence of obj in the params defines if it is new or not).

Access scope or service in ui router onEnter to check access (auth)

Ok, so I have a state for the url "/securepage" which I need to check whenever a user tries to access it. So I read there is an onEnter function I can use. But I cannot seem to get hold on the scope nor a service from there. What am I doing wrong?
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function(){
// if (!$scope.main.isAuthenticated) $state.go("/login");
// if (!myLoginService.currentUser()) $state.go("/login");
Current options as I see it are to use resolve and/or check the authentication in the controller. But wouldnt an auth check be better placed in onEnter?
I ran into a similar problem today. Spent a whole day and finally came up with a workable solution other than the ones already suggested here.
My main goal is to find easy and effective way to selectively secure certain specific webpages. The security check needs to be performed before the HTML or any of the relevant controllers are loaded or called. If the check fails, the page may be forwarded to elsewhere without any side effects from the other controllers.
I tried the other suggested approaches. Each one has their own set of problems:
Using OnEnter:
There is no way to block ui-router from continuing with the state transition while making an async call to do the security check.
Using $rootScope.$on('$stateChangeStart'):
The management of the states that rquire security check will become separated from the $stateProvider.state() definitions. Ideally, I would rather see everything about the definition of a state defined all in one place. Though this is not a showstopper, it is not ideal.
A much bigger problem is that the $stateChangeStart event is not being called for the initial loading of a page. This one is a showstopper.
My solution is to use a resolve function to define a promise that will cause the view controllers to wait for the deferred to complete before they are called. This work perfectly for blocking the controller from starting in an asynchronous fashion.
Here's a rough outline of the code I used:
.config(['$stateProvider', function ($stateProvider) {
// Handler for Restricting Access to a page using the state.resolve call
var accessRestrictionHandler = function($q, $rootScope, $state) {
var deferred = $q.defer();
// make sure user is logged in
asyncCheckForLogin(function(status) {
if (status != "Logged In") {
// You may save target page URL in cookie for use after login successful later
// To get the relative target URL, it is equal to ("#" + this.url).
// The "this" here is the current scope for the parent state structure of the resolve call.
$state.go("loginPage");
}
else // if logged in, continue to load the controllers. Controllers should not start till resolve() is called.
deferred.resolve();
}.bind(this));
return deferred.promise;
};
$stateProvider
.state('userProfile', {
url: '/userProfile',
views: {
'main': {
templateUrl: 'userProfile.html',
controller: 'userProfileCtrl'
}
},
// SIMPLY add the line below to all states that you want to secure
resolve: { loginRequired : accessRestrictionHandler }
})
.state(.... some other state)
.state(.... some other state);
}]);
I hope this will help some of you out there.
Another approach would be to have a service/controller listen to the "$stateChangeStart" event. There, you could check if the called state needs authentication and reroute the request. Here's a snippet:
$rootScope.$on('$stateChangeStart', function (event, nextState, currentState) {
if (!isAuthenticated(nextState)) {
console.debug('Could not change route! Not authenticated!');
$rootScope.$broadcast('$stateChangeError');
event.preventDefault();
$state.go('login');
}
});
isAuthenticated could hold the call to your services, check nextState.data for authentication-related properties, etc.
Check out this issue and this example from their github page. It should give you some clues.
A late answer but well I'd rather write it anyway. I prefer not touching the $rootScope wherever possible. Here's the code that I'm currently working on which sums up another solution for your question:
state('dash', {
url:'/',
views:{
"topNav":{
templateUrl:"user/dash/partials/top-nav.html",
controller:'logoutCtrl',
},
"sideNav":{
templateUrl:"user/dash/partials/side-nav.html"
},
"body":{
templateUrl:"user/dash/partials/body.html",
controller:'testCtrl'
}
},
onEnter: function(Auth, $state){
if(!AuthSvc.isAuthenticated){
$state.go('login');
}
}
})
I'm using JWTs to store the tokens on the localstorage by using ngStorage which gives a $localStorage service which I inject in the Auth factory I injected in onEnter
probably you mean
.state('securepage', {
url: "/securepage",
template: securepage.html,
//this is how you actually inject things to onEnter property
onEnter: ['$state','myLoginService',function($state,myLoginService){
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");`}]
....
I am using angularjs 1.3 with ui.router 0.2.10
I'm sure a lot has changed since this question was asked but as I had to figure it out myself and my search led me here...
As far as checking authentication you could do it like this
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function($scope,$state,myLoginService){ //THIS IS THE CHANGE
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");
You can put pretty much provider/service/factory in the onEnter function to gain access to it, and this does work from inside the .config of the app.
Having said that, they (ui-router makers) suggests to use custom rule function handle it inside .$on('$stateChangeStart', function(e, to) https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-create-rules-to-prevent-access-to-a-state
I don't know why they do this instead of onEnter, maybe someone could expand on that.

Resources