I have the following code in my Auth factory:
login: function(user) {
return $http
.post('/login', user)
.then(function(response) {
Session.setUser(response.data);
$cookies.put('userId', response.data._id);
$state.go('home', { reload: true });
// $window.location.href = '/';
})
;
},
The problem is that my navbar doesn't get updated (I have properties in the view that are data bound to vm.currentUser; see below). It does when I use $window.location.href = '/', but doing that messes up my tests.
I figure that the solution would be to manually reload the navbar directive. How can I do that? This is what the directive currently looks like:
angular
.module('mean-starter')
.directive('navbar', navbar)
;
function navbar() {
return {
restrict: 'E',
templateUrl: '/components/navbar/navbar.directive.html',
controller: NavbarController,
controllerAs: 'vm'
};
}
function NavbarController(Auth) {
var vm = this;
Auth
.getCurrentUser()
.then(function(currentUser) {
vm.currentUser = currentUser;
})
;
vm.logout = function() {
Auth.logout();
};
}
ok, after you explained further what is broken I noticed that the problem is that in your factory you are not properly returning the deferred promise like deferred.resolve. I should explain, I'm specifically looking around the question of how to "manually reload a directive" because you should not have to manually reload a directive, that's an antipattern. Directives should take care of themselves.
I made a demo. After the proper Auth factory finishing it's thing, with deferred.resolve the directive updates all on it's own because the bound model has changed. Check it out, it solves your problem:
http://plnkr.co/edit/u1FakV?p=preview
var app = angular.module('plunker', []);
app.factory('Auth', ['$timeout', '$q',
function($timeout, $q) {
function getCurrentUserFake() {
var deferred = $q.defer();
setTimeout(function() {
deferred.resolve('Joe Smith');
}, 1000);
return deferred.promise;
}
function getCurrentUser() {
var deferred = $q.defer();
$http.get('/login')
.success(function(data) {
deferred.resolve(data)
})
.error(function(data) {
deferred.reject(data);
});
return deferred.promise;
}
return {
getCurrentUser: getCurrentUser,
getCurrentUserFake: getCurrentUserFake
};
}
]);
app.directive('navbar', navbar);
function navbar() {
return {
restrict: 'E',
template: '<div>{{vm.currentUser}}</div>',
controller: NavbarController,
controllerAs: 'vm'
};
}
function NavbarController(Auth) {
var vm = this;
vm.currentUser = 'logging in...';
Auth
.getCurrentUserFake()
.then(function(currentUser) {
console.log(currentUser);
vm.currentUser = currentUser;
});
vm.logout = function() {
Auth.logout();
};
}
html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.3/angular.js" data-semver="1.4.3"></script>
<script src="app.js"></script>
</head>
<body>
<navbar></navbar>
</body>
</html>
Related
I'm new to angular js. I was trying to save and share the scope of two different controllers.
Below is my script and views.
script.js:
var app = angular.module('app', ['ngRoute']);
app.run(function ($rootScope) {
$rootScope.$on('scope.stored', function (event, data) {
console.log("scope.stored", data);
});
});
app.controller('FirstController', function($scope) {
$scope.counter = 0;
$scope.add = function(amount) { $scope.counter += amount; };
$scope.subtract = function(amount) { $scope.counter -= amount; };
});
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/v1', {
templateUrl: 'views/v1.ejs',
controller: 'controller1'
}).
when('/v2', {
templateUrl: 'views/v2.ejs',
controller: 'controller2'
})
}]);
app.controller('controller1', function($scope,Scopes) {
Scopes.store('controller1',$scope);
$scope.data = "controller1";
$scope.buttonClick = function () {
console.log( Scopes.get('controller2').data);
};
});
app.controller('controller2', function($scope,Scopes) {
Scopes.store('controller2',$scope);
$scope.data = "controller2";
$scope.buttonClick = function () {
console.log( Scopes.get('controller1').data);
};
});
app.factory('Scopes', function ($rootScope) {
var mem = {};
return {
store: function (key, value) {
$rootScope.$emit('scope.stored', key);
mem[key] = value;
},
get: function (key) {
return mem[key];
}
};
});
index.html:
<!doctype html>
<html ng-app="app">
<head>
<script src="js/angular.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js/script.js"> </script>
</head>
<body>
<a href='#v1'>View1</a>
<a href='#v2'>View2</a>
<div ng-view>
</div>
</body>
</html>
v1.ejs:
<div ng-controller="controller1">
{{data}}
<br>
<button ng-click="buttonClick()">Clickme!</button>
</div>
v2.ejs:
<div ng-controller="controller2">
{{data}}
<br>
<button ng-click="buttonClick()">Clickme!</button>
</div>
The problem is:
Whenever the page is loaded, #/v1 is triggered.
If i click on 'Clickme', JUST AFTER THE PAGE LOADS, Error: Scopes.get(...) is undefined is fired.
But, if i navigate to #/v2 and then to #/v1, the scopes are getting stored and the error is NOT fired.
Is there is some problem with with routeProvider? All the controllers wont be loaded at on-load event?
Please help me on this.
In Laravel, setting route filters is quite easy. We use a beforeFilter([]) and specify which functions in the controller should be accessible on authentication, and also have except in exceptional scenarios.
Now, I just started using Angular's ui.router and the concept of states is certainly the way forward, but I am asking a asking a noob question here. How do I set route filters on my states. I definitely don't want to do it on individual routes using resolve.
Here's some code. This is what I use for my profile route. I use resolve to make sure it is only accessible when authenticated. But the problem is, my login and signup routes are still accessible when I am logged in. They shouldn't be. It should just redirect to home.
Now I could add resolves to states I don't want accessible when I am logged in, but is that the right way? What if there are many. That would be repeating code. Is there a 'ui.router` way to do this?
.state('profile', {
url: '/profile',
templateUrl: 'js/app/partials/profile.html',
controller: 'ProfileCtrl',
resolve: {
authenticated: function($q, $location, $auth) {
var deferred = $q.defer();
if (!$auth.isAuthenticated()) {
$location.path('/login');
} else {
deferred.resolve();
}
return deferred.promise;
}
}
})
I think one way is to create a service which does your validation and then in a run block you would call that service on any $stateChangeStart events as K.Toress mentioned.
If you want to specify which states need authenticating, for example, you can use the data option in your state definition config to define whether or not it needs authenticating. So to define a state that needs auth you could try something like...
$stateProvider
.state('foo', {
templateUrl: 'foo.html',
// etc...
data: {
requiresAuth: true
}
});
You can then check this in your $stateChangeStart event which gets passed a toState argument from which you can access the data properties.
var app = angular.module('foo', ['ui.router'])
.factory('RouteValidator', ['$rootScope', '$auth', function($rootScope){
return {
init: init
};
function init(){
$rootScope.$on('$stateChangeStart', _onStateChangeStart);
}
function _onStateChangeStart(event, toState, toParams, fromState, fromParams){
// check the data property (if there is one) defined on your state
// object using the toState param
var toStateRequiresAuth = _requiresAuth(toState),
// if user is not authenticated and the state he is trying to access
// requires auth then redirect to login page
if(!$auth.isAuthenticated() && toStateRequiresAuth){
event.preventDefault();
$state.go('login');
return;
}
}
function _requiresAuth(toState){
if(angular.isUndefined(toState.data) || !toState.data.requiresAuth){
return false;
}
return toState.data.requiresAuth;
}
}])
.run(['RouteValidator', function(RouteValidator){
// inject service here and call init()
// this is so that you keep your run blocks clean
// and because it's easier to test the logic in a service than in a
// run block
RouteValidator.init();
}]);
EDIT
Okay I've made a very basic DEMO on plunker that will hopefully show the concept. I'll post the code here too. Help this helps.
app.js
var app = angular.module('plunker', ['ui.router']);
app.config(['$stateProvider', function($stateProvider){
$stateProvider
.state('public', {
url: "/public",
templateUrl: "public.html"
})
.state('login', {
url: "/login",
templateUrl: "login.html",
controller: function($scope) {
$scope.items = ["A", "List", "Of", "Items"];
}
})
.state('admin', {
url: "/admin",
templateUrl: "admin.html",
data: {
requiresAuth: true
},
controller: function($scope) {
$scope.items = ["A", "List", "Of", "Items"];
}
});
}]);
app.factory('User', [function(){
return {
isAuthenticated: false
};
}]);
app.factory('RouteValidator', ['$rootScope', 'User', '$state', function($rootScope, User, $state){
return {
init: init
};
/////////////////////////////////
function init(){
$rootScope.$on('$stateChangeStart', _onStateChangeStart);
}
function _onStateChangeStart(event, toState, toParams, fromState, fromParams){
var toStateRequiresAuth = _requiresAuth(toState);
if(!User.isAuthenticated && toStateRequiresAuth){
event.preventDefault();
$state.go('public');
alert('You are not authorised to see this view');
return;
}
}
function _requiresAuth(toState){
if(angular.isUndefined(toState.data) || !toState.data.requiresAuth){
return false;
}
return toState.data.requiresAuth;
}
}]);
app.run(['RouteValidator', function(RouteValidator){
RouteValidator.init();
}]);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.2/angular.js" data-semver="1.4.2"></script>
<script data-require="ui-router#0.2.15" data-semver="0.2.15" src="//rawgit.com/angular-ui/ui-router/0.2.15/release/angular-ui-router.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<a ui-sref="public">public</a>
<a ui-sref="login">login</a>
<a ui-sref="admin">admin</a>
<div ui-view></div>
</body>
</html>
I'm getting an error saying,
[$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=qProvider%20%3C-%20q%20%3C-%20searchResult
When I use the following config and controller. I'm trying to resolve and http request on a specific route.
.when('/fsr/:first', {
templateUrl: 'views/fsr.html',
controller: 'fsrCtrl',
resolve: {
searchResult: ['$http', 'q', function($http, $q) {
var def = $q.defer();
var samples;
$http.get('/api/fsr').success(function(data, status){
samples = data;
def.resolve(data);
})
return {
getSamples: function() {
return def.promise;
}
}
}]
}
})
.controller('fsrCtrl', ['$scope', 'searchResult', function($scope, searchResult){
searchResult.getSamples().then(function(data){
console.log(data);
})
}])
Why I'm getting this?
Here are the solution, change q to $q.
searchResult: ['$http', '$q', function($http, $q) {
...
}
var app = angular.module('webbapp', ['ngRoute']);
app.config(['$routeProvider', function ($routeProvider) {
console.log('woot');
$routeProvider
.when('/fsr', {
templateUrl: 'fsr.html',
controller: 'fsrCtrl',
resolve: {
searchResult: ['$http', '$q', function($http, $q) {
var def = $q.defer();
var samples;
$http.get('/api/fsr').success(function(data, status){
samples = data;
def.resolve(data);
})
return {
getSamples: function() {
return def.promise;
}
}
}]
}
})
}])
.controller('fsrCtrl', ['$scope', 'searchResult', function($scope, searchResult){
searchResult.getSamples().then(function(data){
console.log(data);
})
}])
angular.bootstrap(document, ['webbapp']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello Plunker!</h1>
Load Fsr view
<div ng-view=""></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular-route.min.js"></script>
<script src="app.js"></script>
</body>
</html>
I'm new to AngularJS. Can someone help me why the following routing will not work? I have a custom directive that submits a user form. After submission, it should navigate to the success page.(views/success.html).
I'm getting an error upon submission. TypeError: Cannot read property
'path' of undefined
If I simply try navigate to "/index.html#/success" on the address bar, it will not redirect to the success page, so I'm suspecting it is a routing issue but I can't seem to understand the cause of it. Any help would be greatly appreciated!
var myApp = angular.module('myApp', ['ngRoute', 'myControllers', 'loginDirective'])
.config(function ($routeProvider) {
$routeProvider.when("/home", {
templateUrl: 'index.html',
controller: 'myApp'
}).when("/success", {
templateUrl: 'views/success.html',
controller: 'myApp'
})
// If no route is selected then use the 'home' route.
.otherwise({ redirectTo: '/home' });
});
// Directive - Modifies HTML behaviour.
var myDirectives = (function () {
var myDirectives = angular.module('loginDirective', []);
// directive() is a factory method to create directives.
myDirectives.directive('login', function () {
return {
restrict: 'A',
scope: {
},
link: function ($scope, elem, attrs, ctrl, $location) {
$scope.submit = function() {
console.log("I clicked on submit");
$location.path("/success");
}
},
templateUrl: function (element, attr) { return 'views/loginForm.html' },
}
});
return myDirectives;
}());
// Controller - dispatches inputs and outputs.
var myControllers = (function () {
var myControllers = angular.module('myControllers', []);
// Controllers are defined by the controller function.
myControllers.controller('AppCtrl', ['$scope', '$routeParams','$location', function ($scope, $routeParams, $location) {
$scope.title = "Sign in";
}]);
return myControllers;
}());
index.html
<!DOCTYPE html>
<html>
<body ng-app='myApp' ng-controller="AppCtrl" class="container">
<div login></div> //custom directive
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-route.min.js"></script>
<script src="js/app.js"></script>
</body>
</html>
$location needs to be injected in the directive definition, not in the link
function, e.g.
// directive() is a factory method to create directives.
myDirectives.directive('login', ['$location', function ($location) {
...
}]);
Also you don't need to use a separate module for controllers, directive, etc. In other words, there only needs to be one angular.module('...') call
Your whole code can be simplified as
// define the app
var app = angular.module('myApp', ['ngRoute']);
// app configuration block
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.when("/home", {
templateUrl: 'index.html',
controller: 'myApp'
}).when("/success", {
templateUrl: 'views/success.html',
controller: 'myApp'
})
// If no route is selected then use the 'home' route.
.otherwise({ redirectTo: '/home' });
}]);
// definition block for 'AppCtrl' controller
app.controller('AppCtrl', ['$scope',
function ($scope) {
$scope.title = "Sign in";
}]);
// definition for 'login' directive
app.directive('login', ['$location',
function ($location) {
return {
restrict: 'A',
scope: {
},
link: function (scope, element, attrs) {
scope.submit = function() {
console.log("I clicked on submit");
$location.path("/success");
}
},
templateUrl: 'views/loginForm.html'
}
}]);
AngulaR resolve API
The API says for resolve:
key – {string}: a name of a dependency to be injected into the controller.
#egghead, there is this video on the topic:
egghead - Angular resolve
What i do not understand is what that key object is for and why the author of the above video does inject the controller into itself
key – {string}: a name of a dependency to be injected into the
controller.
app.config(function($routeProvider) {
$routeProvider.
when('/', {
controller: 'ListCtrl',
resolve: {
myResolve: function(MyService) {
return MyService();
}
},
templateUrl:'./views/list.html'
})
});
Instead of (in the controller)
app.controller('MyController',function($scope,MyService){
$scope.data = MyService();
});
if you use resolve
app.controller('MyController',function($scope,myResolve){
$scope.data = myResolve;
});
UPDATE
a working example
<!doctype html>
<html ng-app="myModule">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="content" data-ng-view=""></div>
<script src="http://code.angularjs.org/1.0.8/angular.min.js"></script>
<script>
var myModule = angular.module('myModule', []);
myModule.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: './index.html',
controller: 'IndexCtrl',
resolve: {
hello: function(Hello) {
return Hello.getMessages();
}
}
})
.otherwise({
redirectTo: '/'
});
});
myModule.factory('Hello', function($q, $timeout) {
var getMessages = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve('Hello');
}, 1000);
return deferred.promise;
};
return {
getMessages: getMessages
};
});
myModule.controller('IndexCtrl',function($scope,hello){
$scope.hello = hello;
});
</script>
</body>
</html>
the view
<p>{{hello}}</p>