AngularJS UI-Router scoping issues - angularjs

I've got what I think is a scoping issue with angular ui-router, but I'm not quite sure.
angular.module('Meeting').controller('MeetingController', ['$scope', 'signalRService', '$stateParams', function ($scope, signalRService, $stateParams) {
$scope.setMeetings = function(meetings) {
$scope.meetings = meetings.map(function(meeting) {
return {
id: meeting.CategoryId,
name: meeting.MeetingName
};
});
$scope.$apply();
};
$scope.connectToSignalR = function () {
signalRService.connect();
signalRService.registerAddMeetingsClientMethod($scope.addMeetings);
};
$scope.requestMeetings = function() {
signalRService.requestMeetings($stateParams.departmentId);
};
$scope.connectToSignalR();
$scope.eventId = $stateParams.eventId;
}]);
Basically, my module is injected with a signalR service, and I register a callback on it to set meetings. I have a button on my view to tell the signalR service to fetch the meetings, which then calls the callback I just registered.
Now, all this works fine with ui-router, but only the first time the page is loaded. Here's my routing config:
angular.module('Meeting')
.config(
['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state("meeting",
{
url: "/meeting/:departmentId/",
templateUrl: '/home/meetingPage',
controller: "MeetingController"
})
.state("meeting.members",
{
url: "/members/",
templateUrl: "/home/memberspage",
controller: "MeetingMemberController"
})
.state("meeting.edit", {
url: "/meetingedit",
views: {
'meetingtime': {
templateUrl: '/home/timepage',
controller: 'MeetingTimeController'
},
'location': {
templateUrl: '/home/locationpage',
controller: 'MeetingLocationController'
}
}
});
}]);
When I load up a meeting state (i.e. mysite/meeting/3), all the signalR methods are called, the meeting model in the MeetingController is populated, and the data appears in the view.
When I navigate to another state (i.e. mysite/meeting/4), the signalR methods are still called, and the meeting model is populated, but then just disappears. If I manually refresh the page with F5, it starts to work again, but navigating to a different page stops everything working.
I'm thinking it's a scoping issue, because when I navigate to a different page, the meetings object is still populated from the previous page.

The way I was registering callbacks with a singleton signalR service was getting really cumbersome, and doesn't play well with ui-router, as I found out.
I switched to using promises, and everything works so much more elegantly. Basically, I have a method on my signalR hub that returns the object I want:
public List<Meeting> GetMeetingsForMember(int memberId)
{
return _meetingRepository.GetAllUpcomingMeetingsForMember(int memberId);
}
Then, in my controller, I create a promise, and pass it to my signalR service for resolution:
var deferred = $q.defer();
deferred.promise.then(
function (meetings) {
setMeetings(meetings);
}
);
signalRService.getMeetingsForMember(memberId, deferred);
The getMeetingsForMember method on my signalR service accepts the promise and resolves it:
getMeetingsForMember = function (memberId, deferred) {
deferred.resolve(signalRService.hub.server.getMeetingsForMember(memberId));
}

Related

AngularJS UI-Router Access $stateParams from State's data object

Given the following state in ui-router:
.state('some.state', {
url: '/some/:viewType',
templateUrl: 'myTemplate.html',
controller: 'SomeStateController',
controllerAs: 'vm',
data: {
authorizedFor: [SOME_ROLE]
}
}
I'm trying to use the "data" object for a state to help control access to authorized states. Separately, I handle the $stateChangeStart event to look at data.authorizedFor and act accordingly.
The problem, though, is that the list of authorized roles might change based on the value of :viewType. I thought I could let data:{} be a function, inject $stateParams, and handle the logic there...but that won't do.
So, I tried using the params object instead, but at the $stateChangeStart time, the :viewType is not yet accessible from $state.params or $stateParams.
Stepping through in dev tools, I noticed that $state.transitionTo.arguments is populated, but it seems awfully hacky to go that route.
params: {
authorizedFor: function($state) {
console.log($state.transitionTo.arguments[1].viewType); // has value I need
}
}
Any suggestions?
My suggestion is to use resolve to provide your controller with content or data that is custom to the state. resolve is 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 $stateChangeSuccess event is fired.
for example:
$stateProvider
.state('profile', {
url: '/profile',
templateUrl: 'profile.html',
resolve:{
'ProfileService': function(ProfileService){
return ProfileService.promise_skillRecommendation_mock;
}
}
})
The profileService code:
var app = angular.module('app').service("ProfileService", function($http){
var myData = null;
var promise_skillRecommendation_mock =
$http.get('Mock/skillRecommendation-mock.json')
.success(function(data){
myData = data;
});
return{
promise_skillRecommendation_mock: promise_skillRecommendation_mock,
get_skillRecommendation: function(){
return myData;
}
};
});
and the controller code which will use this service is:
angular.module('app').controller('ProfileController', function($scope, $http, ProfileService){
$scope.skillRecommendation = ProfileService.get_skillRecommendation();
The object in resolve below must be resolved (via deferred.resolve() if they are a promise) before the controller is instantiated. Notice how each resolve object is injected as a parameter into the controller.
by using this code, the page will be displayed only after that the promise will be resolved.
for more info please view this page: https://github.com/angular-ui/ui-router/wiki

Angular routing based on a condition

I have been trying to find a way to implement angular routing which routes based on a condition rather than which link the user clicks
route1 -> view1
route2 -> view2
To add complication, the condition itself is evaluated on the result of a service. I am retrieving a set of items for my shopping cart checkout app. I need the shopping cart to route to view1 if the number of items is > 0. And a different route if it is 0.
if (itemListService.getItems().length > 0) --> route1
else --> route2
And because of this, the complication which arises is that the routing has to wait until the service result is evaluated before loading the corresponding view. In other words, the promise of the service has to be resolved.
I found the following post where someone suggested using 'resolve' configuration property within the routeprovider, but that only solves the issue of waiting for the service response. I'm not sure if it satisfies checking a condition based on the service response.
Redirecting to a certain route based on condition
Can someone help me with this?
Would ui-router be able to implement this better than routeProvider?
Yes,It can be easily done using ui-router.For example,create four states: the dashboard state,which links you to the resolver state.On entering your resolver state based on your service's data you can redirect to appropriate state.This is done with the help of OnEnter hook of the resolver state.
Demo Plunker
In the plunker,
Click on parent in dashboard
It will navigate to state child1 until the items in service has values
When all the items is popped out it will navigate to state child2
Code:
var myapp = angular.module('myapp', ["ui.router"]);
myapp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard")
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: 'dashboard.html'
}).state('parent', {
url: '/parent',
resolve: {
stateToGo: function(service) {
if (service.getItems().length > 0) {
return 'child1';
} else {
return 'child2';
}
}
},
onEnter: function(stateToGo, $state) {
$state.go(stateToGo);
}
})
.state('child1', {
templateUrl: 'child1.html',
resolve: {
service: function(service) {
return service;
}
},
controller: function($scope, service) {
console.log(service.getItems());
service.removeItem();
console.log(service.getItems());
}
}).state('child2', {
templateUrl: 'child2.html'
});
});
myapp.factory('service', [function() {
var items = [1, 2];
return {
getItems: function() {
return items;
},
removeItem: function() {
items.pop();
}
};
}]);

Refreshing Resolve Data - Ui Router

In ionic I'm resolving my data with the Ui-Router's resolve functionality before the controller is initialized. As of now I don't have to $inject my EventService into the Controller. The EventService's getEvents() method resolves the data before the controller is initialized. Everything works correctly this way, but now i'm trying to implement the Ion Refresher. I could easily refresh my $scope.events array within the controller, bloating the controller itself, because I would have to $inject the EventService into the controller, and that also means that every controller that uses the same data will have to contain logic to handle a refresh. What is the best way refresh the data outside of the controller or is that the best way?
Events State Definition and data resolution
.state('tab.events', {
url: '/events',
views: {
'tab-event': {
templateUrl: 'views/events.html',
controller: 'EventsController',
resolve: {
events: function (EventService) {
return EventService.getEvents(); //resolves data before ctrl initialized
}
}
}
}
})
Events Controller
(function() {
'use strict'
angular
.module('app.events')
.controller('EventsController', EventsController);
EventsController.$inject = ['$scope','events'];
function EventsController ($scope,events) {
$scope.events = events;
}
}
)();
Bloated Events Controller - Example
(function() {
'use strict'
angular
.module('app.events')
.controller('EventsController', EventsController);
EventsController.$inject = ['$scope','events','EventsService'];
function EventsController ($scope,events,EventsService) {
$scope.events = events;
$scope.refresh = refresh;
function refresh () {
clearCache(); //pretend method
EventsService.getEvents()
.then(function (events) {
$scope.events = events;
$scope.$broadcast('scroll.refreshComplete');
})
}
}
}
)();
Rather than bloating the controller can I refresh this data another way?
call $state.reload() which is an alias for:
$state.transitionTo($state.current, $stateParams, {
reload: true, inherit: false, notify: true
});
This will cause all your states to be "re-entered" which fetches the resolves and re-initializes the controllers.
I wish a hard refresh, which is basically what a $state.reload() does wasn't the answer. I too have this issue and would rather be able to call some method that just forces all the resolved data objects to rerun. The reload causes a page refresh, which causes nasty UI artifacts.

AngularJS watch value within service

I'm currently developing an AngularJS web application.
I have a primary view (Index), child view (Dashboard) and grandchild view (Raiding The Rails).
http://localhost:4000/#/dashboard/raiding-the-rails/1
Within the grandchild view (Raiding The Rails) I am displaying dress information relevant to the state ID /1, each dress has a specified state ID e.g /1,/2,/3.
I have a controller/service sending the state ID to a console.log (within the parent) and when viewing raiding-the-rails/1 the console.log displays {stateID: "1"}, If I change the URL to raiding-the-rails/4 the console.log doesn't update unless I refresh the page.
Also, When I completely refresh the browser the console.log spits out three objects instead of one?
I've reviewed many sites and have tried and tried again trying to figure this out, I even tried setting up a Watch service but this failed massively.
If anyone could help me out I would be highly grateful!Thank you.
App:
(function(angular, undefined){
"use strict";
var am = angular.module('virtual-fitting', ['ui.router']);
am.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: '../views/dashboard.html'
})
.state('dashboard.raidingtherails', {
url: '/raiding-the-rails',
templateUrl: '../views/dashboard.raiding-the-rails.html'
})
.state('dashboard.raidingtherails.dress', {
url: '/:id',
templateUrl: '../views/dashboard.raiding-the-rails.dress.html'
});
$urlRouterProvider.otherwise('/');
});
})(angular);
Service:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.factory('raidingService', function () {
var raidingService = {
stateID: null
};
return raidingService;
});
})(angular);
Parent Controller:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.controller('dashboardCtrl', function(raidingService) {
console.log(raidingService);
});
})(angular);
Child Controller:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.controller('raidingtherailsCtrl', function($state, $stateParams, raidingService) {
var self = this;
raidingService.stateID = $stateParams.id;
});
})(angular);
I assume it's in the parent controller that you want to watch your service?
If so, you could watch a function, like so:
$scope.$watch(function() {
return raidingService.stateId;
}, function(value) {
console.log(value);
}, true);
That should work.
MVC pattern used in any framework:
You creating Service with .get() and .set() methods. Set method is common to be used in any place you desire to operate model value and allows you to create one point that will handle changes to your model. The final move in .set() method is .$boradcast() notifying whole application about changes to your model.
Pros:
one access point to value
application is know about any changes
no need to write $watch with watching collections (holly-molly)
having access point to changes with '$on' in your controllers $scope
Cons:
easy to forget to use .set() method instead of simple assignment

Restrict access to route with routeprovider unless variable as been set

I'm in the process of learning AngularJS, working on a more in-depth ToDo app. I'm having an issue with trying to limit access to a url or "route" using angular.
When you hit my dev url on my machine (todo.ang) it brings you to todo.ang/#/home, on this view you see the categories which have todos associated to each. EG (category = cat, cat has a todo of "feed", and "play"), when you click a category I'm calling the $scope.goToCategory function (seen in my JS fiddle) which sets a variable for my firebase ref then redirects you too /#/todo. This is working correctly.
My problem is, I don't want the user to be able to access /#/todo if the todoRef variable is still undefined. But it seems like even after $scope.goToCategory is called and todoRef is set to a firebase URL, the routerprovider never gets recalled to know that todoRef has been set to a different value so it always forces you back to /#/home.
code:
var todoRef = undefined;
if (todoRef !== undefined) {
$routeProvider.when('/todo', {
templateUrl: 'views/todo.html',
controller: 'TodoCtrl'
});
}
$scope.goToCategory = function(catId) {
test = catId;
todoRef = new Firebase("URL HERE");
$location.path('/todo');
}
I didn't include the entire file of code but if thats necessary, I can do that as well.
JSFiddle
All routes are only being set during the config phase.
what happens in your code is that 'todo' route is ignored during the initiation of ngRoute.
What you should do is to setup the route but have a resolve like so:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/todo', {
templateUrl: 'views/todo.html',
controller: 'TodoCtrl',
resolve: {
todoRef: ['$q', function($q) {
return todoRef ? todoRef : $q.reject('no ref');
}]
}
});
}]);
If 'todoRef' is undefined the route is rejected.
Also you should consider moving 'todoRef' into a service and not on global scope.
You can also listen for route errors and for example redirect to home route:
app.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeError', function() {
$location.path('/home');
});
}]);

Resources