Angular 1.6.9, UI-Router 1.0.15
The objective is the use the resolver to return the values for binding into Angular components. I have two third-party examples that work, one the tutorial Hello Galaxy example at the UI Router site and another a simple case in Codepen.
https://plnkr.co/edit/XdKQmifZ69pcYeBnQ945?p=preview
See the Pen Angular JS UI-Router State Params by David (#StretchKids) on CodePen.
However, no matter how I cut and paste, I haven't been able to get the resolver function in my Codepen Project to see the passed object. The object config is in the fred.js and ethel.js under components. The button passing the object is in the index.pug file (Codepen also makes the index.html available).
The program goes to the state and executes the resolver function. However both $stateParameters and $transition$.params() are empty. Ugh!
The snippet does not execute.
.module("MyApp")
.component("fred", {
templateUrl: "components/fred.html",
bindings: { name: "#" },
controller: function($stateParams){
var vm = this;
vm.activeId = $stateParams.id;
vm.resolveId = this.name;
}
})
.config(function($stateProvider) {
$stateProvider.state({
name: "fred",
url: "/fred",
component: "fred",
resolve: {
name: function($transition$, $stateParams) {
;
return $stateParams.id;
}
}
md-button(ng-repeat="name in ac.lvl1" ui-sref-active="active" ui-sref="{{name}}({id: 'bar'})") {{name}}
This is a link to the CodePen project: https://codepen.io/StretchKids/project/editor/AJPvQm#
HELP!!!
Thanks,
David
After quite a bit of searching and testing, I found the answer. First I broke the UI-Router Hello Galaxy example. Searching said passed parameters had to included in the URL.
That explained why the Hello Galaxy example passed the id of the person and then searched the people array of person objects rather than just passing the person object which it already had. Definitely the long way around the barn!
What was needed was the addition of a params object in the state definition to declare the $stateParams variables in advance.
{
name: "person",
url: "/{person.id}",
component: "person",
params:{data:null},
resolve: {
person: function($stateParams,$transition$) {
return $stateParams.data.person;
}
}
}
https://ui-router.github.io/guide/states#state-properties
https://ui-router.github.io/ng1/docs/1.0.14/interfaces/state.statedeclaration.html#params
http://benfoster.io/blog/ui-router-optional-parameters
This may be the best of the group: https://ui-router.github.io/guide/ng1/route-to-component
Related
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'm using ui-router to handle my apps states. I need to use resolve to ensure that a settings value is there when the controller is loaded. I started by making a very simple dummy:
.state('dashboard', {
url: '/dashboard',
resolve: {
simpleObj: function(){
return {value: 'simple!'};
}
},
controller: 'DashboardController',
template: '<dashboard></dashboard>',
});
In the controller I inject this like I would any other dependency:
angular
.module('flowlens')
.controller('DashboardController', DashboardController);
DashboardController.$inject = ['simpleObj'];
function DashboardController(simpleObj){
var vm = this;
activate();
function activate(){}
}
However this throws an unknown provider error on the simpleObj This code is based on the example code on the ui-router git repo so it should work.
According to others this typically happens when you use ng-controller on your template but my template contains no such thing:
%h2 Dashboard
%hr
%pre {{ $root.currentUser | json }}
.card.card-block
%my-input{type: "contact", model: "selected_contact", placeholder: "Choose Contact", allow-new: "true"}
Selected:
%pre {{ selected_contact | json }}
%h2 Settings {{ $root.settings }}
There should be no need to use deferred to resolve this promise as it's just returning a simple value - correct?
What's the issue here?
EDIT Heres a plunkr detailing what's going on.
Thanks to imbalind for the help. Here's an updated plunkr of it working. Check the console to see the value getting printed.
The problem is that you referenced the DashboardController twice in two different places:
inside the $stateProvider, where you add a resolve for simpleObj.
inside dashboard directive, where it cannot have a clue about what simpleObj is.
The error (IMHO) is having the same controller used for different things. You should have two different controllers.
If you need to access simpleObj from both of them you should think about making a service out of it.
EDIT: Here's your plunker edited so to avoid the error. Logic is missing.
I'm new to Angular JS, and need some guidance on building a portfolio site:
main.html > use ng-repeat for each piece of work, currently each ng-href
points to "work" but I'd like each work to get its own URL, for
example ng-href="work#{{title}}"
Each 'work' contains about 5-6 images and text. There are all similar in terms of HTML elements, but the images and text is different. So maybe a template would work here.
Here is my code so far
index.js
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl'
})
.state('work', {
url: '/work',
templateUrl: 'app/work/work.html',
controller: 'WorkCtrl'
});
$urlRouterProvider.otherwise('/');
});
main.html
<section ng-repeat="work in works | orderBy:'rank'" class="showcase {{work.class}}">
<a ng-href="#/work">
<hgroup>
<h4>{{work.title}}</h4>
<p>{{work.sub}}</p>
</hgroup>
</a>
main.controller.js
.controller('MainCtrl', function ($scope) {
$scope.works = [
{
'key': 'title-1',
'title': 'A piece of work',
'url': 'page1.html',
'description': 'A long piece of text goes in here',
'logo': 'image-1.png'
},
{
'key': 'title-2',
'title': 'A piece of work number 2',
'url': 'page2.html',
'description': 'A long piece of text goes in here',
'logo': 'image-2.png'
},
...
];
angular.forEach($scope.works, function(work) {
work.rank = Math.random();
});
});
work.html
Not sure how to go about building a template what can fetch the data from MainCtrl, or should I be using a JSON object and remove the data from main.controller and have both of them reference this data. In any case this template only needs to show one portfolio work not all like the main.controller.js.
work.controller.js
Not sure what to do here, i think i need to configure a path that gets each item id and a directive, but again, this will take me days to figure out on my own.
Hope you can help out. I'm a beginner so examples would be most appreciated. Thanks again :)
Where to get the common data for both templates...
There's a couple of considerations will you do an ajax request to get the data?
NO: Create a factory and store all your data there
angular.module('mod').factory('worksData', function(){
var works = []
function getWorks(){
return works;
}
return {
getWorks: getWorks
}
}
YES:
use ui-router 'resolve'
create an abstract state and a controller for it to...
store the data to the same factory
I am not going in depth because it might not be what you need.
Not sure what you meant with your 2nd question.
I just took an app I'm working on and converted it to a Plunk but Angular and/or ui-router is not populating the two views I have in index.html. On my local box the app loads fine but there I have the app modularize. So when I converted it to a Plunk I had to rewire the files together since I can't make modules in Plunker AFAIK. Also, when I load the Plunk in a separate window and open Dev Tools I get no errors so I'm at a loss right now.
Here is link to the Plunk code I made:
http://plnkr.co/edit/2f1RITT6ysZhB5i0UcUw?p=preview
And here is the link to the embedded view (more convenient if you want to use Dev Tools):
http://embed.plnkr.co/2f1RITT6ysZhB5i0UcUw/preview/posts
I should mention that the route has to end in /posts since that it the url of the state named posts. I have no state defined for the root / url. Also the following url failed:
http://embed.plnkr.co/2f1RITT6ysZhB5i0UcUw/posts
Thanks in advance.
I've made few changes. Here is a working plunker
Firstly I upgraded your version to UI-Router 0.2.13 (fixes some issues, simply always use the latest)
The /post is now default
//$urlRouterProvider.otherwise('/');
$urlRouterProvider.otherwise('/posts');
I changed your controller, to not use router params,
// wrong old
/*
app.controller('ProfileCtrl', function ($scope, $routeParams, Profile) {
var uid = $routeParams.userId;
$scope.profile = Profile.get(uid);
Profile.getPosts(uid).then(function(posts) {
$scope.posts = posts;
});
});
*/
// the way with UI-Router
app.controller('ProfileCtrl', function ($scope, $stateParams, Profile) {
var uid = $stateParams.userId;
$scope.profile = Profile.get(uid);
...
JUST to know what is post holding
Also, the passed userId into state contains values like: "simplelogin:82", to observe taht, I added overview of processed post, which is showing info like this:
{
"creator": "3lf",
"creatorUID": "simplelogin:82", // this is passed as userId, is it ok?
"title": "a",
"url": "http://a",
"$id": "-JazOHpnlqdNzxxJG-4r",
"$priority": null
}
Also, this is a fixed way how to call state posts.postview
<!-- wrong -->
<!-- <a ui-sref="posts({postId:post.$id})">comments</a> -->
<!-- correct -->
<a ui-sref="posts.postview({postId:post.$id})">comments</a>
And alos, if the postview should be injected into main area, this should be its defintion
var postView = {
name: 'posts.postview',
parent: posts,
url: '/:postId',
views: {
'navbar#': {
templateUrl: 'nav.tpl.html',
controller: 'NavCtrl'
},
//'#posts.postview': {
'#': {
templateUrl: 'postview.tpl.html',
controller: 'PostViewCtrl'
}
}
};
Check it all here
SUMMARY: Working navigation is among posts - users... the "comments" link is also working, but the target is just loaded ... with many other errors... out of scope here
I have an app page with 3 columns. The middle column is the main activity and is always displayed. The two side columns are widget lists, that have their own controller and states, and can be either hidden or unhidden, and have multiple views within them as well. Ideally, I'd imagine url routes like the following:
/app - main activity is shown, both panels hidden
/app/1234 - main activity is shown, but shows info for 1234 entity
/app/1234/leftpanel - main activity is shown with 1234 entity, and leftpanel is open
/app/1234/leftpanel/list - main activity is shown with 1234 entity, and leftpanel is showing the list view
/app/leftpanel/list - main activity is showing default state, leftpanel is still showing the list
Is this possible to setup with ui-router? I've seen this example:
https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions
which shows how to use the $stateProvider between multiple modules, but I'm still not seeing how to make this scenario work-
I did resolve the issue. I also posted about this on the angular-ui github, and their response was basically, "well, that case isn't really what the router has been designed for, so if you want "fancier" state management, put the data in parameters and look at them yourself and implement whatever logic you need". I kinda felt that this was what the ui-router was designed for, so I extended it a bit (no source code changes) to accomplish this. The solution is a combination of abstract states, a fake "off" state, parameters in the router urls, and extending the $urlRouterProvider service.
First extend the $urlRouterProvider:
urlRouterProvider.when(/^((?!leftpanel).)*$/, ['$state', '$location', function ($state, $location) {
//we've got just /app or /app/3434, nothing that contains /leftpanel, so turn off
$state.transitionTo("off");
}]);
Then add that "off" state:
$stateProvider.state('off',{
//url: //there is no url
views:{
container:{
template: 'blank',
controller:['$scope', '$stateParams', function($scope, $stateParams){
console.log("off yay"); //just for sanity, not necessary
}]
}
}});
Then setup the rest of the app routing:
appModule.constant('LEFT_PANEL_STATES', function() {
var leftPanelRoot = {
name: 'root.leftpanel', //mandatory
template: '',
url: "/app/:primaryId/leftpanel",
views:{
'container#': {
templateUrl: "partials/leftpanel_container_partial.html",
controller:"LeftPanelRootCtrl",
resolve: {
//etc
}
}
},
"abstract":true //makes this view only viewable from one of its child states
};
var leftPanelItemsList = {
name: 'root.leftpanel.itemslist', //mandatory
parent: leftPanelRoot, //mandatory
url: "/items-list",
views:{
'childview#root.leftpanel': {
templateUrl: "partials/leftpanel_items_list.html",
controller:"LeftPanelItemsListCtrl",
resolve: {
//etc
}
}
}};
var leftPanelListDetail = {
name:"root.leftpanel.itemslist.detail",
parent:leftPanelItemsList,
url:"/:id/detail",
views:{
"detail":{
templateUrl:"partials/leftpanel_item_detail.html",
controller:"LeftPanelItemListDetailCtrl"
}
}};
var leftPanelExtendedDetailList = {
name:"root.leftpanel.itemslist.extendedlist",
parent:leftPanelItemsList,
url:"/:id/extendedDetail/list",
views:{
"extendeddetaillist":{
templateUrl:"partials/leftpanel_extended_detail_list.html",
controller:"LeftPanelExtendedDetailListCtrl"
}
}};