UI-Router not running controller when there is a view section - angularjs

I've a very strange problem, it apparently seems that simple views template are preventing a controller to be executed, I can't understand why.
I've built a simple plunker
Code is here:
angular.module('plunker', ["ui.router"])
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("/nested");
$stateProvider
.state('nested', {
url: '/nested',
controller: function($scope) {
$scope.sayHi = 'Hi';
this.sayHello = 'Hello';
},
controllerAs: 'dog',
//If I comment the "views" section, controller runs correctly
views: {
'main': {
template: 'MainContent'
},
'secondary': {
template: 'SecondaryContent'
}
}
})
$locationProvider.html5Mode(false).hashPrefix('!');
})
html:
<div ui-view>
<h1>{{dog.sayHello}}</h1>
<h1>{{sayHi}}</h1>
<p>Why no "hello" or "Hi" over here?</p>
<div ui-view="main"></div>
<div ui-view="secondary"></div>
</div>
If I comment out the "views" section in the state definition, controller runs correctly.
[edit]
Thanks to Radim, I resolved moving the controller definition in the views section:
views:{
'':{
controller: function ($scope) {
this.page = 'ten';
},
controllerAs:'dog'
},
'main#nested':{
template:'MainContent'
},
'secondary#nested':{
template:'SecondaryContent'
}
}

Check:
UI-Router Multiple Views Single Controller not work
The controller always belongs to view, not to state.
There is an updated plunker
.state('nested', {
url: '/nested',
//controller: function($scope) {
// $scope.sayHi = 'Hi';
// this.sayHello = 'Hello';
//},
//controllerAs: 'dog',
//If I comment the "views" section, controller runs correctly
views: {
'main': {
//template: 'MainContent', - let's use view, to consume dog.sayHello
templateUrl: 'view2.html',
controller: function($scope) {
$scope.sayHi = 'Hi';
this.sayHello = 'Hello';
},
controllerAs: 'dog',
},
'secondary': {
template: 'SecondaryContent',
controller: ...
}
}
check the updated plunekr

Related

How can I get ui-router resolve to work when using ng-controller?

Is it possible to get resolves working when using ng-controller? I prefer to use ng-controller as it allows me access to all 1.6+ life-cycle hooks such as $onDestroy, which I loose when defining the controller on state obj.
Plunker:
https://plnkr.co/edit/2FJ0dGtFQtBtcQ0uVbTi?p=preview
In the example below, the view loaded in 'main' makes the myData available to inject, however in main2 the controller is defined with ng-controller and myData is no longer available to inject.
$stateProvider.state('home', {
url: '/',
views: {
'main': {
controller: 'MainCtrl',
controllerAs: 'vm',
templateUrl: 'main.html'
},
'main2': {
templateUrl: 'main2.html'
}
},
resolve: {
myData: function() {
return ['My', 'resolve', 'is', 'working'];
}
}
});
Instantiated with ui-router state:
app.controller('MainCtrl', function(myData) {
console.log("MainCtrl")
console.log(myData);
console.log(this);
this.message = myData.join(' ');
});
Instatiated with ng-controller:
app.controller('MainCtrl2', function($scope) {
console.log("MainCtrl2");
console.log($scope);
this.message = $scope.$resolve.myData.join(' ');
});
The DEMO on PLNKR.

AngularJS router - parent controller loads when child is clicked

I am having strange results working with AngualarJS states. Here is app code:
/* myApp module */
var myApp = angular.module('myApp', ['ui.router'])
.config(function ($stateProvider) {
$stateProvider.state('home', {
url: "home",
template: '<div ui-view><h3>Home</h3><a ui-sref="home.child({reportID:1})">Child</a></div>',
params: { reportID: null },
controller: function ($scope) {
$scope.homeCtrlVar = "home";
console.log("Home controller loaded");
}
}).state('home.child', {
template: '<div><h3>Child</h3><a ui-sref="home">Back</a></div>',
controller: function ($scope) {
$scope.childCtrlVar = "child";
console.log("Child controller loaded");
}
});
})
.controller('MainCtrl', function ($scope, $state) {
console.log("MainCtrl initialized!");
$state.go("home");
});
And main page:
<div ng-app="myApp" ng-controller="MainCtrl">
<h2>My app</h2>
<div ui-view></div>
What's happening is that as long as there parameters for the home state and reportID value doesn't match between a parameter being sent and the state default the home controller is loaded when I click on Child. Can someone please explain why that's happening?
Fiddle
Here is updated code which works as you expect it to:
var myApp = angular.module('myApp', ['ui.router'])
.config(function ($stateProvider) {
$stateProvider.state('home', {
url: "home",
template: '<div ui-view><h3>Home</h3><a ui-sref="home.child({reportID:1})">Child</a></div>',
controller: function ($scope) {
$scope.homeCtrlVar = "home";
console.log("Home controller loaded");
}
}).state('home.child', {
url: "/:reportID",
params: { reportID: null },
template: '<div><h3>Child</h3><a ui-sref="home">Back</a></div>',
controller: function ($scope) {
$scope.childCtrlVar = "child";
console.log("Child controller loaded");
}
});
})
Problem with your approach:
specifying params reportID in home state instead of home.child state.
When user clicks on home.child({ reportId: 1}) it should load home.child, which is fine, and was working with old approach.
However, If you take notice, as you click on home.child({ reportId: 1}), you are sending new parameter reportID(old value was null). reportID belongs to home state, hence its controller is also loaded.
Note that url: "/:reportID" in state home.child is optional.

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.

UI Router is not injecting resolved value into views at the same level

I have the following state configuration:
$stateProvider
.state('customer', {
url: '/customers',
templateUrl: 'app/components/customer/templates/main.tpl.html',
views: {
'list': {
templateUrl: 'app/components/customer/templates/list.tpl.html',
controller: 'ListCtrl as ctrl'
}
},
resolve: {
customerList: function ($stateParams, CustomerResource) {
console.log('trying to resolve');
var list = CustomerResource.list($stateParams);
return list;
}
}
})
Here is the main template:
<div class="container">
<div class="row">
<div ui-view="list" class="col-lg-4"/>
<div ui-view="details" class="col-lg-8"/>
</div>
</div>
I see on the console that angular is trying to resolve the dependency. But this dependency is never injected into the controller under views. What am I doing wrong here?
P.S. When I move views to some child state like customer.test the resolved dependency is injected as expected:
.state('customer.test', {
url: '/test',
views: {
'list#customer': {
templateUrl: 'app/components/customer/templates/list.tpl.html',
controller: 'ListCtrl as ctrl'
}
}
})
There is a working plunker
The problem here is not with resolve, but with the injection of the main template. This should be the state definition:
.state('customer', {
url: '/customers',
//templateUrl: 'app/components/customer/templates/main.tpl.html',
views: {
'': {
templateUrl: 'app/components/customer/templates/main.tpl.html'
},
'list#customer': {
templateUrl: 'app/components/customer/templates/list.tpl.html',
controller: 'ListCtrl as ctrl'
}
},
resolve: {
customerList: function ($stateParams, CustomerResource) {
console.log('trying to resolve');
var list = CustomerResource.list($stateParams);
return list;
}
}
})
so, with these in play:
.controller('ListCtrl', ['$scope', 'customerList', function ($scope, customerList) {
$scope.resolved = customerList
}])
.factory('CustomerResource', function(){
return {
list: function(params){return "Hello world"}
}
})
we can see that list view like this:
<div>
<h3>list view</h3>
{{resolved}}
</div>
will show Hello world
Check it here in action

Why to use abstract keyword in angular js?

I recently started with Ionic framework but to begin with Ionic Angular.js is pre-requisite. Can any one please tell me why do we use abstract keyword in nested ui-router. I was unable to find a good tutorial on that.
Please tell me what is the significance/advantages of abstract in angular.js.
Here is the code that I am unable to understand
var app = angular.module('ionicApp', ['ionic'])
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/todos')
$stateProvider.state('app', {
abstract: true,
templateUrl: 'main.html'
})
$stateProvider.state('app.todos', {
abstract: true,
url: '/todos',
views: {
todos: {
template: '<ion-nav-view></ion-nav-view>'
}
}
})
All right I will just post some of my experience using ionic.
- Ui router parent/child scope inheritance
This is not specific to ionic. Sometimes if you want to share some common function in relation to views, by using scope inheritance, you can consolidate default/share function in the parent, or setup default states in your view, or inherit resolved dependencies.
$stateProvider.state('app', {
abstract: true,
templateUrl: 'main.html',
resolve: {
commonMeta: ["$stateParams","$http", function($stateParams, $http) {
return $http.get("/commonMeta/" + $stateParams.id);
}]
},
controller: ["$scope", function($scope) {
// controller should be in a separate file
$scope.data = {};
$scope.state = {};
$scope.display = function() {
// common logic for page display
};
$scope.restoreCustomerPreference = function() {
// restore customer preference from say localstorage
};
$scope.init = function() {
// 'this' will be the child $scope
this.state = {
error: false
};
this.restoreCustomerPreference();
};
}],
});
$stateProvider.state('app.dashboard', {
url: '/dashboard',
views: {
todos: {
template: '<ion-nav-view></ion-nav-view>'
}
},
resolve: {
data: ["$http", function($http) {
return $http.get("/getDashboard/");
}]
},
controller: ["$scope","commonMeta","data" function($scope,commonMeta,data) {
// controller should be in a separate file
$scope.data = data;
$scope.display = function() {
// override the display logic
$
};
$scope.init();
}]
});
$stateProvider.state('app.todos', {
url: '/todos',
views: {
todos: {
template: '<ion-nav-view></ion-nav-view>'
}
},
resolve: {
data: ["$http", function($stateParams, $http) {
return $http.get("/getTodos/");
}]
},
controller: ["$scope", function($scope) {
// controller should be in a separate file
$scope.data = data;
$scope.someFn = function() {
// some function
};
$scope.init();
}]
});
Try to use controller: ["$scope", function($scope) { }] this form of dependencies injection, so your codes do not break when minified.
- ionic-slide-box
Using ionic-slide-box can develop app that user can slide left and right to get to next page, similar to facebook, by using a simple directive to fetch your template.
angular.module("directives",[])
.directive("slideItem", ["$http","$templateCache","$compile",function($http,$templateCache,$compile) {
return {
replace: false,
restrict: "EA",
scope: {
template: "#"
},
link: function(scope, iElement, iAttrs, controller) {
var events = scope.events();
$http.get(iAttrs.template, {cache: $templateCache})
.success(function(result) {
iElement.replaceWith($compile(result)(scope));
});
}
};
}]);
The template file can be hardcoded one:
<ion-view>
<ion-slide-box show-pager="false">
<ion-slide>
<slide-item template="templates/dashboard.html"></slide-item>
</ion-slide>
<ion-slide>
<slide-item template="templates/todos.html"></slide-item>
</ion-slide>
<ion-slide>
<slide-item template="templates/sumary.html"></slide-item>
</ion-slide>
</ion-slide-box>
</ion-view>
or using ng-repeat:
<ion-view>
<ion-slide-box show-pager="false">
<ion-slide ng-repeat="o in slides">
<slide-item template="templates/{{o.template}}.html" ></slide-item>
</ion-slide>
</ion-slide-box>
</ion-view>
Weinre is a debug tool that you must have, it can debug even
when your app is installed to a real device. without that, when your
app start with a blank screen you just don't know what to look for.
ngCordova is a wrapper for cordova function develop by ionic
too.
Android device can be test easily, TestFlight is good for ios
testing.
When using nested states an abstract state can have child states but cannot be activated itself.
Reference:
Nested States & Nested Views

Resources