Angular 2 : Call root component's method from nested child component - angularjs

I have few components in my application:
//.ts:
RootComponent
-ParentComponent
--ChildComponent
---LastLevelComponent
//.html
<root-component>
<parent-component>
<child-component>
<last-level-component></last-level-component>
</child-component>
</parent-component>
</root-component>
I want to call a method of Rootcomponent from LastLevelComponent.
Am aware of EventEmitter, but problem with that is I need to pass
value to each child component.
Is there any way I can call directly
RootComponent's method from LastLevelComponent without having
dependency on ParentComponent or ChildComponent.

You could use an #Input() in your LastLevelComponent.
Or an #Output() without Child- and ParentComponent needs to use this Event.
//.html
<root-component #rootCmp>
<parent-component>
<child-component>
<last-level-component [root-comp]="rootCmp" (myOutput)"rootCmp.func()"></last-level-component>
</child-component>
</parent-component>
</root-component>

You can solve this by creating a service. Follow these steps:
Create a service
#Injectable()
export class CompService {
private componentRef = null;
constructor(){
}
getParent(){
return this.componentRef;
}
setParent(ref){
this.componentRef = ref;
}
}
Include this service in your root component, in providers section:
#Component({
. . .
providers: [CompService]
})
export class RootComponent {
constructor(
private cmpService?: CompService)
{
}
}
During initialization of the root component, call the service to set the component value to point to the root:
ngOnInit(){
this.cmpService.setParent(this);
}
In child component or last level component, call this service to retrieve the root component:
#Component({
. . .
})
export class LastLevelComponent {
constructor(
private cmpService?: CompService)
{
}
someMethod(){
if (this.cmpService){
let parentCtrl = this.cmpService.getParent();
// Call your parent method or property here
// if (parentCtrl)
// parentCtrl.rootMethod();
}
}
}
You may notice that CompService is only set in providers section of root component. This enables to have one instance of this service. So all child components will inherit the service settings pointing to the root component.
You only need to call the getParent method of the service in each child component to get access to the root component public methods, properties and events.
Furthermore, this service is reusable and avoids dependency of any other component.

Related

Is there a way to replace an Angular component by a component in a customer module like when using the decorator in AngularJS?

I am developing an application for a wide customer range. Different customers tend to have different needs for customization in their UI. Therefore, we would like to replace those components by customer-specific components. Unfortunately, this seems to be impossible. Could anyone be of help?
The situation we would like:
Module 'Base'
Component 'Scheduler' - displaying a couple of components from template
Component 'SchedulerEvent' (tag 'scheduler-event') with some basic data
Module 'Customer'
Component 'CustomerSchedulerEvent' (tag 'scheduler-event') with customer-specific data
In this situation, we would like to have the CustomerSchedulerEvent displayed instead of the normal SchedulerEvent. Although the code compiles properly this way, still the SchedulerEvent is displayed.
In old AngularJS code, there was the decorator concept which could replace entire directives/components, which is being described here: https://docs.angularjs.org/guide/decorators#directive-decorator-example.
Is there a possibility to get kind-of this behavior working in modern Angular as well?!
Although quite cumbersome, at least there appears to be a workaround:
Make sure you have a component host directive. We will use that one later.
#Directive({
selector: '[componentHost]',
})
export class ComponentHostDirective {
constructor(readonly $viewContainerRef: ViewContainerRef) { }
}
Create a service returning the component type to render:
import { Injectable, Type } from '#angular/core';
[...]
#Injectable()
export class TemplateComponentService extends TemplateComponentBaseService {
getTemplate(): Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent> {
// console.log('Our EventInformationTemplateService...');
return LaneSubscriberSchedulingEventInformationTemplateComponent;
}
}
which you register as follows in your base module:
#NgModule({
...
providers: [
EventInformationTemplateService,
{ provide: EventInformationTemplateBaseService, useExisting: EventInformationTemplateService }
]
})
export class BaseModule {
}
Your component that could be replaced, should look like:
import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, Type, ViewChild } from "#angular/core";
import { ComponentHostDirective } from "src/common/directives/component-host.directive";
import { AggregateServiceFactory } from "src/common/services/aggregates";
import { LaneSubscriberSchedulingEventInformationTemplateBaseComponent } from "src/production-planning/components/lane-subscriber-scheduling/event-information-template/event-information-template-base.component";
import { EventInformationTemplateBaseService } from "src/production-planning/components/lane-subscriber-scheduling/event-information-template/event-information-template-base.service";
import { moduleName } from "src/production-planning/production-planning.states";
#Component({
selector: 'app-template',
templateUrl: './template.component.html',
})
export class TemplateComponent extends TemplateBaseComponent implements AfterViewInit {
componentType: Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent>;
customComponent: boolean;
#ViewChild(ComponentHostDirective, { static: true }) private _componentHost: ComponentHostDirective;
constructor(
$element: ElementRef,
private readonly $componentFactoryResolver: ComponentFactoryResolver,
private readonly _templateComponentService: TemplateComponentBaseService) {
this.componentType = this._templateComponentService.getComponent();
this.customComponent = !isNull(this.componentType) && this.componentType !== TemplateComponent;
// console.group('TemplateComponentService.getComponent()');
// console.log('Component type', this.componentType);
// console.log('Component custom?', this.customComponent);
// console.groupEnd();
}
// Component lifecycle events
ngAfterViewInit(): void {
if (this.customComponent === true) {
const componentFactory = this.$componentFactoryResolver.resolveComponentFactory(this.componentType);
this._componentHost.$viewContainerRef.clear();
const componentRef = this._componentHost.$viewContainerRef.createComponent<LaneSubscriberSchedulingEventInformationTemplateBaseComponent>(componentFactory);
componentRef.instance.event = this.event;
}
}
}
and its template file like:
<ng-container *ngIf="customComponent != true">
<!-- TODO Display more information -->
<strong>{{ event.title }}</strong>
</ng-container>
<ng-template componentHost></ng-template>
Create another service like the one above and register it the same way in another module in your app.
As you can see, we hide the original component content using *ngIf and use the component host on the ng-template to render the replacing component in its place when the service returns another type than the current type. The reason to opt for this strange path, is that the tag will directly map to our base template component and is not replaceable.
A drawback for this scenario is that the component host directive, being a ViewChild, is only available after view init, which is quite late. For very complex scenarios, this workaround could therefore lead to some unwanted timing issues and such...
I hope anyone could provide a better solution?

Accessing current controller instance (`this`) in events

We are using TypeScript with angularjs 1.5. We have a situation where we need to trigger an event to load data from the parent component. The problem we are facing is that when the event is triggered, this is referring to the window object not the controller in which it is present. How to get the current instance in the event?
Because it is TypeScript any method or property can be accessed though this only. And I need the controller instance because there are few initialization done in the constructor which I need to access.
Example implementation
namespace doc.common {
#Component({
selector: name
})
export class myComponent {
#bind.oneWay()
public id:number;
public service:myService;
/*ngInject*/
constructor(private serviceRef:myService){
this.service = serviceRef;
}
public $onChange():void {
//*this* is referred to window object, not controller instance
}
}
}
Usage
<my-component id='id'></my-component>

get ref to child components injected from outside

We have in our current application created react components that are to be used like dumb components by the client apps.
index.js(the entry for webpack) has the following
export {Container} from "./path/to/container";
...
export {ComponentN} from "./path/to/componentN";
We have exported it out as a node distributable. this repo also contains the container component.
We include this distributable and instantiate the components in the client app.
var containerComponent = React.createElement(Container,\*props*\,childControls)
var container = ReactDOM.render(containerComponent,\*the root node*\)
The container code is as follows
export class Container extends Component{
constructor(){
...
}
getValue(){
\*get the children and call getValue on then and return the aggregate value*\
}
render(){
return <div>{this.props.children}</div>;
}
}
but now we want to have an interface in all these components(getValue()) and hence we want reference to these child components. Any control can be passed to this container as long as the component adheres to this interface.
so in client app when we do container.getValue() which in turn should do getValue() on its children.
The problem is we cannot attach the ref callback to these child components that are passed in since the ref is read only. Neither can we clone these component and add the ref callback since React.cloneElement prohibits it and preserves the original ref.
So how do we get a reference to these child components and be able to call the getValue() function of those child components?.
If there is a better way to this entire approach, please do suggest.

How to access data in a child react component from a parent react component

I'm new to ReactJS. I want to be able to set some properties of a React component and then be able to access it from a parent React component. But I'm not entirely sure how to do this. For example, consider the following two classes:
export default class SubWindow extends React.Component {
click(event)
{
this.myCollection.push({name:'receiptNum',value:$(event.currentTarget).html()});
}
render()
{
return (
<ul>
<li onClick={this.click.bind(this)}>0</li>
<li onClick={this.click.bind(this)}>1</li>
<li onClick={this.click.bind(this)}>2</li>
<li onClick={this.click.bind(this)}>3</li>
</ul>
);
}
}
export default class MainWindow extends React.Component {
click(event)
{
console.log(SubWindow.myCollection);
}
render()
{
const SubWindow = require('./SubWindow').default;
return (
<SubWindow />
<button onClick={this.click}>Log subwindow array</button>
);
}
}
Basically, I want the SubWindow to have a property called myCollection which is just an array of JSON objects. myCollection gets populated by each click on the list item.
Later, I want to be able to console.log(SubWindow.myCollection) when I press on a button in the parent window. My question how do I access the SubWindow.myCollection from a parent react component?
I would recommend you to solve this problem by using callback. MainWindow is creating SubWindow, and you can give here a callback function as a property. For example:
<SubWindow onClick={this.onSubwindowClick} />
Now in your SubWindow class, just call this callback inside your click function:
click(event)
{
this.myCollection.push({name:'receiptNum',value:$(event.currentTarget).html()});
this.props.onClick(/* data you want to pass to the parent */);
}
Also you have to define onSubwindowClick in the class MainWindow. This function should receive any data you wish from child class - the data which you pass from child where I put comment /* data you want to pass to the parent */.
Also, don't forget to bind this to that onSubwindowClick function. This is usually done in constructor of the class, which I suggest you to create for each component.
You can find good example about "passing data to parent" on React's pages. You can take a look at the article Thinking in React, particularly section "Step 5: Add inverse data flow".
just pass a function to you child component, and the function is bind to the parent component's 'this', actually you just created a closure.
then in your parent component, the function's args are passed in your child component, meanwhile your parent component's scope has access to the args, so in the parent scope you can get access to the data in the child scope.

Angular pass callback function to child component as #Input similar to AngularJS way

AngularJS has the & parameters where you could pass a callback to a directive (e.g AngularJS way of callbacks. Is it possible to pass a callback as an #Input for an Angular Component (something like below)? If not what would be the closest thing to what AngularJS does?
#Component({
selector: 'suggestion-menu',
providers: [SuggestService],
template: `
<div (mousedown)="suggestionWasClicked(suggestion)">
</div>`,
changeDetection: ChangeDetectionStrategy.Default
})
export class SuggestionMenuComponent {
#Input() callback: Function;
suggestionWasClicked(clickedEntry: SomeModel): void {
this.callback(clickedEntry, this.query);
}
}
<suggestion-menu callback="insertSuggestion">
</suggestion-menu>
I think that is a bad solution. If you want to pass a Function into component with #Input(), #Output() decorator is what you are looking for.
export class SuggestionMenuComponent {
#Output() onSuggest: EventEmitter<any> = new EventEmitter();
suggestionWasClicked(clickedEntry: SomeModel): void {
this.onSuggest.emit([clickedEntry, this.query]);
}
}
<suggestion-menu (onSuggest)="insertSuggestion($event[0],$event[1])">
</suggestion-menu>
UPDATE
This answer was submitted when Angular 2 was still in alpha and many of the features were unavailable / undocumented. While the below will still work, this method is now entirely outdated. I strongly recommend the accepted answer over the below.
Original Answer
Yes in fact it is, however you will want to make sure that it is scoped correctly. For this I've used a property to ensure that this means what I want it to.
#Component({
...
template: '<child [myCallback]="theBoundCallback"></child>',
directives: [ChildComponent]
})
export class ParentComponent{
public theBoundCallback: Function;
public ngOnInit(){
this.theBoundCallback = this.theCallback.bind(this);
}
public theCallback(){
...
}
}
#Component({...})
export class ChildComponent{
//This will be bound to the ParentComponent.theCallback
#Input()
public myCallback: Function;
...
}
In some cases, you might need business logic to be performed by a parent component. In the example below we have a child component that renders table row depending on the logic provided by the parent component:
#Component({
...
template: '<table-component [getRowColor]="getColor"></table-component>',
directives: [TableComponent]
})
export class ParentComponent {
// Pay attention on the way this function is declared. Using fat arrow (=>) declaration
// we can 'fixate' the context of `getColor` function
// so that it is bound to ParentComponent as if .bind(this) was used.
getColor = (row: Row) => {
return this.fancyColorService.getUserFavoriteColor(row);
}
}
#Component({...})
export class TableComponent{
// This will be bound to the ParentComponent.getColor.
// I found this way of declaration a bit safer and convenient than just raw Function declaration
#Input('getRowColor') getRowColor: (row: Row) => Color;
renderRow(){
....
// Notice that `getRowColor` function holds parent's context because of a fat arrow function used in the parent
const color = this.getRowColor(row);
renderRow(row, color);
}
}
So, I wanted to demonstrate 2 things here:
Fat arrow (=>) functions instead of .bind(this) to hold the right context;
Typesafe declaration of a callback function in the child component.
An alternative to the answer SnareChops gave.
You can use .bind(this) in your template to have the same effect. It may not be as clean but it saves a couple of lines. I'm currently on angular 2.4.0
#Component({
...
template: '<child [myCallback]="theCallback.bind(this)"></child>',
directives: [ChildComponent]
})
export class ParentComponent {
public theCallback(){
...
}
}
#Component({...})
export class ChildComponent{
//This will be bound to the ParentComponent.theCallback
#Input()
public myCallback: Function;
...
}
An alternative to the answer Max Fahl gave.
You can define callback function as an arrow function in the parent component so that you won't need to bind that.
#Component({
...
// unlike this, template: '<child [myCallback]="theCallback.bind(this)"></child>',
template: '<child [myCallback]="theCallback"></child>',
directives: [ChildComponent]
})
export class ParentComponent {
// unlike this, public theCallback(){
public theCallback = () => {
...
}
}
#Component({...})
export class ChildComponent{
//This will be bound to the ParentComponent.theCallback
#Input()
public myCallback: Function;
...
}
As an example, I am using a login modal window, where the modal window is the parent, the login form is the child and the login button calls back to the modal parent's close function.
The parent modal contains the function to close the modal. This parent passes the close function to the login child component.
import { Component} from '#angular/core';
import { LoginFormComponent } from './login-form.component'
#Component({
selector: 'my-modal',
template: `<modal #modal>
<login-form (onClose)="onClose($event)" ></login-form>
</modal>`
})
export class ParentModalComponent {
modal: {...};
onClose() {
this.modal.close();
}
}
After the child login component submits the login form, it closes the parent modal using the parent's callback function
import { Component, EventEmitter, Output } from '#angular/core';
#Component({
selector: 'login-form',
template: `<form (ngSubmit)="onSubmit()" #loginForm="ngForm">
<button type="submit">Submit</button>
</form>`
})
export class ChildLoginComponent {
#Output() onClose = new EventEmitter();
submitted = false;
onSubmit() {
this.onClose.emit();
this.submitted = true;
}
}
Passing method with argument, using .bind inside template
#Component({
...
template: '<child [action]="foo.bind(this, 'someArgument')"></child>',
...
})
export class ParentComponent {
public foo(someParameter: string){
...
}
}
#Component({...})
export class ChildComponent{
#Input()
public action: Function;
...
}
Following works for me in Angular 13 (as of March 2022).
P.S.- This is more or less similar to what others answered. Adding this answer just to let people know it works in Angular 13.
Define the Function as Flat Arrow and not regular function in parent component.
callBackFn= (args: string): void => {
// callback code here
// This will work (Flat Arrow)
}
// callbackFn(args: string): void {
// //This type of definition will not work.
// }
Pass callback function as attribute to child component
<app-child [callBack]=”callBackFn”></app-child>
Receive the callback function as Input in the child component. The definition should match as that defined in the parent.
#Input() callBack: (args: string) => void;
Then call this function in the child component. You can also call this is child component template.
this.callBack('Test');
OR
<button (click)="callBack('Test')"></button>
But not sure whether this approach is good or not.
I see a similar approach in ReactJS and it works great but still not sure how it works in angular and what will be its impact.
Any comments on this approach would be appreciated.
Another alternative.
The OP asked a way to use a callback. In this case he was referring specifically to a function that process an event (in his example: a click event), which shall be treated as the accepted answer from #serginho suggests: with #Output and EventEmitter.
However, there is a difference between a callback and an event: With a callback your child component can retrieve some feedback or information from the parent, but an event only can inform that something happened without expect any feedback.
There are use cases where a feedback is necessary, ex. get a color, or a list of elements that the component needs to handle. You can use bound functions as some answers have suggested, or you can use interfaces (that's always my preference).
Example
Let's suppose you have a generic component that operates over a list of elements {id, name} that you want to use with all your database tables that have these fields. This component should:
retrieve a range of elements (page) and show them in a list
allow remove an element
inform that an element was clicked, so the parent can take some action(s).
allow retrieve the next page of elements.
Child Component
Using normal binding we would need 1 #Input() and 3 #Output() parameters (but without any feedback from the parent). Ex. <list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>, but creating an interface we will need only one #Input():
import {Component, Input, OnInit} from '#angular/core';
export interface IdName{
id: number;
name: string;
}
export interface IListComponentCallback<T extends IdName> {
getList(page: number, limit: number): Promise< T[] >;
removeItem(item: T): Promise<boolean>;
click(item: T): void;
}
#Component({
selector: 'list-ctrl',
template: `
<button class="item" (click)="loadMore()">Load page {{page+1}}</button>
<div class="item" *ngFor="let item of list">
<button (click)="onDel(item)">DEL</button>
<div (click)="onClick(item)">
Id: {{item.id}}, Name: "{{item.name}}"
</div>
</div>
`,
styles: [`
.item{ margin: -1px .25rem 0; border: 1px solid #888; padding: .5rem; width: 100%; cursor:pointer; }
.item > button{ float: right; }
button.item{margin:.25rem;}
`]
})
export class ListComponent implements OnInit {
#Input() callback: IListComponentCallback<IdName>; // <-- CALLBACK
list: IdName[];
page = -1;
limit = 10;
async ngOnInit() {
this.loadMore();
}
onClick(item: IdName) {
this.callback.click(item);
}
async onDel(item: IdName){
if(await this.callback.removeItem(item)) {
const i = this.list.findIndex(i=>i.id == item.id);
this.list.splice(i, 1);
}
}
async loadMore(){
this.page++;
this.list = await this.callback.getList(this.page, this.limit);
}
}
Parent Component
Now we can use the list component in the parent.
import { Component } from "#angular/core";
import { SuggestionService } from "./suggestion.service";
import { IdName, IListComponentCallback } from "./list.component";
type Suggestion = IdName;
#Component({
selector: "my-app",
template: `
<list-ctrl class="left" [callback]="this"></list-ctrl>
<div class="right" *ngIf="msg">{{ msg }}<br/><pre>{{item|json}}</pre></div>
`,
styles:[`
.left{ width: 50%; }
.left,.right{ color: blue; display: inline-block; vertical-align: top}
.right{max-width:50%;overflow-x:scroll;padding-left:1rem}
`]
})
export class ParentComponent implements IListComponentCallback<Suggestion> {
msg: string;
item: Suggestion;
constructor(private suggApi: SuggestionService) {}
getList(page: number, limit: number): Promise<Suggestion[]> {
return this.suggApi.getSuggestions(page, limit);
}
removeItem(item: Suggestion): Promise<boolean> {
return this.suggApi.removeSuggestion(item.id)
.then(() => {
this.showMessage('removed', item);
return true;
})
.catch(() => false);
}
click(item: Suggestion): void {
this.showMessage('clicked', item);
}
private showMessage(msg: string, item: Suggestion) {
this.item = item;
this.msg = 'last ' + msg;
}
}
Note that the <list-ctrl> receives this (parent component) as the callback object.
One additional advantage is that it's not required to send the parent instance, it can be a service or any object that implements the interface if your use case allows it.
The complete example is on this stackblitz.
Use Observable pattern. You can put Observable value (not Subject) into Input parameter and manage it from parent component. You do not need callback function.
See example: https://stackoverflow.com/a/49662611/4604351
As for me, besides .bind(this), I had to put a pair of parentheses behind the the method's name for the method to be executed.
In the Parent component:
In the .ts file:
this.pillTabs = [
{ tabName: 'Subscribers', tabMethod: this.showSubscribers.bind(this) },
{ tabName: 'Exemplars', tabMethod: this.showExemplars.bind(this) }
];
In the .html file:
<pill-tabs [pillTabs]="pillTabs"></pill-tabs>
In the Child component:
In the .ts file:
#Input() pillTabs: PillTab[];
In the .html file:
<div *ngFor="let pillTab of pillTabs; let i = index">
<input type="radio" id="{{'radio-' + i}}" name="tabs" [checked]="pillTab.checked"
(click)="pillTab.tabMethod()" />
<label class="tab" for="{{'radio-' + i}}">{{pillTab.tabName}}</label>
</div>
The code was NOT working when I did NOT have the pair of parentheses behind the method:
(click)="pillTab.tabMethod"
And then when I put the pair of parentheses there, the code started to work.
(click)="pillTab.tabMethod()"
I hope that someone finds it helpful.
The current answer can be simplified to...
#Component({
...
template: '<child [myCallback]="theCallback"></child>',
directives: [ChildComponent]
})
export class ParentComponent{
public theCallback(){
...
}
}
#Component({...})
export class ChildComponent{
//This will be bound to the ParentComponent.theCallback
#Input()
public myCallback: Function;
...
}

Resources