Controller loaded twice using ui-router + custom directive - angularjs

I am trying to bring to my homepage a custom directive which will print me some output.
In the network tab in my devtools I just saw that my controller loads twice.
controller:
var homeController = function($log,leaguesFactory){
var self = this;
self.leagues = [];
leaguesFactory.loadLeagues()
.then(function(leagues){
self.leagues = leagues.data.Competition;
});
self.message= 'test message';
};
directive:
var leaguesTabs = function(){
return {
restrict : 'E',
templateUrl : 'app/home/leagues-tabs.tpl.php',
scope: {
leagues: '='
},
controller: 'homeController',
controllerAs: 'homeCtrl'
};
};
ui-router states:
$stateProvider
.state('home',{
url : '/',
templateUrl : 'app/home/home.tpl.php',
controller : 'homeController',
controllerAs: 'homeCtrl'
})...
I just want to use my homeCtrl in the directive, but it seems that the state provider loads it also and make it load twice. If I remove the controller from the directive then I don't get access to the homeCtrl, if I remove the homeCtrl from the stateprovider than I don't have access in the home.tpl.php
home.tpl.php:
<div>
<leagues-tabs></leagues-tabs>
</div>
any idea?

Actually problem related to next steps:
ui-router start handling url '/'
ui-router create an instance of 'homeController'
ui-router render the view 'app/home/home.tpl.php'
Angular see usage a custom directive - 'leagues-tabs'
'leagues-tabs' directive create an instance of 'homeController'
'leagues-tabs' render the view 'app/home/home.tpl.php'
You can follow any of next possible solutions:
Change controller for 'leagues-tabs' to something special
Remove controller usage from ui-router state definition

You can try this one http://plnkr.co/edit/LG7Wn5OGFrAzIssBFnEE?p=preview
App
var app = angular.module('app', ['ui.router', 'leagueTabs']);
UI Router
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/leagues');
$stateProvider
.state('leagues', {
url: '/leagues',
templateUrl: 'partial-leagues.html',
controller: 'LeaguesController',
controllerAs: 'ctrl'
});
}]);
Controller
app.controller('LeaguesController', ['$http', function($http) {
var self = this;
$http.get('leagues.json').success(function(data){
self.leagues = data;
})
}]);
View
<div>
<league-tabs leagues="ctrl.leagues"></league-tabs>
</div>
Directive
var leagueTabs = angular.module('leagueTabs', []);
leagueTabs.directive('leagueTabs', function(){
return {
restrict : 'E',
templateUrl : 'partial-league-tabs.html',
scope: {
leagues: '='
},
controller: 'LeagueTabsController',
controllerAs: 'leagueTabs'
}
});
leagueTabs.controller('LeagueTabsController', function($scope){
var self = this
$scope.$watch('leagues', function(leagues){
self.leagues = leagues;
})
})
Directive View
<div>
<ul ng-repeat="league in leagueTabs.leagues">
<li>{{league.name}}</li>
</ul>
</div>

Related

AngularJS directive scope property doesn't render in directive view

I have a problem with scope property of directive that doesn't render want to render in directive view.
app.js
.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
controllerAs: 'main'
})
main.js
angular.module('todayfmApp')
.controller('MainCtrl', ['$scope', function ($scope) {
this.formsetup = [];
this.formsetup.title = "Find Your Break";
}]);
mainController View - where Form Setup: {{main.formsetup.title}} is rendering properly
<h2>Form Setup: {{main.formsetup.title}}</h2>
<section class="home-banner">
<carousel-partial></carousel-partial>
<overlay-form formsetup="main.formsetup.title"></overlay-form>
directive
angular.
module('directives').
directive('overlayForm', function() {
return {
restrict: 'E',
scope: {
formsetup: '='
},
controller: [ '$http', '$scope',
function OverlayFormController($http, $scope) {
var self = this;
self.getResponseData = function(){
$http.get('data/form-data.json').then(function(response) {
self.formdata = response.data;
});
}
this.logResponseData = function() {
console.log(self.formdata);
}
this.getResponseData();
}
],
controllerAs: 'Ctrl',
bindToController: true,
templateUrl: 'scripts/directives/overlay-form/overlay-form.template.html',
};
});
Directive View
<div class="overlay-form">
<h3>{{formsetup.title}}</h3>
Problem is with template binding.It should be:(when you use controllerAs you need to refer view elements with the alias name)
<div class="overlay-form">
<h3>{{Ctrl.formsetup.title}}</h3>
</div>
And directive HTML code should be:
<overlay-form formsetup="main.formsetup"></overlay-form>
Please check Plunker for more understanding of how it works.

AngularJS $location.url() only works on Page Refresh

I've created a directive, called admin, which I only want to show if the base URL contains the string admin and then depending on the URL path the directive's view will show different content.
So if the user navigates to admin.example.com they will see the directive's view, they won't see it if they go to just example.com.
I've got it working to an extent, it works when I first load the angularJS app, but not when I click on different pages of the app and load different views and routes.
This is my basic App:
/* Define the `app` module */
var app = angular.module('app', ['ngRoute', 'ngTouch', 'ngAnimate', 'ui.bootstrap', 'ngSanitize']); // TODO: 'ngAnimate', 'ui.bootstrap'
app.config(['$routeProvider','$locationProvider', function($routeProvider, $locationProvider){
$routeProvider
.when('/', {
class: 'home-page',
title: 'Home',
templateUrl: '/app/static/home.html',
controller: 'mainController as mainCtrl'
})
.when('/about', {
class: 'about-page',
title: 'About',
templateUrl: '/app/static/about.html',
controller: 'mainController as mainCtrl'
})
.when('/news/id-:newsId', {
class: 'news-page',
title: 'News',
templateUrl: 'app/components/news/details/newsDetailsView.html',
controller: 'newsDetailsController as newsDCtrl'
})
.otherwise({
class: 'page-not-found',
title: '404 Page Not Found',
templateUrl: '/app/static/404.html',
controller: 'mainController as mainCtrl'
});
// use the HTML5 History API
$locationProvider.html5Mode(true);
}]);
app.run(['$rootScope', function($rootScope) {
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
$rootScope.class = current.$$route.class;
$rootScope.title = current.$$route.title;
$rootScope.description = current.$$route.description;
});
}]);
app.controller('adminController', ['$location', function ($location) {
var adminCtrl = this;
adminCtrl.isAdmin = $location.host().includes('admin.example') ? true : false;
adminCtrl.urlPath = $location.url();
}])
app.directive('admin', [function(){
return {
restrict: 'E',
templateUrl: 'app/components/admin-links/adminView.html',
transclude: true,
replace: true,
controller: 'adminController as adminCtrl'
};
}]);
The problem is adminCtrl.urlPath = $location.url(); only updates when I refresh the page, not when I click on the different routes within the app and change the page without refreshing the page.
How can I get this value to update when the user navigates through the different views/routes without refreshing the page?
You need to use $location.path()
This will give you the route paths.
Change
adminCtrl.urlPath = $location.url();
to
adminCtrl.urlPath = $location.path();

How to setup a parent/child with a resolve and nested states in ui-router?

I'm trying to refactor our current layout to add in a dynamic show/hide section above the header on our page. We are using Angularjs 1.4.2 with ui-router and currently we are using separate route files, although we have a main route. The main.html section of the screen, up to now, was the only section with the dynamic content. In order to get my new route working, I'm having to add it and main to each of the existing routes.
My question is, would a better design be something along this line of a single parent route to handle a resolve, with nested states for the dynamic main content and a new view for my new route and content? As the application grows, do you still continue to put the new routes into the parent route or is there a better way to organize it like we were doing with individual route files or a combination of both?
This is what I'm trying, but it's not working yet, as I'm getting a blank page, but the design for future growth with the application is what I'm trying to get right here:
(function () {
'use strict';
angular
.module('myApp')
.config(config);
config.$inject = ['$stateProvider'];
/* #ngInject */
function config($stateProvider) {
$stateProvider
.state('root', {
template: '<ui-view></ui-view>',
abstract: true,
resolve:{
myLoader: ['myLoader', function(myLoader){
// $http returns a promise for the url data
return myLoader.load();
}]
}
}
.state('main'), {
url: '/?param1&param2&param3&param4',
templateUrl: 'app/routes/main/main.html',
controller: 'MainCtrl',
redirectTo: 'main'
})
$stateProvider
.state('basicsearch', {
url: '/basic-search',
templateUrl: 'app/routes/basicsearch/basicsearch.html',
controller: 'searchQuickCtrl'
})
$stateProvider
.state('advancedsearch', {
url: '/advaned-search',
templateUrl: 'app/routes/advancedsearch/advancedsearch.html',
controller: 'advancedSearchkCtrl'
})
$stateProvider
.state('anothersearch', {
url: '/another-search',
templateUrl: 'app/routes/anothersearch/anothersearch.html',
controller: 'anotherSearchCtrl'
})
.state('myChange', {
url: '/myChange?param5&param6&param7&param8',
views: {
"myChangeView": {
templateUrl: '/app/routes/myChange/myChange.html',
controller: 'myChangeCtrl'
}
}
});
}
})();
Here is a basic layout of what we have:
I am not sure about the issue. But I created working plunker, to show you how we can use state nesting and resolve.
So this is our parent controller for root state and the factory used in resolve
.factory('myLoader', function(){
return {
load: function () {return [1,2] }
};
})
.controller('ParenCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {
$scope.myLoader = myLoader;
}])
.controller('MainCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {}])
.controller('searchQuickCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {}])
.controller('advancedSearchkCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {}])
.controller('anotherSearchCtrl', ['$scope', function ($scope) {}])
Next, we will use controller for abstract root state, to assign result into scope:
.state('root', {
template: '<div ui-view></div>',
abstract: true,
controller: 'ParenCtrl',
resolve:{
myLoader: ['myLoader', function(myLoader){
// $http returns a promise for the url data
return myLoader.load();
}]
}
})
And all states will use that as their parent, and inherit the scope:
.state('main', {
parent: 'root',
url: '/?param1&param2&param3&param4',
templateUrl: 'app/routes/main/main.html',
controller: 'MainCtrl',
//redirectTo: 'main'
})
.state('basicsearch', {
url: '/basic-search',
templateUrl: 'app/routes/basicsearch/basicsearch.html',
parent: 'root',
controller: 'searchQuickCtrl'
})
.state('advancedsearch', {
url: '/advaned-search',
templateUrl: 'app/routes/advancedsearch/advancedsearch.html',
parent: 'root',
controller: 'advancedSearchkCtrl'
})
.state('anothersearch', {
url: '/another-search',
parent: 'root',
templateUrl: 'app/routes/anothersearch/anothersearch.html',
controller: 'anotherSearchCtrl'
})
And properly use it inside of this view:
<div >
<h3>current state name: <var>{{$state.current.name}}</var></h3>
<h4>resolved in parent</h4>
<pre>{{myLoader | json }}</pre>
<h5>params</h5>
<pre>{{$stateParams | json}}</pre>
<h5>state</h5>
<pre>{{$state.current | json}}</pre>
</div>
while all these controller do not use that resolved value
.controller('MainCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {}])
.controller('searchQuickCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {}])
.controller('advancedSearchkCtrl', ['$scope', 'myLoader', function ($scope, myLoader) {}])
.controller('anotherSearchCtrl', ['$scope', function ($scope) {}])
So, each controller can be provided with stuff resolved in parent (as parameter). That is shown above. But also, becuase parent already used that and assigned that to some $scope variable... all is alraeady in place.
Also check:
Angularjs ui-router abstract state with resolve
How do I share $scope data between states in angularjs ui-router?

How to pass and read multiple params in URL - angularjs

There are 2 views with respective controllers.
The links are in View1.Clicking on this link should load View2 and also read the parameters.
This is what I have tried but the alert says - undefined.
View2 can load with/without params - each case having a different workflow.
View1.html:
<div ng-controller="view1ctrl">
<a href="#/view2/pqid/775/cid/4" >Link1</a>
</div>
app.js:
var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/view1', {
templateUrl: 'App/views/view1.html',
controller: 'view1ctrl'
})
.when('/view2', {
templateUrl: 'App/views/view2.html',
controller: 'view2ctrl'
})
.when('/view2/:pqid/:cid', {
templateUrl: 'App/views/view2.html',
controller: 'view2ctrl'
})
.otherwise({
redirectTo: '/view1'
});
}]);
view2ctrl.js:
app.controller("view2ctrl", ['$scope','$routeParams',function ($scope, $routeParams) {
var init = function () {
alert($routeParams.pqid);
}
init();
}]);
You are nearly there:
.when('/view2/:pqid/:cid'
maps to a URL in this format :
view2/1234/4567
1234 being the pqid and 4567 the cid.
So your routing, I think is working, just change the link to #/view2/775/4.
Or if you want to keep the same link change your routing to:
#/view2/pqid/:pqid/cid/:cid

child state controller not being reached in angular-ui-router

I have the following setup in my code
.config(function config($stateProvider)
$stateProvider
.state('home', {
url : '/home',
views : {
'main' : {
controller : 'HomeCtrl',
templateUrl : 'home/home.tpl.html'
}
}
})
.state('home.details', {
url : '/details',
views : {
" " : {
template : "<h1>hello</h1>",
controller : function ($scope, $http, $state) {
//do some stuff here
//does not seem to reach code in here
}
}
}
});
})
.controller('HomeCtrl', function ($scope, $http, $state) {
//on a button click do $state.go('.details');
});
When I do this , the button click on my HomeCtrl seems to take me to /home/details but it does not seems to go inside the controller in that particular route at that point. (I checked by putting a break point inside the controller for the details.) Is there something wrong with my setup? I'm trying to do something similar to this sample app shown in the ui-router webpage.
The solution here would in a named-view (not) matching. Here is the working plunker.
We have to place the named ui-view inside of the parent view (or use more precise naming, see for example here)
So, the parent, home template should contain the named ui-view, e.g. nameOtherThanSpace
<div ui-view="nameOtherThanSpace" ></div>
And the child defintion must target that view, the complete snippet is:
$stateProvider
.state('home', {
url: '/home',
views: {
'main': {
controller: 'HomeCtrl',
template: '<div>' +
'<h1>hello from parent</h1>' +
'<hr />' +
'<div ui-view="nameOtherThanSpace" ></div>' +
'<div>',
}
}
})
.state('home.details', {
url: '/details',
views: {
"nameOtherThanSpace": {
template: "<h2>hello from a child</h3>",
controller: function($scope, $http, $state) {},
}
}
});
How to use more specific view names:
View Names - Relative vs. Absolute Names
UI-Router isnt rendering childs correctly with templateurl in Asp.net Mvc
The working plunker using the name nameOtherThanSpace, instead of " " (space)
Try registering your controller on the app instead of on your $stateProvider. e.g.
var app = angular.module('myApp', []);
app.controller('HomeCtrl', function ($scope, $http, $state) {
//on a button click do $state.go('.details');
});
Update 1:
You should only need to specify a view if you have multiple views in which case the view probably needs to have a name. But you only have one view for that state so I would just do this.
.state('home.details', {
url : '/details'
template : "<h1>hello</h1>",
controller : function ($scope, $http, $state) {
//do some stuff here
//does not seem to reach code in here
}
}

Resources