Preserving controller on path change - angularjs

Currently, I'm using URL like
/order
/order?id=123
/orderitem/123
/orderitem/123/id=456
where there's one controller for the first two cases and another one for the last two cases. Each controller shows an entity list and optionally (when an id is given) details of the selected entity.
I'd like to switch to a more logical schema like
/order
/order/123
/order/123/item
/order/123/item/456
without changing the controllers. According to this comment by misko, the controller instance doesn't get preserved on "path" (rather than just "search") changes.
Is there a way to avoid reinstantiating the controller?
I'm using no ui-router, would it help?

With ui-router, you can define which controller is used for which state:
var myApp = angular.module('app', ['ui.router']);
myApp.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('order', {
url: '/order/:orderId',
controller: 'OrderCtrl',
templateUrl: 'orders.html'
}).state('order.item', {
url: '/item/:itemId',
templateUrl: 'items.html',
controller: 'ItemsCtrl'
});
}]);
Then in your template files, you can link to each state (with an input id if needed):
<a ui-sref="order">/order</a>
<a ui-sref="order({id:1})">/order/1</a>
<a ui-sref="order({id:1}).item">/order/1/item</a>
<a ui-sref="order({id:1}).item({id:1})">/order/1/item/1</a>

Related

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.

Routing Without Slash in AngularJS

I am preparing a mobile page for a web site. When guest open a link, php will redirect him/her to mobile page with same url if him/her device is mobile.
There are some problem. The urls like these sitename.com/product-name-334 and sitename.com/category_name_c. They detect the page thanks of _c and product ids. But I dont know how can I provide it by angular?
I want to listen your suggests.
You should try ui-Router's $stateProvider.
StateProvider API Reference
First, you need to reference ui-Router in your index file.
Then, you can assign a state to a hyperlink by using data-ui-sref attribute.
<li>
<a data-ui-sref="product">
</a>
</li>
</ul>`
Then you need to assign a url, a template and a controller to the state.
angular.module('sspRouting', ['ui.router'])
.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('product',
{
url: "/productDetail/{productId}",
templateUrl: 'app/view/productTemplate.html',
controller: 'productController'
})
)}]);
Then inside the controller you can use $routeParams to get the id from the url and then build your html using the data of that specific product.
.controller('productController', function($scope, $http, $routeParams, productService) {
var productId = $routeParams.productId;
// your code here.
//example $scope.productDetail = productService.getProductDetail(productId);
});

How to hide exact url call of $routeProvider from user

Well, I've implemented angularJS and I am calling different views which is being handled by $routeProvider . The problem is that for eg:
$routeProvider.
when('/prodDetails/:prodID', {
templateUrl: 'templates/productDetails.html',
controller: 'ProductController'
}).
Now, if I click on:
<a ng-href="#prodDetails/{{ prod.id }}">View Product</a>
The browser url will show prod.id in the url tab. How can I manipulate url to hide sensitive info in it.
Use Angular UI Router. it will solve your problem without doing anything. you can then pass parameter using params object given by angular-ui-router.
You can use angular ui router
You can achieve that by doing:
$stateProvider.
state('productDetail', {
url: '/prodDetails',
templateUrl: 'templates/productDetails.html',
controller: 'ProductController',
params: {
productId: 'defaultId'
}
})
And in the html:
<a ui-sref="productDetail({productId: prod.id})">View Product</a>
You can have access to the productId from the controller with the service $stateParams.
EDIT:
Let's say you have a encodeId and a decodeId functions:
<a ui-sref="productDetail({productId: encodeId(prod.id)})">View Product</a>
and in the controller:
app.controller(function($stateParams) {
var id = decodeId($stateParams.productId);
});

Angular UI Router, Sub views get unset after a change of state

I debated a while on this but I got a Plunk that reproduce it.
I have a state "Contact" that get loaded by default. with $state.transitionTo
Inside that state I have some views, they all get loaded and everything work.
If I click to change the state to "Home" by default or by "ui-sref" and in the "Home" state/template I have ui-sref="contacts". When we click back to set the state to contacts it should work, but all the sub views are now not being called properly.
It seems that when ui-sref call the state this one behave differently that when it is loaded by default.
Why $state.transitionTo(''); seems to work differently than ui-sref.
<script>
var myapp = angular.module('myapp', ["ui.router"])
myapp.config(function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /
$urlRouterProvider.otherwise("/")
$stateProvider
.state('home', {
templateUrl: 'home.html',
controller: function($scope){
}
})
.state('contacts', {
templateUrl: 'contacts.html',
controller: function($scope){
}
})
.state('contacts.list', {
views:{
"":{
template: '<h1>Contact.List Working wi no Data defined.</h1>'
},
"stateSubView":{
template: '<h2>StateSubView Working</h2>'
},
"absolute#":{
template: '<h2>Absolute item</h2>'
}
}
});
});
myapp.controller('MainCtrl', function ($state) {
$state.transitionTo('contacts.list');
})
Q2:
Why is the Absolute tag that is under contact work when I add the view in the Index, but is not working when it is inside the contact.html file. Absolute reference work only with the Index and not if called everywhere?
"absolute#":{
template: '<h2>Absolute item</h2>'
}
I saw that in index.html you have an empty ui-view tag. What do you expect to go there? I think you can not do this. The router just doesn't know with which state (home or contacts) it should replace. Apparently it picks the second one (contacts). I'd suggest to put url: '/' in the home state and you'll see the difference.
This is for sure one issue.
Other than that:
You can't simply access views from contacts.list in contacts afaik.
The empty ui-view work as a wild card and can be use to switch across multiple route even if we have nested element. But if we have a nested view contact.list it can only be access if we put the whole path in ui-sref="contacts.list" because the list child of contact cannot be access only by using ui-sref="contacts"

AngularJS: Multiple views with routing without losing scope

I'm trying to implement a classic list/details UI. When clicking an item in the list, I want to display an edit form for that item while still displaying the list. I'm trying to work around Angular's 1-view-per-page limitation and decided to do it by having all URLs routed to the same controller/view. (Perhaps this is the root of my problem and I'm open to alternatives.)
Routing:
$routeProvider
.when('/list', { templateUrl: '/Partials/Users.html', controller: UserController })
.when('/edit/:UserId', { templateUrl: '/Partials/Users.html', controller: UserController })
.otherwise({ redirectTo: '/list' });
The view (/Partials/Users.html):
<!-- List of users -->
<div ng-repeat="user in Users">
Edit {{ user.Name }}
</div>
<!-- Edit form -->
<div>
{{ SelectedUser.Name }}
</div>
Controller:
function UserController($scope, $routeParams) {
// the model for the list
$scope.Users = GetUserListFromService();
// the model for the edit form
if ($routeParams.UserId != null)
$scope.SelectedUser = GetUserFromService($routeParams.UserId);
}
Problems:
When clicking an edit link, the controller is reinstantiated with a new scope, so I have to re-init the Users list. (In a more complex example I could have input from the user stored bound to the model and this would also get lost.) I'd prefer to persist the scope from the previous route.
I'd prefer to use a separate controller (or, as many other Angular developers have complained, the ability to have multiple displayed views!) but that leads to the same issue of losing scope.
Try using ui-router: http://github.com/angular-ui/ui-router.
They have nested views and easier state management than angular default routing :-)
Multiple views are not supported in core AngularJS. You can use this library for this purpose which supports any amount of nested views on the page, where each level is configured independently with its own controller and template:
http://angular-route-segment.com
It is much simpler to use than ui-router. Sample config may look like this:
$routeSegmentProvider.
when('/section1', 's1.home').
when('/section1/prefs', 's1.prefs').
when('/section1/:id', 's1.itemInfo.overview').
when('/section1/:id/edit', 's1.itemInfo.edit').
when('/section2', 's2').
segment('s1', {
templateUrl: 'templates/section1.html',
controller: MainCtrl}).
within().
segment('home', {
templateUrl: 'templates/section1/home.html'}).
segment('itemInfo', {
templateUrl: 'templates/section1/item.html',
controller: Section1ItemCtrl,
dependencies: ['id']}).
within().
segment('overview', {
templateUrl: 'templates/section1/item/overview.html'}).
segment('edit', {
templateUrl: 'templates/section1/item/edit.html'}).
up().
segment('prefs', {
templateUrl: 'templates/section1/prefs.html'}).
up().
segment('s2', {
templateUrl: 'templates/section2.html',
controller: MainCtrl});
I've found Angular Multi View to be a godsend for this scenario. It lets you preserve scope as the route changes and lets multiple controllers share the same route without nesting your views.
I recommend Angular Multi View if you have more than 2 views on your page. Otherwise, when using ui-router, nesting multiple views gets messy really fast.
I came up with the same problem and I personnaly don't like plugins when they aren't absolutely unavoidable. I just moved singleton part to a service.
In my case there are :id[/:mode] routes and I want to react different way if user changes just mode or id too. Thus, I have to know previous id.
So, there is a service with activate method which updates its state. And the scope is reinitialized every time with the following code.
module.controller('MyController', ['$scope', '$routeParams', 'navigator', function($scope, $routeParams, navigator) {
var id = null;
var mode = null;
if (typeof($routeParams.id)!='undefined')
{
id = $routeParams.id;
}
if (typeof($routeParams.mode)!='undefined')
{
mode = $routeParams.mode;
}
navigator.activate(id, mode);
$scope.items = navigator.items;
$scope.activeItem = navigator.activeItem;
$scope.modes = navigator.modes;
$scope.activeMode = navigator.activeMode;
}]);
In activate method I can compare id to the singleton's activeItem.id and react differently.

Resources