Passing object to all components from parent state in UI-Router - angularjs

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.

Related

AngularJS UI-Router won't display all Components (only some)

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

How to watch Route changes in AngularJS component (v1.5.8)

I'm building the app in AngularJS 1.5.8.
I've a main root component called app.component.
in app.component, I wanna watch all routes changes which happens navigating between child components.
And in watch, I need to get routeParams, as well.
I've defined the routes via $routeConfig in angularjs component-router
I've tried with this.$routerOnActivate but it wasn't called because I'm on root component.
var controller = function ($scope) {
$scope.$on('$routeChangeSuccess', (event, data) => {
// This is called everytime route is changed
// Here, I need to get the routeParams
// Not sure this is the correct way to watch route changes in angular component-router
});
}
angular
.module('example')
.component('RootComponent', {
templateUrl: 'app/components/enter/enter.component.html',
controller: controller,
$routeConfig: [
{path: '/a', name: 'AComponent', component: 'AComponent', useAsDefault: true},
{path: '/b', name: 'BComponent', component: 'BComponent'},
]
});
Thanks.
PS
I've tried with How to watch for a route change in AngularJS?.
It was called everytime I change the route but I can't get routerParams from it.
On the official doc, it said 3 params are available - $event, current, next but only $event is coming, the rest is undefined.
I thinks it's working when we define routes via $routeProvider but not in my case.

ui-router is reloading controller

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.

Where should I place code to be used across components/controllers for an AngularJS app?

Should it be associated with the app module? Should it be a component or just a controller? Basically what I am trying to achieve is a common layout across all pages within which I can place or remove other components.
Here is what my app's structure roughly looks like:
-/bower_components
-/core
-/login
--login.component.js
--login.module.js
--login.template.html
-/register
--register.component.js
--register.module.js
--register.template.html
-app.css
-app.module.js
-index.html
This might be a bit subjective to answer but what I personally do in a components based Angular application is to create a component that will encapsulate all the other components.
I find it particularly useful to share login information without needing to call a service in every single component. (and without needing to store user data inside the rootScope, browser storage or cookies.
For example you make a component parent like so:
var master = {
bindings: {},
controller: masterController,
templateUrl: 'components/master/master.template.html'
};
function masterController(loginService) {
var vm = this;
this.loggedUser = {};
loginService.getUser().then(function(data) {
vm.loggedUser = data;
});
this.getUser = function() {
return this.loggedUser;
};
}
angular
.module('app')
.component('master', master)
.controller('masterController', masterController);
The master component will take care of the routing.
index.html:
<master></master>
master.template.html:
<your common header>
<data ui-view></data>
<your common footer>
This way, every component injected inside <ui-view> will be able to 'inherit' the master component like so:
var login = {
bindings: {},
require: {
master: '^^master'
},
controller: loginController,
templateUrl: 'components/login/login.template.html'
};
and in the component controller
var vm=this;
this.user = {};
this.$onInit = function() {
vm.user = vm.master.getUser();
};
You need to use the life cycle hook $onInit to make sure all the controllers and bindings have registered.
A last trick to navigate between components is to have a route like so (assuming you use ui-router stable version):
.state('home',
{
url : '/home',
template : '<home></home>'
})
which will effectively route and load your component inside <ui-view>
New versions of ui-router include component routing.

Using ui-router and controllerAs can I simplify the adding of services?

I have this code:
var home = {
name: 'home',
template: '<div data-ui-view></div>',
url: '/',
templateUrl: 'app/access/partials/home.html',
controller: ['accessService', function (accessService: IAccessService) {
this.ac = accessService;
}],
controllerAs: 'home'
};
var homeAccess = {
name: 'home.access',
url: 'Access',
templateUrl: 'app/access/partials/webapi.html',
controller: ['accessService', function (accessService: IAccessService) {
this.ac = accessService;
}],
controllerAs: 'homeAccess',
resolve: {
abc: ['accessService', function (accessService) {
return accessService.getAbc();
}],
def: ['accessService', function (accessService) {
return accessService.getDef();
}]
}
};
Now that I am using controllerAs is there a way that I can simplify this code so as to eliminate adding the accessService into both of the controllers and into the two parts of the resolve? Also if I did this then how could I get to the access service inside the home.html and also the webapi.html?
There are probably a few different ways, just thinking out loud here.
Your home state is the parent of home.access and it uses a ui-view to show the child state. As such the template for the child state can reference the controller in the parent state. This is just the regular inheritance of $scope in Angular views, although it's much cleaner b/c you are using the controllerAs syntax.
For example, your views might end up looking like this:
<home-template>
<p>{{home.someValue}}</p>
<!-- included by the ui-view -->
<home-access-template>
<p>{{homeAccess.anotherValue}}
<!-- this works b/c home is on the parent scope -->
<p>{{home.someOtherValue}}</p>
</home-access-template>
<home-template>
So if it makes sense in your scenario, you only need to inject your accessService into the parent controller. The child views will use the service through methods of the parent controller.
A similar thing can also be done with the resolves: by declaring them on the parent state, they are available to the child states. This is more useful when there are many child states for a given parent.

Resources