Is it possible to autoscroll in AngularUI Router without changing states? - angularjs

I'm using AngularUI Router with Bootstrap. I have two views within the same state, and want to scroll to the second view when a button is clicked. I don't need to do any scrolling when the initial state and views load. I want the page to scroll to #start when the "Add More" button is clicked. I've attempted to use $anchorScroll to accomplish this, but am not having any luck.
Any suggestions on a good way to accomplish this?
HTML:
<!-- index.html -->
<div ui-view="list"></div>
<div ui-view="selectionlist"></div>
<!-- list.html -->
<div id="scrollArea"><a ng-click="gotoBottom()" class="btn btn-danger btn-lg" role="button">Add More</a>
<!-- selectionlist.html -->
<div class="row" id="start"></div>
Javascript for Controller:
myApp.controller('SelectionListCtrl', function (Selections, $location, $anchorScroll) {
var selectionList = this;
selectionList.selections = Selections;
this.selectedServices = Selections;
selectionList.gotoBottom = function() {
$location.hash('start');
$anchorScroll();
};
});
Javascript for Routes:
myApp.config(function ($stateProvider, $urlRouterProvider, $uiViewScrollProvider) {
$uiViewScrollProvider.useAnchorScroll();
$urlRouterProvider.otherwise('/');
$stateProvider
.state('selection', {
url: '/selection',
views: {
'list': {
templateUrl: 'views/list.html',
controller: 'ProjectListCtrl as projectList'
},
'selectionlist': {
templateUrl: 'views/selectionlist.html',
controller: 'SelectionListCtrl as selectionList'
}
}
})

Yes, it is possible to autoscroll in AngularUI Router without changing states.
As mentionned previously in my comment, you need to call the scrolling function with an ng-click="gotoBottom()" instead of an ng-click="gotoSection()"
Also, the function definition gotoBottom() must be in the ProjectListCtrl, not in the SelectionListCtrl. This is because the call gotoBottom() happens in the list view:
'list': {
templateUrl: 'list.html',
controller: 'ProjectListCtrl as projectList'
}
As gotoBottom() is called from the list.html view, the corresponding controller in $stateProvider must be the one where you define gotoBottom().
Here are two working ways of accomplishing your goal:
1. You inject $scope inside the controller ProjectListCtrl. You then define the $scope.gotoBottom function in the same controller.
The scope is the glue between the controller and the view. If you want to call a controller function from your view, you need to define the controller function with $scope
app.controller('ProjectListCtrl', function ($location, $anchorScroll,$scope) {
var selectionList = this;
//selectionList.selections = Selections;
//this.selectedServices = Selections;
$scope.gotoBottom = function() {
console.log("go to bottom");
$location.hash('start');
$anchorScroll();
};
});
In the list.html view, you can then call the $scope.gotoBottom function just with gotoBottom(): ng-click="gotoBottom()"
2. Or you use the controller as notation, as when you wrote ProjectListCtrl as projectList.
this.gotoBottomWithoutScope = function(){
$location.hash('start');
$anchorScroll();
};
With this notation, you write this.gotoBottomWithoutScope in the ProjectListCtrl. But in the view, you must refer to it as projectList.gotoBottomWithoutScope().
Please find a working plunker
To learn more about the this and $scope notations, please read this:
Angular: Should I use this or $scope
this: AngularJS: "Controller as" or "$scope"?
and this: Digging into Angular’s “Controller as” syntax

Related

How can I check the AngularJS UI-Router state from within another controller?

I'm using Angular's UI-Router as shown below to do URL-routing in my web-app as shown below:
$stateProvider.state('myState1', {
url: '/state1',
templateUrl: 'state1.html',
controller: 'MyState1Ctrl'
});
$stateProvider.state('myState2', {
url: '/state2',
templateUrl: 'state2.html',
controller: 'MyState2Ctrl'
});
Within each of the two template files state1.html, state2.html, I have my navigation bar directive: <myapp-navigation-bar></myapp-navigation-bar>. Here is the navigation bar's controller:
raptorApp.controller('NavigationCtrl', function($scope){
var self = this;
$scope.$watch(function() {return "something";}, function (objVal) {
console.log('Hello from NavigationCtrl!');
},true);
});
But I want the navigation bar to behave differently based on weather it is in myState1 or myState2. How can I detect from within NavigationCtrl which state it is in?
we can always place anything into $scope and watch it. Even the $state service and its current.name. Something like (just a draft)
raptorApp.controller('NavigationCtrl', function($scope, $state){
var self = this;
$scope.state = $state;
$scope.$watch("state.current.name", function (objVal) {
console.log('current state name changed');
},true);
});
NOTE: never forget to remove such watch on destroy
But with UI-Router.. there are better ways. Much better ways:
named views
multiple views
So we can have a parent state, with a view called 'myapp-navigation-bar' and that could be filled by each state... with a different implementation. See this for detailed how to and example
Nested states or views for layout with leftbar in ui-router?
How do I load UI-router ui-view templates that are nested 3 states deep?

Angular, ng-href make the page to refresh

I am using ng-route in angularjs to switch beteen views , I made it to work, sample code below:
Html:
Mappings
New Products
angularjs
.config(function ($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "/MbfProduct/Main"
})
.when("/Mappings", {
templateUrl: "/Mappings"
})
.when("/Products", {
templateUrl: "/Products"
})
})
So everything is OK just I had to add the "#" in the ng-href attribute so the page doesn't get refreshed.
So my question how can I have the result, I mean no page refresh, without having the '#' in the href ?
you can write a function in your controller that changes the view. You have to use $location provider to switch between views. There is a method named path that does the switching.
Something like this.
app.controller("TestCtrl", function($scope, $location){
$scope.changeView = function(){
$location.path("/Mappings");
}
})
and call changeView function on ng-click of anchor tag and just remove the ng-href tag altogether. If that doesn't work you can use ng-href="javascript:void(0)" as well to give a void link to anchor tag.

How start route in controller Angular JS?

I need that code will be called in controller ChatController, not in global application:
.config(function($routeProvider){
$routeProvider.when("/chat/dialog/:id",
{
templateUrl: "/template/chat/active_dialog.html",
controller: "ChatController"
}
);
})
How I can do it?
I tried (template is not loaded in div):
Angular JS:
$scope.selectDialog = function (id, event){
$scope.template = '/template/chat/active_dialog.html';
});
HTML:
<div ng-include src="{{template}}"></div>
I agree with #Nano,all the providers that are used angular are injected and used in
.config,you directly cannot use it in your controller.

AngularJs with routes and anchor tags, working on second click

I have a simple view representing a simple menu which should be using anchor behavior. On the same page there's a bunch of H2 tags with id that the links should scroll to.
I'm using the $anchorScroll and $location.
THE ISSUE: The first time I click a link, I can see that the route is updated, e.g.:
http://localhost:60002/#!/docs/view/somedoc#someResourceId
But it triggers a route, the SECOND time I click it, it behaves as expected.
UPDATE: It's not the anchorScroll() did it manually using element.scrollIntoView(true) same behavior. If I don't use $location.hash it works, but then I loose the possibility of linking to anchors.
Any ideas?
VIEW:
<div ng-controller="DocsMenuCtrl">
<ul ng-repeat="menuItem in menuItems">
<li><a ng-click="foo(menuItem.resourceId)">{{menuItem.title}}</a></li>
</ul>
</div>
...
...
<h2 id="...">Test</h2>
...
CONTROLLER:
module.controller('DocsMenuCtrl', ['$scope', '$http', '$location', '$anchorScroll', 'session', function ($scope, $http, $location, $anchorScroll, session) {
$scope.foo = function (resourceId) {
$location.hash(resourceId);
$anchorScroll();
};
$http.get('/api/menu/').success(function (d) {
$scope.menuItems = d;
}).error(function () {
session.logger.log(arguments);
});
}]);
ROUTEPROVIDER CONFIG etc
$locationProvider.hashPrefix('!');
$routeProvider
.when('/default', {
templateUrl: 'clientviews/default',
controller: 'DefaultCtrl'
})
.when('/docs/view/:id', {
templateUrl: 'clientviews/docs',
controller: 'DocsCtrl'
})
.otherwise({
redirectTo: '/default'
});
$location does not reload the page even if it is used to change the url. See the "What does it not do?" section of this page: $location ngDoc.
As Ganonside said, the location service does not reload the url. Once you are certain that the url changes you can use the route service, specifically $route.reload() to trigger your routing.
The best solution I've found is here: https://github.com/angular/angular.js/issues/1699#issuecomment-22509845
Another option, if you don't use search params, is to tell the route provider not to reload on hash or search changes (unfortunately, it is one option for both).
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/group/:groupName', {
templateUrl: '/templates/groupView.html',
reloadOnSearch: false
}).otherwise({redirectTo: '/'});
}]);

use angular-ui-router with bootstrap $modal to create a multi-step wizard

The FAQ for ui-router has a section about integration with bootstrap $modals, but it doesn't mention anything about abstract views. I have 3 views under a single abstract view, so something like the following.
$stateProvider
.state('setup', {
url: '/setup',
templateUrl: 'initialSetup.html',
controller: 'InitialSetupCtrl',
'abstract': true
})
// markup for the static view is
<div class="wizard">
<div ui-view></div>
</div>
.state('setup.stepOne', {
url: '/stepOne',
controller: 'SetupStepOneCtrl',
onEnter: function($stateParams, $state, $modal) {
$modal.open{
backdrop: 'static',
templateUrl: 'setup.stepOne.html',
controller: 'SetupStepOneCtrl'
})
}
})
.state('setup.stepTwo', {
url: '/stepTwo',
controller: 'SetupStepTwoCtrl',
onEnter: function($stateParams, $state, $modal) {
$modal.open({
backdrop: 'static',
templateUrl: 'setup.stepTwo.html',
controller: 'SetupStepTwoCtrl'
})
}
})
.state('setup.stepThree', {
url: '/stepThree',
templateUrl: 'setup.stepThree.html',
controller: 'SetupStepThreeCtrl'
...
});
}]);
I've also tried to only add the onEnter block to the abstract state, and removed onEnter from each of the 3 child states. This actually seems to me like the right approach. The abstract state initializes and opens the $modal and the subsequent states should interpolate into , but when I tried this the ui-view container was empty.
I can think of some other hacky ways to workaround this but thought I'd ask to see if there's a canonical way of handling this.
Alternative way is to use ng-switch with ng-include combination inside $modal controller to dynamically load wizard step templates, that is if you don't mind sharing the same controller for all wizard steps:
<div ng-switch="currentStep.number">
<div ng-switch-when="1">
<ng-include src="'wizardModalStep1.html'"></ng-include>
</div>
<div ng-switch-when="2">
<ng-include src="'wizardModalStep2.html'"></ng-include>
</div>
<div ng-switch-when="3">
<ng-include src="'wizardModalStep3.html'"></ng-include>
</div>
</div>
Here is Plunker with working example: http://plnkr.co/edit/Og2U2fZSc3VECtPdnhS1?p=preview
Hope that helps someone !!
I used following approach to develop a wizard. this might be help for you.
I used states like below sample with parent property.
var home = {
name: 'home',
url: '/home',
controller: 'MainController',
templateUrl: '/html/main.html'
},
sampleWizard = {
name: 'sampleWizard',
url: '/sampleWizard',
controller: 'sampleWizardController',
templateUrl: '/html/sd/sample/sampleWizard.html'
},
sampleSectionOne = {
name: 'sampleSectionOne',
url: '/sampleSectionOne',
parent: sampleWizard,
controller: 'sampleSectionOneController',
templateUrl: '/html/sd/sample/sampleSectionOne.html'
},
sampleSectionTwo = {
name: 'sampleSectionTwo',
url: '/sampleSectionTwo',
parent: sampleWizard,
controller: 'sampleSectionTwoController',
templateUrl: '/html/sd/sample/sampleSectionTwo.html'
};
$stateProvider.state(home);
$stateProvider.state(sampleWizard);
$stateProvider.state(sampleSectionOne);
$stateProvider.state(sampleSectionTwo);
I'm not sure you want to fire the modal every single time you go to the next step.
I think all you have to do is create a modal view () then each step has a modal a templateUrl assigned to it.
each template should look like:
<div class="modal fade in" id="whatever" style="display:block">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body…</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<a ui-sref="next_page_route_id" type="button" class="btn btn-primary">Next</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="modal-backdrop fade in"></div>
On the last screen you can add a data-dismiss="modal" to the submit and you are done
I have dealt with similar scenario, where I had to create a wizard (which allows you to go through steps and finally hit on summary and save).
For this, I had created different views but with one controller.
Since controller scope dies after each re-routing, I had to save the scope of controller(Basically the model object associated with the wizard) in a service object for each routing in the wizard (Captured through location.path()) and load this object back from service object on load of controller.Something like below:-
// Saving data on routing
$scope.nextPage = function () {
service.ModelForWizard = $scope.ModelForWizard;
switch ($location.path()) {
case RouteOfPage1:
//Do some stuff
break;
case RouteOfPage2:
//Do some stuff
break;
default:
}
Service or factory is persisted throughout life time of user session and is ideal to hold user data.
One more thing which was helpful , was use of 'Resolve' in the routes.
It ensures that next page in not rendered until the required data(generally lookup data) is not loaded. Code of resolve is something like this:
.when('/RouteForWizardPage1', {
templateUrl: '/templates/ViewPage1Wizard.html',
caseInsensitiveMatch: true,
controller: 'wizardController',
resolve: {
wizardLookupDataPage1: function (Service) {
return service.getwizardModelLookupDataPage1().$promise;
}
},
})
I was running into the same thing.. What worked for me was emptying the url property of the first sub state. Hence for the first sub state, your code should look as follows:
.state('setup.stepOne', {
url: '',
controller: 'SetupStepOneCtrl',
onEnter: function($stateParams, $state, $modal) {
$modal.open{
backdrop: 'static',
templateUrl: 'setup.stepOne.html',
controller: 'SetupStepOneCtrl'
})
}
})
Also, incase you're not using the url property to call the other 3 sub states, and are calling them using the state name only, you don't necessarily need to mention a url property for them.
If you want to show a wizard in a modal dialog and have a separate state for each of the wizard's steps, you need to keep in mind that the modal dialog is rendered completely outside your view hierarchy. Therefore, you cannot expect any interaction between ui-router's view rendering mechanisms and the dialog contents.
A robust solution is to put the wizard contents manipulation logic onto the parent state scope
$scope.wizard = {
scope: $scope.$new(),
show: function (template) {
// open the $modal if not open yet
// purge scope using angular.copy()
// return $scope.wizard.scope
},
// private
open: function () {
$modal.open({
scope: $scope.wizard.scope,
// ...
});
}
};
and then manually show the appropriate content from each of the sub-states and manipulate the wizard's scope as needed
$scope.wizard.show('someTemplate');
$scope.wizard.scope.user = ...;
When we faced this problem in our project, we decided after some discussion, that we didn't actually need separate ui-router states for the wizard steps. This allowed us to create a wizard directive used inside the dialog template to read wizard configuration from scope (using a format similar to ui-router state definition), provide methods to advance the wizard, and render appropriate view/controller inside the dialog.
To create multi-step wizards, you can use this module (I am the author): https://github.com/troch/angular-multi-step-form.
It allows you to create steps like views, and you can enable navigation (back / forward button, url with an URL search parameter). Examples are available here.

Resources