I've successfully replicated my issue with a fork of the "Hello Galaxy!" plunk embedded within the UI-Router tutorial page.
My plunk: https://plnkr.co/edit/2GqCtEJ4mhBIdJOFHy9c?p=preview
On the existing "Helo Galaxy!" plunk, I added the following module and route config:
// file: hello.js
// existing "Hello Galaxy!" hello module code above this ↑↑↑
// then my new module below...
angular.module('hello.product-management', ['ui.router']).config(function($stateProvider) {
// An array of state definitions
var states = [
{
name: 'product-management-template',
url: '/product-management-template',
// Using component: instead of template:
template: '<h1>Product Management</h1>'
},
{
name: 'product-management-component',
url: '/product-management-component',
// Using component: instead of template:
component: 'product-management'
},
]
// Loop over the state definitions and register them
states.forEach(function(state) {
$stateProvider.state(state);
});
});
The issue: You can click on the Product Management - Template tab to see the Product Management template, like so:
But you can't view the component template, using the Product Management - Component tab. It just shows an empty view:
I left the original "Hello Galaxy!" plunk's components and routes alone, and they still work fine:
In the state definition, use camelCase for the component name:
{
name: 'product-management-component',
url: '/product-management-component',
// Using component: instead of template:
̶c̶o̶m̶p̶o̶n̶e̶n̶t̶:̶ ̶'̶p̶r̶o̶d̶u̶c̶t̶-̶m̶a̶n̶a̶g̶e̶m̶e̶n̶t̶'̶
component: 'productManagement'
},
And define the component with camelCase:
̶a̶p̶p̶.̶c̶o̶m̶p̶o̶n̶e̶n̶t̶(̶'̶p̶r̶o̶d̶u̶c̶t̶-̶m̶a̶n̶a̶g̶e̶m̶e̶n̶t̶'̶,̶ ̶{̶
app.component('productManagement', {
template: '<h1>Product Management</h1>',
controller: function () {
console.log("product management component");
}
});
For more information, see AngularJS Developer Guide - Directive Normalization
Related
Learning Angular. Working with 1.6.6.
Trying to use ui.router, running into an issue with injecting components.
I've been using the following as resources to structure my project:
AngularJS: Understanding design pattern
https://github.com/angular-app/angular-app
Both these resources suggest using module as a container for the code underneath them. For example from my own project:
angular.
module('randomTownGenerator.module', [
'randomTown.service',
'randomTown.controller'
]);
Each of those dependancies is defined in its own file. When I specify the above module as the component for the the route:
var randomTownGenerator = {
name: 'randomTownGenerator',
url: '/random-town',
component: 'randomTownGenerator.module'
}
I get:
Error: [$injector:unpr] Unknown provider: randomTownGenerator.moduleDirectiveProvider <- randomTownGenerator.moduleDirective
How can I pass the randomTownGenerator.module, which is just a wrapper around the service, template, and controller, to ui.router?
You have provided a module where it is expecting an angular component.
component: 'randomTownGenerator.module'
Here angular-ui-router is expecting a angular component to generate as the view for the state 'randomTownGenerator'. Please refer the angularjs documentation on how to create a component.
https://code.angularjs.org/1.6.6/docs/guide/component
You are trying to mixup the angularjs earlier version of injecting a module and new way of injecting module.
You should provide a component as a view with the later version so that will be loaded when it is required.
var States = {
"app": {
path: "",
routing: null,
definition: {
name: "app",
url: "",
onEnter: function () {
console.info("App state entered.");
},
params: {
//
},
resolve: {
//
},
views: {
"app#": {
component: "appComponent"
}
},
abstract: true
}
}
};
where component should be a component not a module. Here is a complete example of how to create states with ui-router and angularjs 1.6 version
Using ui-router 1.0.6.
Every time I return to an url (using ui-sref) it reloads the controller. I would like to avoid that and to load the controller only the first time it is accessed.
In this example Plunkr: every time I switch repeatedly between Hello and About it logs the console.
It can be wrapped in a parent controller to track who's already loaded
Here is a working example: Plnkr
Basically you create another controller that holds an object with an empty list:
myApp.controller('ModuleNumCtrl', function() {
loadedCtrl = {};
});
And set it to be parent by setting the abstract attribute to true:
var parentState = {
abstract: true,
name: 'parent',
controller: 'ModuleNumCtrl'
};
Then you set the the exiting controllers to be his children by prefixing their names with 'parent.'
var helloState = {
name: 'parent.hello',
url: '/hello',
template: '<h3>hello world!</h3>',
controller: 'ModuleTwoCtrl'
};
var aboutState = {
name: 'parent.about',
url: '/about',
template: '<h3>Its the UI-Router hello world app!</h3>',
controller: 'ModuleOneCtrl'
};
$stateProvider.state(parentState);
$stateProvider.state(helloState);
$stateProvider.state(aboutState);
Then on each controller you want to load only once, you can add it to the list the first time it's loaded and the code that you want to run only once put in an if statement:
myApp.controller('ModuleOneCtrl', function() {
if (!loadedCtrl.one) {
console.log("One");
}
loadedCtrl.one = true;
});
Last thing, don't forget to change the HTML with the new controllers names:
<a ui-sref="parent.hello" ui-sref-active="active">Hello</a>
<a ui-sref="parent.about" ui-sref-active="active">About</a>
There's a plugin for ui-router which can do that, named sticky-states: https://github.com/ui-router/sticky-states
I would build on top of your plunker, but i can't find a CDN that's hosting sticky states. I found a CDN for ui-router-extras which is the equivalent for sticky states in ui-router 0.x, but for 1.x that won't work.
What you'll need to do is
1) Add the plugin. The github page for sticky-states gives instructions on how to do this, which i'll replicate here:
import {StickyStatesPlugin} from "ui-router-sticky-states";
angular.module('myapp', ['ui.router']).config(function($uiRouterProvider) {
$uiRouterProvider.plugin(StickyStatesPlugin);
});
2) For the state definitions that you want to remain active, add the property sticky: true, as in:
var aboutState = {
name: 'about',
url: '/about',
template: '<h3>Its the UI-Router hello world app!</h3>',
controller : 'ModuleOneCtrl',
sticky: true
}
With this flag, moving from a state to a sibling state will not exit the old state, but rather will "inactivate" it. The controller remains loaded. If you try to enter that old state, it will be "reactivated". The state is now active, but the existing controller is reused.
Note that sticky states will still be exited if you do one of the following:
1) exit the parent of the sticky state
2) directly activate the parent of the sticky state
So you'll need to arrange your tree of states so that that either can't happen , or only happens when you want it to.
I revisited AngularJS after a long. Pleased to see they've gone ahead with components. I am trying to make use of components in my project.
I have few child component states that require access to parent component state. Basically an object will be shared across all child states. I am unable to achieve that with components.
Here's how the structure looks like -
{
name: 'wizard',
url: '/wizard',
component: 'wizard',
redirectTo: 'wizard.init'
},
{
name: 'wizard.init',
url: '/init',
component: 'init'
},
{
name: 'wizard.customize',
url: '/customize',
component: 'customize'
},
{
name: 'wizard.finish',
url: '/finish',
component: 'finish'
}
There is an object / variable called $ctrl.settings in wizard component which I want to be shared across all it's child states.
I tried doing something like this -
<div ui-view settings="settings">
And accessing them in child components like this -
(function(angular){
var app = angular.module('app');
app.component('init', {
templateUrl:'Scripts/dist/views/modules/wizard/init.html',
controller: WizInit,
bindings : {
'settings' : '='
}
});
function WizInit(){
var $ctrl = this;
$ctrl.$onInit = function(){
console.log("init Start Step");
console.log($ctrl.WizInit);
$ctrl.WizInit= "start";
}
}
})(window.angular);
But I get undefined in every state even though I have assigned it to some value in parent component /wizard.
How can I make it work with components and ui-router?
Never mind,
Solved it on my own. I just had to add $ctrl in ui-view directive of parent component state..
<div ui-view settings="$ctrl.settings">
Drawback of working late. You are not productive enough sometimes.
I make an app with Angular 1.5 Components. I provide a data into component via resolve parameter, in that way I can display any data from different sources in the same component. But I don't understand, how to change some data in my component.
For example, I have a User service, which works with users through the API. In my state I load a component and use method Users.get(). I use UI Router.
//...
$stateProvider
.state('users', {
url: '/users',
component: 'formPage',
resolve: {
values: function(Users) {
return Users.get();
});
//...
//...
component('formPage', {
bindings: {
values: '<'
},
//...
I have a form in this component and want to change the data. I want to call Users.update() method, when form will be submitted. But the component don't know anything about Users service and that's right.
How i may specify that component must use Users.update() for update the data in this state? And how I call this method in the component when form will be submitted?
resolve: {
values: function(Users) {
return Users.get();
},
onUpdate: function(){
return Users.update.bind(Users);
}
);
bindings: {
values: '<',
onUpdate: '<'
},
and you can call it like $ctrl.onUpdate(data).then(...
I believe I'm on the right track with these terms, "nested parameterized routes", but haven't found what I'm looking for yet.
My objective is to create intuitive routes for my API, something like the following example:
/api/v1/project/list
/api/v1/project/1/item/list
/api/v1/project/1/item/1/edit
/api/v1/project/2/item/3/delete
It's relatively easy and clear how to setup project states, but not the item states within each project.
{
state: 'project'
config: {
url:'/project'
}
},
{
state: 'project.list'
config: {
url: '/list'
}
},
{
state: 'project.detail'
config: {
url: '/:project_id'
}
}
It's not clear to me where to go from there so that items are relative or nested within projects.
I'll assume you have a REST api (based on your example containing /api/v1) which you want to expose/parallel as a UI. I'll assume you want to allow the user to drill down some hierarchical data model.
Choices!
There are many ways you could organize your states, for this drill-down list/details pattern. None is the "correct" way, but some are probably better than others. I will highlight two approaches that I've used:
Sibling states for list and details
One approach is to keep the "item list" states and "item details" states as siblings. This is what you did with project.list and project.details. This approach can be seen in the UI-Router Extras Demos source code.
When taking this approach
you must take care to move the user from the list state to the detail state when drilling down.
This approach has the benefit of easy-to-understand nesting of UI-Views. The ui-view for the detail view replaces the ui-view for the list view, when drilling down, because you are navigating to a sibling state.
Your choice whether or not the detail for an entity also retrieves the list of sub-entities (does the detail for a project also show the items list for that product?)
States:
projectlist // template plugs into parent ui-view
projectdetail // template plugs into parent ui-view, replacing projectlist
projectdetail.itemslist // template plugs into parent ui-view (#projectdetail)
projectdetail.itemdetail // template plugs into parent ui-view (#projectdetail), replacing itemslist
Details state as a substate of List state
Another approach is to make the detail state a child of the list state. This is organized similar to your REST routes.
When taking this approach
States hierarchy closely resembles the REST routes being exposed
Drilling down is simple and intuitive
You must manage the visual display of list/detail.
When drilling down from list state to the details substate, you probably want to hide the list.
We use named views, and absolute naming in order to replace the parent list state's template with the template for the the detail state. This is called "view targetting".
States:
top // theoretical parent state
top.projects // lists projects. Plugs into parent ui-view (#top)
top.projects.project // details for project. Its named view targets the grandparent ui-view (#top), replacing the template from top.projects list state.
top.projects.project.items // lists items. Plugs into parent ui-view (#top.projects.project)
top.projects.project.items.item // details for item. Its named view targets the grandparent ui-view (#top.projects.project), replacing the template from top.projects.project.items list state.
Here's an example of using named view targeting to accomplish the second approach:
$stateProvider.state('top', {
url: '/',
template: '<ui-view/>',
});
$stateProvider.state('top.projects', {
url: '/projects',
resolve: {
projects: function(ProjectsRoute) {
return ProjectsRoute.getProjects();
}
},
controller: function($scope, projects) { $scope.projects = projects; },
template: '<li ng-repeat="project in projects"> <ui-view/>'
});
$stateProvider.state('top.projects.project', {
url: '/:projectid',
resolve: {
project: function(ProjectsRoute, $stateParams) {
return ProjectsRoute.getProject($stateParams.projectid);
}
}
views: {
'#top': {
controller: function($scope, project) { $scope.project = project; },
template: 'Project details: {{ project.name }} <a ui-sref=".items">view items</a> <ui-view/>'
}
});
$stateProvider.state('top.projects.project.items', {
url: '/projects',
resolve: {
items: function(ItemsRoute, project) {
return ItemsRoute.getItemsForProject(project.id);
}
},
controller: function($scope, items) { $scope.items = items; },
template: '<li ng-repeat="item in items"> <ui-view/>'
});
$stateProvider.state('top.projects.project.items.item', {
url: '/:itemid',
resolve: {
item: (ItemsRoute, $stateParams) {
return ItemsRoute.getItem($stateParams.itemid);
}
},
views: {
'#top.projects.project': {
controller: function($scope, item) { $scope.item = item; },
template: 'Item details: {{ item.name }}'
}
});
I checked the Github Wiki, the Abstract States is enough.