Angular2 - Accessing parent component's variable from child component - javascript

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

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.

Angular: Getter/Setter - Getter is returning undefined

I'm trying to pass a variable that is set on a component, to the parent component via a getter/setter in a service. The setter is applied correctly, but the getter returns undefined.
The below code was pulled out of another project I work on that does just fine with this code so I'm not sure why it isn't working here.
I simply just need to pass the pageTitle that is set on the child component, pass it to the parent component to display in its HTML.
Parent Component
TS: styleguide.component.ts
import { Component } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { StyleguideService } from './styleguide.service';
#Component({
selector: 'styleguide',
templateUrl: './styleguide.component.html',
host: {'class': 'route'},
})
export class StyleguideComponent {
constructor( private ss: StyleguideService ) {}
}
Relevant HTML: styleguide.component.html
<a [routerLink]="[]" aria-current="page" class="crumbs__link crumbs__link--active" [title]="ss.pageTitle">{{ss.pageTitle}}</a>
Parent Module: styleguide.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { StyleguideService } from './styleguide.service';
import { StyleguideComponent } from './styleguide.component';
import { TemplatesComponent } from './templates/templates.component';
...
#NgModule({
imports: [
CommonModule,
FormsModule,
...
],
declarations: [
StyleguideComponent,
TemplatesComponent,
...
],
exports: [
...
],
providers: [
StyleguideService
]
})
export class StyleguideModule {}
Service: styleguide.service.ts
import { Injectable } from '#angular/core';
#Injectable()
export class StyleguideService {
pageTitleS: string;
get pageTitle(): string {
console.log('get title: ', this.pageTitleS); // <-- Returns undefined
return this.pageTitleS;
}
set pageTitle(s: string) {
console.log('set title: ', s);
this.pageTitleS= s;
}
}
Child Component: templates.component.ts
import { Component } from '#angular/core';
import { StyleguideService } from '../styleguide.service';
#Component({
selector: 'templates',
templateUrl: './templates.component.html',
host: {'class': 'route__content'}
})
export class TemplatesComponent {
constructor( private ss: StyleguideService ) {
this.ss.pageTitle = "Templates";
}
}
You should implement the Service with Observables. A quick example would be something like this:
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Injectable} from '#angular/core'
#Injectable()
export class Service {
private value: BehaviorSubject<string>;
constructor() {
this.value = <BehaviorSubject<string>>new BehaviorSubject();
}
setValue(value=""){
this.value.next(value);
}
getValue() {
return this.value.asObservable();
}
}
The Parent Component would subscribe to it like so:
import {Component, OnInit} from '#angular/core'
import { Service } from './service';
#Component({
selector: 'parent-component',
template: `
<div>
<h2>Value {{value}}</h2>
<child-component></child-component>
</div>
`,
})
export class ParentComponent implements OnInit {
value:string;
constructor(private service: Service) {
}
ngOnInit(){
this.service.getValue().subscribe((newValue)=>{
this.value = newValue;
})
}
}
And the Child Component would set the value and also subscribe to it like so:
import {Component, OnInit} from '#angular/core'
import { Service } from './service';
#Component({
selector: 'child-component',
template: `
<div>
<h2>Child Value {{value}}</h2>
</div>
`,
})
export class ChildComponent implements OnInit {
value:string;
constructor(private service: Service) {
this.service.setValue('New Value');
}
ngOnInit(){
this.service.getValue().subscribe((newValue)=>{
this.value = newValue;
})
}
}
Your setter is never called. You instantiate the service using StyleguideComponent, not the TemplatesComponent which does call the setter, and the constructor of StyleguideComponent does not call the setter on the service which is why the value remains undefined.
The TemplatesComponent has an element selector templates which I do not see in the styleguide.component.html you have in the question which is why I believe TemplatesComponent is never being created.
You are not calling the setter function in your child.component.ts instead you are setting the value of variable but I think you are accessing it wrongly as you are missing the last S in the variable name. You should be doing
export class TemplatesComponent {
constructor( private ss: StyleguideService ) {
this.ss.pageTitle("Templates");
// Now to get it you should call
this.ss.pageTitle(); // Should console.log the value
}
}
Okay so it was related to my routing setup, I didn't have my child routes setup correctly so this really had nothing to do with the getter/setter after all.

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{
}

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

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.

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);
});
}
}

Categories