Angular issue with $scope and $routeParams - javascript

I'm trying to run a function if someone hits the submit button OR there is a value in routeParams (if user hits the page with param already filled out.) I would like a function to run. Im having a brain fart and can't seem to get this to work!
myApp.config([ '$routeProvider', function($routeProvider) {
$routeProvider.when('/:params?', {
templateUrl: 'myTemplate',
controller : 'myController'
}).otherwise({
redirectTo : '/'
});
} ]);
myApp.controller('ipCntrl', function($scope,$log,$http,$q,$routeParams, $location) {
$scope.runReport = function() {
$location.search({'ip': $routeParams['ip']})
}
});
myApp.controller('myController', function($scope,$log,$http,$q,$routeParams, $location) {
if ($routeParams['ip'])
{
$scope.ip = $routeParams['ip'];
runMyFunction();
}
<div ng-controller="ipCntrl">
<form ng-submit="runReport()">
<input class="form-control" ng-model="ip">
</form>
<div ng-view ></div>
</div>
<script type="text/ng-template" id="myTemplate">
HI!
</script>

Since you're trying to call a function on button click or on initialization after checking the $routeParams, just include that code in the angular controller you are using (ipCntrl)
myApp.controller('ipCntrl', function($scope,$log,$http,$q,$routeParams, $location) {
$scope.runReport = function() {
$location.search({'ip': $routeParams['ip']})
}
//Just put the if statement here
//you're already using the ng-submit to call this function from your form
if ($routeParam.ip != null) //other definition check logic
$scope.runReport();
});

You can use...
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
console.log('toState ->');
console.log(toState);
console.log('toParams ->');
console.log(toParams);
console.log('fromState ->');
console.log(fromState);
console.log('fromParams ->');
console.log(fromParams);
runMyFunction();
})
...in your controller's function to intercept state transitions and requested URls ($stateParams).
See State Change Events.
You also have View Load events that may be useful.
You can also intercept state changes (and evaluate requested routes) in Angular's .run().
See .run() ui-router's Sample App.

Related

Controller not registered according to console

For some reason my controller doesn't seem to be registered and I'm honestly not sure why...
I've been following along an AngularJS and Django development course on PluralSight. The only difference between the instructor's stack and mine is that I'm using Angular 1.6.4 and he is using 1.5.0. I've previously hit some errors (like routing syntax), but it's overall been fine.
EDIT:
I should mention that I'm simply following the instructor's instructions and writing the same code as him.
Right now, however, I'm simply stuck. I've got this routing in scrumboard.config.js:
(function () {
"use strict";
angular.module('scrumboard.demo', ["ngRoute"])
.config(["$routeProvider", config])
.run(["$http", run]);
function config($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "/static/html/scrumboard.html",
controller: "ScrumboardController",
})
.when('/login', {
templateUrl: "/static/html/login.html",
controller: "LoginController",
})
.otherwise('/');
}
function run($http) {
$http.defaults.xsrfHeaderName = 'X-CSRFToken';
$http.defaults.xsrfCookieName = 'csrftoken';
}
})();
And this controller for login.html:
(function() {
"use strict";
angular.module("scrumboard.demo", [])
.controller("LoginController",
["$scope", "$http", "$location", LoginController]);
function LoginController($scope, $http, $location) {
$scope.login = function () {
$http.post('/auth_api/login/', $scope.user)
.then(function (response){
$location.url("/");
},
function(error) {
//failure
$scope.login_error = "Invalid username or password";
});
};
};
})();
When navigating to localhost:8000/#!/login I can see my form:
<form ng-submit="login()">
<div style="...">{{ login_error }}</div>
<div>
<label>Username</label>
<input type="text" ng-model="user.username"/>
</div>
<div>
<label>Password</label>
<input type="password" ng-model="user.password"/>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
But for some strange reason my console keeps telling me that my LoginController isn't registered.
Could anyone please help me in the right direction?
Sorry if I'm missing any sort of files, but I'm very new to Angular, so I don't really know what to add.
If you need any additional information, please tell me!
remove [] from your controller's defination for login.html.
angular.module("scrumboard.demo")
.controller("LoginController",
["$scope", "$http", "$location", LoginController]);
You are declaring the module scrumboard.demo twice.
Remove the [] in
angular.module("scrumboard.demo")
.controller("LoginController",
["$scope", "$http", "$location", LoginController]);
Without the second argument of angular.module() it is a getter, otherwise it is a setter
In the entry point html, the script tag for file scrumboard.config.js should precede the file controller file. Also as #charlietfl said declaring twice should be removed.
Like this,
<script src="your_path/scrumboard.config.js"></script>
<script src="your_path/scrumboard.controller.js"></script>
Hope this would rectify the problem.

Detect route change in an Angular service

I'm trying to create a service to check if a certain route needs a user to be logged in to access the page. I have a working code but I want to place the $scope.$on('routeChangeStart) function inside the service. I want to place it in a service because I want to use it in multiple controllers. How do I go about this?
Current code:
profileInfoCtrl.js
angular.module('lmsApp', ['ngRoute'])
.controller('profileInfoCtrl', ['$scope', '$location', ' 'pageAuth', function($scope, $location, pageAuth){
//I want to include this in canAccess function
$scope.$on('$routeChangeStart', function(event, next) {
pageAuth.canAccess(event, next);
});
}]);
pageAuth.js
angular.module('lmsApp')
.service('pageAuth', ['$location', function ($location) {
this.canAccess = function(event, next) {
var user = firebase.auth().currentUser;
//requireAuth is a custom route property
if (next.$$route.requireAuth && user == null ) {
event.preventDefault(); //prevents route change
alert("You must be logged in to access page!");
}
else {
console.log("allowed");
}
}
}]);
routes.js
angular.module('lmsApp')
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){
$routeProvider
.when('/admin', {
templateUrl: 'view/admin.html',
css: 'style/admin.css',
controller: 'adminCtrl',
requireAuth: true //custom property to prevent unauthenticated users
})
.otherwise({
redirectTo: '/'
});
}]);
By using $routeChangeStart, you are listening to a broadcast sent by $routeProvider on every change of the route. I don't think you need to call it in multiple places ( controllers ), just to check this.
In your service:
angular.module('lmsApp')
.service('pageAuth', ['$location', function ($location) {
var canAccess = function(event,next,current){
var user = firebase.auth().currentUser;
//requireAuth is a custom route property
if (next.$$route.requireAuth && user == null ) {
event.preventDefault(); //prevents route change
alert("You must be logged in to access page!");
}
else {
console.log("allowed");
}
}
$rootScope.$on('$routeChangeStart',canAccess);
}]);
And then inject your service in the .run() part of your application. This will ensure the check will be done automatically ( by the broadcast as mentioned earlier ).
In you config part :
angular.module('lmsApp')
.run(function runApp(pageAuth){
//rest of your stuff
});
You would add an event handler in the Service to $rootScope instead of $scope.
Also it would be much better if you would add the $routeChangeSuccess in the ".run" section so all the pages can be monitored from one point rather than adding it in every controller
You need to listen $rootScope instead of $scope.
And I think it's better to call that listener on the init of wrapped service, for instance (Services are singletons, so it will be run once you will inject it to any controller).
angular.module('lmsApp')
.service('stateService', ['$rootScope', function ($rootScope) {
$rootScope.$on('$locationChangeStart', (event, next, current) => {
// do your magic
});
}]);

have my angular controller check to see what ui-view it's located in with ui-router

I'm using ui-router with angularjs. I want to write a template for a view that will show a image depending on what the view is. here's my example state.
$stateProvider
.state('index', {
url: "",
views: {
"topStormtrooper": {
templateUrl: '/components/stormtroopers/stormtroopers.html',
controller: "stormtroopersCtrl"
},
"bottomStormtrooper": {
templateUrl: '/components/stormtroopers/stormtroopers.html',
controller: "stormtroopersCtrl"
}
}
})
my controller looks like this
.controller('stormtroopersCtrl', ['$scope', '$http', '$stateParams', function ($scope, $http, $stateParams) {
//
$scope.stormtrooper = $stateView; //it should be something like this hopefully
}]);
The template is all the same just the image will be different depending which view it is loaded into. Currently I just added a new controller for each view and load the image based on that. But I feel like I should be able to do this with just one controller and the controller should be able to detect what the view is. I know you can detect the state but I want go deeper and get the view.
Any Ideas?
You can access the current state configuratin object like this:
$state.current
For further information take a look at the $state
You can listen to the $viewContentLoaded function in your controller as per the ui-router documentation
For example:
.controller('stormtroopersCtrl', ['$scope', '$http', '$stateParams', function ($scope, $http, $stateParams) {
$scope.$on("$viewContentLoaded",function(event,viewName){ //listen for when the content is loaded into the view
$scope.currentView = viewName; //assign the view name in a model
console.log($scope.currentView);
//do something because you got the view name now....
});
}]);
You do not need to change your state just for an image in template. You can make it with scope variables.
angular.module('app',[])
.controller('stormtrooperCtrl', ['$scope', function ($scope) {
//
$scope.selected = 0;
$scope.images = [
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSz6dBOOsFVAeSilVEIO9dqwrY4R5gCzEMcrRVZguhYhr9PVJsThQ",
"http://www.wallpapereast.com/static/images/Wallpaper-Nature-8B71.jpg",
"http://www.intrawallpaper.com/static/images/1250654-for-laptop-nature.jpg"
];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="stormtrooperCtrl">
<button ng-click="selected=0">image1</button>
<button ng-click="selected=1">image2</button>
<button ng-click="selected=2">image3</button>
<div>
<img width="100" ng-src="{{images[selected]}}"/>
</div>
</div>
</div>

AngularJS: forward to login page for authentication, retain route as GET parameter?

I'm a bit of an Angular newbie. I'm trying to write an Angular service that on any page, will check if a user is logged in, and if not, forward them to a login page, passing their current path as a a GET parameter.
I'm almost there, but it's not quite working. The problem I'm having is as follows: if the user goes to #/articles/my-articles/, they get forwarded to #/login/?next=%2Farticles%2F:module%2F.
In other words, it looks as though Angular is passing the route pattern, not the actual URL.
This is my authentication code:
auth.run(['$rootScope', '$location', '$user', 'TOKEN_AUTH', 'PROJECT_SETTINGS', function ($rootScope, $location, $user, TOKEN_AUTH, PROJECT_SETTINGS) {
var MODULE_SETTINGS = angular.extend({}, TOKEN_AUTH, PROJECT_SETTINGS.TOKEN_AUTH);
$rootScope.$on('$routeChangeStart', function (e, next, current) {
if (next.$$route && !next.$$route.anonymous && !$user.authenticated) {
var nextParam = next.$$route.originalPath;
$location.url(MODULE_SETTINGS.LOGIN + '?next=' + nextParam);
}
});
}]);
I can get the original path in a hacky way using current.params.module - but that doesn't help me, because it seems that routeChangeStart is fired several times and the current object is undefined on all but the last fire.
This is my routes file:
articles.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/articles/:module/', {
templateUrl: 'views/articles/article_list.html',
controller: 'ArticlesListCtrl'
})
.when('/articles/:module/:id/', {
templateUrl: 'views/articles/article_detail.html',
controller: 'ArticlesDetailCtrl'
});
}]);
How can I fix this problem?
auth.run(['$rootScope', '$location', '$user', 'TOKEN_AUTH', 'PROJECT_SETTINGS', function ($rootScope, $location, $user, TOKEN_AUTH, PROJECT_SETTINGS) {
var MODULE_SETTINGS = angular.extend({}, TOKEN_AUTH, PROJECT_SETTINGS.TOKEN_AUTH);
$rootScope.$on('$routeChangeStart', function (e, next, current) {
if (!$user.authenticated) {
$location.url(MODULE_SETTINGS.LOGIN + '?next=' + $location.path());
$location.replace();
}
});
}]);
If logging in is not a AngularJS view, you may have to provide an otherwise route:
(depends on your $locationProvider config)
$routeProvider.otherwise({
template: 'Redirecting…',
controller : 'Redirect'
});
...
articles.controller('Redirect', ['$location', function($location) {
if (someConditionThatChecksIfUrlIsPartOfApp) {
location.href = $location.path();
return;
} else {
// Show 404
}
}]);
Side note: you shouldn't read $$-prefixed properties, they are private AngularJS variables.
Also note: don't use $ prefixes ($user) in your own code, these are public properties, reserved for AngularJS.
My solution works on Angular 1.2.13 :
preventDefault stops angular routing and $window.location sends me out to login page. This is working on a ASP.NET MVC + Angular app.
$rootScope.$on("$locationChangeStart", function (event, next, current) {
event.preventDefault();
$window.location = '/Login';
}
});

Delaying AngularJS route change until model loaded to prevent flicker

I am wondering if there is a way (similar to Gmail) for AngularJS to delay showing a new route until after each model and its data has been fetched using its respective services.
For example, if there were a ProjectsController that listed all Projects and project_index.html which was the template that showed these Projects, Project.query() would be fetched completely before showing the new page.
Until then, the old page would still continue to show (for example, if I were browsing another page and then decided to see this Project index).
$routeProvider resolve property allows delaying of route change until data is loaded.
First define a route with resolve attribute like this.
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: PhoneListCtrl,
resolve: PhoneListCtrl.resolve}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: PhoneDetailCtrl,
resolve: PhoneDetailCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
notice that the resolve property is defined on route.
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone, $q) {
// see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
},
delay: function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
}
Notice that the controller definition contains a resolve object which declares things which should be available to the controller constructor. Here the phones is injected into the controller and it is defined in the resolve property.
The resolve.phones function is responsible for returning a promise. All of the promises are collected and the route change is delayed until after all of the promises are resolved.
Working demo: http://mhevery.github.com/angular-phonecat/app/#/phones
Source: https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831
Here's a minimal working example which works for Angular 1.0.2
Template:
<script type="text/ng-template" id="/editor-tpl.html">
Editor Template {{datasets}}
</script>
<div ng-view>
</div>
JavaScript:
function MyCtrl($scope, datasets) {
$scope.datasets = datasets;
}
MyCtrl.resolve = {
datasets : function($q, $http) {
var deferred = $q.defer();
$http({method: 'GET', url: '/someUrl'})
.success(function(data) {
deferred.resolve(data)
})
.error(function(data){
//actually you'd want deffered.reject(data) here
//but to show what would happen on success..
deferred.resolve("error value");
});
return deferred.promise;
}
};
var myApp = angular.module('myApp', [], function($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/editor-tpl.html',
controller: MyCtrl,
resolve: MyCtrl.resolve
});
});​
​
http://jsfiddle.net/dTJ9N/3/
Streamlined version:
Since $http() already returns a promise (aka deferred), we actually don't need to create our own. So we can simplify MyCtrl. resolve to:
MyCtrl.resolve = {
datasets : function($http) {
return $http({
method: 'GET',
url: 'http://fiddle.jshell.net/'
});
}
};
The result of $http() contains data, status, headers and config objects, so we need to change the body of MyCtrl to:
$scope.datasets = datasets.data;
http://jsfiddle.net/dTJ9N/5/
I see some people asking how to do this using the angular.controller method with minification friendly dependency injection. Since I just got this working I felt obliged to come back and help. Here's my solution (adopted from the original question and Misko's answer):
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: PhoneListCtrl,
resolve: {
phones: ["Phone", "$q", function(Phone, $q) {
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
]
},
delay: ["$q","$defer", function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
]
},
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: PhoneDetailCtrl,
resolve: PhoneDetailCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}]);
Since this code is derived from the question/most popular answer it is untested, but it should send you in the right direction if you already understand how to make minification friendly angular code. The one part that my own code didn't requires was an injection of "Phone" into the resolve function for 'phones', nor did I use any 'delay' object at all.
I also recommend this youtube video http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp , which helped me quite a bit
Should it interest you I've decided to also paste my own code (Written in coffeescript) so you can see how I got it working.
FYI, in advance I use a generic controller that helps me do CRUD on several models:
appModule.config ['$routeProvider', ($routeProvider) ->
genericControllers = ["boards","teachers","classrooms","students"]
for controllerName in genericControllers
$routeProvider
.when "/#{controllerName}/",
action: 'confirmLogin'
controller: 'GenericController'
controllerName: controllerName
templateUrl: "/static/templates/#{controllerName}.html"
resolve:
items : ["$q", "$route", "$http", ($q, $route, $http) ->
deferred = $q.defer()
controllerName = $route.current.controllerName
$http(
method: "GET"
url: "/api/#{controllerName}/"
)
.success (response) ->
deferred.resolve(response.payload)
.error (response) ->
deferred.reject(response.message)
return deferred.promise
]
$routeProvider
.otherwise
redirectTo: '/'
action: 'checkStatus'
]
appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->
$scope.items = items
#etc ....
]
This commit, which is part of version 1.1.5 and above, exposes the $promise object of $resource. Versions of ngResource including this commit allow resolving resources like this:
$routeProvider
resolve: {
data: function(Resource) {
return Resource.get().$promise;
}
}
controller
app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {
$scope.data = data;
}]);
This snippet is dependency injection friendly (I even use it in combination of ngmin and uglify) and it's a more elegant domain driven based solution.
The example below registers a Phone resource and a constant phoneRoutes, which contains all your routing information for that (phone) domain. Something I didn't like in the provided answer was the location of the resolve logic -- the main module should not know anything or be bothered about the way the resource arguments are provided to the controller. This way the logic stays in the same domain.
Note: if you're using ngmin (and if you're not: you should) you only have to write the resolve functions with the DI array convention.
angular.module('myApp').factory('Phone',function ($resource) {
return $resource('/api/phone/:id', {id: '#id'});
}).constant('phoneRoutes', {
'/phone': {
templateUrl: 'app/phone/index.tmpl.html',
controller: 'PhoneIndexController'
},
'/phone/create': {
templateUrl: 'app/phone/edit.tmpl.html',
controller: 'PhoneEditController',
resolve: {
phone: ['$route', 'Phone', function ($route, Phone) {
return new Phone();
}]
}
},
'/phone/edit/:id': {
templateUrl: 'app/phone/edit.tmpl.html',
controller: 'PhoneEditController',
resolve: {
form: ['$route', 'Phone', function ($route, Phone) {
return Phone.get({ id: $route.current.params.id }).$promise;
}]
}
}
});
The next piece is injecting the routing data when the module is in the configure state and applying it to the $routeProvider.
angular.module('myApp').config(function ($routeProvider,
phoneRoutes,
/* ... otherRoutes ... */) {
$routeProvider.when('/', { templateUrl: 'app/main/index.tmpl.html' });
// Loop through all paths provided by the injected route data.
angular.forEach(phoneRoutes, function(routeData, path) {
$routeProvider.when(path, routeData);
});
$routeProvider.otherwise({ redirectTo: '/' });
});
Testing the route configuration with this setup is also pretty easy:
describe('phoneRoutes', function() {
it('should match route configuration', function() {
module('myApp');
// Mock the Phone resource
function PhoneMock() {}
PhoneMock.get = function() { return {}; };
module(function($provide) {
$provide.value('Phone', FormMock);
});
inject(function($route, $location, $rootScope, phoneRoutes) {
angular.forEach(phoneRoutes, function (routeData, path) {
$location.path(path);
$rootScope.$digest();
expect($route.current.templateUrl).toBe(routeData.templateUrl);
expect($route.current.controller).toBe(routeData.controller);
});
});
});
});
You can see it in full glory in my latest (upcoming) experiment.
Although this method works fine for me, I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise object; it would make things soooOOOOOooOOOOO much easier.
Edit: used Angular v1.2(rc2)
Delaying showing the route is sure to lead to an asynchronous tangle... why not simply track the loading status of your main entity and use that in the view. For example in your controller you might use both the success and error callbacks on ngResource:
$scope.httpStatus = 0; // in progress
$scope.projects = $resource.query('/projects', function() {
$scope.httpStatus = 200;
}, function(response) {
$scope.httpStatus = response.status;
});
Then in the view you could do whatever:
<div ng-show="httpStatus == 0">
Loading
</div>
<div ng-show="httpStatus == 200">
Real stuff
<div ng-repeat="project in projects">
...
</div>
</div>
<div ng-show="httpStatus >= 400">
Error, not found, etc. Could distinguish 4xx not found from
5xx server error even.
</div>
I worked from Misko's code above and this is what I've done with it. This is a more current solution since $defer has been changed to $timeout. Substituting $timeout however will wait for the timeout period (in Misko's code, 1 second), then return the data hoping it's resolved in time. With this way, it returns asap.
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function($q, Phone) {
var deferred = $q.defer();
Phone.query(function(phones) {
deferred.resolve(phones);
});
return deferred.promise;
}
}
Using AngularJS 1.1.5
Updating the 'phones' function in Justen's answer using AngularJS 1.1.5 syntax.
Original:
phones: function($q, Phone) {
var deferred = $q.defer();
Phone.query(function(phones) {
deferred.resolve(phones);
});
return deferred.promise;
}
Updated:
phones: function(Phone) {
return Phone.query().$promise;
}
Much shorter thanks to the Angular team and contributors. :)
This is also the answer of Maximilian Hoffmann. Apparently that commit made it into 1.1.5.
You can use $routeProvider resolve property to delay route change until data is loaded.
angular.module('app', ['ngRoute']).
config(['$routeProvider', function($routeProvider, EntitiesCtrlResolve, EntityCtrlResolve) {
$routeProvider.
when('/entities', {
templateUrl: 'entities.html',
controller: 'EntitiesCtrl',
resolve: EntitiesCtrlResolve
}).
when('/entity/:entityId', {
templateUrl: 'entity.html',
controller: 'EntityCtrl',
resolve: EntityCtrlResolve
}).
otherwise({redirectTo: '/entities'});
}]);
Notice that the resolve property is defined on route.
EntitiesCtrlResolve and EntityCtrlResolve is constant objects defined in same file as EntitiesCtrl and EntityCtrl controllers.
// EntitiesCtrl.js
angular.module('app').constant('EntitiesCtrlResolve', {
Entities: function(EntitiesService) {
return EntitiesService.getAll();
}
});
angular.module('app').controller('EntitiesCtrl', function(Entities) {
$scope.entities = Entities;
// some code..
});
// EntityCtrl.js
angular.module('app').constant('EntityCtrlResolve', {
Entity: function($route, EntitiesService) {
return EntitiesService.getById($route.current.params.projectId);
}
});
angular.module('app').controller('EntityCtrl', function(Entity) {
$scope.entity = Entity;
// some code..
});
I like darkporter's idea because it will be easy for a dev team new to AngularJS to understand and worked straight away.
I created this adaptation which uses 2 divs, one for loader bar and another for actual content displayed after data is loaded. Error handling would be done elsewhere.
Add a 'ready' flag to $scope:
$http({method: 'GET', url: '...'}).
success(function(data, status, headers, config) {
$scope.dataForView = data;
$scope.ready = true; // <-- set true after loaded
})
});
In html view:
<div ng-show="!ready">
<!-- Show loading graphic, e.g. Twitter Boostrap progress bar -->
<div class="progress progress-striped active">
<div class="bar" style="width: 100%;"></div>
</div>
</div>
<div ng-show="ready">
<!-- Real content goes here and will appear after loading -->
</div>
See also: Boostrap progress bar docs
I liked above answers and learned a lot from them but there is something that is missing in most of the above answers.
I was stuck in a similar scenario where I was resolving url with some data that is fetched in the first request from the server. Problem I faced was what if the promise is rejected.
I was using a custom provider which used to return a Promise which was resolved by the resolve of $routeProvider at the time of config phase.
What I want to stress here is the concept of when it does something like this.
It sees the url in url bar and then respective when block in called controller and view is referred so far so good.
Lets say I have following config phase code.
App.when('/', {
templateUrl: '/assets/campaigns/index.html',
controller: 'CampaignListCtr',
resolve : {
Auth : function(){
return AuthServiceProvider.auth('campaign');
}
}
})
// Default route
.otherwise({
redirectTo: '/segments'
});
On root url in browser first block of run get called otherwise otherwise gets called.
Let's imagine a scenario I hit rootUrl in address bar AuthServicePrivider.auth() function gets called.
Lets say Promise returned is in reject state what then???
Nothing gets rendered at all.
Otherwise block will not get executed as it is for any url which is not defined in the config block and is unknown to angularJs config phase.
We will have to handle the event that gets fired when this promise is not resolved. On failure $routeChangeErorr gets fired on $rootScope.
It can be captured as shown in code below.
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
// Use params in redirection logic.
// event is the routeChangeEvent
// current is the current url
// previous is the previous url
$location.path($rootScope.rootPath);
});
IMO It's generally a good idea to put event tracking code in run block of application. This code run just after the config phase of the application.
App.run(['$routeParams', '$rootScope', '$location', function($routeParams, $rootScope, $location){
$rootScope.rootPath = "my custom path";
// Event to listen to all the routeChangeErrors raised
// by the resolve in config part of application
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
// I am redirecting to rootPath I have set above.
$location.path($rootScope.rootPath);
});
}]);
This way we can handle promise failure at the time of config phase.
I have had a complex multi-level sliding panel interface, with disabled screen layer. Creating directive on disable screen layer that would create click event to execute the state like
$state.go('account.stream.social.view');
were producing a flicking effect. history.back() instead of it worked ok, however its not always back in history in my case. SO what I find out is that if I simply create attribute href on my disable screen instead of state.go , worked like a charm.
<a class="disable-screen" back></a>
Directive 'back'
app.directive('back', [ '$rootScope', function($rootScope) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
element.attr('href', $rootScope.previousState.replace(/\./gi, '/'));
}
};
} ]);
app.js I just save previous state
app.run(function($rootScope, $state) {
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
$rootScope.previousState = fromState.name;
$rootScope.currentState = toState.name;
});
});
One possible solution might be to use the ng-cloak directive with the element where we are using the models e.g.
<div ng-cloak="">
Value in myModel is: {{myModel}}
</div>
I think this one takes least effort.

Categories

Resources