I am new to the AngularJS community and was hoping someone could help me with the following issue.
I have created a light CMS system based on an incomplete tutorial and have filled in some of the pieces myself, but i cannot get the HTML partials to update when the $scope changes;
HTML partial (admin-login.html)
<div ng-if="loggedInUser">
Welcome {{loggedInUser}} | My Admin | Logout
</div>
my directive (directives.js)
directive('adminLogin', [
function() {
return {
controller: function($scope, $cookies) {
var user = $cookies.get('loggedInUser', {path: "/"});
$scope.loggedInUser = user;
},
templateUrl: 'partials/directives/admin-login.html'
};
}
])
my controller (controllers.js)
controller('AdminLoginCtrl', ['$scope', '$location', '$cookies', 'AuthService','$log','flashMessageService',
function($scope, $location, $cookies, AuthService, $log, flashMessageService) {
$scope.credentials = {
username: '',
password: ''
};
$scope.login = function(credentials) {
AuthService.login(credentials).then(
function(res, err) {
$cookies.put('loggedInUser', res.data);
$location.path('/admin/pages');
},
function(err) {
flashMessageService.setMessage(err.data);
$log.log(err);
});
};
}
])
The scope updates but i have to refresh the page to show or hide the admin-login.html.
Any help is greatly appreciated, thank you in advance.
$scope.loggedInUser won't be updated once you update cookie, you should watch for cookie change in directive and update $scope manually.
directive('adminLogin', [
function() {
return {
controller: function($scope, $cookies) {
$scope.$watch(function () {
return $cookies.get('loggedInUser', {path: "/"});
}, function (value) {
$scope.loggedInUser = value;
});
},
templateUrl: 'partials/directives/admin-login.html'
};
}
])
Related
I am using UI Router and UI Bootstrap in my Angular app. I'd like to use a service so that I can display alert messages from various controllers. I want the alert to display at the top of the screen. Any suggestions on how to modify the code below so that the alert will display at the top of the page and display messages from different controllers?
I'm using this Stack Overflow post as a model.
HTML:
<alert ng-repeat="alert in allInfos()" type="{{alert.type}}" close="closeAlert($index)"
ng-cloak>{{alert.msg}}</alert>
Service:
.factory('Informer', function(){
var messages = [];
var Informer = {};
Informer.inform = function(msg, type) {
messages.push({
msg: msg,
type: type
});
};
Informer.allInfos = function() {
return messages;
};
Informer.remove = function(info) {
messages.splice(messages.indexOf(info), 1);
};
return Informer;
})
Controller:
.controller('PaymentFormCtrl',
function ($scope, $http, Informer) {
$scope.handleStripe = function () {
Informer.inform("There was a problem authorizing your card.", "danger");
$scope.messages = 'problem';
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
}
};
});
.controller('ContactFormCtrl',
function ($scope, $http, Informer) {
//. . .
Informer.inform("There is already an account with that email address", "danger");
$scope.messages = 'problem';
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
}
};
});
Routers:
.state('home', {
url: '/',
views: {
'top': {
templateUrl: 'views/bigHero.html'
},
'bottom': {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
}
}
})
.state('payment', {
url: '/payment',
views: {
'top': {
templateUrl: 'views/customerinfo.html',
controller: 'ContactFormCtrl'
},
'bottom': {
templateUrl: 'views/creditcard.html',
controller: 'PaymentFormCtrl'
},
}
});
});
You really have three good options that I can think of off the top of my head.
Create a global or what i like to call a "RootController" of your application bound higher up in your DOM so that the other controllers scope naturally extends it. i.e.:
<div ng-controller="RootController">
<div ui-view></div>
</div>
You can create a parent state with UI Router that both your child states inherit, giving a similar effect to the case above:
$stateProvider.state('parent', {controller: 'ParentController'});
$stateProvider.state('parent.child1', {controller: 'Child1Controller'});
$stateProvider.state('parent.child2', {controller: 'Child2Controller'});
You can pass all shared functionality through a service, which acts as an error message to your necessary controllers.
myService.service('errorService', function() {
this.errorMessage = 'Everything is happy!';
});
myService.controller('PaymentFormCtrl', function($scope, errorService) {
$scope.errorService = errorService;
$scope.setError = function() {
errorService.errorMessage = 'An error happened!';
};
});
How do I assign resolved data into state data w/o going in the controller code?
following code fails
.state('restaurant', {
abstract: true,
url: '/:restaurantId/:restaurantName',
resolve: {
restaurant: ['RestaurantFactory', '$stateParams', '$state', function (RestaurantFactory, $stateParams, $state) {
return RestaurantFactory.query({ restaurantId: $stateParams.restaurantId });
}]
},
data: {
restaurant: resolve.restaurant // fails
},
views: {
'content#': {
templateUrl: 'app/modules/restaurant/restaurant.html',
controllerAs: 'RestCtrl',
controller: ['restaurant', 'RestaurantFactory', 'AppLib', 'Page', '$scope', '$state', function (restaurant, RestaurantFactory, AppLib, Page, $scope, $state) {
var vm = this;
// public properties
vm.AppLib = AppLib;
vm.Page = Page;
vm.restaurant = restaurant;
$state.restaurant = restaurant;
$scope.$on('Restaurant_Changed', function (event, data) { if (data.id === vm.restaurant.id) { vm.restaurant = data; $state.restaurant = data; } });
}]
}
}
})
The reason I need this functionality is that one of the child state overwrites the content# view (kind of brings entire content of its own) and as a result parent's controller is never fired if I access child states page directly. Is there a way to accomplish this as I want to use parent state URL and ability to share data down to children/grandchildren states
Any ideas?
Solution
Big thanks to Yang Li for giving me this suggestion. Here is how I solved it
resolve: {
restaurant: ['RestaurantFactory', '$stateParams', '$state', '$q', function (RestaurantFactory, $stateParams, $state, $q) {
var deferred = $q.defer();
RestaurantFactory.query({ restaurantId: $stateParams.restaurantId })
.then(function (data) {
$state.restaurant = data;
deferred.resolve($state.restaurant);
})
.then(function (data) {
deferred.reject(data);
})
return deferred.promise;
}],
},
I'm using ui-router and ui-bootstrap/modal
I've got a sale screen split in 2 so I have a left side with the cart and the right one can have the catalog, an edit product or a payment section.
I need to have a modal in all states so I've created a function to add in some ui-router states.
Here's the function:
var modalSaleDelete = ['$state', '$modal',
function($state, $modal) {
$modal.open({
templateUrl: 'views/sale/delete.html',
resolve: {
parentScope: function($rootScope) {
return $rootScope.parentScope;
}
},
controller: function($scope, parentScope) {
$scope.delete = function() {
// TODO: change the way this is called
parentScope.resetOrder();
parentScope = null;
$scope.$close('cancel');
};
$scope.cancel = function() {
parentScope = null;
$scope.$dismiss('cancel');
};
}
}).result.then(function() {
return $state.transitionTo($state.$current.parent);
}, function() {
return $state.transitionTo($state.$current.parent);
});
}
];
Then I put that in every state:
.state('sale.new.catalog.delete', {
url: '/delete',
onEnter: modalSaleDelete
})
It works great on development but when I minify this I get an error:
Error: [$injector:unpr] Unknown provider: aProvider <- a
http://errors.angularjs.org/1.2.24/$injector/unpr?p0=aProvider%20%3C-%20a
at http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:3:26944
at http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:4:11462
at Object.c [as get] (http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:4:10723)
at http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:4:11557
at c (http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:4:10723)
at Object.d [as invoke] (http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:4:11008)
at http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:8:20044
at Object.f [as forEach] (http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:3:27387)
at j (http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:8:19961)
at Object.k.open (http://localhost/ociWeb/code/dist/scripts/vendor.29d508bc.js:8:20414)
I debugged that and aProvider should be '$state'.
Any idea on how to make that work?
You need to annotate EVERY injection for minification to work. Or, if you are using an angular-aware minifier, it probably doesn't understand which functions are injected by UI-Router and which are standard functions.
var modalSaleDelete = ['$state', '$modal',
function($state, $modal) {
$modal.open({
templateUrl: 'views/sale/delete.html',
resolve: {
parentScope: [ '$rootScope', function($rootScope) {
return $rootScope.parentScope;
}]
},
controller: [ '$scope', 'parentScope', function($scope, parentScope) {
$scope.delete = function() {
// TODO: change the way this is called
parentScope.resetOrder();
parentScope = null;
$scope.$close('cancel');
};
$scope.cancel = function() {
parentScope = null;
$scope.$dismiss('cancel');
};
}]
}).result.then(function() {
return $state.transitionTo($state.$current.parent);
}, function() {
return $state.transitionTo($state.$current.parent);
});
}
];
Try injecting them manually by creating an inject property. Do you have a jsfiddle or plunker set up?
modalSaleDelete.$inject = ['$state', '$modal'];
Well, I've figured it out. I don't see why but the problem was in resolve.
I solved it injecting '$state' in resolve though it's not needed.
When debugging I just saw the aProvider was trying to be injected in there.
var modalSaleDelete = ['$rootScope', '$state', '$modal',
function($rootScope, $state, $modal) {
$modal.open({
templateUrl: 'views/sale/delete.html',
resolve: {
parentScope: ['$state', '$rootScope', function($state, $rootScope) {
return $rootScope.parentScope;
}]
},
controller: ['$scope', '$state', 'parentScope', function($scope, $state, parentScope) {
$scope.delete = function() {
// TODO: change the way this is called
parentScope.resetOrder();
parentScope = null;
$scope.$close();
};
$scope.cancel = function() {
parentScope = null;
$scope.$dismiss();
};
}]
}).result.then(function() {
// close
return $state.transitionTo($state.current.name.replace('.delete', ''));
}, function() {
// dismiss
return $state.transitionTo($state.current.name.replace('.delete', ''));
});
}
];
I am trying to follow this example to show a bootstrap modal on a certain state. It works fine without a modal (so the state config should be ok). All needed dependencies (ie angular bootstrap) should be available.
when I do a console.debug($stateParams) before $modal.open I get the correct data, within the $modal.open-method however the stateParams from the last state are returned (the state I am coming from)
Any hints?
EDIT
the relevant state cfg:
.state('publications.view', {
parent: 'publications.productSelection',
url: '/{productSlug:[a-zA-Z0-9-]+}/{docID:[0-9]+}_{slug:[a-zA-Z0-9-]+}',
onEnter: ['restFactory', '$state', '$stateParams', '$modal',
function(restFactory, $state, $stateParams, $modal) {
console.debug($stateParams.docID);
$modal.open({
templateUrl: 'partials/publication.html',
resolve: {
publication: ['restFactory', '$stateParams',
function(restFactory, $stateParams) {
console.debug($state.params);
console.debug($stateParams);
return restFactory.view($stateParams.language, $stateParams.productSlug, $stateParams.docID);
}
]
},
controller: ['$scope', '$sce', 'publication', '$rootScope',
function($scope, $sce, publication, $rootScope) {
$rootScope.pageTitle = publication.data.data.publication.Publication.title;
$scope.publication = $sce.trustAsHtml(publication.data.data.publication.Publication.content);
}
]
});
}
]
});
You can get around this issue by injecting the current $stateParams into the onEnter function, save them as state in some service, and inject that service instead into your modal resolves.
I am adapting the code from here: Using ui-router with Bootstrap-ui modal
.provider('modalState', function($stateProvider) {
var modalState = {
stateParams: {},
};
this.$get = function() {
return modalState;
};
this.state = function(stateName, options) {
var modalInstance;
$stateProvider.state(stateName, {
url: options.url,
onEnter: function($modal, $state, $stateParams) {
modalState.stateParams = $stateParams;
modalInstance = $modal.open(options);
modalInstance.result['finally'](function() {
modalInstance = null;
if ($state.$current.name === stateName) {
$state.go('^');
}
});
},
onExit: function() {
if (modalInstance) {
modalInstance.close();
}
}
});
};
})
Then in your app config section
.config(function($stateProvider, $urlRouterProvider, modalStateProvider) {
modalStateProvider.state('parent.child', {
url: '/{id:[0-9]+}',
templateUrl: 'views/child.html',
controller: 'ChildCtrl',
resolve: {
role: function(Resource, modalState) {
return Resource.get({id: modalState.stateParams.id}).$promise.then(function(data) {
return data;
});
}
}
});
}
I would like to before the advent of the home page if the user is not logged in is to move it to the login page.
At this moment Main page appears (half a second) and then loads the login page.
var app = angular.module("app", ["ngRoute", "ngAnimate"])
.constant('ACCESS_LEVELS', {
guest: 1,
user: 2,
admin: 3
});
app.config(["$routeProvider", "$locationProvider", "ACCESS_LEVELS", function($routeProvider, $locationProvider, ACCESS_LEVELS) {
$routeProvider
.when("/", {
template: "",
access_level: ACCESS_LEVELS.user
});
}]);
app.run(["$rootScope", "$window", "LoginService", function($rootScope, $window, LoginService) {
$rootScope.$on('$routeChangeStart', function(evt, next, curr) {
if(next.access_level !== undefined) {
LoginService.checkSession().then(function(response) {
$rootScope.isLoggedIn = response;
if(!response) {
$window.location.href = '/login.html';
}
}, function(error) {
$rootScope.isLoggedIn = error;
});
}
})
}]);
Is there a way to angular done without flash home page?
regards
You can use the ‘resolve’ property of the routeProvider.
From the docs:
resolve - An optional map of dependencies which should be >injected into the controller. If any of these dependencies are promises, the router will wait >for them all to be resolved or one to be rejected before the controller is instantiated.
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
So your routeProvider could look as follows, with the session promise specified as a dependency of your home route. You would then need to inject the session into the home controller.
$routeProvider
.when("/", {
template: "",
access_level: ACCESS_LEVELS.user,
controller: ‘homeCtrl’,
resolve: {
session: function(LoginService) {
return LoginService.checkSession();
}
}
});
A good blog post: http://blog.brunoscopelliti.com/show-route-only-after-all-promises-are-resolved
My current code:
in main app.js:
var app = angular.module("app", ["ngRoute", "ngAnimate", "ngResource"])
.constant('ACCESS_LEVELS', {
guest: 1,
user: 2,
admin: 3
});
besecure.config(["$routeProvider", "$locationProvider", "ACCESS_LEVELS", function($routeProvider, $locationProvider, ACCESS_LEVELS) {
$routeProvider
.when("/", {
template: "",
label: "Home",
access_level: ACCESS_LEVELS.user,
resolve: {
session: function(LoginService) {
return LoginService.checkSession();
}
}
});
}]);
app.run(["$rootScope", "$window", "$location", "LoginService", function($rootScope, $window, $location, LoginService) {
$rootScope.loadingView = false;
$rootScope.$on('$routeChangeStart', function(evt, curr, prev) {
if(curr.access_level !== undefined) {
if(curr.$$route && curr.$$route.resolve) {
$rootScope.loadingView = true;
} else {
$window.location.href = '/login.html';
}
}
})
}]);
Service:
app.factory("LoginService", function($resource, $q) {
var LoginService,
Login = $resource("/auth/login");
LoginService = {
checkSession: function() {
var deferred = $q.defer();
Login.get().$promise.then(function(response) {
deferred.resolve(response.result);
}, function(error) {
deferred.reject(false);
});
return deferred.promise;
}
};
return LoginService;
});
Service is OK.
and not work redirecting