I am trying to implement something like a delegation pattern in Angular.
When the user clicks on a nav-item, I would like to call a function which then emits an event which should in turn be handled by some other component listening for the event.
Here is the scenario: I have a Navigation component:
import {Component, Output, EventEmitter} from 'angular2/core';
#Component({
// other properties left out for brevity
events : ['navchange'],
template:`
<div class="nav-item" (click)="selectedNavItem(1)"></div>
`
})
export class Navigation {
#Output() navchange: EventEmitter<number> = new EventEmitter();
selectedNavItem(item: number) {
console.log('selected nav item ' + item);
this.navchange.emit(item)
}
}
Here is the observing component:
export class ObservingComponent {
// How do I observe the event ?
// <----------Observe/Register Event ?-------->
public selectedNavItem(item: number) {
console.log('item index changed!');
}
}
The key question is, how do I make the observing component observe the event in question ?
Update 2016-06-27: instead of using Observables, use either
a BehaviorSubject, as recommended by #Abdulrahman in a comment, or
a ReplaySubject, as recommended by #Jason Goemaat in a comment
A Subject is both an Observable (so we can subscribe() to it) and an Observer (so we can call next() on it to emit a new value). We exploit this feature. A Subject allows values to be multicast to many Observers. We don't exploit this feature (we only have one Observer).
BehaviorSubject is a variant of Subject. It has the notion of "the current value". We exploit this: whenever we create an ObservingComponent, it gets the current navigation item value from the BehaviorSubject automatically.
The code below and the plunker use BehaviorSubject.
ReplaySubject is another variant of Subject. If you want to wait until a value is actually produced, use ReplaySubject(1). Whereas a BehaviorSubject requires an initial value (which will be provided immediately), ReplaySubject does not. ReplaySubject will always provide the most recent value, but since it does not have a required initial value, the service can do some async operation before returning it's first value. It will still fire immediately on subsequent calls with the most recent value. If you just want one value, use first() on the subscription. You do not have to unsubscribe if you use first().
import {Injectable} from '#angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class NavService {
// Observable navItem source
private _navItemSource = new BehaviorSubject<number>(0);
// Observable navItem stream
navItem$ = this._navItemSource.asObservable();
// service command
changeNav(number) {
this._navItemSource.next(number);
}
}
import {Component} from '#angular/core';
import {NavService} from './nav.service';
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'obs-comp',
template: `obs component, item: {{item}}`
})
export class ObservingComponent {
item: number;
subscription:Subscription;
constructor(private _navService:NavService) {}
ngOnInit() {
this.subscription = this._navService.navItem$
.subscribe(item => this.item = item)
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
}
#Component({
selector: 'my-nav',
template:`
<div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
<div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>`
})
export class Navigation {
item = 1;
constructor(private _navService:NavService) {}
selectedNavItem(item: number) {
console.log('selected nav item ' + item);
this._navService.changeNav(item);
}
}
Plunker
Original answer that uses an Observable: (it requires more code and logic than using a BehaviorSubject, so I don't recommend it, but it may be instructive)
So, here's an implementation that uses an Observable instead of an EventEmitter. Unlike my EventEmitter implementation, this implementation also stores the currently selected navItem in the service, so that when an observing component is created, it can retrieve the current value via API call navItem(), and then be notified of changes via the navChange$ Observable.
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import {Observer} from 'rxjs/Observer';
export class NavService {
private _navItem = 0;
navChange$: Observable<number>;
private _observer: Observer;
constructor() {
this.navChange$ = new Observable(observer =>
this._observer = observer).share();
// share() allows multiple subscribers
}
changeNav(number) {
this._navItem = number;
this._observer.next(number);
}
navItem() {
return this._navItem;
}
}
#Component({
selector: 'obs-comp',
template: `obs component, item: {{item}}`
})
export class ObservingComponent {
item: number;
subscription: any;
constructor(private _navService:NavService) {}
ngOnInit() {
this.item = this._navService.navItem();
this.subscription = this._navService.navChange$.subscribe(
item => this.selectedNavItem(item));
}
selectedNavItem(item: number) {
this.item = item;
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
#Component({
selector: 'my-nav',
template:`
<div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
<div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>
`,
})
export class Navigation {
item:number;
constructor(private _navService:NavService) {}
selectedNavItem(item: number) {
console.log('selected nav item ' + item);
this._navService.changeNav(item);
}
}
Plunker
See also the Component Interaction Cookbook example, which uses a Subject in addition to observables. Although the example is "parent and children communication," the same technique is applicable for unrelated components.
Breaking news: I've added another answer that uses an Observable rather than an EventEmitter. I recommend that answer over this one. And actually, using an EventEmitter in a service is bad practice.
Original answer: (don't do this)
Put the EventEmitter into a service, which allows the ObservingComponent to directly subscribe (and unsubscribe) to the event:
import {EventEmitter} from 'angular2/core';
export class NavService {
navchange: EventEmitter<number> = new EventEmitter();
constructor() {}
emit(number) {
this.navchange.emit(number);
}
subscribe(component, callback) {
// set 'this' to component when callback is called
return this.navchange.subscribe(data => call.callback(component, data));
}
}
#Component({
selector: 'obs-comp',
template: 'obs component, index: {{index}}'
})
export class ObservingComponent {
item: number;
subscription: any;
constructor(private navService:NavService) {
this.subscription = this.navService.subscribe(this, this.selectedNavItem);
}
selectedNavItem(item: number) {
console.log('item index changed!', item);
this.item = item;
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
#Component({
selector: 'my-nav',
template:`
<div class="nav-item" (click)="selectedNavItem(1)">item 1 (click me)</div>
`,
})
export class Navigation {
constructor(private navService:NavService) {}
selectedNavItem(item: number) {
console.log('selected nav item ' + item);
this.navService.emit(item);
}
}
If you try the Plunker, there are a few things I don't like about this approach:
ObservingComponent needs to unsubscribe when it is destroyed
we have to pass the component to subscribe() so that the proper this is set when the callback is called
Update: An alternative that solves the 2nd bullet is to have the ObservingComponent directly subscribe to the navchange EventEmitter property:
constructor(private navService:NavService) {
this.subscription = this.navService.navchange.subscribe(data =>
this.selectedNavItem(data));
}
If we subscribe directly, then we wouldn't need the subscribe() method on the NavService.
To make the NavService slightly more encapsulated, you could add a getNavChangeEmitter() method and use that:
getNavChangeEmitter() { return this.navchange; } // in NavService
constructor(private navService:NavService) { // in ObservingComponent
this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
this.selectedNavItem(data));
}
You can use either:
Behaviour Subject:
BehaviorSubject is a type of subject, a subject is a special type of observable which can act as observable and observer
you can subscribe to messages like any other observable and upon subscription, it returns the last value of the subject
emitted by the source observable:
Advantage: No Relationship such as parent-child relationship required to pass data between components.
NAV SERVICE
import {Injectable} from '#angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class NavService {
private navSubject$ = new BehaviorSubject<number>(0);
constructor() { }
// Event New Item Clicked
navItemClicked(navItem: number) {
this.navSubject$.next(number);
}
// Allowing Observer component to subscribe emitted data only
getNavItemClicked$() {
return this.navSubject$.asObservable();
}
}
NAVIGATION COMPONENT
#Component({
selector: 'navbar-list',
template:`
<ul>
<li><a (click)="navItemClicked(1)">Item-1 Clicked</a></li>
<li><a (click)="navItemClicked(2)">Item-2 Clicked</a></li>
<li><a (click)="navItemClicked(3)">Item-3 Clicked</a></li>
<li><a (click)="navItemClicked(4)">Item-4 Clicked</a></li>
</ul>
})
export class Navigation {
constructor(private navService:NavService) {}
navItemClicked(item: number) {
this.navService.navItemClicked(item);
}
}
OBSERVING COMPONENT
#Component({
selector: 'obs-comp',
template: `obs component, item: {{item}}`
})
export class ObservingComponent {
item: number;
itemClickedSubcription:any
constructor(private navService:NavService) {}
ngOnInit() {
this.itemClickedSubcription = this.navService
.getNavItemClicked$
.subscribe(
item => this.selectedNavItem(item)
);
}
selectedNavItem(item: number) {
this.item = item;
}
ngOnDestroy() {
this.itemClickedSubcription.unsubscribe();
}
}
Second Approach is Event Delegation in upward direction child -> parent
Using #Input and #Output decorators parent passing data to child component and child notifying parent component
e.g Answered given by #Ashish Sharma.
If one wants to follow a more Reactive oriented style of programming, then definitely the concept of "Everything is a stream" comes into picture and hence, use Observables to deal with these streams as often as possible.
you can use BehaviourSubject as described above or there is one more way:
you can handle EventEmitter like this:
first add a selector
import {Component, Output, EventEmitter} from 'angular2/core';
#Component({
// other properties left out for brevity
selector: 'app-nav-component', //declaring selector
template:`
<div class="nav-item" (click)="selectedNavItem(1)"></div>
`
})
export class Navigation {
#Output() navchange: EventEmitter<number> = new EventEmitter();
selectedNavItem(item: number) {
console.log('selected nav item ' + item);
this.navchange.emit(item)
}
}
Now you can handle this event like
let us suppose observer.component.html is the view of Observer component
<app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component>
then in the ObservingComponent.ts
export class ObservingComponent {
//method to recieve the value from nav component
public recieveIdFromNav(id: number) {
console.log('here is the id sent from nav component ', id);
}
}
You need to use the Navigation component in the template of ObservingComponent ( dont't forget to add a selector to Navigation component .. navigation-component for ex )
<navigation-component (navchange)='onNavGhange($event)'></navigation-component>
And implement onNavGhange() in ObservingComponent
onNavGhange(event) {
console.log(event);
}
Last thing .. you don't need the events attribute in #Componennt
events : ['navchange'],
I found out another solution for this case without using Reactivex neither services. I actually love the rxjx API however I think it goes best when resolving an async and/or complex function. Using It in that way, Its pretty exceeded to me.
What I think you are looking for is for a broadcast. Just that. And I found out this solution:
<app>
<app-nav (selectedTab)="onSelectedTab($event)"></app-nav>
// This component bellow wants to know when a tab is selected
// broadcast here is a property of app component
<app-interested [broadcast]="broadcast"></app-interested>
</app>
#Component class App {
broadcast: EventEmitter<tab>;
constructor() {
this.broadcast = new EventEmitter<tab>();
}
onSelectedTab(tab) {
this.broadcast.emit(tab)
}
}
#Component class AppInterestedComponent implements OnInit {
broadcast: EventEmitter<Tab>();
doSomethingWhenTab(tab){
...
}
ngOnInit() {
this.broadcast.subscribe((tab) => this.doSomethingWhenTab(tab))
}
}
This is a full working example:
https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE
Related
Up to at least Vaadin Flow 23 the official components are Polymer3 (from
what I saw), which is basically deprecated in favour of Lit.
Given a server side AbstractSinglePropertyField (see below for code),
that wraps a simple checkbox and is supposed to "mirror" a property
called checked from the client.
The server side then listens for checked-changed events from the
client, which Polymer3 happily fires for such a property.
Now consider the use of a webcomponent using Lit:
import {LitElement, html} from "lit-element";
export class MyCheckBox extends LitElement {
static get properties() {
return {checked: Boolean};
}
render() {
return html`<label><input type="checkbox" ?checked=${this.checked} #click=${this.toggleChecked}/>Toggle</label>`
}
toggleChecked(e) {
this.checked = e.target.checked;
}
}
customElements.define('my-checkbox', MyCheckBox);
Lit no longer automatically fires the checked-changed event.
So what is the official/easy/... way to deal with client-side property
changes and notify the server (which expects "Polymer3-style") about
them?
As of now, as a workaround, I fire my own event:
import {LitElement, html} from "lit-element";
export class MyCheckBox extends LitElement {
// ...
update(_changedProperties) {
super.update(_changedProperties);
this.fireChanged(_changedProperties, 'checked'); // XXX
}
fireChanged(_changedProperties, property) {
if (_changedProperties.has(property)) {
let htmlChangedEvent = new CustomEvent(property.concat("-changed"), {
detail: {
propertyName: property,
value: this.html,
oldValue: _changedProperties.get(property),
userOriginated: true
}
});
this.dispatchEvent(htmlChangedEvent);
}
}
}
customElements.define('my-checkbox', MyCheckBox);
The server side (for both client sides):
#Tag('my-checkbox')
#JsModule('./my-checkbox.js')
class MyCheckbox extends AbstractSinglePropertyField<MyCheckbox, Boolean> {
MyCheckbox() {
super('checked', false, false)
}
}
And a trivial test:
#Route("")
class MyForm extends Div {
MyForm() {
def mcb = new MyCheckbox().tap{
addValueChangeListener{
Notification.show("Value changed to ${it.value}")
}
}
add(mcb)
}
}
Without the firing of the "manual" checked-changed event, the
notification never shows.
<container>
<navbar>
<summary></summary>
<child-summary><child-summary>
</navbar>
<content></content>
</container>
So, in I have a select that do send value to and .
OnSelect method is well fired with (change) inside component.
So, I tried with #Input, #Output and #EventEmitter directives, but I don't see how retrieve the event as #Input of the component, unless to go on parent/child pattern. All examples I've founded has a relation between component.
EDIT : Example with BehaviorSubject not working (all connected service to API works well, only observable is fired at start but not when select has value changed)
Start by creating a BehaviorSubject in the service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject("default message");
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
I create a sample to send message between component by using BehaviorSubject
#Injectable()
export class MyService {
private messageSource = new BehaviorSubject<string>('service');
currentMessage = this.messageSource.asObservable();
constructor() {
}
changeMessage(message: string) {
this.messageSource.next(message)
}
}
You can refer at https://stackblitz.com/edit/behavior-subject-2019
Here, I need to access the child value in parent class.
But I am not able to get it. If I am using directives it showing error.
Can someone please help me in displaying the child values in parent component and how the child value can be validated in reactive form?
parent.html :
<browser [template]="coreActivity" (onselect)="onselect($event)" ></browser>
parent.ts :
onselect(select: any)
{
console.log(select.value);
}
child.html :
<md-grid-list cols="3" rowHeight="100px">
<md-grid-tile *ngFor="let core of template" (click)="selectcore(core)">
{{core.value}}
</md-grid-tile>
</md-grid-list>
child.ts :
#Component({
selector: 'template-browser',
templateUrl: './template-browser.component.html',
styleUrls: ['./template-browser.component.css']
})
export class TemplateBrowserComponent implements OnInit {
#Input() template;
#Output() notify: EventEmitter<string> = new EventEmitter<string>();
constructor(private community: CreateCommunityComponent ) { }
selectcore(core: any) {
// alert(core.value);
this.notify.emit(core.value);
}
}
As you are passing Values through EventEmitter you need to pass it with Child component Declaration in parent.html.
<browser (notify)="onSelect($event)" [template]="coreActivity" (onselect)="onselect($event)" ></browser>
And in function under parent.ts :
onSelect(select)
{
console.log(select.value);
}
In order to access child.html in parent, you need to create a another component and use that component name as tag in parent.
like component name is "child"
keep <child></child> in parent
I want to make navigation from child components that render inside router-outlet.
My parent component have a router config and I want to navigate manually on some event. But I don't know how I can pass from child to parent some data (for navigation) without output. Because this construction is non working
<router-outlet (navigateTo)="navigateToMessagePart($event)"></router-outlet>
How I can do it in right way? Maybe navigate it from child? But how I can get parent methods from child.
Many thanks for any help!
<router-outlet></router-outlet> can't be used to emit an event from the child component. One way to communicate between two components is to use a common service.
Create a service
shared-service.ts
import { Observable } from "rxjs/Observable";
import { Injectable } from "#angular/core";
import { Subject } from "rxjs/Subject";
#Injectable()
export class SharedService {
// Observable string sources
private emitChangeSource = new Subject<any>();
// Observable string streams
changeEmitted$ = this.emitChangeSource.asObservable();
// Service message commands
emitChange(change: any) {
this.emitChangeSource.next(change);
}
}
Now inject the instance of the above service in the constructor of both the parent and child component.
The child component will be emitting a change every time the onClick() method is called
child.component.ts
import { Component } from "#angular/core";
#Component({
templateUrl: "child.html",
styleUrls: ["child.scss"]
})
export class ChildComponent {
constructor(private _sharedService: SharedService) {}
onClick() {
this._sharedService.emitChange("Data from child");
}
}
The parent component shall receive that change. To do so,capture the subscription inside the parent's constructor.
parent.component.ts
import { Component } from "#angular/core";
#Component({
templateUrl: "parent.html",
styleUrls: ["parent.scss"]
})
export class ParentComponent {
constructor(private _sharedService: SharedService) {
_sharedService.changeEmitted$.subscribe(text => {
console.log(text);
});
}
}
<router-outlet></router-outlet> is just a placeholder for adding routed components. There is no support for any kind of binding.
You can create a custom <router-outlet> that allows you to do that or more common, use a shared service to communicate between parent component and routed component.
For more details see https://angular.io/docs/ts/latest/cookbook/component-communication.html
update
There is now an event that allows to get the added component
<router-outlet (activate)="componentAdded($event)" (deactivate)="componentRemoved($event)"></router-outlet>
which allows to communicate (call getters, setters, and methods) with the component in componentAdded()
A shared service is the preferred way though.
The answer given above is correct and complete. I just want to add for those who the solution didn't work for them that they should add the service to providers only in the parent component and not the child to ensure that you get a singleton of the service, otherwise two service instances will be created.
This response is inspired by the comment of #HeisenBerg in the previous response.
I changed a little from Antara Datta's answer.
I created a Subscriber service
import {Injectable} from '#angular/core';
import {Subject} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class Subscriber<T>
{
protected observable = new Subject<T>();
public next(item: T)
{
this.observable.next(item);
}
public subscribe(callback: (item:T)=>void) {
this.observable.subscribe(callback);
}
}
Whenever I need two components to share some information, I inject this service in the constructor which subscribe to it:
constructor(protected layoutOptions: Subscriber<Partial<LayoutOptions>>)
{
layoutOptions.subscribe(options => this.options = Object.assign({}, this.options, options));
}
and the one which updates it
constructor(protected router: Router, protected apiService: ApiService, protected layoutOptions: Subscriber<Partial<LayoutOptions>>)
{
this.layoutOptions.next({showNavBar: false});
}
It escapes my understanding why the router does not forward the "#Outputs".
I ended up dispatching barebones DOM events
// dom node needs to be a reference to a DOM node in your component instance
// preferably the root
dom.dispatchEvent(
new CustomEvent('event', {
detail: payload, // <- your payload here
bubbles: true,
composed: true,
})
);
You can catch it anywhere up the DOM tree like any other DOM event
Note: you need to unpack the payload from { detail: payload } on the receiving end..
I have a component and I would like accessing some child nodes from the template. I achieved to access the details div, but I don't know why the code works. What exactly does the Future class? And why the first line prints null? Is this the correct way to access child nodes from the template?
#Component(selector: 'hero-detail', template: '<div #details></div>')
class HeroDetailComponent implements OnInit {
Hero hero;
#ViewChild('details')
var details;
Future ngOnInit() async {
// why this command prints null?
print(details);
// why this command prints "Instance of 'ElementRef_'"
new Future(() => print(details));
}
}
#Component(selector: 'hero-detail', template: '<div #details></div>')
class HeroDetailComponent implements OnInit {
Hero hero;
// Angular generates additional code that looks up the element
// from the template that has a template variable `#details
// and assigns it to `var details`
#ViewChild('details')
var details;
// I don't think Future does anything here.
Future ngOnInit() async {
// why this command prints null?
// this is too early. `#ViewChild()` is only set in `ngAfterViewInit`
// at this point the view is not yet fully created and therefore
// `#details can't have been looked up yet
print(details);
// why this command prints "Instance of 'ElementRef_'"
// this delays `print(details)` until the next Dart event loop
// and `details` is then already lookup up and assigned
new Future(() => print(details));
}
// this is the right place
// needs `class HeroDetailComponent implements OnInit, AfterViewInit {
ngAfterViewInit() {
print(details);
}
}