Angularjs 1.5 Nested Components bindings - angularjs

am trying to pass a value from a parent-component to his nested child-component in angular 1.5
The value can be updated from the parents, but child cannot edit it, just show it. So is a one-way binding '<' right ?
And i cannot pass the child component right in the parent component declaration, because the parent component would have other uses too.
The point is my parent-component have common data stored, but them
children gonna use it in different ways.
And the parent-component gonna be used multiples times, with different
children, thats why i cannot pass the children inside parent
declaration. I need to bind the info, for auto updates purposes, when
parents updates the data, must be reflected by the children
HTML
<parent-component ng-transclude>
<child-component name="$ctrl.characters.arya"></child-component>
<child-component name="$ctrl.characters.john"></child-component>
</parent-component>
JS
// Parent Component declaration
// /////////////////////////
(function() {
'use strict';
angular
.module('app')
.component("parentComponent", {
transclude: true,
controller: "ParentComponentController",
template:
'<div class="parent-c"></div>'
});
})();
// Parent Component Controller
// /////////////////////////
(function() {
'use strict';
angular
.module('app')
.controller('ParentComponentController', ParentComponentController);
function ParentComponentController() {
var $ctrl = this;
$ctrl.characters = {};
$ctrl.characters.arya = "Arya Stark";
$ctrl.characters.john = "John Snow";
}
})();
//CHILD Component declaration
// /////////////////////////
(function() {
'use strict';
angular
.module('app')
.component("childComponent", {
bindings: {
name: '<'
},
controller: "ChildComponentController",
template:
'<div class="child-c"' +
'<h3>Im a child Component</h3>' +
'<p><strong>Name: </strong>{{$ctrl.name}}</p>' +
'</div>'
});
})();
// CHILD Component Controller
// /////////////////////////
(function() {
'use strict';
angular
.module('app')
.controller('ChildComponentController', ChildComponentController);
function ChildComponentController() {
var $ctrl = this;
}
})();
Check the WORKING SAMPLE on plunkr
The require attribute is for components communication, but i'm trying to use it with no success :(, need a piece of light here.

you have to use : <child-component name="$parent.$ctrl.characters.arya"></child-component>to pass a value from a parent-component to his nested child-component

There are different issues with your code:
function ParentComponentController() {
var $ctrl = this;
$ctrl.characters = {};
$ctrl.characters.arya = "Arya Stark";
$ctrl.characters.john = "John Snow";
}
You don't need to define a local variable for this since not changing context anywhere.
controller: "ParentComponentController",
Don't pass a string to this property, pass a reference:
controller: ParentComponentController,
Then if you want to pass name through the parent controller with require in the child component:
require: { parent: '^^parentComponent' },
Now that you have the parent controller bound to child you can use it with:
{{$ctrl.parent.characters.arya}}
in the template.
http://plnkr.co/edit/3PRgQSGdBEIDKuUSyDLY?p=preview
If you need to pass the name as an attribute to your child components, you have to put the child components inside the parent's template so you can call $ctrl.
http://plnkr.co/edit/1H7OlwbumkNuKKrbu4Vr?p=preview

Related

AngularJS (1.5) detach panel to child window and vice versa

I have a child controller which I can use inside some main controller. For example, it's a panel inside the main page controller. Now I need to open a child window with this child controller inside. How can I do this?
For example, I have a video player panel and I want it to become detachable and scalable in size.
My child controller has its own template-file and I inject it to the main controller with this:
...
<div class="col-sm-6 col-md-6">
<span ng-include="AVTemplateUrl" ng-controller="VideoCtrl as AV" data-ng-init="AV.init()"></span>
</div>
...
and contructor for the child:
angular.module('myApp.video', ['ngRoute'])
.controller('VideoCtrl', ['$scope', ...],
function ($scope, ...) {
$scope.AVTemplateUrl = 'view/device/video.html';
let AV = $scope.AV = { ... }
...
P.S. sorry for my English.
Found the solution here https://stackoverflow.com/a/17066146/2982280 and here http://embed.plnkr.co/dz1A1h/
Parent controller:
.controller(
...
let model = $scope.model = {
detach() {
// remove the controller panel from dom
},
maximize() {
// detach panel to a new child window
this.detach();
window.$windowScope = $scope; // $scope contains 'model' field
$window.open('_child_window_url_', '_child_window_name_', 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no - window params');
},
onClick() {
// some DOM handler - button click, for example
},
...
};
...
);
Child controller:
1) use router:
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('_child_window_url_', {
templateUrl: '_template_file_path_',
controller: '_child_controller_name_'
});
}])
2) implement the DOM handlers to make them call the parent's handlers:
onClick: function () {
window.opener.$windowScope.model.onClick();
},

Trouble understanding Angular components

I am trying to learn about Angular components but am having trouble getting them to work in a nested component configuration. Basically what I want to do is to have a parent component that periodically updates a value. I then want the inner child component to bind to the value in the parent and trigger a call to $onChanges when the value changes.
I made a jsFiddle demonstrating what I'm trying to accomplish. The parent component seems to be working and displaying the updated value, but for some reason the child component doesn't render at all. Here is the code I am using to accomplish this:
let app = angular.module('app', []);
class ParentController {
constructor($interval) {
this.value = 0;
$interval(() => this.value++, 1000);
}
}
let ParentComponent = {
controller: ParentController,
template: `<div>parent value: {{$ctrl.value}}</div>`
};
app.component('parent', ParentComponent);
class ChildController {
$onChanges(changesObj) {
console.log(changesObj);
}
}
let ChildComponent = {
bindings: {
value: '='
},
controller: ChildController,
require: {
parent: '^^parent'
},
template: `<div>child value: {{$ctrl.value}}</div>`
};
app.component('child', ChildComponent);
And the HTML:
<div ng-app="app">
<parent>
<child value="$ctrl.parent.value"></child>
</parent>
</div>
Am I doing something wrong or is what I'm trying to accomplish not possible?
The thing about components is that anything inside of them typically gets replaced unless you use the 'ng-transclude' directive.
To get your child element to appear, you need the following changes:
let ParentComponent = {
controller: ParentController,
transclude: true,
template: `<div>parent value: {{$ctrl.value}}</div><div ng-transclude></div>`
};
To clarify, you add transclude: true to the component object as well as an element with the ng-transclude directive within the template. Whatever element has the ng-transclude directive will have its contents replaced with whatever is in the <parent> tag.
This will only get your child component to render, you still have another error in your HTML being that you're trying to use
<child value="$ctrl.parent.value"></child>
Which, in the JavaScript, is equivalent to $scope.$ctrl.parent.value which is undefined.
Just change your HTML to:
<div ng-app="app">
<parent>
<child></child>
</parent>
</div>
And the child component to:
let ChildComponent = {
controller: ChildController,
require: {
parent: '^^parent'
},
template: `<div>child value: {{$ctrl.parent.value}}</div>`
};
And then you're all good!
EDIT (from comment):
In order to trigger a update call whenever the parent value changes, you would need to replace your ChildController with the following:
let ChildController = function (scope) {
scope.$watch('$ctrl.parent.value', function (newValue, oldValue) {
console.log(newValue);
});
};
This adds a watch on the child controller that will call the function each time the parent.value changes.
i have made som modifications to your code.
there is a different way of achieving what you want to do.
here is my code I will try to explain how it should be done.
let app = angular.module('app', []);
class ParentController {
constructor($interval) {
this.value = 0;
$interval(() => this.value++, 1000);
}
}
let ParentComponent = {
controller: ParentController,
//here is the important edit. loading child component inside the parent component
template: `<div>
parent value: {{$ctrl.value}}
<child value="$ctrl.value"></child>
</div>`
};
app.component('parent', ParentComponent);
class ChildController {
$onChanges(changesObj) {
if(changesObj.hasOwnProperty('value')){
this.value=changesObj.value.currentValue //this referes to current scope.
}
}
}
let ChildComponent = {
bindings: {
value: '<'
},
controller: ChildController,
require: {
//parent: '^^parent' no need for this. we will use $onChanges
},
template: `<div>child value: {{$ctrl.value}}</div>`
};
app.component('child', ChildComponent);
<div ng-app="app"><parent></parent></div>
things I have done in this code sample :
I have included component inside the parent component template
because what you want to do is load child inside parent. so added child inside the parent template.
Removed Require in child component definition because it would make your child component dependent on the parent component. there is a different use case where you must use the parent. it's best you use bindings and $onChanges to update data in a child from a parent,
added some validations to the $onChanges data.
if(changesObj.hasOwnProperty('value')){
this.value=changesObj.value.currentValue
}
you need to understand that the change obj is a simple change Object. it has multiple properties like is first change(), current value, OldValue etc.
you must always validate what you want from here.
also i am setting it to this.value which is the current scope.
we are not directly using the parent scope or binding scope variable. i am creating a new scope object which you will use to display the data. it will be populated by the binding object.
overall this is happening
1.parent interval updates parents scope
2.this.value is passed to child as Bindings
3.$onChanges validates bindings and assigns it to local scope this.value
4.child template renders this.value in the UI

Child controller not inheriting from parent controller

I've been having a lot of trouble with understanding scope inheritance and I've tried my best to pass data objects from a parent controller into the child controller, but I can't seem to get things to work. Can someone explain why this isn't functioning? Thank you!
EDIT: I didn't specify this earlier, but it's a project requirement to use the John Papa style guide, so I can't solve this problem by using $scope in either of the controllers.
UPDATE: It seems I misunderstood the purpose of using this... based on help from posters below, I now understand that certain actions require the use of $scope and John Papa's style guide simply asks developers to use this when appropriate to avoid scope conflicts, not as a replacement for scope
JS
//parent.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('ParentController', ParentController);
ParentController.$inject = ['$scope'];
function ParentController($scope) {
var vm = this;
console.log(this);
vm.test = {};
vm.test.label = "This is being set in the parent controller.";
}
})();
//child.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('ChildController', ChildController);
ChildController.$inject = ['$scope'];
function ChildController($scope) {
var vm = this;
vm.test = vm.test;
}
})();
HTML
<div ng-controller="ParentController as vm">
<div>PARENT: {{vm.test.label}}</div>
<div ng-controller="ChildController as vm">
<div>CHILD: {{vm.test.label}}</div>
</div>
</div>
RESULT
PARENT: 'This is being set in the parent controller.'
CHILD:
Issue is: vm is also the part of the $scope itself. So you can not have same this instance for Parent & Child controller. Otherwise you will be facing issues while using them.
If you want to isolate this instance for Parent & Child then give different names.
Since vm is also the part of the controller so if you want to access Parent's vm inside Child controller then you will have to do $scope.vm
Working code as per your requirement is attached below:
Controller
---------
(function () {
'use strict';
angular
.module('app', [])
.controller('ParentController', ParentController);
ParentController.$inject = ['$scope'];
function ParentController($scope) {
var vm = this;
console.log(this);
vm.test = {};
vm.test.label = "This is being set in the parent controller.";
}
})();
(function () {
'use strict';
angular
.module('app')
.controller('ChildController', ChildController);
ChildController.$inject = ['$scope'];
function ChildController($scope) {
var childVm = this;
childVm.test = $scope.vm.test;
}
})();
HTML
---
<div ng-app="app">
<div ng-controller="ParentController as vm">
<div>PARENT: {{vm.test.label}}</div>
<div ng-controller="ChildController as childVm">
<div>CHILD: {{childVm.test.label}}</div>
</div>
</div>
</div>
Cheers!
After checking the plnkr,hope I was able to understand your question, and giving answer based on it:
In controller file, for ChildController
var childCtrl = this;
// Why don't either of these work?
// childCtrl.test = parentCtrl.test;
// childCtrl.test = this.parentCtrl.test;
assigning parentCtrl.test, doesn't makes any sense, as it is the object of the parent controller.
this.parentCtrl.test will get evaluated to childCtrl.parentCtrl.test which is invalid.
Following worked because-
// But this does...
childCtrl.test = $scope.parentCtrl.test;
During the code execution
Two separate scopes will get created each for ParentController and ChildController.
child will inherit the properties of parent, those which are not present in childController and will be assigned to the childController scope.
Due to which you where able to access value in child with scope.

Communication between child and parent directive

I'm trying to figure out how to make a child directive communicate with it's parent directive
I basically have this html markup
<myPanel>
<myData dataId={{dataId}}></myData>
</myPanel>
In the myData directive, if there is no data available, I want to hide the myPanel.
In the myData directive controller I've tried
$scope.$emit('HideParent');
And in the myPanel controller I've tried
$scope.$on('HideParent', function () { $scope.hide = true; });
And also
$scope.$watch('HideParent', function () { if (value) { $scope.hide = true; }});
In either situation, the myPanel directive isn't receiving the $emit
You may create controller in myPanel directive.
Then require this controller in myData directive. And when child directive has no data, call controller method to hide parent.
For example in you parent (myPanel) directive:
controller: function($scope, $element){
$scope.show = true;
this.hidePanel = function(){
$scope.show = false;
}
}
In myData directive require this controller:
require:'^myPanel'
And then, call controller function when you need
if (!scope.data){
myPanelCtrl.hidePanel();
}
Look this Plunker example
The answers above where the parent directive's controller is required is a great one if there truly is a dependency. I had a similar problem to solve, but wanted to use the child directive within multiple parent directives or even without a parent directive, so here's what I did.
Your HTML.
<myPanel>
<myData dataId={{dataId}}></myData>
</myPanel>
In your directive simply watch for changes to the attribute.
controller: function ($scope, $element, $attrs) {
$attrs.$observe('dataId', function(dataId) { $scope.hide = true; } );
}
It's explicit and simple without forcing a dependency relationship where one may not exist. Hope this helps some people.

AngularJS - Access to child scope

If I have the following controllers:
function parent($scope, service) {
$scope.a = 'foo';
$scope.save = function() {
service.save({
a: $scope.a,
b: $scope.b
});
}
}
function child($scope) {
$scope.b = 'bar';
}
What's the proper way to let parent read b out of child? If it's necessary to define b in parent, wouldn't that make it semantically incorrect assuming that b is a property that describes something related to child and not parent?
Update: Thinking further about it, if more than one child had b it would create a conflict for parent on which b to retrieve. My question remains, what's the proper way to access b from parent?
Scopes in AngularJS use prototypal inheritance, when looking up a property in a child scope the interpreter will look up the prototype chain starting from the child and continue to the parents until it finds the property, not the other way around.
Check Vojta's comments on the issue https://groups.google.com/d/msg/angular/LDNz_TQQiNE/ygYrSvdI0A0J
In a nutshell: You cannot access child scopes from a parent scope.
Your solutions:
Define properties in parents and access them from children (read the link above)
Use a service to share state
Pass data through events. $emit sends events upwards to parents until the root scope and $broadcast dispatches events downwards. This might help you to keep things semantically correct.
While jm-'s answer is the best way to handle this case, for future reference it is possible to access child scopes using a scope's $$childHead, $$childTail, $$nextSibling and $$prevSibling members. These aren't documented so they might change without notice, but they're there if you really need to traverse scopes.
// get $$childHead first and then iterate that scope's $$nextSiblings
for(var cs = scope.$$childHead; cs; cs = cs.$$nextSibling) {
// cs is child scope
}
Fiddle
You can try this:
$scope.child = {} //declare it in parent controller (scope)
then in child controller (scope) add:
var parentScope = $scope.$parent;
parentScope.child = $scope;
Now the parent has access to the child's scope.
One possible workaround is inject the child controller in the parent controller using a init function.
Possible implementation:
<div ng-controller="ParentController as parentCtrl">
...
<div ng-controller="ChildController as childCtrl"
ng-init="ChildCtrl.init()">
...
</div>
</div>
Where in ChildController you have :
app.controller('ChildController',
['$scope', '$rootScope', function ($scope, $rootScope) {
this.init = function() {
$scope.parentCtrl.childCtrl = $scope.childCtrl;
$scope.childCtrl.test = 'aaaa';
};
}])
So now in the ParentController you can use :
app.controller('ParentController',
['$scope', '$rootScope', 'service', function ($scope, $rootScope, service) {
this.save = function() {
service.save({
a: $scope.parentCtrl.ChildCtrl.test
});
};
}])
Important:
To work properly you have to use the directive ng-controller and rename each controller using as like i did in the html eg.
Tips:
Use the chrome plugin ng-inspector during the process. It's going to help you to understand the tree.
Using $emit and $broadcast, (as mentioned by walv in the comments above)
To fire an event upwards (from child to parent)
$scope.$emit('myTestEvent', 'Data to send');
To fire an event downwards (from parent to child)
$scope.$broadcast('myTestEvent', {
someProp: 'Sending you some data'
});
and finally to listen
$scope.$on('myTestEvent', function (event, data) {
console.log(data);
});
For more details :- https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Enjoy :)
Yes, we can assign variables from child controller to the variables in parent controller. This is one possible way:
Overview: The main aim of the code, below, is to assign child controller's $scope.variable to parent controller's $scope.assign
Explanation: There are two controllers. In the html, notice that the parent controller encloses the child controller. That means the parent controller will be executed before child controller. So, first setValue() will be defined and then the control will go to the child controller. $scope.variable will be assigned as "child". Then this child scope will be passed as an argument to the function of parent controller, where $scope.assign will get the value as "child"
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('myApp',[]);
app.controller('child',function($scope){
$scope.variable = "child";
$scope.$parent.setValue($scope);
});
app.controller('parent',function($scope){
$scope.setValue = function(childscope) {
$scope.assign = childscope.variable;
}
});
</script>
<body ng-app="myApp">
<div ng-controller="parent">
<p>this is parent: {{assign}}</p>
<div ng-controller="child">
<p>this is {{variable}}</p>
</div>
</div>
</body>
</html>

Resources