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

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>

Related

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

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

Pass data from child component to parent and back down

Say I have a component that looks like this:
parent.component.js
...
template: `<div>
<child-one value="value" callback="callback"></child-one>
<child-two value="value"></child-two>
</div>`,
controller: function() {
this.callback = function(n) {
this.value = n;
}
}
...
Then the child components look like this:
childOne.component.js
...
bindings: {
value: '<',
callback: '<'
},
template: `<input type="text"
ng-model="$ctrl.value"
ng-change="$ctrl.callback($ctrl.value)"
/>`
...
childTwo.component.js
...
bindings: {
value: '<'
},
template: `<div>{{$ctrl.value}}</div>`
...
(binding technique thanks to krawaller)
I want the value that is set in childOne to go to childTwo. Updating the value in childOne does update the value in the parent but does not pass it down to childTwo.
Note:
You are setting values in the this object. Not directly in the $scope.
Use $scope, instead of this
Modified code:
parent.component.js
...
template: `<div>
<child-one value="value" callback="callback"></child-one>
<child-two value="value"></child-two>
</div>`,
controller: function($scope) {
$scope.callback = function(n) {
$scope.value = n;
console.log($scope.value);
}
}
...
If you want to write a code with this keyword, then use controllerAs syntax.
Refer below code for parent.component.js
...
template: `<div>parent: {{vm.value}} <br/><div/>
<div>
<child-one value="vm.value" callback="vm.callback"></child-one>
<child-two value="vm.value"></child-two>
</div>`,
controller: function() {
const vm = this;
vm.callback = function(n) {
vm.value = n;
console.log(vm.value);
}
},
controllerAs: "vm"
...
Result:
Updating the value in childOne will update the value in the parent as well as childTwo.

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>

Modify views of a UI Router template

I'm trying to inject default data into a ui-view template.
$stateProvider.state('foo', {
template: '<div ui-view=bar></div>',
abstract: true,
views: {
bar: {
template: 'test'
}
}
});
$stateProvider.state('foo.whiz', {
// ...
});
This example doesn't work, but I hope it's enough to show you what I mean. So what I'm basically trying to do is, when I enter the state foo.whiz, which has a parent of foo, I'd like foo to inject default data into the bar ui-view. Currently it only seems you can populate named views via the child state (bar#foo)? How can I, essentially, initialize data via the route object?
I would say, there are 4 ways (maybe more) how to pass some stuff from child to parent. There is a working plunker.
Let's have these anchors/targets in the index.html:
<div ui-view="title"></div>
<div ui-view="scopeInheritance"></div>
<div ui-view="factory"></div>
<div ui-view="content"></div>
And this would be the common def of parent state 'foo'
.state('foo', {
url: '/foo',
views: {
'title' : { template: '<h3>foo - parent state title</h3>',},
'scopeInheritance' :
{
template: '<h3 ui-view="inheritance">{{parentModel.greeting}}</h3>',
controller: 'parentCtrl',
},
'factory' : { template: '<h3>{{dataHolder.message}}</h3>',},
'content' : {
template: '<div >Content of foo (parent) <ui-view /></div>',
},
},
})
I. child view replaces parent view completely
In this case, we will define different content for the view 'title' in our child state:
.state('foo.whiz', {
views: {
'title#' : {
template: '<h3>{{resolved}} - child state title</h3>',
controller : 'titleCtrl',
resolve : { resolvedData : function() { return "whiz"; }, },
},
...
So, we are using Absolute Names 'title#' to place our view into root template.
II. scope inheritance, child can fill properties of parents $scope.parentModel reference
.state('foo.whiz', {
views: {
'inheritance' :
{
controller: 'childCtrl',
},
...
Here we declare some model in parent $scope, and due to Scope Inheritance by View Hierarchy Only we can change these values in a child:
// parent declares model
.controller('parentCtrl', function($scope){
$scope.parentModel = { greeting : "Parent greets"};
...
// child changes value and does clean up
.controller('childCtrl', function($scope){
var remember = $scope.parentModel.greeting;
$scope.parentModel.greeting = "Child greets"
$scope.$on("$destroy", function (){$scope.parentModel.greeting = remember;});
...
III. factory as data holder
Simply let's inject some singleton, in angular world service/factory, as a holder of data into $rootScope
// singleton
.factory('dataHolder', function(){
return {};
});
// available everywhere
app.run(
['$rootScope', 'dataHolder',
function($rootScope, dataHolder) {
$rootScope.dataHolder = dataHolder;
...
// child can assign these, while parent will render them
.controller('childCtrl', function($scope){
$scope.dataHolder.message = "Child sent message"
Check that all here
IV. child view is nested in parent:
the most typical with UI-Router
// the anchor is inside of the parents view
.state('foo', {
views: {
'content' : { // the anchor for child
template: '<div >Content of foo (parent) <ui-view /></div>',
},
...
// child inject directily into parent
.state('foo.whiz', {
views: {
'' : {
template: '<h4>Content of whiz (Child)</h4>',
},
...
These are the basic ways how to pass data/view/messages with UI-Router (skipping the eventing system of angular at all)

Resources