I was working on Angular2 project where I have a folder called layout where I split common layouts of certain pages into 3 other components(3 folders-header,sidebar, and footer). I have an anchor tag(...) on my header. Upon hitting on it I need to extend the header upto the left end of screen and at the same time sidebar need to be hidden. On hitting the anchor tag again both should came into actual position(toggling). I tried like
[ngClass]="{'hide-sidebar' : flags}" class="fix-header"...
where flags is a boolean variable and toggles it's value on hitting anchor tag. I stored the state of this variable in localStorage so that it can be accessed on sidebar, another component.
but this helped me to extend header to left end of screen and sidebar remain unchanged.
I think there can be another logic for achieving both in same hit on anchor tag(button).
Communication using #input and #Output
if the components are in parent-child relationship e.g. navbar-component is in header-component template or header-component is in navbar-component template, then you can use angular #Input #Output decorators to pass data between components
More on #Input and #Output and Component interaction in angular documentation here
Communication Using Service
if these components are not directly related then, you can use a service to communicate between these components, where one component pushes data to service and the other gets it from the service,
More on communication among components using a service is explained in Angular documentation here
Update: Siblings with Same Parent Communication Using #Input() #Output()
If you don't want to use service for communication and keep things less complex, you can use the common parent for sibling components.
Lets suppose the variable which you want to toggle resides in header component,
now you create an EventEmitter in your header.component.ts like this
#Output() onToggle = new EventEmitter();
then in the function where you toggle the variable,
you can write this,
toggle() {
if(this.flags === true) {
this.flags = false;
}else {
this.flags = true;
}
this.onToggle.emit(this.flags);
}
then in your parent template, you'll bind the function to the header which will be executed when the above event is triggered,
so in your parent template you can write this,
<app-header (onToggle)="notifySidebar($event)"></app-header>
And the notifySidebar method in you parent.component.ts will be like this,
notifySidebar(flags) {
console.log(flags);
this.flags = flags;
}
You need to define the above flags property (you can choose any name) in parent.component.ts,
Now the final part, you need to tell the sidebar.component.ts that you flag property has changed, so you will define #Input() property in your sidebar.component.ts
#Input() flags: boolean;
and a method ngOnChanges (keep in mind to use ngOnChanges you need to implement OnChanges interface on your sidebar.component.ts more on OnChanges here )
ngOnChanges() {
if(this.flags){
//do whatever you want to do with flags
}
}
and the flags property will be passed to the sidebar from parent like this
<app-sidebar [flags]="flags"></app-sidebar>
Related
EDIT 2: This appears to be my general problem, and solution (using setTimeout so Angular's lifecycle can happen). I'll either close this or post an answer to my own question when I can.
See EDIT for a simpler repro that doesn't involve Subjects/Observables but is essentially the same problem.
I have a parent component that's responsible for fetching data from a service.
export class ParentComponent implements OnInit {
public mySubject: Subject<Foo[]> = new Subject<Foo[]>();
public buttonClicked = false;
private currentValues: Foo[] = null;
constructor(private SomeService myService) { }
this.myService.get().subscribe(values => {
this.mySubject.next(values); // Does NOT work when a child component is hidden, as expected.
this.currentValues = values; // Keep value so we can manually fire later.
});
private buttonClickHandler() {
this.buttonClicked = true;
this.mySubject.next(this.currentValues);
}
}
This data is subscribed to in the HTML by a child component. This component is hidden by default via *ngIf, and only becomes visible on a button click:
<app-child-component [values]="mySubject.asObservable()" *ngif="buttonClicked" />
In the parent component above you see I'm trying to pass the current available data to the child by invoking next() when the component is made visible in some way:
this.mySubject.next(this.currentValues);
This does not work when initially un-hiding the component via *ngIf. If I click the button a second time, which then calls next() again, then it works as expected. But when Angular is in the current context of un-hiding something, observables aren't getting their data. (This also happens when things are unhidden by other means, but the result is the same: If in the same method, the subject/data passing does not work; the component has to already be visible as of the method call.)
I'm guessing the binding to the observable is not happening until after *ngIf shows the child component, after the method call resolves. Is there some place I can hook into that I can then pass child data down?
EDIT for clarification: I don't believe this is an issue of Subject vs. BehaviorSubject. I'm not having issue passing the data. The issue is that the data-passing (confirmed via console.log()) is not occurring at all in the first place. It's not that the child component is receiving a null value. The subscription just isn't firing to the child.
I found I can reproduce this in a simpler fashion too: Trying to select an element in the DOM of *ngIf HTML reveals undefined if I make *ngIf's value true within the same Angular method.
<div *ngIf="buttonClicked">
<div id="someElement">Foo</div>
</div>
public someMethod(): void {
this.buttonClicked = true;
const container = document.getElementById('someElement'); // DOES NOT WORK if this.buttonClicked was false at the start of this method!
}
You going to need to use a BehaviourSubject instead of Subject, which emits the previously set value initially.
What is the difference between Subject and BehaviorSubject?
I'm using two components, SelectTag and the main component, SettingsComponent. The select component is a simple directive with a selector, but the issue I'm having is updating a parent variable from the child component.
For example: I have an ngModel binding to a variable (name) in the selector component, but how can I access this from the parent component (settings)?
What I've tried so far is using the Angular EventEmitter:
#Output() onNameChange: EventEmitter<string>;
...and accessing it through
<select-tag (onNameChange)="name = $event">
Is there a better practice to doing this?
You have a few options to share data between Child and Parent Components.
The best way is probably using an EventEmitter like you already tried, just make sure to trigger the event when you want to update.
For example:
export class ChildComponent {
#Output() nameUpdate: EventEmitter<string> = new EventEmitter<string>();
updateParent(name){
this.nameUpdate.emit(name)
}
}
child.component.html:
<input (input)="updateParent($event.target.value)">
Now the parent who's using this child selector can listen to the nameUpdate event:
<app-child (nameUpdate)="nameUpdate($event)">
You will get the new value within the $event.
You can also consider using services and DI to share data across components, or any other state management tools. But, if your components are in a sibling relationship, Use the Input and Output decorators as the best practice.
You can create a centralized centralized.service.ts component to distribute the data to your components, this way you get it centralized and easy to control the data flow when your app grow:
export class CentralizedService {
private sharedData = {};
getSharedData(){
return this.sharedData
}
updateSharedData(data){
//Statements
}
}
And inject the service to your module and get access to it in both your component
constructor(private centralizedService: CentralizedService){}
ngOnInit(){
this.componentData = this.centralizedService.getSharedData();
}
//Create a method to update shared data
updateSharedData(data) {
this.centralizedService.updateSharedData(data);
}
I have a parent component which has two templates (Stackblitz):
the first contains a simple data-bound text
the second contains a child component which updates data model (from its constructor and ngOnInit in this demo)
Here is the parent component template:
<ng-template [ngIf]="true">
<div>
<b>Value from TemplateComponent: </b>{{ dataService.value | async }}
</div>
</ng-template>
<ng-template [ngIf]="true">
<template-component></template-component>
</ng-template>
Both parent and child components are ChangeDetectionStrategy.OnPush and this is a requirement.
The issue is that the data updates starting from ngOnInit (and later ones) of the child component don't get picked up by the change detection in the parent component. This causes the parent component in the demo to display:
Value from TemplateComponent: value from constructor
instead of
Value from TemplateComponent: value from ngOnInit
since the CD picks up an update on constructor stage only, but not in ngOnInit and later:
export class TemplateComponent implements OnInit {
public constructor(public readonly dataService: DataService) {
this.dataService.setValue("value from constructor");
}
public ngOnInit(): void {
// This update is NOT picked up by CD in parent component
this.dataService.setValue("value from ngOnInit");
}
}
Except this approach with markForCheck(), I've also tried a simple #Output event in the child, but it obviously doesn't work, since the child is created via ng-template and actually is not a direct child of the parent component.
Could someone give an idea of what is the cleanest and Angular's OnPush-friendly approach for passing data to the parent from ng-template created control?
P.S.: By
the cleanest and Angular's OnPush-friendly approach
, I mean something that keeps all parts (components and service loosely-coupled), for example, without involving setTimeout() or other dirty fixes.
You've grasped the idea of using observables, async pipes and OnPush strategy, but the problem is that you have to go a little deeper and understand how angular component lifecycle events work.
Let's see what happens (simplified version)
Parent component constructor()
Child component constructor()
Parent component onInit() - evaluate #Input templates and ngIf directives
Child component onInit() - trigger changes to some properties used in parent
Parent afterViewInit()
Once onInit in child component is called, the directives in parent component were already evaluated! From angular perspective content inside [ngIf] template is already what it is suppose to be and there's need to run change detection again or update anything.
You shouldn't change data that is used in parent component from onInit() in child component, otherwise you will surely break something.
If you wait until all components are fully instantiated and trigger change, then all views will be updated.
eg. in your child component:
setTimeout(()=>this.dataService.setValue("hello"),1000)
That also works, if you try to markForCheck in ngAfterViewInit in your parent component
ngAfterViewInit(): void {
this.changeDetector.markForCheck();
}
or if you won't use *ngIf the value will be re-evaluated.
<div><b>Value from regular div: </b>{{ dataService.value | async }}</div>
All of mentioned workarounds are not advisable, just don't change data from child component's onInit and you will be fine.
I have a problem working with two components (pop ups) in which i have to send data from a chlid component to another one (parent) who doesn't have an event to extract this data.
logically i have to find a sort of function that makes the parent listen to the changes made in the child.
The Changes have to appear in the same time in both components.
Could any one help ?
The answer is in your question. You need an Output property, which is the Angular generalization of a JS event.
In your child component:
class ChildComponent {
#Input() someProperty: string;
#Output() dataChanged = new EventEmitter<string>();
whenSomethingHappensInChild() {
this.dataChanged.emit('something');
}
}
In your parent template:
...
<app-child [someProperty]="'someValue'" (dataChanged)="doSomething($event)"></app-child>
...
And in you Parent code:
class ParentComponent {
...
doSomething(theValue: string) {
// TA-DAA! you have the value.
}
...
}
Please, do yourself a favor and READ THE DOCS, or, better, a book ;)
In particular:
https://angular.io/guide/architecture-components has a full overview of the basics of binding, which this problem falls into.
Have a nice day.
Yes, you can use a shared BehaviorSubject to push values and both components have to subscribe to get this changes
Problem Solved: I used the #Host() tag to get the current instance of the Parent component and access the methode that changes it's attributes.
Here is what you should do.
First:
You should declate your parent component in the child
parent:ParentComponent;
Second :
you should pass your current parent instance to your new declaration in the constructor
constructor(#Host() currentParent:ParentComponent){
this.parent=currentParent
}
Third:
Now try just to access the methods and attributes in the parent components
changeParentAttribute(){
this.parent.iAmInTheParent();
}
I hope you find this helpful
How do you properly pass a function from a parent to a child component when the function takes in parameters?
In the ngOnInit, how to scope a function like:
addToList(id) {
this.store.dispatch(this.listActions.addToList(id));
}
ngOnInit, which is wrong right now.
ngOnInit() {
this.addToList = this.addToList.bind(this, id);
}
In my parent component, I have the addToCart(id) function.
I want to pass that function to my child component, which has a list of items, and on clicking the ADD button on an item, I want to callback addToCart(item_id) to the parent.
#Maarek's answer is a good one, and is the 'right' way to do it, probably. What I am presenting here is a simpler means of communicating specifically from the Child to the Parent.
What you proposed in the original post was to have the Parent send a callback method to the Child, so the Child can call it with data when appropriate. To accomplish this specific task (data from Child to Parent on some action in the Child) using Events is appropriate, using the EventEmitter from inside the Child. See this API reference which has an example: https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html and this Plunker I made as a demo: https://embed.plnkr.co/T1wFqVOhMXgX6NRfTuiC/
In the child, you have code like this:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'item',
template: `
<div class="item">
<button type="button" (click)="addItem()">Add</button>
<p>{{id}}
</div>
`
})
export class ItemComponent {
#Input() id: string;
//key line here: this emitter can be bound to by parent to get notifications
#Output() add: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
addItem() {
//then when the button is clicked, emit events to the parent.
this.add.emit(this.id);
}
}
The Parent would call create the component like this:
<item id="1" (add)="addToList($event)"></item>
Where addToList() is a function on the Parent that does the work your callback was intended to do. The $event is the data passed from the child (the id).
There's not a lot of detail here, but from what I'm gathering I think what you will want is an injectable service (demonstrated here: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html) to handle the data objects being shared between the components. Rather than type a bunch of code in here (which is better shown at that page in the tutorial) I'll describe what I think you're trying to do and how I'd go about doing it.
The entire store data model can be handled via a service (store.service.ts maybe). Which will have your CRUD functions exposed for the different properties of the store model. The list you are adding to here should have a public getter that returns an observable of the list in the service as well as a public function for adding and deleting from the list. Something like this:
#Injectable
export class StoreService {
private _storeList:BehaviorSubject<Array<any>> = new BehaviorSubject<Array<any>>([]);
/*I'm sure the store has other properties, set them up here. I'd suggest
breaking any arrays out of the general object (unless you want to use
pipes which are awesome but keeping it simple here) but if the store has
a lot of general properties (name, address, whatever) they can be stored
in a single BehaviorSubject of type any.
*/
constructor(){}
get StoreList() { return this._storeList.asObservable() }
public addToList(id) {
let curVal = this._storeList.getValue();
curVal.push(id);
this._storeList.next(curVal);
}
}
You would then inject this service into the constructor of both the parent and the child constructor(private _storeService:StoreService){} (and any other components that need it). The child could then subscribe to the list: get List() { return this._storeService.StoreList } and the parent can call the add function to add to the list. One thing to note, when you add this to your template as an *ngFor, make sure to pass the value through the async pipe. *ngFor="List | async" or your may tear your hair out trying to figure out why you're getting errors.
This article helped me a lot with this as well (although I might suggest avoiding immutable at first until you're comfortable with Angular 2 completely): http://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/