How to have component interact with nested dialog component in Angular 2? - javascript

I have a generic component. This component has N buttons, where N can be set by the parent. A parent component can have multiple instances of this generic component. When a button is clicked on the generic component, I need to notify the parent component that this button has been pressed, and some way to identify which button has been pressed. I then need to have that parent component be able to call some function on the nested component. Here's a really rough example of what i'm looking to do:
#Component({
selector: 'parent-component',
...
})
export class ParentComponent{
public OnGenericComponentButtonPress(someId){
if (someId === "foo"){
genericComponentInstance.closeComponent();
}else{
doOtherThing();
}
}
}
#Component({
selector: 'generic-component',
...
})
export class GenericComponent{
public closeComponent(){}
}
I need some way to communicate back and forth like this. Assuming a parent component can have multiple instances of GenericComponent, is this possible?

You need 2 communication forms:
Child to Parent
Parent to child
Child to Parent
Communicating from the child to the parent can be done by adding EventEmmiter on the child and subscribing to the event on the parent.
Calling the parent is done by calling emit on the EventEmitter
Child component code:
import { Component, EventEmitter, Output } from '#angular/core';
#Component({
selector: 'my-child',
template: '<h2>{{myText}}</h2><button (click)="onClick()">do something</button>'
})
export class ChildComponent {
private myText = "Child Component";
#Output() clicked: EventEmitter<any> = new EventEmitter();
public onClick() {
this.clicked.emit();
}
}
Parent component code:
import { Component} from '#angular/core';
import { ChildComponent } from "./child.component";
#Component({
selector: 'my-app',
template: `<h1>Parent component</h1>
<my-child (clicked)="doSomething()"></my-child>`,
directives: [ChildComponent]
})
export class AppComponent {
public doSomething() {
do something...
}
}
Parent to Child
To call a child component we first need to get a reference to the component, this is done by using ViewChildren if we have multiple components of the same type.
Parent component code:
import { Component, ViewChildren, QueryList} from '#angular/core';
import { ChildComponent } from "./child.component";
#Component({
selector: 'my-app',
template: `<h1>Parent component</h1>
<my-child></my-child>`,
directives: [ChildComponent]
})
export class AppComponent {
#ViewChildren(ChildComponent) children:QueryList<ChildComponent>;
public callChild(){
this.children.toArray()[0].doSomething();
}
}
Example in this plnkr
Note: There are other ways to communicate between components such as using a common service.

Related

Angular 6 #Viewchild is not working with lazy loading

Here is my code that gives error cannot read property title undefined.
Parent Component
import { Child } from './child.component';
#Component({
selector: 'parent',
})
export class ParentComponet implements OnInit, AfterViewInit {
constructor(){}
#ViewChild(Child) child: Child;
ngAfterViewInit(){
console.log("check data", this.child.title)
}
}
And Child Component is.
#Component({
selector: 'child',
})
export class ChildComponet {
public title = "hi"
constructor(){}
}
routing.module.ts is like
{
path: "",
component: ParentComponent,
children: [
{
path: '/child',
component: ChildComponent
}
]
}
And Gives error is
ERROR TypeError: Cannot read property 'title' of undefined(…)
I think you are missing 'template' or 'templateUrl' in relevance to creating a Component
ParentComponent
import { ChildComponent } from './child.component'; // {ChildComponent} not {Child} as we are referencing it to the exported class of ChildComponent
#Component({
selector: 'parent',
template: `<child></child>`
})
export class ParentComponet implements OnInit, AfterViewInit {...}
ChildComponent
#Component({
selector: 'child',
template: `<h1>{{ title }}</h1>`
})
export class ChildComponent {...} // Be sure to spell it right as yours were ChildComponet - missing 'n'
UPDATE as per the user's clarification on this thread
Had added a Stackblitz Demo for your reference (Check the console)
If you want to access the ChildComponent that is rendered under the Parent Component's <router-outlet> you can do so by utilizing (activate) supported property of router-outlet:
A router outlet will emit an activate event any time a new component is being instantiated
Angular Docs
ParentComponent's Template
#Component({
selector: 'parent',
template: `<router-outlet (activate)="onActivate($event)"></router-outlet>`
})
export class ParentComponent {
onActivate(event): void {
console.log(event); // Sample Output when you visit ChildComponent url
// ChildComponent {title: "hi"}
console.log(event.title); // 'hi'
}
}
The result will differ based on the visited page under your parent's children
If you visit Child1Component you will get its instance Child1Component {title: "hi"}
If you visit Child2Component you will get its instance Child2Component {name: "Angular"}
These results will then be reflected on your ParentComponent's onActivate(event) console for you to access
That's not how it's supposed to work. You'll be only able to get the ChildComponent in your ParentComponent ONLY if you have the <app-child></app-child> tag in your ParentComponent Template.
Something like this:
...
<app-child></app-child>
...
But since you're using child routing, and the ChildComponent will load on the router-outlet of your ParentComponent you won't have access to that using ViewChild
PS: You'll only have access to it inside ngAfterViewInit as ViewChild can only be considered safe to have instantiated after the View has loaded:
import { Component, OnInit, ViewChild } from '#angular/core';
import { ChildComponent } from '../child/child.component';
...
#Component({...})
export class ParentComponent implements OnInit {
#ViewChild(ChildComponent) childComponent: ChildComponent;
...
ngAfterViewInit() {
console.log(this.childComponent);
}
}
Here's a Working Sample StackBlitz for your ref that illustrates your scenario in both the cases.
PS: To get the ChildComponent properties in your ParentComponent, with Routing, you'll have to either use a SharedService or you'll have to pass the ChildProperty in the route as a QueryParam and read it in your ParentComponent using the ActivatedRoute
UPDATE:
Sharing Data using Route Query Params:
Although this won't make much sense, but in your ChildComponent, you can have a Link that would route the user to the ChildComponent with the title property passed as a queryParam. Something like this:
<a
[routerLink]="['/child']"
[queryParams]="{title: title}">
Go To Child Route With Query Params
</a>
And in your ParentComponent have access to it using ActivatedRoute like this:
...
import { ActivatedRoute } from '#angular/router';
...
#Component({...})
export class ParentComponent implements OnInit {
...
constructor(
private route: ActivatedRoute,
...
) { }
ngOnInit() {
this.route.queryParams.subscribe(queryParams => {
console.log('queryParams[`title`]', queryParams['title']);
});
...
}
...
}
Using a SharedService
Just create a SharedService with a private BehaviorSubject that would be exposed as an Observable by calling the asObservable method on it. It's value can be set by exposing a method(setChildProperty) that will essentially call the next method with the updated childProperty value :
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
#Injectable()
export class SharedService {
private childProperty: BehaviorSubject<string> = new BehaviorSubject<string>(null);
childProperty$: Observable<string> = this.childProperty.asObservable();
constructor() { }
setChildProperty(childProperty) {
this.childProperty.next(childProperty);
}
}
You can then inject it both in your ParentComponent and in your ChildComponent:
In ChildComponent set the value:
import { Component, OnInit } from '#angular/core';
import { SharedService } from '../shared.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
public title = "hi"
constructor(private sharedService: SharedService) { }
ngOnInit() {
this.sharedService.setChildProperty(this.title);
}
}
And in your ParentComponent get the value:
...
import { SharedService } from '../shared.service';
#Component({...})
export class ParentComponent implements OnInit {
...
constructor(
...,
private sharedService: SharedService
) { }
ngOnInit() {
...
this.sharedService.childProperty$.subscribe(
childProperty => console.log('Got the Child Property from the Shared Service as: ', childProperty)
);
}
...
}
Make sure inside your parent.component.html template you've added the <child></child> tag.

Call function from one component to another component Angularjs 2 [duplicate]

This question already has answers here:
How to communicate between component in Angular?
(8 answers)
Closed 5 years ago.
I want to pass value from one component to another component.
i.e., I need to pass value from Dashboard Component to Header Component
Here is my Dashboard Component
import{Component}from '#angular/core';
import { Header } from '../../layout/header.component';
export class Dashboard{
showAlert(id : any)
{
setItem(id);
}
}
Here is my Header component
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'header',
templateUrl: './header.component.html',
})
export class Header{
public setItem(id : any)
{
console.log('Exported value'+id)
}
}
But it is always giving Cannot find setItem
What is the mistake i am doing and how can i fix this ?
Note : I am doing this in Angularjs 2
If the element raises events, you can listen to them with an event binding. Refer to the angular doc https://angular.io/guide/template-syntax#event-binding for in depth knowledge.
Dashboard Component
import{Component}from '#angular/core';
import { Header } from '../../layout/header.component';
#Component({
selector: 'dashboard',
templateUrl: './dashboard.component.html',
})
export class Dashboard{
#Output() setItemEvent = new EventEmitter();
showAlert(id : any)
{
this.setItemEvent.emit(id);
}
}
Header component
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'header',
template: '<dashboard (setItemEvent)="setItem(param)"></dashboard>',
})
export class Header{
public setItem(id : any)
{
console.log('Exported value'+id)
}
}
You can use:
localStorage.setItem('name', 'value');
where name is the variable name you will use to access the value. You can access the value using:
var x = localStorage.getItem('name');
You can use event-binding like this :
////First Component
#component({
selector:'componentA',
})
export class ComponentA{
yourMethod(yourPassedParameter:any){...}
}
////// Second Component
#component({
selector:'componentB',
template: `<button (click)="yourMethod(yourParameter)">click</button>`
)
export class ComponentB{
#Output() eventLis = new EventEmitter();
yourMethod(yourParameter:any){...
this.eventLis.emit(yourData);
}
}
////Your crosscomponent
#component({
selector:'crosscomponent',
template: `<componentA #componentA></componentA>
<componentB (eventLis)="componentA.yourMethod(yourParameter)"></componentB>`
)
export class crosscomponent{
}

A way to load service to all of the components in angular 2

I built a service that called lang.service.ts. What it does is simply a key: value mechanism. Whenever I need it to my component, I import it and declare it in the constructor and then I use it as {{lang('key').text}}. so far, so good.
The thing is that I've noticed that I'm gonna load it for each component, like for view-header.component.ts, view-footer.components.ts and a lot of other components. I never used Angular 1, but IIRC, I could do there something like rootScope.lang(..) and it could've accomplish what I was looking for. Is there a way to do something like that in Angular 2?
If you register your service at your root componet, all child components will have access to the service.
On your root component...
import { Component } from '#angular/core';
import { YourService } from './my-servive.service.ts';
import { ChildComponent } from './child-component.component.ts';
#Component({
selector: 'root-component',
providers: [YourService],
directives: [ChildComponent],
template: `<child-component></child-component>`
})
export class RootComponent {}
On your child component...
import { Component } from '#angular/core';
import { YourService } from './my-servive.service.ts';
#Component({
selector: 'child-component'
})
export class ChildComponent {
contructor(private myService: YourService) {}
}
Hope this help you.

Using input/output events to trigger methods in a parent component in Angular 2

How can a child service notify a parent component of a change? I used to do this in angular 1 by $watching a variable in the child service. Unfortunately, this is no longer possible.
I tried injecting the service back into the component, but this fails, probably due to circular dependencies. Based on what I could find in current documentation, I came up with the code below:
AppComponent
|
SomeComponent
|
SomeService
AppComponent
#Component({
selector: '[app-component]',
templateUrl: 'partials/app.html',
directives: [
SomeComponent
],
providers: [
SomeService
]
})
export class AppComponent {
constructor() { }
}
bootstrap(AppComponent);
SomeComponent
import {Component, Input} from 'angular2/core'
import {SomeService} from '../services/some.service'
#Component({
selector: 'foo',
templateUrl: 'partials/foo.html'
})
export class SomeComponent {
constructor() {}
#Input set someEvent(value) {
console.log(value);
}
}
SomeService
import {EventEmitter, Output} from 'angular2/core'
export class CoreService {
constructor() {
this.someEvent = new EventEmitter();
}
#Output() someEvent: EventEmitter<any>;
public foo() {
this.someEvent.emit(true); // Or next(true)?
}
}
#Output must be used for components only not in services. At this level you can register on this event using the (...) syntax.
From the angular.io documentation (https://angular.io/docs/ts/latest/api/core/Output-var.html):
Declares an event-bound output property.
When an output property emits an event, an event handler attached to that event the template is invoked.
For a service you need to explicitly subscribe on this event, as described below:
import {Component, Input} from 'angular2/core'
import {SomeService} from '../services/some.service'
#Component({
selector: 'foo',
templateUrl: 'partials/foo.html'
})
export class SomeComponent {
constructor(service:CoreService) {
service.someEvent.subscribe((val) => {
console.log(value);
});
}
}

Angular2 - Accessing parent component's variable from child component

I want to access a variable on the parent component from the child component and do something with it. Here is how I am doing it but it looks like its not working as it suppose to:
parent.ts
import {Component,DynamicComponentLoader,ElementRef,AfterViewInit} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {ChildComponent} from './child.ts';
#Component({
selector: 'app',
template: `<div #child></div>`
})
class AppComponent implements AfterViewInit{
public parentValue = "some value."
constructor(private _loader:DynamicComponentLoader,private _elementRef:ElementRef) {}
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'child').then(child => {
child.instance.log = this.callback;
});
}
callback(){
console.log(this.parentValue); //logs undefined rather than "some value"
}
}
bootstrap(AppComponent);
child.ts
import {Component} from 'angular2/core';
#Component({
selector: "child-component",
template: "<button (click)='logParentValue()' >Log Value</button>"
})
export class ChildComponent{
log:any;
logParentValue(){
this.log();
}
};
When I try to log the value of parent component's variable from the child component it always logs undefined. Any solution to this?
It seems the scope of the method is not preserved the way you pass it.
It works when passed as closureized arrow function
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'child').then(child => {
child.instance.log = () => this.callback();
});
}
https://plnkr.co/edit/VQ3c2Lv5KEzHUa2ZbGLk?p=preview

Categories