How to use the input of one component in another - Angular4 - javascript

I'm a new in Angular and I have a problem: I need to use one variable from ComponentA in ComponentB So this is my code below (I need to use "favoriteSeason" input result in component "Result"
Component A
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl, ValidatorFn }
from '#angular/forms';
import {MatRadioModule} from '#angular/material/radio';
import { ResultComponent } from '../result/result.component';
import { HostBinding } from '#angular/core';
#Component({
selector: 'app-answer-three',
templateUrl: './answer-three.component.html',
styleUrls: ['./answer-three.component.css']
})
export class AnswerThreeComponent {
disableBtn: boolean;
favoriteSeason: string;
seasons: string[] = ['Cheesburger', 'Cheesecake', 'Fondue', 'Pizza'];
submit() {
this.disableBtn = !this.disableBtn;
const result = this.favoriteSeason;
console.log(result);
}
}
<div class="co">
<mat-radio-group class="example-radio-group" [(ngModel)]="favoriteSeason" (ngSubmit)="submit()">
<div class="form-check">
<h1>Choose a food:</h1>
</div>
<mat-radio-button class="example-radio-button" *ngFor="let season of seasons" [value]="season">
{{season}}
</mat-radio-button>
</mat-radio-group>
<div class="example-selected-value">Your favorite food is: {{favoriteSeason}}</div>
<nav>
<div class="column">
<button class="btn btn-primary" [disabled]="disableBtn" name="button" (click)="submit()">save
</button>
<button class="btn btn-primary" [disabled]="!disableBtn" name="button" (click)="submit()">
<a routerLink="/result">Next</a>
</button>
</div>
</nav>
</div>
And I need to use the result of "favoriteSeason" in component Result
Component B
import { NgModule, Output } from '#angular/core';
import { Component, OnInit, Input } from '#angular/core';
import {Subject} from 'rxjs';
import { Injectable } from '#angular/core';
import { AnswerThreeComponent } from '../answer-three/answer-three.component';
import { HostListener } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css'],
})
export class ResultComponent {
#Input () answer: AnswerThreeComponent;
#Input () res: AnswerThreeComponent['submit'];
#HostListener('click')
click() {
const result = this.answer.favoriteSeason;
console.log(this.answer.favoriteSeason);
}
}
But i received an error - "can't find favoriteSeason name". What I do wrong? Thank you for any help and sorry if I wrote this question wrong (it's my first time)

Sanchit Patiyal's answer is correct, but I would like to elaborate on that.
When you use the #Input() decorator on a component's field, that means that you now can set that variable in a parent component's template. Consider the following example:
Component A:
#Component({
selector: 'aComponent',
templateUrl: './a.component.html'
})
export class AComponent {
inputValue: string;
//doesn't matter what you do here
}
<input [(ngModel)]="inputValue">
<bComponent [inputValue]="inputValue">
</bComponent>
Component B:
#Component({
selector: 'bComponent',
templateUrl: './b.component.html'
})
export class BComponent {
#Input() inputValue: string;
//this variable will be set by the parent component
}
<h1>{{inputValue}}</h1>
This is an example of one-way data flow, meaning that the data only flows into the component B. Now if you need to get some data out of the component, you need to use #Output() decorator, which uses an EventEmitter to emit events out to the parent component when something happens. Let's introduce the third component, cComponent:
#Component({
selector: 'cComponent',
templateUrl: './c.component.html'
})
export class CComponent {
#Output() output: EventEmitter<string>;
private counter: number;
click() {
this.output.next(counter++);
}
}
<button (click)="click()">Click me!</button>
...and then edit our AComponent like this:
#Component({
selector: 'aComponent',
templateUrl: './a.component.html'
})
export class AComponent {
inputValue: string;
buttonClicked(event: string) {
this.inputValue = event;
}
}
<cComponent (output)="buttonClicked($event)"></cComponent>
<bComponent [inputValue]="inputValue"></bComponent>
So, to recap, the component's output works just like other events (say (click) or (focus)), and can be used to get the data out of the component. HOpe this helps ;)
Welcome, by the way!

You can use RxJS BehaviorSubject for this. In this case we create a private BehaviorSubject that will hold the current value of the message which can be set in one component and get in other one. Follow this link for the tutorial.
Sharing Data Between Angular Components

Related

HTML button onclick performs action on other HTML page

It is a simple problem but I cannot get around it as I am new to angular and web development. Basically there are two components home and dashboard. The button in home.component.html changes the source of the image from bulbOn.png to bulbOff.png. I want that the same button should also change the source similarly also on dashboard.component.html. I think I need to use typescript for that but I dont know how. Basically how should onClick on one html performs actions on other html?
home.component.html
<mat-card >
<button onclick="document.getElementById('myImage').src='assets/BulbOn.svg'">Turn on the bulb.</button>
<img id="myImage" src="assets/BulbOn.svg" style="width:100px">
<button onclick="document.getElementById('myImage').src='assets/BulbOff.svg'">Turn off the bulb.</button>
</mat-card>
dashboard.component.html
<mat-card class="bulbCard">
<div class="bulbimg"> <img src="assets/BulbOn.svg"> </div>
</mat-card>
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.less']
})
export class DashboardComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
home.component.ts
import { Component } from '#angular/core';
import { User } from '#app/_models';
import { AccountService } from '#app/_services';
#Component({ templateUrl: 'home.component.html',
styleUrls: ['./home.component.less'] })
export class HomeComponent {
user: User;
constructor(private accountService: AccountService) {
this.user = this.accountService.userValue;
}
}
You should avoid manipulating the DOM like this with Angular.
And instead of using onclick, you should use Angulars event bindings. https://angular.io/guide/event-binding
<mat-card>
<button (click)="changeBulbState(true)">
Turn on the bulb.
</button>
<img [src]="bulbState ? 'assets/BulbOn.svg' : 'assets/BulbOff.svg'" style="width:100px">
<button (click)="changeBulbState(false)">
Turn off the bulb.
</button>
</mat-card>
Within your component's typescript add a variable for bulbState. The value of which will be changed when you interact with the buttons in your card.
The src of the image will change depending on if the bulbState variable is true or false.
import { Component } from '#angular/core';
import { User } from '#app/_models';
import { AccountService } from '#app/_services';
#Component({ templateUrl: 'home.component.html',
styleUrls: ['./home.component.less'] })
export class HomeComponent {
user: User;
bulbState: boolean;
constructor(
private accountService: AccountService,
private bulbStatusService: BulbStatusService
) {
this.user = this.accountService.userValue,
this.bulbStatusService.bulbStatus.subscribe(data => this.bulbState = value)
}
changeBulbState(state: boolean) {
this.bulbStatusService.changeBulbState(state);
}
}
In order to share this across multiple components I would suggest using a service.
https://medium.com/front-end-weekly/sharing-data-between-angular-components-f76fa680bf76
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class BulbStatusService {
private bulbState = new BehaviorSubject(false);
bulbStatus = this.bulbState.asObservable();
constructor() { }
changeBulbState(state: boolean) {
this.bulbState.next(state)
}
}
What you would like to is to have a bulb state somewhere. Usually in Angular it's either the parent component which passes the state down to it's children or you can have a service to get/set the state. RxJS comes bundled with Angular and has some great utilities (observables) for sharing the state.
e.g. app-state.service.ts
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class AppState {
public readonly lightBulb = new BehaviorSubject<'on' | 'off'>('on');
}
Now inject this to you home component:
import { Component } from '#angular/core';
import { User } from '#app/_models';
import { AccountService } from '#app/_services';
import { AppState } from 'app-state.service';
#Component({ templateUrl: 'home.component.html',
styleUrls: ['./home.component.less'] })
export class HomeComponent {
user: User;
constructor(
private accountService: AccountService,
public state: AppState
) {
this.user = this.accountService.userValue;
}
}
In HTML:
<mat-card>
<button (click)="state.lightBulb.next('on')">Turn on the bulb.</button>
<img id="myImage" [src]="(state.lightBulb | async) === 'on' ? 'assets/BulbOn.svg' : 'assets/BulbOff.svg'" style="width:100px">
<button (click)="state.lightBulb.next('off')">Turn off the bulb.</button>
</mat-card>
Then do the same thing for dashboard component:
import { Component } from '#angular/core';
import { AppState } from 'app-state.service';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.less']
})
export class DashboardComponent {
constructor(public state: AppState) { }
}
And in HTML:
<mat-card class="bulbCard">
<div class="bulbimg"><img [src]="(state.lightBulb | async) === 'on' ? 'assets/BulbOn.svg' : 'assets/BulbOff.svg'"></div>
</mat-card>
So in the nut shell, Subjects are things that holds some value and that value can be changed with Subject.next([value here]).
Subjects are Observables and Observables can be subscribed to to get those values over time. In Angular we have async pipe which does this subscription for you and disposes that as well after the component is destroyed.
There are few things you could do better with this "observable store pattern" but here it is at it's simplest form.
Few notes related to other things: use (click) instead onclick as () is the Angular's way to bind outputs. Don't directly manipulate (or at least avoid) anything in DOM e.g. ´document.getElementById('myImage').src='assets/BulbOn.svg'´ but rather bind a value for that attribute with [] e.g. [bulbSvgSource] where ´bulbSvgSource´ would be defined in the component class.

getting ExpressionChangedAfterItHasBeenCheckedError Angular 4

Im aware similar questions exist but none of those have provided me with an answer that works..
Basically I have a site with some services that inject data dynamically
In my app.component.ts I have two headers.. one when your on the home page and one for when your on any other page
app.component.html
<app-header *ngIf="router.url !== '/'"></app-header>
<app-header-home *ngIf="router.url != '/'"></app-header-home>
<router-outlet></router-outlet>
<app-footer></app-footer>
app.component.ts
import { Component } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'app';
router: string;
constructor(
private _router: Router
) {
this.router = _router.url;
}
}
now I also have a service that dynamically injects the title of the header
headerTitle.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class HeaderTitleService {
title = new BehaviorSubject('');
constructor() { }
setTitle(title: any) {
this.title.next(title);
}
}
then In my home component for example I set the title
home.component.ts
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { HeaderTitleService } from '../../services/headerTitle.service';
import { HeaderImageService } from '../../services/headerImage.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(
private headerTitleService: HeaderTitleService,
private headerImageService: HeaderImageService
) { }
ngOnInit() {
}
ngAfterViewInit() {
this.headerTitleService.setTitle(`
We strive to create things
<br> that are engaging, progressive
<br> & above all
<span class="highlight">
<em>innovative.</em>
</span>
`);
}
}
now basically it was all working until I put in the if statements on the two headers
now Im getting this error
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: '
We strive to create things
<br> that are engaging, progressive
<br> & above all
<span class="highlight">
<em>innovative.</em>
</span>
'.
not sure how I can fix this.. I tried setting the values in ngAfterViewInit but it did nothing
or does anyone know another way I could accomplish this??
Thanks
You can try using a setTimeOut method instead and set the values
inside of that
setTimeout(this.headerTitleService.setTitle(`
We strive to create things
<br> that are engaging, progressive
<br> & above all
<span class="highlight">
<em>innovative.</em>
</span>
`), 0);
note this is a work around and not a full proff solution to the problem .
To know why this error occurs in Angular change detection you need to know how the change detection works in Angular for this you can refer to this blog by Maxim NgWizard K
I know i fixed this in mine.
here is a great post
everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror
i have forced the change detection
export class AppComponent {
name = 'I am A component';
text = 'A message for the child component';
constructor(private cd: ChangeDetectorRef) {
}
ngAfterViewInit() {
this.cd.detectChanges();
}

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 manually register to an inner component event in Angular?

In a parent component I have this code :
<div class="app">
<counter [init]="myValue" (change)="myValueChange($event);"></counter>
</div>
Which registers the (change) event via template.
The inner component has :
#Output('change')counterChange = new EventEmitter();
Which emits (on click) :
this.counterChange.emit(...)
Question:
In the parent component, how can I register the (change) event via code and not via template?
You should subscribe to a Subject in the child component by adding the counter child component as a ViewChild.
IMPORTANT: As mentioned by #echonax an EventEmitter should never be used to subscribe to, as this class might eventually become for internal use only and is not guaranteed to be an Observable in the future, More details can be found in 'What is the proper use of an EventEmitter?'.
An example using a Subject and Subscription (untested):
app.component.ts:
import {Component, AfterViewInit, OnDestroy, ViewChild} from '#angular/core';
import {CounterComponent} from './counter.component';
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'my-app',
template: `
<div class="app">
<counter [value]="myValue"></counter>
</div>`
})
export class AppComponent implements AfterViewInit, OnDestroy {
#ViewChild(CounterComponent) counter: CounterComponent;
public myValue = 2;
private counterSubscription: Subscription;
ngAfterViewInit() {
this.counterSubscription = this.counter.subject.subscribe(
value => console.log('value', value)
);
}
ngOnDestroy() {
if (!!this.counterSubscription) {
this.counterSubscription.unsubscribe();
}
}
}
counter.component.ts:
import {Component, Input} from '#angular/core';
import {Subject} from 'rxjs/Subject';
#Component({
selector: 'counter',
template: `
<div class="counter">
<div class="counter__container">
<button (click)="decrement();" class="counter__button">
-
</button>
<input type="text" class="counter__input" [value]="value">
<button (click)="increment();" class="counter__button">
+
</button>
</div>
</div>`
})
export class CounterComponent {
#Input() value = 0;
private _subject = new Subject<number>();
public subject = _subject.asObservable();
increment() {
this.value++;
this.subject.next(this.value);
}
decrement() {
this.value--;
this.subject.next(this.value);
}
}

angular 2 output and eventemitter doesn't emit

i'm learning angular 2, and i follow this tutorial: https://egghead.io/lessons/angular-2-angular-2-building-a-toggle-button-component
but the whole part of the output and eventemitter doesn't work.
i do not get any errors and i can't figure out why it doesn't work.
this is my code for the togglelink component:
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'app-togglelink',
templateUrl: 'togglelink.component.html',
styleUrls: ['togglelink.component.css']
})
export class TogglelinkComponent implements OnInit {
#Input() state:boolean = true;
#Output() onChange = new EventEmitter();
onClick(){
this.state = !this.state;
this.onChange.emit(this.state);
}
constructor() {}
ngOnInit() {
}
}
and this is the code for the firstpage component that uses the togglelink component:
import { Component, OnInit } from '#angular/core';
import {TogglelinkComponent} from '../togglelink/togglelink.component';
#Component({
moduleId: module.id,
selector: 'app-firstpage',
templateUrl: 'firstpage.component.html',
styleUrls: ['firstpage.component.css'],
directives: [TogglelinkComponent]
})
export class FirstpageComponent implements OnInit {
thetogglestate:boolean = false;
firsttitle = "the first title";
constructor() {}
ngOnInit() {
}
}
and this is the code for the template of the firstpage component:
{{thetogglestate ? 'On' : 'Off'}}
</p>
<h2 *ngIf="thetogglestate">
{{firsttitle}}
</h2>
<p>
firstpage works!
</p>
when i change manually thetogglestate it does work, so i understand that the issue is with the output and the eventemitter part.
any idea why?
best regards
In the firstpage.component.html template, you need to register some processing for this event to toggle the value of the thetogglestate variable.
Something like that:
<app-togglelink (onChange)="thetogglestate = !thetogglestate"></app-togglelink>

Categories