Suppose my web app needs to make a http request to get my site title, site description and so on. Since these variables are common to all pages, it makes sense to request those every time a user enter the site.
The question is, where do I make those calls? In run block? Or to create a root controller to do these tasks?
You can use one of these two approaches:
Make your call in run block and store the value in $rootScope and
use anywhere you want,
In your states, use resolve to get the page title and details , and
get it in the views , For ease use resolve in root state and use the
resolved variable as a dependency in other child or sibling routes
to get values..
$stateProvider.state('root', {
resolve:{
// Example using function with simple return value.
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'});
}
})
.state('sibling',{
controller:function($scope,promiseObj){
$scope.title = promiseObj.title;
}
})
You could define controller at the <html> level.
<html ng-app="app" ng-controller="initCtrl">
<head>
<title>{{ Page.title() }}</title>
...
You create service: Page and modify from controllers.
myModule.factory('Page', function() {
var title = //Code to get the title
.... //Other vars
return {
title: function() { return title; },
setTitle: function(newTitle) { title = newTitle }
...
};
});
Inject Page and Call 'Page.setTitle()' from controllers.
Related
I'm building a rest api with fosrestbundle and I manage the frontend with angular and Twig template. One on my url address looks like this :
http://mywebsite/myroute/idContain
When I load the url in my browser, in a twig template (kind of html), I retrieve the parameter "idContain" (comming from a fosrestbundle controller) with ng-init of angularjs like this :
<div class="container-fluid" ng-init="getContainByID({{ idContain }})">
//...lot html div with angularjs directives
</div>
And immediately, ng-init will go to my angularJS app finds getContainByID(idContain) to run it.
This one looks like this :
angular.module("myApp", ["ngSanitize", 'angular.filter', 'ui.tinymce', ...])
.config(function($interpolateProvider, ...) {
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
})
.controller("myCtrl",function ($filter,..., myService)
{
// lot of code...
$scope.getContainByID = function(idContain)
{
$scope.currentContain = myService.getContains(idContain);
$scope.containRoot = $scope.currentContain.contain.containRoot;
...
}
// lot of code...
}
The fact is that, myService.getContains(idContain) come from my rest service looking like this :
angular.module("MyServiceRest", ['ngResource'])
.factory("myService", function ($rootScope, $resource) {
var apiData = $resource(
"/api", {},
{
...
"getContains": {method: 'GET', isArray: false, url: "mywebsite/api/myroute/:containid"}
...
});
return {
getContains: function (idContain) {
return apiData.getContains({containid: idContain});
}
}
});
Now the problem is, when I run my angularjs App, $scope.containRoot doesn't wait until myService.getContains(idContain) (coming from my asynchroeous $resource service) finished to load, and caused errors making my webapp crash.
How can I do, to force $scope.containRoot and the rest of my angular code to waiting until myService.getContains(idContain) (connected to the api resource) completly finished to load before continuing ?
$resource makes an asynchronous request but immediately reurns an empty object or array.
There are numerous ways you could handle your issue.
One would be not to worry about declaring $scope.containRoot and just using currentContain.contain.containRoot in the view. This property will get rendered after the request is received
Another is to use the $promise that is also returned by $resource and assign the scope property in promise callback
$scope.currentContain = myService.getContains(idContain);
$scope.currentContain.$promise.then(function(){
$scope.containRoot = $scope.currentContain.contain.containRoot;
});
Another is to use a routing resolve based on the same promise so the route( or state depending on router) is noot entered until the request is complete
My project uses MVC to deliver the initial markup of my site
The MVC controller is super simple:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
I have my ng-app tag, bundling, and #RenderBody in a layout view:
<!DOCTYPE html>
<html ng-app="myAppName">
<head>
#Styles.Render("~/Content/css")
</head>
<body>
<div class="container body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/aBundle")
</body>
</html>
And my Index view is stripped down as simple as possible:
<ng-view></ng-view>
My angular app.ts file looks like this:
module app {
var main = angular.module("myAppName", ["ngRoute", "breeze.angular"]);
main.config(routeConfig);
routeConfig.$inject = ["$routeProvider"];
function routeConfig($routeProvider: ng.route.IRouteProvider): void {
$routeProvider
.when("/home",
{
templateUrl: "app/views/homeView.html",
controller: "HomeController as vm"
})
.when("/itemDetail/:itemId",
{
templateUrl: "app/views/itemDetailView.html",
controller: "ItemDetailController as vm"
})
.when("/addItem",
{
templateUrl: "app/views/addItemView.html",
controller: "AddItemController as vm"
})
.when("/login",
{
templateUrl: "app/views/loginView.html",
controller: "LoginController as vm"
})
.otherwise("/home");
}
}
I can inspect the Request sent by the user in the MVC controller, or in the Razor view using #Request.IsAuthenticated to see if the user is logged in, but what is the best way to pass this information to my angular app so that I can properly route the user to a login page when they first sign on, but skip the login page if they have an active session on the server?
The research I have done to try and figure this out has suggested to me that I probably need to create an angular service to store a boolean value regarding whether the user is authenticated or not. Then, I need to add some code to check this service for every route using $routeChangeStart, and redirecting to the login page only when necessary. I have looked at many examples, but can't quite put the pieces together in the context of my own application.
Could someone help me connect the dots, please?
I'm developing a project that I'm not using razor. Only html and angular in the views. So... What I did is:
I created a directive "appWrapper" that holds the layout. Inside of it there is a section that has the ng-view.
This directive uses a controller, the AuthCtrl, and this controller uses a service, the AuthService.
Because of this, I have access to everything that's inside this controller in the entire html. So I could say ng-click="vm.logout()" in an item in the sidebar, for example.
This "AuthService" has some methods that can be called by the controller. Login and logout being 2 of them.
When I execute login, I set some cookies with some information I get back from my controller.
When I logout, I remove these cookies.
On the app.js, where my routes are, I have I .run at the end that checks these cookies every time the location (url) is going to be changed. If they exist, it lets the user continue. If not, it redirects the user to the login.
Did it help? If needed, I can post the code for you to use as example.
No need to create an action inside a controller for checking this. It would require your application to go to the server, back to the browser, back to the server and back to the browser for every action the user takes. This is not good.
I can share what I do in my own apps, and although I am using ui-router, the same technique can easily be applied to the built in router.
Basic logical workflow:
Automatically add a resolve to every route that fetches the user's current auth status and caches it.
Check if route requires authentication, and reject if user is not logged in, otherwise allow to proceed normally.
On $routeChangeError (or something similar) check to see what action to perform.
The advantage of this is it allows you to have a great deal of flexibility with regard to providing a nice client side experience with security.
Adding Credentials to Every Route:
let originalStateFunction = $stateProvider.state;
$stateProvider.state = function (state, config) {
//
// The "allowAnonymous" is something we added manually
// This will become important later because we might have
// routes that don't require authentication, like say
// the login page
//
if (angular.isDefined(config) && !config.allowAnonymous) {
_.defaults(config.resolve || (config.resolve = {}), {
/*#ngInject*/
userSession: function userSessionSecurityCheck($q, sessionService) {
let def = $q.defer();
sessionService.getSession()
.then(session => {
if(!session.isAuthenticated){
def.reject({
error:'AUTHENTICATION_REQUIRED'
});
}
});
return def.promise;
//You could also do more complex handling here...
// like check permissions for specific routes
// and reject the promise if they fail.
}
});
}
//Now call the original state/when method with our
// newly augmented config object
return originalStateFunction.apply(this, arguments);
};
Inspecting The Route Error
.run($rootScope => {
"ngInject";
$rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error) => {
event.preventDefault();
if(error.error === "AUTHENTICATION_REQUIRED"){
//Now you can redirect the user appropriately
}
});
});
You can create a simple endpoint to return the current user's status by inspecting the Principal, and so long as you are smart about caching that on the client, you will only incur the hit once per user.
The full sample code is too large for SO, but hopefully this gives you a good starting point.
I am thinking you would do it along the lines of below:
MVC controller:
[Authorize] // Make sure we're authorising the whole controller.
public class ProfileController : Controller
{
public ActionResult IsLoggedIn()
{
return Json(this.Request.IsAuthenticated);
// you may need allow get as the second parameter
}
}
Angular:
app.factory('ProfileService', function ($http) {
var service = {
isLoggedIn: isLoggedIn
};
return service;
function isLoggedIn() {
return $http.get('Profile/IsLoggedIn').then(function (response) {
return response.data; // this depends on what the response object looks like
});
}
});
app.run(function (ProfileService, $location) {
ProfileService.isLoggedIn().then(function (isLoggedIn) {
if (!isLoggedIn) {
$location.path('/unauthorised'); // just redirect them to the unauthorised route.
}
});
});
This means that everytime your app runs it will check to see whether you are logged in. You can also use this profile service to go grab other information about the user! And you can pull this service into any other module you wish to perform this kind stuff.
Remember that it is pointless trying to secure the javascript because it is run in a sandbox. But always make sure that you use [Authorize] attributes in your MVC code so that the server is always enforcing Authorisation and Authentication.
Before anyone says this isn't Typescript, any Javascript is also valid Typescript. I leave it to the user to put in the type defs.
-- More Info --
You can add a xmin cache expiry on the is logged in or store these details into local storage if you are constantly requesting the information:
app.factory('ProfileService', function ($http, $q) {
var service = {
isLoggedIn: isLoggedIn
};
var cacheExpiries = {
loggedIn: { value: null, expiry: null }
};
return service;
function isLoggedIn() {
var cacheObj = cacheExpiries['loggedIn'];
var useCache = false;
if (cacheObj.expiry) {
useCache = new Date() < cacheObj.expiry;
}
if (useCache) {
// because http returns a promise we need to
// short circuit function with a promise
return $q(function (res, rej) {
res(cacheObj.value);
});
}
// set the new expiry for the cache, this just adds 5 minutes to now
cacheObj.expiry = new Date(new Date().setMinutes(new Date().getMinutes() + 5));
return $http.get('Profile/IsLoggedIn').then(function (response) {
cacheObj.value = response.data;
return response.data; // this depends on what the response object looks like
});
}
});
You could easily encapsulate the cache into a factory with a proper key value storage api. This would extract some of the logic out of your thin service ( to keep it thin )
Local Storage and Cookie Storage
I have used this module for years to do local storage access, this can be slotted in place of the cache or you could still have your cache service to wrap this storage solution so that you can completely stay decoupled from your dependencies.
I'm new on AngularJS. I would like to know what is the best way to use pure AngularJS to initiate a controller that uses a service that makes an HTTP request to an external source and as a response receives a JSON object.
The controller is being used to fetch information that will be shown as part of the landing page (welcome information).
I have tried:
In the HTML invoke the function with the ng-init, with alias for the controller and without alias.
In the controller make the explicit call to the service, and in the configuration of routes, resolve primitive to call the Service and save it a a variable at the configuration file.
In the controller receive as a parameter the response of the service and in the configuration of routes, use the resolve primitive to call the Service and save it as a variable with the name of the parameter that the controller receives.
In the controller save the response as a $scope variable, but it is always undefined an nothing is bound to the HTML. Is it necessary to create a value or a directive or something for saving an object in the scope? Also tried doing it at the service with $scope as parameter with the same results (undefined $scope variable).
These options effectively trigger the HTTP request and pass through the lines of the controller function. The problem is that the response is not available for the controller. Under debug mode I can only see that is an object but it doesn't behaves as a JSON object so can't access to none of the properties.
I used the .then at the controller, but although now the data is saved in the $scope, it shows [OBJECT OBJECT] and I can't access to the properties of the JSON object that is saved as the response of the http request. Any ideas?
The function of the service that makes the request like the following:
myAppModule.factory('ClimateService', function ($http) {
return {
getLocation: function () {
return $http.get("some_url/json")
then(function successCallback(response) {
return response.data;
}, function errorCallback(response) {
//
});
}
}
});
Under debug I can see the 200 response and the JSON of it. Content-Type:application/json; charset=utf-8
After several changes, none of them are triggering and I am getting an injection error. I have included the local angular-routes in the HTML header, fetched with bower.jason to the project at NetBeans. And included as the first dependency or parameter to the ngRoute
angular.module('app',['ngRoute', ...
angular.module('app.routes', ['ngRoute', 'app.core'])
.config(config);
angular.module('app.core', []);
By the way, the Angello project at GitHub injects neither services nor much parameters and the book doesn't cover this topic in depth.
Is it the version of AngularJS?
Uncaught Error: [$injector:modulerr]
http://errors.angularjs.org/1.4.8/$injector/modulerr?p0=app&p1=Error%3A%20%5B%24injector%3Amodulerr%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.4.8%2F%24injector%2Fmodulerr%3Fp0%3Dapp.config%26p1%3DError%253A%2520%255B%2524injector%253Anomod%255D%2520http%253A%252F%252Ferrors.angularjs.org%252F1.4.8%252F%2524injector%252Fnomod%253Fp0%253Dapp.config%250A%2520%2520%2520%2520at%2520Error%2520(native)%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A6%253A416%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A24%253A186%250A%2520%2520%2520%2520at%2520b%2520(http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A23%253A251)%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A23%253A494%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A38%253A117%250A%2520%2520%2520%2520at%2520n%2520(http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A7%253A333)%250A%2520%2520%2520%2520at%2520g%2520(http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A37%253A488)%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A38%253A134%250A%2520%2520%2520%2520at%2520n%2520(http%253A%252F%252Flocalhost%253A8383%252Fapp_demo_app%252Fbower_components%252Fangular%252Fangular.min.js%253A7%253A333)%0A%20%20%20%20at%20Error%20(native)%0A%20%20%20%20at%20http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A6%3A416%0A%20%20%20%20at%20http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A38%3A391%0A%20%20%20%20at%20n%20(http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A7%3A333)%0A%20%20%20%20at%20g%20(http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A37%3A488)%0A%20%20%20%20at%20http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A38%3A134%0A%20%20%20%20at%20n%20(http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A7%3A333)%0A%20%20%20%20at%20g%20(http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A37%3A488)%0A%20%20%20%20at%20eb%20(http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A41%3A249)%0A%20%20%20%20at%20c%20(http%3A%2F%2Flocalhost%3A8383%2Fapp_demo_app%2Fbower_components%2Fangular%2Fangular.min.js%3A19%3A463) (23:05:42:235 | error, javascrip
It's a bit tricky because you have to use promises (.then), but here's a super simple 'get' example.
On the Service:
function mainService($http) {
this.getData = function () {
return $http.get('/api/yourUrl') //a basic 'get' api call
.then(function (response) { //it takes time, so include a promise
return response.data;
});
};
}
And the Controller:
function homeController($scope, friendService) {
$scope.getData = function () {
mainService.getData()
.then(function (data) { //you also need a promise on controller
$scope.ourData = data; //finally put what you get on your scope
});
};
}
Note these examples don't show the defining of the controller and the service - let me know if you're confused.
let's suppose you'll use $http service to request data.
angular.module('app.core', [])
.controller('CoreController', CoreController);
CoreController.$inject = ['$scope', '$http'];
function CoreController($scope, $http) {
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
$scope.data = response.data;
}, function errorCallback(response) {
alert('something odd happens.');
});
};
in your html
<div ng-bind-html="data">
</div>
or if you want to fill a form input:
<input type="text" ng-model="data" />
From what I understood, I am suggesting few things to keep in mind . May be its not what you are looking for but it might help.
ng-init is called as the very first thing inside a controller. You can use promise in case you have to keep things in sync.
If you dont want to get into promise, simply do $http call from controller as it by default handles promise using .success & .error function.
You can't expect some $scope variable to be valid under html if it is inside service. Refer docs.
var mainApp = angular.module("mainApp",[]);
mainApp.controller("serviceController",function($scope,$http){
$http.get("/fetchData_url/").success(function(response){
$scope.data = response;
});
});
I am trying to run an $http function when my AngularJS application first loads.
This $http function needs to finish before any of the controllers in my application could properly function. How would I go about doing this? This sounds like a promise, but it sounds like I would be creating a promise in each controller...
I currently have the function that I want to run first like this:
app.run(function() {
$http.get('link').success(function(data) {
// success function. The data that I get from this HTTP call will be saved to a service.
}).error(function(error) {
});
});
However, sometimes the controller will load before the http call finishes.
The problem
Angular is not dynamic, you cannot add controller dynamically neither factory, etc. Also you cannot defer controller bootstrap, angular loads everything together, and it's quite disadvantage (will be fixed in Angular 2)
The cure
But javascript itself has very important feature - closure, which works anywhere, anytime.
And angular has some internal services that can be injected outside of angular ecosystem, even into browser console. Those services injected as shown below. We technically could use anything else (jQuery.ajax, window.fetch, or even with XMLHttpRequest), but let's stick with total angular solution
var $http_injected = angular.injector(["ng"]).get("$http");
The act
First of all, we defer whole angular app bootstrap, inject http service. Then you make your needed request, receive data and then closure get's to work, we pass received data into some service, or we could also assign in to some angular.constant or angular.value but let's just make demo with angular.service, so when your service has data, bootstrap whole app, so that all controllers get initialized with your needed data
Basically that kind of tasks solved like this
<body>
<div ng-controller="Controller1">
<b>Controller1</b>
{{text}}
{{setting.data.name}}
</div>
<hr>
<div ng-controller="Controller2">
<b>Controller2</b>
{{text}}
{{setting.data.name}}
</div>
<script>
//define preloader
var $http_injected = angular.injector(["ng"]).get("$http");
$http_injected.get('http://jsonplaceholder.typicode.com/users/1').then(function(successResponse) {
//define app
angular.module('app', []);
//define test controllers
//note, usually we see 'controller1 loaded' text before 'settings applied', because controller initialized with this data, but in this demo, we will not see 'controller1 loaded' text, as we use closure to assign data, so it's instantly changed
angular.module('app').controller('Controller1', function($scope, AppSetting) {
$scope.text = 'controller1 loaded';
$scope.setting = AppSetting.setting;
$scope.$watch('setting', function(e1 ,e2){
$scope.text = 'settings applied'
});
});
angular.module('app').controller('Controller2', function($scope, AppSetting) {
$scope.text = 'controller2 loaded';
$scope.setting = AppSetting.setting;
$scope.$watch('setting', function(e1 ,e2){
$scope.text = 'settings applied'
});
});
//define test services, note we assign it here, it's possible
//because of javascript awesomeness (closure)
angular.module('app').service('AppSetting', function() {
this.setting = successResponse;
});
//bootstrap app, we cannot use ng-app, as it loads app instantly
//but we bootstrap it manually when you settings come
angular.bootstrap(document.body, ['app']);
});
</script>
</body>
Plunker demo
You can do this when you configure your routes
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
controller: 'MainCtrl',
templateUrl: 'main.html',
resolve: {
data: ['$http',
function($http)
{
return $http.get('/api/data').then(
function success(response) { return response.data.rows[0]; },
function error(reason) { return false; }
);
}
]
}
});
}]);
Similar question:
AngularJS - routeProvider resolve calling a service method
AngularJS: $routeProvider when resolve $http returns response obj instead of my obj
Heres a plunkr I found using a service, which is what I would recommend.
http://plnkr.co/edit/XKGC1h?p=info
I have angularjs project implemented multi-language and using ui-router for routing. Every language will be have different url. Ex:
http://example.com/!#/en-us/english-title
http://example.com/!#/es-es/spanish-title
All state with url registered automatically when app run and load them from database. Ex:
angular.module('bxApp').run(["$http", function ($http) {
$http.get('/Home/Routes').success(function (result) {
result = result || {};
if (angular.isDefined(result) && result !== null) {
_.each(result.Routes, function (route) {
stateProvider.state(route.Name, {
url: route.Url,
templateUrl: route.TemplateUrl,
controller: route.Controller,
});
});
}
});
}]);
It work well but it will not work when user copy this link and paste to browser or click this link from other website . I think because of state can't found so it will be redirect to default and it does not keep url that user enter or copy.
In this case , How to do that?
Thanks,
You're declaring your states as a result of an HTTP call to your server: the problem is that these states are defined too late for the user to navigate to them when he pastes the URL in a new tab.
To understand, let's deconstruct what happens :
The user is on the initial page / other website, and copies the URL.
He pastes it in a new tab
Your angular application loads, finishes its config phase without having declared any of those states, and sends an HTTP call.
ui-router fails to route to a state matching the pasted URL, since the corresponding state is not here yet, and redirects to default
The HTTP response comes back, and your states are created (but too late).
How to make it work ?
My first reaction would simply not to store your states on your server. Unless you want the very core of your UX to be language-dependent, you don't have to do that.
But hey, let's say we want to do it anyway. I suggest you try this : declare a toplevel 'language' state, and have it load the other states in a resolve clause. This will 'block' the routing until the other states are declared :
angular.module('bxApp')
.config(['$urlRouterProvider', function ($urlRouterProvider) {
$urlRouterProvider
.state('language',{
url: '/:language',
resolve: {
childrenLoaded: ['$http', function ($http) {
// returning a promise is essential to have the 'waiting' behavior
return $http.get('/Home/Routes').then(function (data) {
var result = data.result || {};
if (angular.isDefined(result) && result !== null) {
_.each(result.Routes, function (route) {
$stateProvider.state(route.Name, {
url: route.Url,
templateUrl: route.TemplateUrl,
controller: route.Controller
});
});
}
});
}]
}
})
}]);
Again, this approach is probably asking for trouble : I strongly recommend you hardcode your states instead of storing them in a database. If all that varies from one language to another is the text and URL, then you will be fine with an URL param.