Angular ui router shared param object between states with components - javascript

I am writing an angular app where a parent controller manages the global state, and it transitions between states to display information to users.
I followed this answer and used params to send objects to the child component/controller. It works fine, but if I update the values in the child object, these changes are not reflected in the parent.
My code is organised like this:
index.html
<bodyng-controller="main-loop">
...
<li ui-sref="supernova({data:data})" ui-sref-active="active">
data is an object, {"a":1,"b":2}
routes
.state("supernova", {
component: 'supernova',
params: { 'data': null }
component
component('supernova', {
templateUrl: 'views/supernova.html',
controller: ['$stateParams', supernova],
controllerAs: 'supernova'
});
controller
function supernova($stateParams) {
var ctrl = this;
ctrl.data= $stateParams.data;
I can read the value of data fine from both the view and the controller. However if I make a change like this:
ctrl.stuff = function () {
ctrl.data.a = 20;
The changes are visible in the component but not in the parent controller.
Is there any way to share the same object instance between both using ui-router constructs? That is, changes in one are reflected in the other (in a two-way binding style).

Related

Does parent controller get re-initialized again while navigating between child tabs?

I have a parent controller and two tabs within it, each having its own controller as shown in image below:
Parent and Child View Model
I am broadcasting an event from Tab1/Tab2 controllers using $rootScope and catching it directive's controller scope using $scope. When I am switching between tabs, the parent controller and the directive's controller are getting initialized again and the event is caught twice in directive's controller even though it is broadcasted just once.
I tried to debug and noticed the directive's controller $id when it was being caught. When the event was caught first time, the directive's id was the one which got created with previous tab(tab from which i switched). And second time, directive's id was the one which got created with current tab (tab to which i switched).
Note: 1) Even after using reload: false option, parent controller and directive controller is getting re-initialized.
2) I am not initializing parent controller by using ng-controller in html. It is getting initialized only via $stateProvider routing.
This is how i configured my states:
$stateProvider
.state('app.parent', {
url: 'parent/:paramId',
params: {
paramId: 'sample'
},
views: {
'content-parent#app': {
templateUrl: 'parent.html',
controller: 'ParentController',
resolve: {
defaultParams: function(parentService, $stateParams) {
return parentService.getDefaultParams($stateParams.paramId);
}
}
}
}
})
.state('app.parent.tab1', {
url: '/tab1/',
views: {
'content-child#app.parent': {
templateUrl: 'tab1.html',
controller: 'Tab1Controller'
}
}
})
.state('app.parent.tab2', {
url: '/tab2/',
views: {
'content-child#app.parent': {
templateUrl: 'tab2.html',
controller: 'Tab2Controller'
}
}
});
No, the parent state (and thus controller/component) is not re-initialized when the child states are changed. This includes any resolve functions and state event handlers associated with the parent state.
Here's a Plunker that illustrates that the parent scope remains the same when you navigate between child states.
You can force it to reload the parent though, by setting the reload option of $state.go(..) to true. Like this:
$state.go('parent.childA', null, { reload: true });
The parent State remains unchanged as long as you do not push the changes to the parent controller using emit.
Here is a c-sharpcorner link that might help you!

AngularJS : pass data from controller to controller without factory, service or broadcast?

I don't want to use a service or a factory and would like to pass for example an array of data. I would like to access data in my parent controller from my child component.
Factory and service are excluded since i eventually want to migrate my app to angular 2, and i don't want to use ngclick which seems inseperable with broadcast/up/on.
If anyone knows how to pass data on the background (without user interaction, like input or ngclick) using broadcasting, it would work aswell :)
What are my options ?
Thank you !
If you have nested components, you can access the parent's data with require
var child = {
bindings: {},
require: {
parent: '^^parent'
},
controller: childController,
templateUrl: 'child.template.html'
};
Now in your child controller you have access to an instance of the parent controller and thus can call methods and access it's properties:
this.parent.parentMethod();
You have some more detailed code in a previous answer here:
Where should I place code to be used across components/controllers for an AngularJS app?
Your other choices:
bindings
Just like directives' scope or bindToController you can bind data and methods through html attributes using the bindings propety of your component
<component-x shared="$ctrl.shared"></component-x>
var componentX = {
bindings: { shared: '=' }
...
$rootScope
Never use it to store data. It works but it's not made for that purpose and will lead to unmaintainable code.
Services
It's a common misconception that shared data should be done through services.
It was true and good practice before 1.5 though.
Controller inheritance
Another bad practice (imo).
In a classic MVC app nested controllers can inherit parents with the $controller service:
.controller('MainController', function ($scope) {
$scope.logMessage = function(message) {
console.log("Message: " + message);
}
})
.controller('ServicesController', function($scope, $controller) {
$controller('MainController', {$scope: $scope});
});
Broadcast and emit Events
It's the way to go if the event you're broadcasting makes sense application wide (login, logout...etc.) If you're updating a variable in a component, don't use it.
I don't want to use a service or a factory and would like to pass for
example an array of data
You can use localStorage/sessionStorage to store and fetch the data

Angular ui-router: Child not getting Parent state

I have an Angular app using ui-router with the following states defined in my app.
$stateProvider
.state('classes', {
url: '/classes/:type',
templateUrl: 'classes.html',
controller: 'ClassesCtrl',
})
.state('classes.detail', {
url: '/:id',
views: {
"#": {
templateUrl: 'class.html',
controller: 'ClassCtrl'
}
}
});
and my controllers look like:
app.controller('ClassesCtrl', function($scope, $stateParams){
$scope.foo = "bar";
});
app.controller('ClassCtrl', function($scope, $stateParams){
console.log($scope.foo);
});
As you can see, class.detail hides the parent view by targeting the root level unnamed ui-view. The issue I'm having is that the child is not inheriting the parent's scope (I can't access $scope.foo in class.detail). It seems like the parent state gets destroyed when I go to the child state because if I click back, it has to reload all the data.
How do I effectively hide the parent view but still access the parent data?
From the ui-router documentation:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
Well, you're using # to define your view but your parent state is named, so you should name it #classes
Edit: I made this fiddle to explain about names.

Angular UI router - does a nested view's controller automatically become a child of the parent view's controller?

Here's my UI-Router configuration. I have a main products view and two nested states underneath that. I want each nested view to have its own controller, but I also want to be able to inherit some basic information from the parent controller (productsCtrl). My assumption would be that I could access productsCtrl from productShowCtrl, but it keeps coming back as undefined. Do productsCtrl and productShowCtrl not have a parent-child relationship by default?
var myProducts = angular.module('Products', [
'ProductsShow'
])
.config(function($stateProvider, $urlRouterProvider){
$stateProvider.state('store.products', {
url: '/products',
views: {
'subhead#': {
templateUrl:'products/list/subhead.tmpl.html'
},
'content#': {
templateUrl:'products/list/list.tmpl.html',
controller: 'ProductsCtrl as productsCtrl'
}
}
})
.state('store.products.create', {
url: '/create',
views: {
'subhead#': {
templateUrl: 'products/create/subhead.tmpl.html'
},
'content#': {
templateUrl: 'products/create/create.tmpl.html'
}
}
})
.state('store.products.show', {
url: '/:productId',
views: {
'subhead#': {
templateUrl: 'products/show/subhead.tmpl.html'
},
'content#': {
templateUrl: 'products/create/create.tmpl.html',
controller: 'ProductShowCtrl as productShowCtrl',
}
}
});
});
ProductsCtrl (for the main products view):
myProducts.controller('ProductsCtrl', function($stateParams) {
var productsCtrl = this;
productsCtrl.parentTest = "PARENT";
});
ProductsShowCtrl (for the child view):
angular.module('ProductsShow', [])
.controller('ProductShowCtrl', function($stateParams) {
var productShowCtrl = this;
productShowCtrl.childTest = "CHILD";
console.log('parent: ', productsCtrl.parentTest);
});
I can't access productsCtrl.parentTest from ProductShowCtrl. How can I access it?
There are related Q & A:
ui router - nested views with shared controller
How do I share $scope data between states in angularjs ui-router?
I created special working plunker
The example here with controllerAs could be like this:
The state defintion:
.state("main", {
controller:'mainController',
controllerAs:'mainCtrl',
url:"/main",
templateUrl: "main_init.html"
})
.state("main.1", {
controller:'childController',
parent: 'main',
url:"/1",
templateUrl: 'form_1.html'
})
.state("main.2", {
controller:'childController',
parent: 'main',
url: "/2",
templateUrl: 'form_2.html'
})
Main controller will define the Model:
app.controller('mainController', function ($scope) {
var Model = {Name : "xxx"}; // controller property Model
})
And in form_1 or form_2 we can access that model with controllerAs syntax:
<div>
<h5>Child state FORM 2</h5>
<pre>{{mainCtrl.Model}}</pre>
Change the value in a child state FROM 2
<input ng-model="mainCtrl.Model.Name" />
</div>
Where mainCtrl.Model represents the reference to parent controller (inside of our and its $scope)
Check it here
The $scope doesn't work like that. They don't have a parent-child relationship by default like you asked. You can read more about it in the angular api docs.
The $rootScope has, but still it's bad practice in my opinion.
What you should do in angular when you want to access a variable in two different controllers like your trying you should create a service (factory) to do that.
Maybe you should have a products service in which you could declare variables and functions to access those variables in the conrollers.
Please note that from the ui-Router official docs
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain
if the views of your states are nested. Inheritance of scope
properties has nothing to do with the nesting of your states and
everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates
populate ui-views at various non-nested locations within your site. In
this scenario you cannot expect to access the scope variables of
parent state views within the views of children states.
So you can not have the scope variables in above way.
If you wants to share data across the states you can do in following ways :
1). Any time you need to share data across states you will need to create a service/factory that you can instantiate in your controllers associated with those states. The factory will consist of basic getters and setter for the different data you need to share. Just like when you build getters and setters in java to share across classes.
Demo : Working plunker with this approach.
2). You can set a global variable with $rootScope. It will be accessible everywhere since its global, I strongly advise you don't do this but though I would point it out to you anyway.
3).When a state is "active"—all of its ancestor states are implicitly active as well.So you can build your states considering the parent-child relationship and share data across scopes in hierarchical manner.
Demo : Working plunker with mentioned approach.

How to update directives based on service changes?

I have a directive(parent-directive) containing a slider(mySlider), that on stop event, call an angular $resource service with 2 params and the service return an object.
Directives structure:
<parent-directive>
<div ui-slider="slider.options" ng-model="mySlider" id="my-slider">
<span child-directive-one></span>
<span child-directive-two></span>
<span child-directive-three></span>
<div>
<span child-directive-four></child-directive-four>
</div>
</parent-directive
Whenever the user drag the slider, the service is called with different params and retieve new result, based on it I need to update the child directives.
I have in mind three ways:
using ng-model for all child elements instead directives, binding them on the scope of a controller in parent-directive;
the second one, that I don't know how to do it, is to create a controller in the parent-directive, that send and receive data from the service and share it to child-directives in order to update them.
the last one is to to create a state variable in the service and update it using a controller like to point 1.(see it above) and use a $watch to supervise the variable state and when it's changed then update the child-directives.
How should I proceed?
Please have a look here to see a brief code:
http://jsfiddle.net/v5xL0dg9/2/
Thanks!
ngModel is intended for two way binding, i.e. controls that allow the user to interfere with the value. From the description, it seems they are display-only components. So I would advise against using the ngModel.
Normally child directives require their parent. This allows them to call methods on the parent controller. What you need is the opposite: the parent controller needs to call methods on the children. It can be done: the children call a registerChild() method, and the parent iterates all registered children when it needs to call them. I find this implementation cumbersome.
Services are globals/singletons. I would vote against tying the service implementation to the UI needs.
My advice looks like your implementation of option 3, but with the parent controller holding the data:
1) Place the data you want to share with the child directives in a member variable of the parent controller:
myApp.directive('parentDirective', ['myService', function(myService){
...
controller: function($scope) {
...
this.sharedThing = ...;
}
}]);
The sharedThing can be updated when the service returns new data, or any other time it is necessary.
2) Have the children require the parent (just like your option 2), and watch this property:
myApp.directive('childDirectiveOne', function() {
return {
...
require: 'parentDirective',
link: function(scope, elem, attrs, parentDirective) {
scope.$watch(
function() {
return parentDirective.sharedThing;
},
function(newval) {
// do something with the new value; most probably
// you want to place it in the scope
}
});
}
};
});
Depending on the nature of the data, a deep watch may be required.

Categories

Resources