Pass data from parent to child angular.js components [duplicate] - angularjs

This question already has an answer here:
How to update child components from the updated list of parents
(1 answer)
Closed 4 years ago.
I have parent component:
(function () {
angular.module('app.module')
.component('parentComponent', {
templateUrl: 'parent.html',
controllerAs: 'vm',
controller: function ($scope) {
this.$onInit = () => {
$scope.parentData = 'test'
}
})
})()
child component
(function () {
angular.module('app.module').component('childComponent', {
templateUrl: 'child.html',
controllerAs: 'vm',
controller: function () {
this.$onInit = () => {
}
}
})
})()
parent.html
<child-component></child-component>
child.html
<p>{{parentData}}</p>
So I want to have access to parentData in my child component for display string 'test' in my child component. How can I do it? I read something about bindings but I don't know how to use it in this example.
Thanks for any suggestions.

Use one-way < binding:
<child-component in-data="$ctrl.parentData"></child-component>
The child component:
app.component("childComponent", {
bindings: {
inData: '<',
},
template: `
<div>{{$ctrl.inData}}</div>
`,
})
The DEMO
angular.module("app",[])
.component("parentComponent", {
template: `
<fieldset>
Inside parent component<br>
parentData={{$ctrl.parentData}}
<child-component in-data="$ctrl.parentData"></child-component>
</fieldset>
`,
controller: function () {
this.$onInit = () => {
this.parentData = 'test'
};
},
})
.component("childComponent",{
bindings: {
inData: '<',
},
template: `
<fieldset>Inside child component<br>
inData={{$ctrl.inData}}
</fieldset>
`,
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<parent-component>
</parent-component>
<body>
For more information, see
AngularJS Developer Guide - Component-based application architecture
AngularJS Comprehensive API Reference - scope

You can access parent's data via the parent controller, for that you can use require in your component declaration:
Here is an example:
app.component('childComponent', {
require: {
parentCtrl: '^parentComponent'
},
controller: function() {
var self = this;
this.$onInit = function() {
self.parentCtrl.anyData;
};
}
});
Take a look at codelord.net - Advanced Angular 1.x: Component Communication with Require
If you don't need the parent controller you can bind data to your child component: refer to #georgeawg answer

Related

Unable to pass data to a controller inside a component in AngularJS

I have following component
componentsModule.component('detailComponent', {
templateUrl: 'detailTemplate.html',
scope: {},
bindings: {
textParam: '='
},
controller: ['$mdDialog', function($mdDialog) {
this.textParam // this is undefined always
}]
});
I am loading template in a dialog from other controller like this
$scope.textParam = tParam;
const parentEl = angular.element(document.body);
$mdDialog.show({
template: '<detail-component text-param="$scope.textParam"></detail-component>',
ariaLabel: 'detailComponentDialog',
parent: parentEl
}).then((savedDatails) => {});
If I use binding as # then I get $scope.textParam as a string inside my controller in this.textParam but if I set binding as < or = then I get undefined

AngularJS calling from Parent to Child Directive controller function

I am use to working in Angular and now I am on AngularJS ( The otherway round)
I've a directive:
<li ng-mouseover="vm.setCurrentEditedTile(item.id)">
<panel-buttons-directive ></panel-buttons-directive>
</li>
My panel-buttons-directive has a controller called ButtonsController.
What I would like when user hovers on top of <li> element, it run a function that is inside the child controller. So that I have a separate "Module" where I have buttons HTML in the directive and function in the controller and from the parent I can call the function.
Link: https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
One approach is to have the directive publish an API when initialized:
<fieldset ng-mouseover="pbdAPI.setCurrentEditedTile(item.id)">
Mouseover Me
</fieldset>
<panel-buttons-directive on-init="pbdAPI=$API">
</panel-buttons-directive>
app.directive("panelButtonsDirective", function() {
return {
scope: { onInit: '&' },
bindToController: true,
controller: ButtonsController,
controllerAs: '$ctrl',
template: `<h3>Panel Buttons Component</h3>
<p>Current edited tile = {{$ctrl.id}}</p>
`,
};
function ButtonsController() {
var $ctrl = this;
var API = { setCurrentEditedTile: setCurrentEditedTile };
this.$onInit = function() {
this.onInit({$API: API});
};
function setCurrentEditedTile(id) {
$ctrl.id = id;
}
}
})
The directive in the above example uses expression & binding to publish its API when initialized.
The DEMO
angular.module("app",[])
.directive("panelButtonsDirective", function() {
return {
scope: { onInit: '&' },
bindToController: true,
controller: ButtonsController,
controllerAs: '$ctrl',
template: `<h3>Panel Buttons Component</h3>
<p>Current edited tile = {{$ctrl.id}}</p>
`,
};
function ButtonsController() {
var $ctrl = this;
var API = { setCurrentEditedTile: setCurrentEditedTile };
this.$onInit = function() {
this.onInit({$API: API});
};
function setCurrentEditedTile(id) {
$ctrl.id = id;
}
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<h3>Mouseover Component DEMO</h3>
<p><input ng-model="item.id" ng-init="item.id='tile0'"/></p>
<fieldset ng-mouseover="pbdAPI.setCurrentEditedTile(item.id)">
Mouseover Me
</fieldset>
<panel-buttons-directive on-init="pbdAPI=$API">
</panel-buttons-directive>
</body>

Angular (1.5.8) Dynamic Components

I'm trying to build a sort of dynamic dashboard using Angular 1.5.8. I've made decent progress up until the final hurdle. Which is actually rendering the dynamic component.
I've tried 2 options, either adding a ui-view and programatically passing in the name of the widget, or, and this is the route I'm guessing is more correct, I need to figure out how to render a dynamic widget.
For Example: As I append and item to the dashItems collection, it should render a new widget (based on the name I've provided)
I have seen that I can swap out templates using ngInclude, but I'm still unclear as to how to get a template and controller to be injected dynamically. (EG all my templates wont be sharing a common controller).
JavaScript:
angular
.module('myDashboard', [])
.config(routesConfig)
.component('dashboard', {
templateUrl: 'dashboard/dashboard.tpl.html',
controller: dashboardController
})
.component('widgetPie', {
template: '<h3>Pie Graph</h3>',
controller: function($log) {
$log.info('widgetPie: loaded');
}
})
.component('widgetLine', {
template: '<h3>Line Graph</h3>',
controller: function($log) {
$log.info('WidgetLine: loaded');
}
});
function routesConfig($stateProvider) {
// this is only needed if I go the ui-view route.. I assume
$stateProvider
.state('widgetPie', { component: 'widgetPie'})
.state('widgetLine', { component: 'widgetLine'});
}
function dashboardController($log) {
$log.info('in dashboard');
this.dashItems = [
{ widget:'widgetPie' },
{ widget:'widgetLine' }
];
}
Markup (dashboard/dashboard.tpl.html):
<div>
<ul>
<li ng-repeat="item in dashItems">
<!--somehow render dynamic-->
<!--<widget-pie></widget-pie>-->
<!--<div ui-view="item.widget"></div>-->
</li>
</ul>
</div>
Question(s):
1.
I've looked into ngInclude, but to be perfectly honest, I'm not sure how to go about using it in this instance, and IF it is the right tool for this, or am I approaching this incorrectly?
2.
Should I even be adding items to the state provider for this, EG i / could a widget be seen as a child state (thus I'm not sure what would be seen as best practice)
I ended up changing the dashboard.tpl.html file to:
<div>
<ul>
<li ng-repeat="item in dashItems">
<div ng-include="item.widget"></div>
</li>
</ul>
</div>
But I also needed to add a build task to run through my widgets folder and generate the following (or you can manually add, whatever floats your boat I guess).
angular
.module('myDashboard')
.run(function ($templateCache) {
$templateCache.put('widgetPie', '<widget-pie></widget-pie>');
$templateCache.put('widgetLine', '<widget-line></widget-line>');
});
The above allows me to either use templateUrl, or inline templates.
.component('widgetPie', {
templateUrl: 'dashboard/widgetPie.tpl.html',
controller: function($log) {
$log.info('widgetPie: loaded');
}
})
.component('widgetLine', {
template: '<h1>Some Content</h1>',
controller: function($log) {
$log.info('widgetLine: loaded');
}
})
You can do it. Firstly, you need to use wrapper component which helps you compile your dynamic component:
app.component('dynamicWrapper',
{
controller: function widgetClientCtrl($scope, $compile, $element) {
var self = this;
self.$onInit = function () {
renderWidget(self.name, self.payload);
};
function renderWidget(name, payload) {
var template = '<' + name;
if (payload) {
$scope.payload = payload;
template += ' payload="payload"';
}
template += '></' + name + '>';
$element.append($compile(template)($scope));
}
},
bindings: {
name: '#',
payload: '=?'
}
});
your dynamic component:
app.component('someDynamicComponent', {
templateUrl: 'yourPath',
controller: dynamicComponentCtrl,
controllerAs: 'vm',
bindings: {
payload: '<'
}
});
and then:
<li ng-repeat="(name, payload) in vm.dynamicComponents">
<dynamic-wrapper name="{{name}}" payload="payload"></dynamic-wrapper>
</li>

How can I pass a function with argument to a component?

I want to pass a function from the parent component to the child component and give it an argument that has also been given from the parent component to the child. (showOrHideSub="item.showOrHideSub(item.id)" ) I have tried different ways and it doesn't work.
This is my html (parent component) in which I want to use the child component tag. vm is the controller of this scope:
<li ng-repeat="item in vm.menuItems">
<menu-item-comp id="item.id" showOrHideSub="item.showOrHideSub(item.id)" />
</li>
Here is the child component template. itemVm is the controller of this component:
<div id="{{itemVm.id}}" ng-mouseover="itemVm.showOrHideSub(itemVm.id)">
<div id="itemVm.subId" class="menuItemImgText">{{ itemVm.label }}</div>
Here is the child component js:
module.component('menuItemComp', {
templateUrl: '/webapp/app/components/menu/menuItemComponent.html',
bindings: {
id: '<',
showOrHideSub: '&',
label: '<',
submenuId: '<',
},
controllerAs: 'itemVm',
controller: ['LogService', menuCtrl]
});
function menuCtrl($scope, LogService) {
var itemVm = this;
}
And here is the showOrHideSub() function in the parent controller:
vm.showOrHideSub = function (submenu) {
console.log(submenu);
switch (submenu) {
case 'menuItemDivPositions':
console.log('position');
break;
case 'menuItemDivOther':
console.log('other');
break;
}
}
I know that in directives the way to do it is by object mapping such as showOrHideSub="item.showOrHideSub({item: item.id})" but it doesn't seem to work in component.
If you're working with components, you have to do it the components way.
It looks like you have a hierarchy of components (child / parent).
Functions and attributes inside the parent can be inherited by children using require.
require: {
parent: '^^parentComponent'
}
This way, if the parent defines a function showOrHideSub, the children can call it directly using this.parent.showOrHideSub(xxx)
This is not the only way to solve your issue but this is the right way™ for the architecture you chose.
var parentComponent = {
bindings: {},
controller: ParentController,
template: `
<li ng-repeat="item in $ctrl.menuItems">
<child-component item="item"></child-component>
</li>
`
};
var childComponent = {
bindings: {
item: '<'
},
require: {
parent: '^^parentComponent'
},
controller: ChildController,
template: '<button ng-click="$ctrl.buttonClick($ctrl.item.id);">{{$ctrl.item.name}}</button>'
};
function ParentController() {
this.menuItems = [{id:1, name:"item1"},{id:2, name:"item2"}];
this.showOrHideSub = function(param) {
console.log("parent function called with param: " + param);
}
}
function ChildController() {
var vm = this;
this.buttonClick = function(id) {
vm.parent.showOrHideSub(id);
}
}
angular.module('app', []);
angular.module('app')
.component('parentComponent', parentComponent)
.component('childComponent', childComponent);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="app">
<parent-component></parent-component>
</div>
Change 'item' to 'vm' in below code. You are binding the item function 'showOrHideSub(item.id)' which doesn't exist. Here is the updated code.
<li ng-repeat="item in vm.menuItems">
<menu-item-comp id="item.id" showOrHideSub="vm.showOrHideSub(item.id)" />
</li>

how to use angular 1 variables inside the angular 2 component

login.js
var app= angular.module('login',[]);
app.controller('LoginCtrl', function($scope)
{
$scope.sayhello="Hello"+ $scope.username;
}).directive('loginDir', function(){
return {
scope:{},
templateUrl: 'logintpl.html',
controller: 'LoginCtrl'
};
});
var adapter = new ng.upgrade.UpgradeAdapter();
AppComponent = ng.core
.Component({
selector: 'login',
directives: [adapter.upgradeNg1Component('loginDir')],
template: '<login-dir></login-dir>'
})
.Class({
constructor: function() {}
});
app.directive('login', adapter.downgradeNg2Component(AppComponent));
document.addEventListener('DOMContentLoaded', function() {
adapter.bootstrap(document.body, ['login']);
console.log(adapter);
});
logintpl.html
<input type="name" ng-model="username">
how can i use $scope.sayhello variable inside the component.
eg: component template should be,template:'<login-dir></login-dir>{{sayhello}}
AppComponent = ng.core
.Component({
selector: 'login',
directives: [adapter.upgradeNg1Component('loginDir')],
template: '<login-dir></login-dir> {{sayhello}}'
})
.Class({
constructor: function() {
this.sayhello = "Hello World !!!";
}
});
Explanation
In Angular 2, there is no model called $scope. They replaced it with simple variables with in the Class.
We can consider the whole Class as a controller in Angular 1.x. We can create variable with this.variable_name with in a Class. constructor is the function which will be invoked first in the component. So, we can initialize all our variable here.
So, $scope.variable_name in Angular 1.x is same (or likely to) as this.variable_name in Angular 2.
In fact the sayhello attribute can be only used within your loginDir directive since it's defined in its associated scope.
You could have a use case like that:
app.controller('LoginCtrl', function($scope) {
$scope.sayhello = function() {
console.log("Hello"+ $scope.username);
}
}).directive('loginDir', function() {
return {
scope:{},
templateUrl: 'logintpl.html',
controller: 'LoginCtrl'
};
});
In the template of your Angular1 directive, you will be able to use this function:
<input type="name" ng-model="username">
<span ng-click="sayhello()">Test</span>
I don't know what you exactly want to do. Here is the corresponding plunkr: https://plnkr.co/edit/ribHwk8uSXHRv0JkLlo0?p=preview.
Edit
You can have access to attributes of the parent component from the directive since the Angular1 directive scope is internal to your directive. With Angular1, you can't either. The only thing you could do is to define a parameter to your Angular1 directive that corresponds to an attribute of your parent component and update it by reference.
Here is a sample
app.controller('LoginCtrl', function($scope) {
$scope.updateSayhelloInParent = function() {
console.log("Hello"+ $scope.username);
$scope.sayhello.message = $scope.username;
}
}).directive('loginDir', function(){
return {
scope:{
sayhello: '='
},
templateUrl: 'logintpl.html',
controller: 'LoginCtrl'
};
});
And the way to use the directive in the component:
AppComponent = ng.core
.Component({
selector: 'login',
directives: [adapter.upgradeNg1Component('loginDir')],
template: `
<login-dir [sayhello]="sayHello"></login-dir>
<br/><br/>
SayHello in component:
{{sayHello | json}}
`
})
.Class({
constructor: function() {
this.sayHello = {
message: 'default message'
}
}
});
Corresponding plunkr is here: https://plnkr.co/edit/ribHwk8uSXHRv0JkLlo0?p=preview.
Hope it helps you,
Thierry

Resources