sibling component communication angular 6 - javascript

I have two sibling components inside a parent component like below
<parentComponent>
<sibling1></sibling1>
<sibling2></sibling2>
</parentComponent>
Am emitting data from sibling 2 to parent. Then am passing it as Input from parent to sibling 1. But as the sibling 1 gets initialized before sibling 2 am unable to get the data on sibling 1. How to get the data on sibling 1 with the same setup.

When you receive data in parent component you need run a callback function to update sibling1 data. In order to run a callback in parent you can do something like this.
SIBLING2:
class Sibling2 {
#Output() private onChange: EventEmitter<string> = new EventEmitter<string>();
ngOnInit () {
this.onChange.emit("hello parent")
}
}
PARENT:
class Parent {
private parentData: string = null;
ngOnInit () {
this.onChange.emit("hello parent")
}
onSibling2Change(data) {
this.parentData = data; //this will update sibling1 data
}
}
HTML:
<parentComponent>
<sibling1 [data]="parentData"></sibling1>
<sibling2 (onChange)="onSibling2Change($event)"></sibling2>
</parentComponent>

Related

Output from child to parent component doesn't work properly

I want to pass data from child to parent component but it doesn't work in a proper way: function isn't invoked.
<router-outlet (activeElement)='getActive($event)'></router-outlet>
<div class="myDIV">
<button *ngFor="let i of numberOfButtons" (click)="navigate(i)" [ngClass]="(i === active) ? 'btn active': 'btn'">{{i}}</button>
</div>
child component ts.file
#Output() activeElement = new EventEmitter();
constructor(private activatedRoute:ActivatedRoute,private getPlanets:GetPlanetsService,private router: Router, private renderer:Renderer2,private elRef: ElementRef) {
activatedRoute.params.subscribe(value=>{
this.fivePlanetIndex=value.id;
this.activeElement.emit(value.id);
});
}
parent component .ts file
getActive(i){
console.log(i); //it is not invoked
}
Output will work by using component selector, not with router-outlet.
Because router-outlet used to render multiple components and it doesn't make sense to set all Inputs and Outputs on there.
If you wanna use router-outlet and catch events from children components, you can use Observables; where child component send result and parent component subscribe to it.
sample.service.ts
// you will use subject to emit event from child
subject = new Subject<any>();
// you will use observable to listen to events from parent
observable = this.subject.asObservable();
child.component.ts
constructor(service: SampleService) {}
// instead of emit function
this.service.subject.next();
parent.component.ts
constructor(service: SampleService) {}
// instead of event listener
this.service.observable.subscribe();

call method on component through #ViewChild in views into a QueryList

I want to do a component reusable, setting multiple children and render one component at time, I have a collection of views got with #ContenChildren, at parent component, something like this:
#ContentChildren(ChildComponent) children: QueryList<ChildComponent>;
//getCurrentChild function
getCurrentChild(){
//index is a position of child
return this.children.toArray()[this.index];
}
and render parent template:
<ng-container *ngTemplateOutlet="getCurrentChild().content"></ng-container>
content is rendered in the child component with #ViewChild:
#ViewChild('currentChild') child;
and the template looks like this:
<ng-template #currentChild>
<ng-content>
</ng-content>
</ng-template>
When I want to implement this structure, I do something like this:
<parent>
<child>
<specific-one></specific-one>
</child>
<child>
<specific-two></specific-two>
</child>
</parent>
Now I have a method in the parent component that is fired on click button and need to call a method in the specific components (specific-one or specific-two depending on currentChild):
export class SpecificOneComponent implements OnInit{
action(){
//this action will be called when parent is clicked
}
}
I've tried calling method trough child reference but the action doesn't exit. also passing a context and neither. It looks like the way I'm setting the content in parent template is not right.
Any help would be appreciate it.
Every child component will have its own content, so this means that it would be enough to only get the reference to the ng-template in which there would be ng-content.
Also, because you want to call methods from specific components when a certain event occurs on the parent, we are going to use a service in order to be able to notify the specific components.
specific.service.ts
private _shouldCallAction$ = new Subject();
shouldCallAction$ = this._shouldCallAction$.asObservable();
constructor() { }
triggerAction (uniqueCompId) {
this._shouldCallAction$.next(uniqueCompId);
}
specific-{one|two|...n}.component.ts
This goes for every component that depends on some events that occur in the parent component.
private shouldCallActionSubscription: Subscription;
uniqueId: number | string;
constructor(private specificService: SpecificService) {
this.uniqueId = randomId();
}
ngOnInit() {
this.shouldCallActionSubscription = this.specificService.shouldCallAction$
.pipe(
filter(id => id === this.uniqueId)
)
.subscribe(() => {
console.log('calling action for specific-one')
});
}
ngOnDestroy () {
this.shouldCallActionSubscription.unsubscribe();
}
child.component.html
<ng-container *ngIf="instanceIdx === crtIdx">
<h3>trigger action of current specific component</h3>
<button (click)="triggerAction()">trigger</button>
</ng-container>
<ng-template #currentChild>
<ng-content></ng-content>
</ng-template>
child.component.ts
Here you'll also need to get a reference to the specificComponent in order to get its unique id.
// Before class
let instances = 0;
#ViewChild('currentChild', { static: true }) tpl: TemplateRef<any>;
#ContentChild('specific', { static: true }) specificComp;
get uniqueSpecificCompId () {
return this.specificComp.uniqueId;
}
constructor (private specificService: SpecificService) {
this.instanceIdx = instances++;
}
triggerAction () {
this.specificService.triggerAction(this.uniqueSpecificCompId);
}
parent.component.ts
#ViewChildren(ChildOneComponent) children: QueryList<ChildOneComponent>;
#ViewChild('container', { static: true, read: ViewContainerRef }) container: ViewContainerRef;
crtIdx = 0;
ngAfterViewInit () {
this.setNewView();
}
setNewView () {
this.container.clear();
this.container.createEmbeddedView(this.children.toArray()[this.crtIdx].tpl);
}
updateIndex (idx) {
this.crtIdx = idx;
this.setNewView();
}
parent.component.html
<app-child [crtIdx]="crtIdx">
<!-- ... -->
<app-specific-two #specific></app-specific-two>
</app-child>
<app-child [crtIdx]="crtIdx">
<!-- ... -->
<app-specific-one #specific></app-specific-one>
</app-child>
<ng-container #container></ng-container>
<h3>Select a child</h3>
<button
*ngFor="let _ of [].constructor(n); let idx = index;"
(click)="updateIndex(idx)"
>
Select {{ idx + 1 }}
</button>
Here is the demo.
Best of luck!

How to emit an event from grandchildren to grandparent in modern angular?

If I have multiple levels of angular components, how can I use #Output to emit an event from child to the grand parent?
Grandparent:
<parent (handleClick)="grandmaHandleClick($event)">
<parent>
...
grandmaHandleClick(event) {
console.log('grandma knows you clicked')
}
Parent:
<child (handleClick)="handleClick($event)">
</child>
Child:
<div (click)="onClick()">Click button
</div>
...
#Output() handleClick = new EventEmitter
onClick() {
this.handleClick.emit('clicked a button')
}
I am trying to have it so that #Output can prop drill a few components deep, whats the best way to accomplish this, and can you provide example?
There could be 2 ways:
Using #output:
Grandparent
<parent (notifyGrandParent)="grandmaHandleClick($event)">
<parent>
...
grandmaHandleClick(event) {
console.log('grandma knows you clicked')
}
Parent:
<child (handleClick)="childEvent($event)">
</child>
#Output() notifyGrandParent= new EventEmitter();
childEvent(event) {
this.notifyGrandParent.emit('event')
}
Child is implemented properly in the code so it is good to go.
Using BehaviorSubject via Service: With this much level of nesting, you can actually create some service like EventService, and then create BehaviorSubject which can directly be subscribed by the GrandParent. Also, to make this service more component specific, you can keep this service in a module which will have other 3 components (GrandParent, Parent and Child)
export class EventService{
private childClickedEvent = new BehaviorSubject<string>('');
emitChildEvent(msg: string){
this.childClickedEvent.next(msg)
}
childEventListner(){
return this.childClickedEvent.asObservable();
}
}
and then in components:
ChildComponent
export class ChildComponent{
constructor(private evtSvc: EventService){}
onClick(){
this.evtSvc.emitChildEvent('clicked a button')
}
}
GrandParent
export class GrandComponent{
constructor(private evtSvc: EventService){}
ngOnInit(){
this.evtSvc.childEventListner().subscribe(info =>{
console.log(info); // here you get the message from Child component
})
}
}
Please note that, with #output event, you create a tight coupling of components and so a strong dependency (parent-child-grandchild) is created. If the component is not reusable and is only created to serve this purpose, then #output will also make sense because it'll convey the message to any new developer that they have parent-child relationship.
Creating a service to pass data also exposes the data to other components which can inject service in constructor.
So, the decision should be taken accordingly.
Use rxjs/subject, it can be observer and observable in the same time.
Usage:
Create Subject property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
When event happens in child page/component use:
logout() {
this.authService.loginAccures.next(false);
}
And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {
this.isLoggedIn = isLoggedIn;
})
}

Pass data from parent component into child entry component angular 6

So I know that I can pass data from a parent to a child using #Input like so..
<app-component [data]="data"></app-component>
#Input() data: any;
now I have a component that gets dynamically added like so..
HTML
<app-header></app-header>
<div #entry [vimeoId]="vimeoId"></div>
<router-outlet></router-outlet>
APP.COMPONENT.TS
ngOnInit() {
this._videoService.videoSource.subscribe((result) => {
if (result !== '') {
this.vimeoId = result;
this.createComponent();
}
});
}
createComponent() {
const factory = this._resolver.resolveComponentFactory(VideoComponent);
const component = this.entry.createComponent(factory);
}
now this isn't working I'm getting this error
[Angular] Can't bind to 'vimeoId' since it isn't a known property of 'div'.
now I know why I'm getting this error, its because div doesn't have the #Input so.. my question is how can I pass down data to a dynamically created child component??
Thanks

Executing a parent function from child doesn't update the parent's properties

In Angular 2 you work a lot with this, which is fine but I've found that it also creates an issue when you want to pass down a function down the component hierarchy.
Take this for example:
export class ParentComponent {
myFunctionFromParent() {
this.isActive = true;
}
}
Then we pass this function down to a child:
<parent>
<child [onClick]="myFunctionFromParent"></child>
</parent>
And let's say child is a simple button:
<button (click)="onClick()"></button>
Now, when myFunctionFromParent runs, this should be the ParentComponent but it's not.
Instead it's the ChildComponent that will have it's this.isActive property changed.
This creates a lot of issues as you can't execute parent functions from a child component and expect the parent properties to change.
Passing down functions works as you would expect them to do in Angular 1, but now it seems broken.
Is this no longer the way to do things like this? What is the correct way to to this in Angular 2?
Instead of passing functions around use default Angular data binding with inputs and outputs:
class ParentComponent {
myFunctionFromParent() {
this.isActive = true;
}
}
class ChildComponent {
#Output() onClick = new EventEmitter();
}
<parent>
<child (onClick)="myFunctionFromParent()"></child>
</parent>
<button (click)="onClick.emit()"></button>
I would use this instead:
<parent>
<child (onClick)="myFunctionFromParent()"></child>
</parent>
and define an #Output in the child component:
#Component({
selector: 'child',
template: `
<button (click)="onClick()"></button>
`
})
export class ChildComponent {
#Output('onClick')
eventHandler:EventEmitter<any> = new EventEmitter();
onClick() {
this.eventHandler.emit();
}
}

Categories