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);
}
}
Related
Hello I have 3 components in Angular 13:
Child : Burger-menu
Parent : Header
Grand-Parent : app.component.html
I have a close burger menu button in the burger-menu component.
when I click on it, I want to remove a class that is on the header. The header is located in the grandparent component.
Here is my code :
Grand-parent component:
app.component.html
<header app-header class="headerMain"
[class]="headerClasses"
(addClassEvent)="receiveClass('--openned')">
</header>
app.component.ts
import {Component, ViewEncapsulation} from '#angular/core';
import {BurgerMenuComponent} from "./burger-menu/burger-menu.component";
export {BurgerMenuComponent} from './burger-menu/burger-menu.component';
#Component({
selector: '[app-root]',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class AppComponent {
title = 'sweet4U';
headerClasses = "headerMain";
receiveClass($event: string) {
this.headerClasses = $event;
}
removeClassHeader($event: string) {
this.headerClasses = $event;
}
}
Parent component :
header.component.html
<button type="button" aria-label="ouverture menu burger" class="btn-burger btn" (click)="onClickButtonToggle()" >
<img src="../../assets/images/svg/menu-burger.svg" alt="">
</button>
<nav app-burger-menu
[class]="burgerClasses"
(closeBurgerMenu)="receiveClass('menuBurger')" >
</nav>
<a [routerLink]="['/home']">
<figure class="headerMain__logo">
<img [src]="logoImg" alt="logo de la crèche sweet4u" loading="lazy">
</figure>
</a>
<nav app-menu-main class="menuMain"></nav>
header.component.ts
import {Component, OnInit, Output, EventEmitter, ViewEncapsulation} from '#angular/core'
import {BurgerMenuComponent} from "../burger-menu/burger-menu.component";
export {BurgerMenuComponent} from '../burger-menu/burger-menu.component';
#Component({
selector: '[app-header]',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
encapsulation:ViewEncapsulation.None ,
})
export class HeaderComponent implements OnInit {
logoImg = "assets/images/svg/logo-site-Sweet4U.svg";
headerClasses = "headerMain";
burgerClasses = "menuBurger";
#Output() addClassEvent= new EventEmitter<string>();
constructor () { }
onClickButtonToggle(): void{
this.burgerClasses ="menuBurger --openned";
this.addClassEvent.emit('headerMain --openned')
}
receiveClass($event: string) {
this.burgerClasses = $event;
}
ngOnInit(): void {
}
}
Child Component :
burger-menu.component.html
<button type="button" id="closeMenuburger" aria-label="fermeture menu burger" class="btn-burger btn" (click)="onClickButtonClose()" >
<img src="../../assets/images/svg/close.svg" alt="">
</button>
burger-menu.component.ts
import {Component, EventEmitter, OnInit, Output, ViewEncapsulation} from '#angular/core'
import {HeaderComponent} from "../header/header.component";
export {HeaderComponent} from "../header/header.component";
import {AppComponent} from "../app.component";
export {AppComponent} from "../app.component";
#Component({
selector: '[app-burger-menu]',
templateUrl: './burger-menu.component.html',
styleUrls: ['./burger-menu.component.scss'],
encapsulation:ViewEncapsulation.None ,
})
export class BurgerMenuComponent implements OnInit {
class: string ="";
#Output() closeBurgerMenu = new EventEmitter<string>();
#Output() resizeHeighHeaderNavBurger = new EventEmitter<string>();
onClickButtonClose() {
this.closeBurgerMenu.emit(this.class);
this.resizeHeighHeaderNavBurger.emit(this.class);
}
constructor() { }
ngOnInit(): void {
}
}
When I click on the open menu button (in the parent component), I have the --openned class added to the header (grand parent component).
Now where I have a problem is to remove this class when I click on the burger close button (child component)
Any tips to help me solve this problem?
Thank you in advance
You're not taking advantage of the emitted value from header component. You're emitting this.addClassEvent.emit('headerMain --openned') and then in the app component statically passing a string to the function (addClassEvent)="receiveClass('--openned')".
Change it to (addClassEvent)="receiveClass($event)" and emit an empty string on burger close in header component like this
receiveClass($event: string) {
this.burgerClasses = $event;
this.addClassEvent.emit('')
}
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.
I am trying to setup a click event to a Button dynamically using fromEvent and defer modules of rxjs.
It works fine when using normal html button, but doesn't work with Angular Material button.
Here is the code works fine with Normal Button:
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
import { defer, fromEvent } from 'rxjs';
import { map, tap } from 'rxjs/operators';
#Component({
selector: 'app-test',
template: `
<button #testBtn>Click me</button>
`
})
export class TestComponent implements OnInit {
#ViewChild('testBtn', { static: true }) testBtn: ElementRef<HTMLButtonElement>;
event$ = defer(() => fromEvent(this.testBtn.nativeElement, 'click')).pipe(
map(() => new Date().toString()),
tap(console.log)
)
constructor() { }
ngOnInit() {
this.event$.subscribe();
}
}
And here is the code which doesn't work with Angular Material Button
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
import { defer, fromEvent } from 'rxjs';
import { map, tap } from 'rxjs/operators';
#Component({
selector: 'app-test',
template: `
<button mat-raised-button #testBtn>Click me</button>
`
})
export class TestComponent implements OnInit {
#ViewChild('testBtn', { static: true }) testBtn: ElementRef<HTMLButtonElement>;
event$ = defer(() => fromEvent(this.testBtn.nativeElement, 'click')).pipe(
map(() => new Date().toString()),
tap(console.log)
)
constructor() { }
ngOnInit() {
this.event$.subscribe();
}
}
I couldn't guess why this problem is happening.
Can you help me understand it ?
EDIT: add it { read: ElementRef } your ViewChild;
Use it for your matButton.
#ViewChild("myButton", { read: ElementRef }) myButtonRef: ElementRef;
You need to use your button as nativeElement ElementRef;
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
I have a MainComponent with a <router-outlet> in which child components are loaded.
On the /messages-url the messagesComponent is loaded. I have added an evenlistener on the MainComponent which fires when the user scrolls in the container in which the <router-outlet> resides like this:
#Component({
selector: "main-component",
template: `
<div (scroll)="onContainerScrollEvent($event)">
<router-outlet></router-outlet>
</div>
`
})
export class MainComponent {
private messagePage: number = 0;
onContainerScrollEvent(event: any) {
this.messagePage += 1;
}
}
When the onContainerScrollEvent fires I want to call a function on the messagesComponent to get some new messages.
I have added an EventEmitter on the messagesComponent which fires on the onInit and which passes itself to the parent event, but <router-outlet> doesn't support that.
UPDATE
Below the answer to my question incorporating Ahmed's answer:
MessageService:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessageService {
private messagesPageSource = new Subject<number>();
messagesPage$ = this.messagesPageSource.asObservable();
public setPage(page: number)
{
this.messagesPageSource.next(page);
}
}
MainComponent:
import { Component, OnInit } from '#angular/core';
import { MessageService } from './messages.service';
#Component({
selector: "main-component",
template: `
<div (scroll)="onContainerScrollEvent($event)">
<router-outlet></router-outlet>
</div>
`
})
export class MainComponent {
private pageNumber: number = 1;
constructor(private messageService: MessageService) {
messsageService.messagesPage$.subscribe(p => { });
}
onContainerScroll(event: any) {
this.pageNumber += 1;
this.messageService.setPage(this.pageNumber);
}
}
messagesComponent
import { Component, OnInit } from '#angular/core';
import { MessageService } from './messages.service';
import { Subscription } from "rxjs/Subscription";
#Component({
selector: "messages",
templateUrl: "messages.view.html"
})
export class messagesComponent implements OnInit {
private pageNumber: number = 1;
subscription: Subscription;
constructor(private messageService: MessageService) {
this.subscription = messageService.messagesPage$.subscribe(p => {
this.pageNumber = p;
this.getMessages();
});
}
ngOnInit() {
this.getMessages();
}
private getMessages() {
//Call service to retrieve messages
}
}
Use a bi-directional service as described in the section Parent and children communicate via a service of this angular cookbook:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Create a message service which you'll use to communicate between your parent and child component and any other component you want as well.
I'll skip providing a code example as the angular cookbook above has a pretty good example.