AngularUI Nested Routing Issue - angularjs

$stateProvider
.state('dashboard', {
url:'/dashboard',
controller: 'MainCtrl',
templateUrl: 'views/dashboard/main.html'
})
.state('dashboard.exchange',{
templateUrl:'views/dashboard/exchange.html',
controller: 'ExchangeCtrl',
url:'/exchange/{exchangeId:[0-9]}',
})
.state('dashboard.exchange.module',{
templateUrl:'views/dashboard/exchangeModule.html',
controller: 'ExchangeModuleCtrl',
url:'/module/{exchangeModuleHostName}',
})
'/dashboard' correctly correctly routes to MainCtrl
'/dashboard/exchange/1' correctly routes to ExchangeCtrl
'/dashboard/exchange/1/module/ae38596496d3' incorrectly routes to ExchangeCtrl
Why doesn't the third url route to ExchangeModuleCtrl? How do I fix this?

In case, that we want last child state: 'dashboard.exchange.module' to totally replace the content of its parent 'dashboard.exchange', we have to options:
First option, whole parent is a target
We can place ui-view="" into the parent root <element>. There is a working example. And this would be the 'dashboard.exchange' state template views/dashboard/exchange.html:
<div ui-view="">
<h3>dashboard.exchange</h3>
<br />
context just for the state: <b>dashboard.exchange</b>
</div>
The most important is the root <div ui-view="">, because child will totally replace parent.
Second approach, target grand parent
In this case, we will skip parent. We will directly target grand parent 'dashboard'. There is a working plunker. Here we use absolute naming to target grand parent unnamed view:
.state('dashboard.exchange.module',{
views : {
'#dashboard' : {
templateUrl:'views/dashboard/exchangeModule.html',
controller: 'ExchangeModuleCtrl',
},
},
url:'/module/{exchangeModuleHostName}',
})
Check these similar Q & A for more details about absolute naming:
Angularjs ui-router not reaching child controller
Angular UI router nested views
Original part of the answer
If we want to follow standard approach - There is a working example
Your code should be workig as is.
The most important is, that each parent must contain target for a child: ui-view="", e.g.:
<div ui-view=""></div>
The view views/dashboard/main.html must contain a target for child state 'dashboard.exchange'
<div >
<h2>dashboard</h2>
<br />
<div ui-view=""></div> // this is for child exchange
</div>
The view views/dashboard/exchange.html must contain a target for child state 'dashboard.exchange.module'
<div >
<h3>dashboard.exchange</h3>
<br />
<div ui-view=""></div> // this is for child module
</div>
Check it here

Related

How to preserve scope data when changing states with ui-router?

I'm building a single page web app using AngularJS with ui-router. I have two different states, one parent and one child. In the parent state, 'spots', users can make a selection from an ng-repeat and their selection is shown using the scope.
When a user makes the selection, I have ng-click fire a function which uses $state.go to load the child state 'details'. I would like to load their selection in the child state, but it appears that the scope data is gone?
I've tried using the same controller for each state. ui-sref doesn't work either.
From the parent state HTML template
<div class="card-column mx-0" data-ng-click="makeSelection = true">
<div class="card mx-0 mb-3 ng-scope" data-ng-click="showSpot(spot);" data-ng-repeat="spot in spots | filter:{'game':gameID} | filter:{'walking':distanceID} | filter:{'vehicle':vehicleID} | orderBy:'price' | filter as results">
<div class="row no-gutters">
<div class="col-sm-12 col-md-3 col-lg-3">
<img src="{{ spot.image }}" alt="parking spot"/>
</div>
<div class="col-sm-12 col-md-9 col-lg-9">
<div class="card-body px-4 pt-4">
<h6 class="text-small-extra text-muted font-weight-normal text-uppercase"><span style="letter-spacing: .05rem;">{{ spot.type }}</span></h6>
<h5 class="card-title">{{ spot.address }}</h5>
<h4 class="text-muted float-md-right">${{ spot.price }}<span style="font-size: 1rem; font-weight: 400">/day</span></h4>
</div>
</div>
</div>
</div>
Snippet from the controller
$scope.showDetails = function() {
$state.go('spots.details'); //my route...
}
$scope.showSpot = function(spot) {
$scope.spot = spot;
$scope.showDetails();
}
Snippet from app.js
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/")
$stateProvider
.state('spots',{
url: '/',
templateUrl: "/parkit/master/spots-available.html",
controller: 'parkitController'
})
.state('details', {
parent: 'spots',
url: '/details',
templateUrl: '/parkit/master/details.html',
})
.state('statetwo', {
url: '/statetwo',
template: '<h1>State Two</h1>',
controller: 'parkitController'
});
})
I expected the user selection to show on the child state after ng-click is fired.
You need to under stand how prototypal inheritance works. When a parent puts a property value on the scope with
$scope.value = 'something';
In a child component if you access $scope.value the inheritance chain will find $scope.value.
If the child sets
$scope.otherValue = 'something';
If follows the inheritance chain, doesn't find a value of otherValue and creates a property on the child scope, not the inherited prototype so the parent component and any other children of the parent do not see it.
You can use what is called the dot rule of prototypal inheritance. If the parent creates an object on the scope called something like data
$scope.data = { value: 'something' };
Now if the child puts a property on the data object
$scope.data.otherValue = 'something';
It looks for the data object, finds it in the inheritence chain and because you are adding a property to an instance of an object it is visible to the parent and any children of the parent.
let parent = {
value: 'some value',
data: { value: 'some value' }
};
let child = Object.create(parent);
console.log(child.value); // Finds value on the prototype chain
child.newValue = 'new value'; // Does not affect the parent
console.log(parent.newValue);
child.data.newValue = 'new value'; // newValue is visible to the parent
console.log(parent.data.newValue);
Short answer is to just never inject $scope and use controllerAs syntax.
To share data between controllers you use a service that is injected to both controllers. You have the spots collection on the service and use a route param to identify which spot the other controller should use or have a place on the service called currentSpot set by the other controller.
Services are a singleton object that you create at the module level and then all controllers that ask for them in their dependency list get the same instance. They are the preferred way to share data between controllers, $scope hierarchies are bound to lead to confusion as the prototypal inheritance nature of them can be confusing. A child $scope is prototypally inherited from it's parent, this seems like you should be sharing data but when a child controller sets a property it is not visible to the parent.
You are learning an outdated way of Angular programming. Injecting $scope is no longer a recommended way. Look at using components. Components are a wrapper for a controller with an isolated scope and using contollerAs syntax. Isolated scopes make it much cleaner to know where data comes from.
Take a look at my answer on this question
Trying to activate a checkbox from a controller that lives in another controller

ui-router child view of an angularjs component not showing

I have an angularjs component(almost empty markup) acting as a shell/parent to its child views(markups with different columns/formats). I am able to see the component but no child views are loaded.
ui-router config code:
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('parent', {
url: '',
views: {
'parentComponent': {
component: 'parentComponent'
}
}
})
.state('parent.child', {
url: '/child',
templateUrl: 'child.html'
});
index.html:
<body ng-app="app" >
<div ng-controller='RoutingCtrl'>
<parent-component></parent-component>
</div>
</body>
The parent, which is a component:
<!DOCTYPE html>
<div>
<br />
<br />
<div>I am the parent, a angularjs component.</div>
<div>There sould be the nested/child view right below:</div>
<div ui-view></div>
</div>
child:
<div>Hi! I am the child!</div>
My controller tells ui-router to go to child view:
$state.go('parent.child');
I don't want to declare parent as abstract because, in my real app, I have views parallel to the parent component view(together, they form a multiple named view) here and these other high level views(except the parent component) must be visible regardless of the child views.
I am using angularjs 1.5.8 and ui-router version 0.4.2
Plunker:
http://plnkr.co/edit/tBhVTjttMagaJzHQNvro?p=preview
You haven't provided any details what you are trying to accomplish, so based on what it looks right now, you have two options: either go with the ui-view template throughout your app and use the parent-child relationship of ui-router or go with hybrid approach of loading your component, and inside it, a state you want to show.
Example with parent-child relationship - This example uses ui-view throughout the app. parent-component is loaded with it's own state and it's not abstract. This approach is good if you want to load different templates for different states in the same place.
Example with hybrid approach. This example follows your current app structure with parent-component loaded in index.html and a state loaded in your parent-component. You can see that you really don't need a parent state here as you have put your parent-component directly in index.html file. State child is loaded in inside your parent-component and it doesn't need a parent state.

Use ui-view as the template for state inside ui-router instead of an external template

I have an app that is currently using the angular ui-router module dependency. The only aspect of the ui-router that I'm currently employing is the ability to apply/modify $stateParams to $scope and vice versa so the URL can change the way data is displayed in the controller to a user on arrival (i.e. url?param=something will filter the data by something).
I have the following in my app.config to set the state:
$stateProvider
.state('root', {
url: '/?param',
templateUrl: 'template.html',
controller: 'listController',
params: {
param: {
value: 'something',
squash: true
}
}
});
On my homepage, template.html successfully loads when the app is instantiated as such:
<div ng-app="myApp">
<div ui-view>
</div>
</div>
However, I have reached a roadblock and realize that calling the template from within templateUrl isn't going to work, as this app is being built inside another framework and therefore needs to be called from within the homepage itself to access its full capabilities.
Being a noob at AngualrJS, I was wondering if anyone can tell me what the best way is to accomplish this while still keeping the logic of $stateParams and other ui-router capabilities intact for the future.
For instance, could I just remove the templateUrl parameter from my state and call the controller directly inside the ui-view like this:
<div ng-app="myApp">
<div ui-view>
<div ng-controller="listController">
do something
</div>
</div>
</div>
I also looked into changing the entire logic from using ui-router to simply using the $location service but I'm wondering if there is a way to accomplish this without needing to over-do everything.

ui-sref set child view to parent view

I am using ui-sref for routing depending upon state.
Currently I am getting child's view inside parent as hierarchy. I want to assign a child's view to a parent view. Current state is as below plunker.
[link][//plnkr.co/edit/fpsTWglicbcGMotJIlll]
I want to see welcome after clicking click me.
If I understood your question correctly, the problem is you have inner as a child of tab1... so it is displayed in the ui-view of tab1. If you want it instead to make it replace tab1, then you don't need the ui-view in tab1.
Here's a forked plunker showing what I mean.
http://plnkr.co/edit/t4cxejVLGf4kywKp2bb5?p=preview
The parts I changed:
.state("main", { abtract: true, url:"/main", templateUrl:"main.html" })
.state("main.tab1", { url: "/tab1", templateUrl: "tab1.html" })
.state("main.inner",{
url:"/inner",
templateUrl:"inner.html"
})
.state("main.tab2", { url: "/tab2", templateUrl: "tab2.html" })
And in tab1.html
<div>
This is the view for tab1
<a ui-sref="main.inner">click me</a>
</div>
Is this what you're shooting for?
Add $scope.$state = $state;in the controller.
Then add ng-hide="$state.current.name === 'main.tab1.inner'" to
<h2 ng-hide="$state.current.name === 'main.tab1.inner'">View:</h2>
and
<div ng-hide="$state.current.name === 'main.tab1.inner'">
This is the view for tab1
<a ui-sref="main.tab1.inner">click me</a>
</div>
Here's the link [link] http://plnkr.co/edit/Yvlp6RNF69yiSq1HfMcf?p=preview

Updating url on transition from child to parent state in UI router

I have an Angular (1.2.1) app running UI-router (0.2.13), and the following state structure:
$stateProvider.state('home', {
template: "<div home></div>",
url: '/'
}).state('home.geo', {
url:'/geo/{geo}'
}
Transitioning from parent to child or between children with different {geo} parameter values works as expected. Transitioning from child to parent works - i.e. the contents of the template and $state.current change as expected - but the URL does not update in the browser.
To be clear, an example: I'm in /geo/california and I click a button with ui-sref='home'. I've confirmed that the correct href='#/' has been placed on the button, and clicking it causes the $state to transition back to the home state, but /geo/california remains in my address bar.
What am I missing here?
Update in respose to #UlukBiy's comment: No, home does not have a ui-view in its template. The ui-view is in the template of it's parent: The overall structure is:
<body>
<div app-nav></div>
<div ui-view></div>
</body>
So the home directive gets inserted into the ui-view, but it contains no ui-views of its own. Is that my problem? I'm new to UI-router, and assumed there was some low-level misunderstanding about the role of states vs. directives when I posted this. If so, please help me correct it.
This scenario should be working. There is a working example (click the blue button right-top to run example in separate window, showing the address bar)
I updated your state def a bit:
$stateProvider
.state('home', {
url: "/",
template: 'Home view <hr /> Geo view: <div ui-view></div>',
})
.state('home.geo', {
url:'^/geo/{geo}',
templateUrl: 'tpl.html',
})
All these links do work as expected:
<a href="#/home">
<a href="#/geo/california">
<a href="#/geo/czechia">
<a ui-sref="home">
<a ui-sref="home.geo({geo:'california'})">
<a ui-sref="home.geo({geo:'czech'})">
So, the most important change here is that for a child state we should use this url:
url:'^/geo/{geo}',
instead of the url:'/geo/{geo}'. Check the doc:
Absolute Routes (^)
If you want to have absolute url matching, then you need to prefix your url string with a special symbol '^'.
Check the working example here

Resources