$rootScope property is not updating on route change in angularjs - angularjs

In my index.html <head> I have the following element:
<link
rel="alternate"
type="application/rss+xml"
title="{{feedJobsTagName}} jobs feed"
href="{{feedJobTagsUrl}}"
ng-if="feedJobTagsUrl">
When I change routes to a new tag, I have a resolver that fetches the tag and updates the two properties on the root scope (TagShowCtrl):
$rootScope.feedJobTagsUrl = '/feeds/jobs/'+tag.type+'s/'+tag.nameLower;
$rootScope.feedJobsTagName = tag.name;
If I refresh the page, I get the correct tag name, however if I just change routes by following links to a new tag name it seems to cache the first name and not update it with the new one:
.when('/tags/:slug/:id', {
templateUrl: 'partials/tags/show',
controller: 'TagShowCtrl',
resolve: {
tag: ['$route', 'Tag', function($route, Tag){
return Tag.get({ id: $route.current.params.id}).$promise;
}]
}
})

To make your changes apply to the view, you have to bind the $routeChangeSuccess event on the $scope of your controller.
$scope.$on('$routeChangeSuccess', function(next, current) {
$rootScope.feedJobTagName = "new value";
});
Please mind that it won't work correctly on $rootScope inside your controller.
Although your controller's scope is updated, the changes won't apply to the view, because your route's resolve object returns a promise. (See what happens exactly here)
As for all asynchronous tasks, you either need to $apply the changes to the digest loop or use the events provided by Angular - which is in your case $routeChangeSuccess.
Mind that the controller is instantiated after the asyncronous get promise has been fulfilled.

Related

What is the best way to resolve data before loading view and controller in Angular JS?

I've a page with Navbar and Sidebar that remains common across most of the pages and hence I load all data related to LoggedInUser in NavbarController.
This data ($scope.loggedInUser) is used in other Controllers (child controller) as well. Since call to get User data is async, many times, child controller tries to access data before it is returned from the server.
What is the best way to ensure, all promises of parent controller are resolved before child controller starts it's work?
You should use resolve from $stateProvider. This function ensures that all the promises are resolved before loading a new state.
A resolve is a property you can attach to a route in both ngRoute and the more robust UI router. A resolve contains one or more promises that must resolve successfully before the route will change. This means you can wait for data to become available before showing a view, and simplify the initialization of the model inside a controller because the initial data is given to the controller instead of the controller needing to go out and fetch the data.
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
in controller
app.controller("newsController", function (message) {
$scope.message = message;
});
Hope this will help you

jasmine $timeout triggers ui-router resolve elsewhere

I have a jasmine test for a directive that flushes a $timeout in order to test certain code within the directive. At another point in the application, I have a ui-router handling navigation with the following resolve
$stateProvider
.state('home', {
url: '/home',
component: 'home',
resolve: {
myservice: "myservice",
myItems: function(myservice) {
return myservice.resource.query().$promise;
}
}
})
The directive is in the same angular module as this home component (the directive is nested inside of the component).
Whenever the test runs for the directive and $timeout.flush() is called, I get an 'error: unexpected request' with the url of the query. This is happening even though the directive and that component shouldn't be associated. A quick fix is to just add in an $httpBackend to fix it, but this shouldn't be happening, and as more things are added I think the problem will be replicated.
I have confirmed that a) it is that resolve function that is triggering the request, b) I have checked the $state in the directive tests but it returns an empty string so I don't think its trying to set up the home state c) There is no code in the directive test referencing this component or anything like that
If it helps, the setup code in the test is as follows:
scope = $rootScope.$new();
var element = $compile('..../*doesnt include home component at all*/......')(scope);
scope.$digest();
$timeout.flush();
Okay..... after reading the following https://github.com/angular-ui/ui-router/issues/212 for way too long, the appropriate solution is:
beforeEach(module(function($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}))
The aforementioned resolve that was the problem is called in the state that $urlRouterProvider.otherwise() points to. By deferring that we prevent the home state from being realized and that resolve from triggering the resource call

Angular UI Router Nested State resolve in child states

In an angular app I'm working on, I'd like there to be an abstract parent state which must resolve certain dependencies for all of its children's states. Specifically, I'd like all states requiring an authenticated user to inherit that dependency from some authroot state.
I'm running into issues having the parent dependency not always being re-resolved. Ideally, I'd like to have the parent state check that the user is still logged in for any child state automatically. In the docs, it says
Child states will inherit resolved dependencies from parent state(s), which they can overwrite.
I'm finding that the parent dependency is only being re-resolved if I enter any child state from a state outside the parent, but not if moving between sibling states.
In this example, if you move between states authroot.testA and authroot.testB, the GetUser method is only called once. When you move to the other state and back, it will get run again.
I am able to put the User dependency on each of the child states to ensure the method is called every time you enter any of those states, but that seems to defeat the purpose of the inherited dependency.
Am I understanding the docs incorrectly? Is there a way to force the parent state to re-resolve its dependencies even when the state changes between siblings?
jsfiddle
<!doctype html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.0/angular-ui-router.min.js"></script>
<script>
(function(ng) {
var app = ng.module("Test", ["ui.router"]);
app.config(["$stateProvider", "$urlRouterProvider", function(sp, urp) {
urp.otherwise("/testA");
sp.state("authroot", {
abstract: true,
url: "",
template: "<div ui-view></div>",
resolve: {User: ["UserService", function(UserService) {
console.log("Resolving dependency...");
return UserService.GetUser();
}]}
});
sp.state("authroot.testA", {
url: "/testA",
template: "<h1>Test A {{User|json}}</h1>",
controller: "TestCtrl"
});
sp.state("authroot.testB", {
url: "/testB",
template: "<h1>Test B {{User|json}}</h1>",
controller: "TestCtrl"
});
sp.state("other", {
url: "/other",
template: "<h1>Other</h1>",
});
}]);
app.controller("TestCtrl", ["$scope", "User", function($scope, User) {$scope.User = User;}]);
app.factory("UserService", ["$q", "$timeout", function($q, $timeout) {
function GetUser() {
console.log("Getting User information from server...");
var d = $q.defer();
$timeout(function(){
console.log("Got User info.");
d.resolve({UserName:"JohnDoe1", OtherData: "asdf"});
}, 500);
return d.promise;
};
return {
GetUser: GetUser
};
}]);
})(window.angular);
</script>
</head>
<body ng-app="Test">
<a ui-sref="authroot.testA">Goto A</a>
<a ui-sref="authroot.testB">Goto B</a>
<a ui-sref="other">Goto Other</a>
<div ui-view>Loading...</div>
</body>
</html>
The way I find the ui-router exceptional, is in the behaviour you've just described.
Let's think about some entity, e.g. Contact. So it would be nice to have a right side showing us the list of Contacts, the left part the detail. Please check the The basics of using ui-router with AngularJS for quick overvie about the layout. A cite:
ui-router fully embraces the state-machine nature of a routing system.
It allows you to define states, and transition your application to
those states. The real win is that it allows you to decouple nested
states, and do some very complicated layouts in an elegant way.
You need to think about your routing a bit differently, but once you
get your head around the state-based approach, I think you will like
it.
Ok, why that all?
Because we can have a state Contact representing a List. Say a fixed list from perspective of the detail. (Skip list paging filtering now) We can click on the list and get moved to a state Contact.Detail(ID), to see just selected item. And then select another contact/item.
Here the advantage of nested states comes:
The List (the state Contact) is not reloaded. While the child state Contact.Detail is.
That should explain why the "weird" behaviour should be treated as correct.
To answer your question, how to handle user state. I would use some very top sibling of a route state, with its separated view and controller and some lifecycle arround... triggered in some cycles
Real Short answer is use:
$rootScope.$on("$stateChangeStart")
to listen for any scope changes and do appropriate actions.
Longer answer is, check out the fiddle:
http://jsfiddle.net/jasallen/SZGjN/1/
Note that I've used app.run which means i'm resolving the user for every state change. If you want to limit it to state changes while authRoot is in the parentage, put the check for $stateChangeStart in the authRoot controller.

Wait until scope variable is loaded before using it in the view in angular.js

I've seen this and this but it seems like there might be a simpler way.
In my view I have several menu options that are controlled through permissioning - i.e., not everyone can see a "Dashboard" view. So in my menu option in my view I have something like the following:
<li ng-show="validatePermission('Dashboard')">Dashboard</li>
In my controller I have a validatePermission method defined where it is looking at the permissions of the current user. For example:
$scope.validatePermission = function(objectName) {
if $scope.allPermissions......
Also in my controller I'm loading those permissions via an $http call:
$http.get('permissions/' + userid + '.json').success(function(data) {
$scope.allPermissions = data;....
The issue is that $scope.allPermissions doesn't get loaded before the view makes the call to validatePermission. How can I wait for allPermissions to be loaded before the view renders?
You ask:
How can I wait for allPermissions to be loaded before the view renders?
To prevent the entire view from rendering, you must use resolve. You don't have to use the promise library though, since $http returns a promise:
var app = angular.module('app');
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'template.html',
controller : 'MyCtrl',
resolve : MyCtrl.resolve
});
});
function MyCtrl ($scope, myHttpResponse) {
// controller logic
}
MyCtrl.resolve = {
myHttpResponse : function($http) {
return $http({
method: 'GET',
url: 'http://example.com'
})
.success(function(data, status) {
// Probably no need to do anything here.
})
.error(function(data, status){
// Maybe add an error message to a service here.
// In this case your $http promise was rejected automatically and the view won't render.
});
}
}
But if you simply want to hide the dashboard <li>, then do as Joe Gauterin suggested. Here's a very simple example plunkr if you need it.
Have the validatedPermission function return false when allPermissions hasn't been loaded. That way the element with your ng-show won't be displayed until allPermissions has been loaded.
Alternatively, put an ng-show="allPermissions" on the enclosing <ul> or <ol>.
You can also specify on your routecontroller a resolve object that will wait for that object to resolve prior to rendering that route.
From the angular docs: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $routeChangeSuccess event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller.
A google group reference as well: https://groups.google.com/forum/#!topic/angular/QtO8QoxSjYw
I encountered an similar situation, you might also want to take a quick look at
http://docs.angularjs.org/api/ng/directive/ngCloak
if you're still seeing a "flicker" effect.
As per the angularjs documentation:
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
Wrapping the code in ng-if fixed the issue for me:
<div ng-if="dependentObject">
<!-- code for dependentObject goes here -->
</div>

angularJS: ui-router equivalent to $location.search

I'm using the following to do paging in my datagrid:
$location.search('page', page);
where page is the current page number.
Then I listen to the following event:
$scope.$on('$routeUpdate', function(next, current) {
$scope.currentPage = $routeParams.page ? Number($routeParams.page) : 1;
$scope.search();
});
That triggers a call to a search() method on my scope after updating the currentPage that is in the URL.
How would I translate that to states with ui-router ? The $routeUpdate is no longer triggered since I'm using a state manager instead of routes.
My route is now defined in the state provider as such:
$stateProvider
.state('mandats', {
url: '/domiciliations/mandats',
templateUrl: 'domiciliations/views/mandats.html',
controller: 'mandatsCtrl'
})
I ended up listening to the $locationChangeSuccess event instead and that solved it.
I would try to use the controller (mandatsCtrl in your example) to put that logic. the view controller will be called with every parameter change, and you could inject the $stateParams into it to get the page #
other similar option to explore is the view controllers which is what I've used at some point
last, since $stateParams is injectable, you might want to inject it to your custom-built directive and use it there straight

Resources