Access Children in Angular - javascript

We want to write a little tab component in Angular.
I wrote a component like this:
<portal-flex-grid-tab-bar>
<portal-flex-grid-tab TabName="Apotheke" TabIdentifier="Apotheke" TabSelected="true" (TabClicked)="OnTabClick(0)"></portal-flex-grid-tab>
<portal-flex-grid-tab TabName="Bundesland" TabIdentifier="Bundesland" (TabClicked)="OnTabClick(1)"></portal-flex-grid-tab>
<portal-flex-grid-tab TabName="ARZ Einzugsgebiet" TabIdentifier="ARZ" (TabClicked)="OnTabClick(2)"></portal-flex-grid-tab>
</portal-flex-grid-tab-bar>
We want to offer the Tab Bar and clicked event which returns the identifier of the clicked tab. If a tab is clicked (selected) we want to deselect the other tabs.
To achieve this goal, we need a way for the parent to talk with his children components and the other way around we need the tabs to send an event to the tab bar. How can we achieve this goal? We would be glad for any ideas as we are starters in Angular.

Generally you shouldn't "talk to" child components, but if you have to, use #ContentChild directive - https://angular.io/api/core/ContentChild
In your case you can control children selection in parent component, e.g. with property selectedTab.
So in parent component in you ts:
public selectedTab: 0;
public onTabSelected(value: number): void {
this.selectedTab = value;
}
and in parent component in html:
<section>
<app-child-tab isSelected="selectedTab === 0" (tabClicked)="onTabSelected(0)">
</app-child-tab>
<app-child-tab isSelected="selectedTab === 1" (tabClicked)="onTabSelected(1)">
</app-child-tab>
</section>
In you child component you have to add EventEmitter:
export class ChildComponent {
#Output() tabClicked: EventEmitter<void> = new EventEmitter();
public click(): void {
this.tabClicked.emit();
}
}

The most common way for parent and child to communicate is via a shared service, you have the parent provide the service in it's providers array and then the same instance of the service will be dependency injected into all the child components.
The second method is for the child component the ask for the instance of the parent component in it's dependency list of it's constructor.
Either way you expose methods from either the service or the parent component and observables, usually a behavior subject that can be subscribed to.
If you don't know what I mean by observables or behavior subjects then you are not ready to be learning Angular just yet. If so pause you Angular journey for a day or two and come back after you have a sound understanding of the fundamentals of RxJs as Angular is heavily built upon RxJs. Go read up on Subjects, Behavior Subjects and the RxJs operators.

Related

Sibling component does not receive emitted changes

In my Angular 9 project I have 2 components which are siblings and the parent component. On change in component A, I emit a value and it's set in the parent component and calls a method in component B. The method in component B emits another value and it's set in the parent component. The on change in component A continues, but the emitted value from component B that is set in the parent component (which is an input in component A) is not changed. I don't know why it's not the input for component A does not change even though the parent updates the value.
Parent Component
setSomeNum(someNum: number) {
// this is run on someNumberEmitter in Component A
this.num = someNum;
if (this.viewChildComponentB) {
this.viewChildComponentB.remove(someNum);
}
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum = someOtherNum
}
Component A
componentAOnChange(someNum: number) {
this.someNumberEmitter.emit(someNum);
// this.inputFromComponentB is the original value instead of the one emitted in Component B (this.someDiffNum)
this.someService.applyCalc(someNum, this.inputFromComponentB);
}
Component B
remove(someNum: number) {
this.someDiffNumEmitter.emit(this.someDiffNum);
this.someService.applyCalc(someNum, this.someDiffNum);
}
I'm using the OnPush change detection strategy, but nothing changed. How can the sibling component A run with the data changes from component B?
I'm not sure why you're using ViewChild there but if it is to update the child components manually when there's change then that should be a red flag something is being done wrong, if you have data that needs to be shared it should be shared across the board and update accordingly on the single source of data changes without having to manually update the rest of the places.
Now to your problem:
If you're using the OnPush change detection strategy you have to update your data in an immutable way or use Observables, otherwise the change detection won't trigger.
Some people will advice triggering change detection manually but I'd recommend avoiding that as the whole point of using OnPush is to avoid a whole page render unnecessarily.
A simple solution I like to use is to use a Subject or BehaviorSubject instead with the async pipe. This way it ensures smooth work with the OnPush change detection strategy because ChangeDetection will run when the Observable emits a new value, and the async pipe takes care of unsubscribing the Observable for you.
If I were to modify your current components, it'd look something like this:
Parent:
num$ = new Subject<number>();
otherNum$ = new Subject<number>();
setSomeNum(someNum: number) {
this.num$.next(someNum);
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum$.next(someOtherNum)
}
Then in the HTML you can use the async pipe, like this:
<some-component [num]="num$ | async" [otherNum]="otherNum$ | async"></some-component>
(You could use the async pipe in the component itself, doesn't really matter).
And that's pretty much it. You should have a Subject as an Observable, then share it with child components, once the Observable is updated, the child components data will be updated as well.
One small caveat is that when using a Subject instead of a BehaviorSubject is to make sure to subscribe before emitting any values to the Subject, otherwise the data will not update. So for certain cases BehaviorSubject is a better fit.

Two way data binding in angular not working

I am trying to implement two way data binding in angular components. Currently its in a parent child mode.
parent.component.html
<child [(title)]="title"></child>
<span style="color: red">This is parent component {{title}}</span>
parent.component.ts
title = 'app';
child.component.html
<span style="color: blue">This is child component {{title}}</span>
child.component.ts
#Input() title: any;
#Output() pushTitle = new EventEmitter();
constructor() { }
ngOnInit() {
this.title = 'new title';
this.pushTitle.emit(this.title);
}
The title should implement on the parent component as well, when I change it from the child. Also, I am not sure why the parent code keeps going in a loop for no reason. I have added text in html just to test if its updated in both the components, but its updating only in the child, and not in the parent. I am coming from the angularjs background, and two way data binding worked seamlessly in it. I am just confused what I am doing wrong(I know its a noob question).
Demo here: https://stackblitz.com/edit/angular-xttmxg
There is another way that you can achive the same.
#Input() title: any;
#Output() titleChange: EventEmitter<any> = new EventEmitter<any>();
changeValue() {
this.title= !title;
this.titleChange.emit(this.title);
}
Have a look at Angular documentation about two way binding
Two way data binding only works for template - component interaction.
If you want to send title change to parent component, you should do something like this:
Parent template and component:
<child [title]="title" (pushTitle)="onTitleChange(value)"></child>
<span style="color: red">This is parent component {{title}}</span>
onTitleChange(value) {
this.title = value;
}
Followup question:
Template:
<input [(ngModel)]="inputModel">
Component:
inputModel: string;
Now, every time you type something into input field you will see changes in component model, OR when change inputModel value programmatically, you will see the change in HTML input.
You are somehow creating an infinite update cycle using the 2-way-binding. This leads to the infinite loop and eventual stack overflow you noticed.
To fix this, preferably, you want to add some logic to the titleChange event (this is the banana-part of the banana-in-a-box syntax, i.e. the part in parens in [(title)] which is getting automatically translated into an event emitter named titleChange).
For example, you might want to skip updating the title property of the parent component if its equal to the update emitted by the child component.
This means you should split up [(title)] into (titleChange)="titleChange($event)" and [title]="title". The first part lets you pass the updated title as $event and then process it in a function titleChanged (name is arbitrary in this case). The second part has the effect that the child component receives updates of the parent component's title property.
Another common pattern is to make title private (commonly with a prefixed underscore, e.g. _title) and then add a getter get title() { return this._title;} so that you can (1) encapsulate this property and (2) add some processing.
In your case this is not needed, but it doesn't hurt either. ;-)
Here's a plunkr containing these changes.

Angular 2 Child1-Parent-Child2 event propagating

I am working on Angular 2 for only couple of weeks, so try to be considerate, please, and maybe give me some useful advice? Thank you. I have a main component(pageComponent), that inside its template has other two smaller components(tableComponent and filterComponent), each with its own functions and properties. How do I catch an event created in one of the child components and propagate it to another child component of the same parent? In other words, how to propagate and communicate events between two sibling components within the same parent component's template?
I think you've got a couple of options here.
The first child component could emit the event using the #Ouput decorator, and
have that event handler invoke an action on the sibling.
E.g. the sibling components
export class TableComponent {
#Output() anEvent: EventEmitter<any> = new EventEmitter<any>();
..
}
export class FilterComponent {
callMeWhenSomethingHappens($event){
..
}
}
The parent component template, which uses the #theFilter local variable to call the filter component when the event is emitted.
<app-filter #theFilter>
</app-filter>
<app-table (anEvent)="theFilter.callMeWhenSomethingHappens($event)">
</app-table>
You could also look at using a shared service if the sibling components don't have access to each other in the template (e.g. if they are created by a router-outlet).
You can use the Flux pattern. Basically have your components subscribe to events exposed from your service. Since services are singletons, you can share the data that it provides across components, no matter how deep they are in the component tree. Here is an example: How can I maintain the state of dialog box with progress all over my Angular 2 application?

Angular2 directive wait for component to be initialised

I've got an wrapper component loading multiple components by using createComponent and in every component i want to append an canvas element. Now i figured there are a few ways of doing so, and with every way of doing i get kind of stuck.
Number 1
In the wrapper component access the child component (which i've not been able to do because nativeElement in this.cmpRef = this.target.createComponent(factory); is a private property), and i need to know the child component is initialised (so i'll have to use a service for this).
Number 2
Create a directive and place the canvas element with in the loaded component: <canvas projectCanvas class="overlay"></canvas> Now i'm already able to access the template through the directive itself. (Which is great but kind of repetitive).
Only thing is the directive is constructed before the component's view is initialised, so i have to create an service that will keep track of the components status.
Actual question
How would you approach this? In my point of view there's no escaping a service, or is there a way to check if the view is initialised from the wrapper component?
---------------Update------------------
Tried to make a version with a service but ngAfterViewInit / ngOnInit seems not to work when the component is loaded by createComponent see Pliunkr
How did you try ngAfterViewInit ?
Did you try injecting the children components in the wrapper like so?
#Component({...})
export class WrapperComponent implements AfterViewInit{
#ViewChildren(InnerComponent) innerComponents : QueryList<InnerComponent>;
ngAfterViewInit(){
this.innerComponents.forEach(c => c.doSomething());
}
}

How can a parent component communicate with a child component in Vue.js?

This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0

Categories

Resources