Angular 1.5 Nested Component Bind parent Value - javascript

I am new to angularjs. I am trying angular 1.5 nested component. Can I bind parent component property in child component.
Ex:
<div ng-app='cbsApp' ng-controller='cbsCnt as ct'>
<cbs-cus-comp com-bind='ct.name'>
<child child-com-bind='cbsCusCompCntAs.name'></child>
</cbs-cus-comp>
</div>
I can get ct.name value in com-bind. But can't get cbsCusCompCntAs.name in child-com-bind. (cbsCusCompCntAs is cbs-cus-comp controller)
Working Plunker : https://plnkr.co/edit/axQwTn?p=preview
Thanks in advance.

In your first case you are referring directly to the controller scope via controllerAs.
When using components in angular 1.5 you can get hold of your parent component via require which will make parent's properties available after $onInit as per Components Documentation:
Note that the required controllers will not be available during the
instantiation of the controller, but they are guaranteed to be
available just before the $onInit method is executed!
In your specific case you can update the child component to require the parent:
var child = {
require : {parentComp:'^cbsCusComp'},
template : 'Child : <b{{cbsCusChildCompCntAs.childComBind}}</b>',
controller : cbsCusChildCompCnt,
controllerAs: 'cbsCusChildCompCntAs'
};
and its controller to get the data you need (I used the same names as you just to see it work):
function cbsCusChildCompCnt(){
this.$onInit = function() {
this.childComBind = this.parentComp.name;
};
}
Updated plunker is here.

Wow... what a wonderful example...
Took me a while to analyse it... so, I wrote my own (I think a bit more readable) version.
I really do not know how to work with Plunker... so here's the code...
Extract from my index.html file
<div ng-controller='appCtrl as ctrl'>
<parent bind-id='ctrl.name'>
<child bind-toid='parentCtrlAs.name'></child>
</parent>
</div>
The .js file
(function () {
'use strict';
var
parentComponent =
{
bindings :
{
bindId:'='
},
controller : parentCtrl,
controllerAs: 'parentCtrlAs',
restrict : 'A',
transclude : true,
templateUrl : 'parent.html',
};
var
childComponent =
{
controller : childCtrl,
controllerAs: 'childCtrlAs',
restrict : 'A',
require :
{
myParent :'^parent'
},
templateUrl : 'child.html',
};
angular
.module('app', [])
.controller('appCtrl' , appCtrl)
.component('parent' , parentComponent)
.component('child' , childComponent);
function appCtrl(){
this.name = 'Main..';
}
function childCtrl(){
this.$onInit = function() {
this.bindToid = this.myParent.name;
};
}
function parentCtrl(){
this.name = 'Parent Component';
}
})();
Hope it helps,
Regards,
Johnny

Although using the "require" parameter works, it creates a tightly bound relationship between the component acting as a child, which uses the "require" parameter, and the component acting as a parent, which consumes the child functionality.
A better solution is to use component communication as shown here.
Basically, you define a binding function in the child component definition, like so,
angular.module('app').component('componentName', {
templateUrl: 'my-template.html',
bindings: {
myFunction: '&'
},
controller: function() { // Do something here}
});
Then, in the parent markup you provide a function to call,
Parent HTML
<user-list select-user="$ctrl.selectUser(user)">
</user-list>
Finally, in the parent controller, provide an implementation of the selectUser function.
Here's a working Plunk.

Related

OnChange of a Component binding in Angular Js

I have created a component in angular js which shows some data
But on change of my customer binding i want to call some service in component controller which is not reflecting,Here is the below code
In my Test1.html
<tab-customer title="Test Comp" customerCode="vm.customerCode" functioname="fnTest"></tab-customer>
vm.CustomerCode is changing on basis of dropdown so its changing
app.comp.TestComp is already defined
angular
.module('app.comp.TestComp')
.component('tabCustomer', {
templateUrl: 'Component.html',
controller: tabCompCustomer,
controllerAs: 'vm',
bindings: {
title: '#',
functioname: '#',
customerCode: '<'
}
});
function tabCompCustomer(tableCustomerService, $rootScope,$scope, $filter) {
var vm = this;
$scope.$watch('vm.customerCode', function (newValue, oldValue) {
console.log('got1')
console.log(newValue) // This is also not working
});
$onChanges = function (changes) {
console.log('got')
console.log(changes) // This is also not working
};
};
On Change of a Customer Code i need to fire my component again but i am not getting any fire event can anyone help
If the question is still unclear comment i will detailed it
You should be changing your attribute binding from customerCode to customer-code, So that will pass vm.customerCode to component. And remove $watch function from component controller, as $onChanges function will get fire automatically on each bindings change.
customerCode="vm.customerCode"
should be
customer-code="vm.customerCode"
Note: make $onChanges = to this.$onChanges =

Creating reusable component in Angularjs 1.5.x which can access parent controller fields

I am trying to create a reusable component which can be reused accross the application. We are using Angular 1.5.8
There is some data that needs to be passed from the parent component to child component. (Typically an object holding information). It can be
After some reading i found out there is attribute called require where you can mention the name of the parent component and then can access the methods of parent controller.
The main drawback is the parent component name is hardcoded. And that limits the reusability of the component.
Is there anyway where we can pass data from parent component to child in dynamic way.
Code sample
app.component('parent',
{ restrict: 'E',
scope: {},
templateUrl: 'app/parent.html',
controller: function(){
var vm = this;
vm.sayHello = function (){
return {
parentName : 'parent1',
parentCode : 'parentCode1'
};
};
},
controllerAs: 'vm'});
app.component('child', {
require: {
parentCtrl: '^^parent'
},
controller: function() {
var self = this;
this.$onInit = function() {
self.parentCtrl.sayHello();
};
}
});
Thanks
add bindings to child component in the definition object:
app.component('child',{
bindings:{
data: '<'
},
templateUrl: 'app/child.html',
controller: childController});
then in parent.html you use the following:
Plunkr here : https://plnkr.co/edit/d6wS1dHVsYT3fNkMUVNY?p=preview
angular.noop is just an empty function so you can put if you do not have any controller for the component
$ctrl is default if you do not specify controllerAs alias name
Also you can use on child component $onInit() $onChanges() and $onDestroy() lifecycle hooks to control what the component will do at certain points.
If you use .component drop the restrict: 'E' is already an element

Is there any way to require a controller in a component that is not from another directive or component?

Can I, from a directive or component, require a controller that is not from another directive or component?
Here is what I have tried:
Attempt 1
Throws Injector Error.
angular.module('test', [])
.controller('mainController', function(){
this.someData = [{
someKey : Math.random()*10
}];
this.someFunction = function(data){
this.someData.push(data);
}
})
.component('master', {
require:{
main:'mainController'
},
controller:function(){
this.$onInit = function(){
console.log(this);
}
}
});
Attempt 2
Creates a new copy of the controller – not what I need.
angular.module('test', [])
.controller('mainController', function(){
this.someData = [{
someKey : Math.random()*10
}];
this.someFunction = function(data){
this.someData.push(data);
}
})
.component('master', {
controller:function($controller){
this.$onInit = function(){
this.main = $controller('mainController');
console.log(this);
}
}
});
To see what I mean in the second example, please see This Plunkr.
I doubt there is a way, but if I'm honest I've never fully looked into how angular does what it does. Odds are you have to create a new component/directive and you can just include its controller from there, but I was hopeful!
ng-controller is a directive. So regarding the abilities, the nearest controller can be required with
require: '^ngController'
It is not necessarily main controller, just some controller.
And regarding 'best practices', it can't, this smells bad. The code above needs no main controller at all. If there should be some global someData and someFunction - make them a service (or two). It can be injected to any component controller then, disregarding their places in DOM hierarchy.

Angular 1.5.x - Issue with nested components

First of all, I'm using components.
I have this "parent" component:
(function() {
'use strict';
angular
.module('parentModule', [])
.component('parent', {
templateUrl: 'parent.tpl.html',
controller: ParentCtrl,
transclude: true,
bindings: {
item: '='
}
});
function ParentCtrl() {
var vm = this;
vm.item = {
'id': 1,
'name': 'test'
};
}
})();
And I'm simply trying to share the object item with another component, like this:
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
require: {
parent: '^item'
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
View (Parent):
Parent Component:
<h1 ng-bind='$ctrl.item.name'></h1>
<child></child>
View (Child):
Child component:
Here I want to print the test that is in the parent component
<h2 ng-bind='$ctrl.item.name'></h2>
Actually I'm getting the following error:
Expression 'undefined' in attribute 'item' used with directive
'parent' is non-assignable!
Here's the DEMO to illustrate better the situation
Can you explain to me how I can make it work?
You need to remove the bindings from yor parent component.
bindings binds to the component controller like scope binds to a directive's scope. You're not passing anything to <parent></parent> So you have to remove it.
Then your child component requires a parent component, not an item.
So
require: {
parent: '^parent'
}
Of course the child template should be modified to:
<h2 ng-bind='$ctrl.parent.item.name'></h2>
Finally, if from the child controller you want to log the item that is inside the parent, you will have to write:
function ChildCtrl($timeout) {
var vm = this;
$timeout(function() {
console.log(vm.parent.item);
});
}
I never need the timeout in my components, so there might be something obvious that I missed.
http://plnkr.co/edit/0DRlbedeXN1Z5ZL45Ysf?p=preview
EDIT:
Oh I forgot, you need to use the $onInit hook:
this.$onInit = function() {
console.log(vm.parent.item);
}
Your child should take the item as input via bindings.
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
bindings: {
item: '='
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
So your parent template will look like
<h1 ng-bind='$ctrl.item.name'></h1>
<child item="$ctrl.item"></child>
The rest should work same.

How to access parameter from nested state in parent state?

I have an example here. How i can access 'itemId' parameter in the parent state controller? Parent view must not be reloaded.
angular.module('app',['ui.router'])
.config(function($stateProvider){
$stateProvider.state('step', {
url: '/orders/:step',
templateUrl: 'parent.html',
controller: function ($scope, $stateParams) {
$scope.itemId = $stateParams.itemId;
$scope.step = $stateParams.step;
}
})
.state('step.item', {
url: '/:itemId',
templateUrl: 'child.html',
controller: function ($scope, $stateParams) {
$scope.itemId = $stateParams.itemId;
$scope.step = $stateParams.step;
}
});
}).controller('SimpleController', function($scope, $state){
$scope.items = [1,2,3,4,5,6];
$scope.steps = ["first", "second"];
})
I see only two ways:
add an service and inject it to both controllers
access a parent scope from a child controller and pass a parameter
But both cause me to add watchers at a parent scope.
Maybe i can accomplish it easier?
First, you need to recognize that a parent state's controller runs only once when you enter the subtree of its child states, so switching between its children would not re-run the controller.
This is to say that if you want the itemId parameter to always be up to date, you'd need a $watch (which you tried to avoid).
For the first time, you could collect the itemId like so in the parent's state:
$scope.itemId = $state.params.itemId;
If you need it to be kept up-to-date, then you'd need to watch for changes, for example:
$scope.$watch(function(){
return $state.params;
}, function(p){
$scope.itemId = p.itemId;
});
plunker
Similarly, if you only need to place it in the view, then set $state on the scope:
$scope.$state = $state;
and in the view:
<div>parent /orders/{{step}}/{{$state.params.itemId}}</div>
EDIT:
I guess another way would be to call a scope-exposed function on the parent to update the value. I'm not a fan of such an approach, since it relies on scope inheritance (which is not always the same as state inheritance) and scope inheritance in a large application is difficult to track. But it removes the need for a $watch:
In the parent controller:
$scope.updateItemId = function(itemId){
$scope.itemId = itemId;
};
and in the child controller:
if ($scope.updateItemId) $scope.updateItemId($stateParams.itemId)
I can see, that you've already found your answer, but I would like to show you different approach. And I would even name it as "the UI-Router built in approach".
It has been shown in the UI-Router example application, where if we go to child state with some ID = 42 like here we can also change the other, than the main view (the hint view in this case).
There is a working example with your scenario, showing that all in action.
What we do use, is the Multiple Named Views
The parent state, now defines root view, which is injected into unnamed ui-view="" inside of the root/index.html.
A new parent.html
<div ui-view="title"></div>
<div ui-view=""></div>
As we can see, it also contains another view target (than unnamed) - e.g. ui-view="title" which we fill with another template immediately... in parent state:
$stateProvider.state('step', {
url: '/orders/:step',
views : {
'': {
templateUrl: 'parent.html',
},
'title#step': {
templateUrl:'parent_title_view.html',
}
}
})
And child? It can continue to handle main area... but also can change the title area. Both views are independent, and belong to child.
.state('step.item', {
url: '/:itemId',
views : {
'': {
templateUrl: 'child.html',
},
'title': {
templateUrl:'child_title_view.html',
}
}
So, there are no watchers, nothing... which is not shipped with the UI-Router by default. We just adjust many places (views) with the child implementation. We can still provide their content with some defaults in parent..
Check that example here. Hope it will help to see it a bit differently...
I'm not sure if this will really solve your problem but you can do
$stateProvider.state('step', {
url: '/orders/:step/:itemId',
templateUrl: 'parent.html',
http://plnkr.co/edit/sYCino1V0NOw3qlEALNj?p=preview
This means the parent state will collect the itemID on the way through.
include $state in resolve function for parent state
.state('parent', {
url: '/parent',
controller: 'parentController',
resolve: {
params: ['$state', function ($state) {
return $state.params;
}]
}
})
then in parent controller inject the resloved vars
app.controller('parentController', ['$scope', 'params',
function ($scope, params) {
$scope.params = params;
}]);
in this way params available in both child and parent.

Categories

Resources