Alright, I'm pretty new to Angular, spent the last two days trying to find a good way to do this and failed. I'm not sure if the title is good either.
I'm trying to make a simple page with a menu. The person would click a menu link and then the view that's below the menu should render accordingly (without refreshing the whole page of course). Sounds easy.
But the thing is, there's going to be a menu for admins and a menu for regular users, so the menu is loaded through ajax. This is an example of what I get:
[
{
name: "Manage games",
templateUrl: "/view/mygames.html",
url: "/games",
},
{
name: "Weekly Reports",
templateUrl: "/view/myreports.html",
url: "/reports"
},
{
name: "Manage Users",
templateUrl: "/view/users.html",
url: "/users",
adminRequired: true
}
];
After the menu is loaded, when a menu element is clicked, I want to get data making an ajax call to the url property, and the template to fetch this data to will also be an ajax call to the templateUrl property.
So, how is that achievable? Basically what I want is to have a directive/component/whatever, that by default will be empty, not displayed or rendered. But when I click on a element from the menu, I'm going to $broadcast an event with the dataUrl/templateUrl to the directive/component/whatever, and it will make two ajax calls, one two get the data, and another to get the template, after both get done, it will render and appear on the page.
Any way to this, or suggestion to do something similar to this would be greatly appreciated
By the way, I'm using Angular 1.5.7
You should use routing (in my example it's the ui-router) for achieving this.
The ui-router has a resolve property that lets you resolve controller dependencies and lets you then inject them into your controller for using them.
Here's a full example that I've made (sorry for the poor ui):
HTML:
<div ng-app="app">
<div ng-controller="homeCtrl as vm">
<menu items="vm.items"></menu>
</div>
<div>
<ui-view></ui-view>
</div>
</div>
JS:
var app = angular.module('app', ['ui.router']);
app.controller('gamesCtrl', function(data) {
this.title = data.title;
}).
controller('reportsCtrl', function(data) {
this.title = data.title;
}).
controller('usersCtrl', function(data, adminData) {
this.title = data.title;
this.removedUsers = adminData.removedUsers;
}).
controller('homeCtrl', function() {
this.items = [{
name: 'Manage games',
state: 'games'
}, {
name: 'Weekly Reports',
state: 'reports'
}, {
name: 'Manage Users',
state: 'users',
adminRequired: true
}];
});
app.component('menu', {
bindings: {
items: "="
},
template: '<div ng-repeat="item in $ctrl.items"><span ng-click="$ctrl.goToState(item)">{{item.name}}</span></div>',
controller: function($state) {
this.goToState = function(item) {
console.log('redirecting to state:' + item.state);
$state.go(item.state);
}
}
});
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.
state('games', {
url: '/games',
template: '<div><h1>{{vm.title}}</h1></div>', // use templateUrl..
resolve: {
data: function($q) {
return $q.when({
title: 'games'
})
}
}, // return injectables who return promises and inject them into your ctrl
controller: 'gamesCtrl as vm'
}).
state('reports', {
url: '/reports',
template: '<div><h1>{{vm.title}}</h1></div>', // use templateUrl..
resolve: {
data: function($q) {
return $q.when({
title: 'reports'
})
}
}, // return injectables who return promises and inject them into your ctrl
controller: 'reportsCtrl as vm'
}).
state('users', {
url: '/users',
template: '<div><h1>{{vm.title}}</h1><div>Removed Users:</div><div ng-repeat="user in vm.removedUsers">{{user}}</div></div>', // use templateUrl..
// return injectables who return promises and inject them into your ctrl
resolve: {
data: function($q) {
return $q.when({
title: 'users'
})
},
adminData: function($q) {
return $q.when({
removedUsers: ['user1', 'user2', 'user3']
})
}
},
controller: 'usersCtrl as vm'
}).
state('default', {
url: '/default',
template: '<h1>This is the default state</h1>'
});
$urlRouterProvider.otherwise('/default');
});
JSFIDDLE.
Remarks:
You should use templateUrl instead of template when configurating the states.
I've used $q.when to demonstrate a return of a promise. You will probally use $http.get\post instead.
Related
I'd like to load a template using Angular controller. I have created everything needed:
routes.config.js
index.component.js
index.config ( here is the config of the parent controller )
html
js controller
connected with php controller
I already created templates and controllers exactly the same way and I have no idea what am I doing wrong. It's interesting that when I open elements tab in the browser I can see the <statistic-test></statistic-test> tags, and url also change. If I change <statistic-test></statistic-test> to another tag which is already used in another controller then it loads.
test-question.component.js
class TestQuestionController{
constructor($scope, $state,$stateParams, $compile, DTOptionsBuilder, DTColumnBuilder, API){
'ngInject';
this.API = API
this.$state = $state
if ($stateParams.mode) {
this.mode=$stateParams.mode
}
let Statics = API.service('show', API.all('statisticQuestion'))
this.userlistaction = "app.statisticanswer.userlist"
}
$onInit(){}
}
export const TestQuestionComponent = {
templateUrl: './views/app/components/statistic-question-userlist/test-question.component.html',
controller: TestQuestionController,
controllerAs: 'vm',
bindings: {}
}
I also have an html named test-question.component.html
routes.config.statistic.question.js
export function RoutesConfigStatisticQuestion ($stateProvider) {
'ngInject'
$stateProvider
.state('app.statisticquestion', {
url: '/statistic-question',
data: {
auth: true
},
views: {
'main#app': {
template: '<statistic-question></statistic-question>'
}
},
ncyBreadcrumb: {
label: 'Kérdések statisztika',
}
})
.state('app.statisticquestion.test', {
url: '/statistic-test/',
data: {
auth: true
},
views: {
'main#app': {
template: '<statistic-test></statistic-test>'
}
},
params: {
contentsId : null,
alerts: null
},
ncyBreadcrumb: {
label: 'Címke szerkesztése',
parent: 'app.statisticquestion'
}
})
If you have ANY idea how to solve this please share it with me.
I have an app with multiple states that each have nested views. The one state has a conditional templateUrl, and based on a $state.param will show specific HTML/JS. I want to set a query on the URL of the state, so that I know which list item is being looked at when I click it. I cannot figure out how to set a url query and transition to the desired state's view.
My states:
.state('index', {
url: '/',
views: {
'#' : {
templateUrl: 'views/layout.html'
},
'top#index' : {
templateUrl: 'views/top.html',
controller: function($scope, $state) {
$scope.logOut = function() {
$state.go('login');
};
}
},
'left#index' : { templateUrl: 'views/left.html' },
'main#index' : { templateUrl: 'views/main.html' }
}
})
.state('index.list', {
url: 'list?item',
templateUrl: 'views/lists/list.html',
controller: 'ListCtrl'
})
.state('index.list.details', {
url: '/',
params: {
detail: 'overview'
},
controller: ctrl,
views: {
'details#index' : {
templateUrl: function($stateParams) {
if($stateParams.detail === 'status' ) {
ctrl = 'StatusCtrl';
return 'views/details/status.html';
} else if ($stateParams.detail === 'overview') {
ctrl = 'OverviewCtrl';
return 'views/details/overview.html';
}
}
}
},
})
In my controller for the index.list, this is where I have the list and click and populate the details view.
HTML & Js:
<div class="channel" ng-repeat="item in items" ng-click="viewListDetails(item)">
$scope.viewListDetails = function(item) {
$location.search('item', item.id);
$state.go('index.list.details', {detail: 'status'}, {reload: true})
};
My JS above runs the through the function however it does nothing! It will not set the query or transition to the desired view for that sate.
Any help is appreciated!
index.deviceList.detail is not a defined state. You probably intended to write index.list.details.
I am using UI-Router and want to change routing to be 'component based'. So Instead of defining a controller / template I want to use it like this:
.state('issue', {
url: '/someUrl/:number',
template: '<my-directive></my-directive>',
resolve: {
data: function(dataService) {
return dataService.getData().then(function(response) {
console.log(response.data);
return response.data;
});
}
}
})
Now, I know that with Angular's ngRoute I can use resolved data directly in the template, for example:
$routeProvider
.when('/', {
template: `<my-directive data="resolve.data"></my-directive>`,
resolve: {
data: function (dataService) {
return dataService.getData();
}
}
})
I couldn't do it using UI-Router (value was undefined).
Am I doing something wrong? Is it possible using ui-router?
The point with UI-Router is - result of resolve is available for a controller (related to template). So, we could do it like this:
.state('issue', {
url: '/someUrl/:number',
template: '<my-directive data="stateCtrlData"></my-directive>',
controller: function($scope, data) { $scope.stateCtrlData = data },
resolve: {
data: function(dataService) {
return dataService.getData().then(function(response) {
console.log(response.data);
return response.data;
});
}
}
})
The data are passed into controller and from its scope we pass it to directive.
If you mean injecting your data service, then you can do it like this (remember that the '' is telling to inject):
state('issue', {
url: '/someUrl/',
template: '<my-directive data="pep.data"></my-directive>',
controller: function( data) { this.data = data },
controllerAs: 'pep',
resolve:{
dataSvc : 'YourDataSvc',
campaign : function(dataSvc){
return dataSvc.getData();
}
}
Please remember a ui-view will be expected if you want to put additional views or child states.
Actually you can (tested only in ui-router v0.3.2)
There's an undocumented $resolve variable which is automatically injected in the controller.
Simply add 'controllerAs' property to the state as follows, and you can use $resolve in the template:
.state('issue', {
url: '/someUrl/:number',
template: '<my-directive data="vm.$resolve.data"></my-directive>',
controllerAs: 'vm',
resolve: {
data: function(dataService) {
return dataService.getData().then(function(response) {
console.log(response.data);
return response.data;
});
}
}
})
I'm following this sample on adding custom data to state objects but can't figure out what I'm doing wrong.
The idea is to have a Users state with a template that have 3 views: header, content and footer. The header-view gets updated with the child state's title and breadcrumb. The unnamed content-view will show the child's template (like a users list) and the footer will show summary data.
I have 2 issues:
My custom data object on my header's current state is undefined. ['Cannot read property 'title' of undefined]
If I exclude the custom data and just set the $scope values directly in the controller, it works fine but now get a 404 error. [GET http://localhost:3000/users 404 (Not Found)]. This makes no sense to me.
Am I on the right track with what I want to do? I'm unsure if I need the custom data or can I just set it in the controller?
angular.module('users').config(['$stateProvider',
function($stateProvider) {
// Users state routing
$stateProvider
.state('users', {
onEnter: function(){
console.log('state change to users');
},
abstract: true,
url: '/users',
views: {
'': {
templateUrl: 'modules/users/views/users.html'
},
'header#users': {
templateUrl: 'modules/core/views/page-header.html',
// data: { //<- this is undefined in the controller
// title: 'Users',
// breadcrumb: 'Home / Users'
// },
controller: function($scope, $state) {
$scope.title = 'Users'; //$state.current.data.title;
$scope.breadcrumb = 'Home / Users'; //$state.current.data.breadcrumb;
}
},
'footer#users': {
templateUrl: 'modules/core/views/page-footer.html'
}
}
})
.state('users.list', {
onEnter: function(){
console.log('state change to users.list');
},
url: '',
templateUrl: 'modules/users/views/users-list.html'
})
.state('signin', {
url: '/signin',
templateUrl: 'modules/users/views/authentication/signin.client.view.html'
});
}
]);
<section>
<div ui-view="header"></div>
<div ui-view=""></div>
<div ui-view="footer"></div>
</section>
You're receiving undefined because you're trying to access a data object belonging to the state itself, rather than the view you've defined it on. Typically, data would be defined against the state, but if you wish to do so against the view, you will need to use:
views: {
'header#users': {
data: {
title: 'Users',
},
controller: function($scope, $state) {
$scope.title = $state.current.views['header#users'].data.title;
}
}
}
As for your 404 error, there is nothing presented in the code that would cause this, so the problem must lay elsewhere.
$stateProvider
.state('root', {
url: '',
views: {
'root.base': { templateUrl: '/templates/root/root.html' },
'root.sidebar': {
templateUrl: '/templates/root/root-sidebar.html',
controller: 'SomeDataController',
resolve: {
someData: function(DataService) { // DataService is an angular service for data retrieval
return DataService.getDataList(); // returns an array
}
},
}
}
});
If I attempt to run a resolve on a subview with angular-ui-router, the page simply comes up blank. If I omit the resolve, it loads fine -- but I need to resolve for some data before my controller is instantiated. Do I have my syntax correct? I've searched for examples high and low but can't seem to find any that match my situation.
Given my above example, shouldn't the "root-sidebar.html" have the scope of "SomeDataController", and shouldn't SomeDataController have the data resolved in the route definition declared above?
Your concept is working, maybe just some small settings are broken... I created working plunker
Here is the controller consuming someData
.controller('SomeDataController', function($scope, someData) {
$scope.data = someData
})
And here is our service loading some JSON and returning that as async:
.factory('DataService', ['$http',
function($http) {
return {
getDataList: function() {
return $http
.get("data.json")
.then(function(response) {
return response.data;
});
},
};
}
]);
The state defintion is unchanged (just adjusted to display the data):
$stateProvider
.state('root', {
url: '',
views: {
'root.base': {
template: '<div>root base view</div>',
},
'root.sidebar': {
template: '<div>root sidebar view' +
'<h5>the resolve data</h5>' +
'<pre>{{data | json}}</pre>' +
'</div>',
controller: 'SomeDataController',
resolve: {
someData: function(DataService) { // DataService is an angular service for data retrieval
return DataService.getDataList(); // returns an array
}
},
}
}
});
You can test that all here