I want to access the text I have in a text area in my child component to put it on the parent component and keep it updated.
I was told that #input in angular 4 is supposed to perform two-way binding. But I can't do that that way, and I don't understand why.
I found a workaround for this issue. It includes an #Output to send the info to the parent component. But if Input already does that (in some way I don't know), I want to avoid it.
For example, this is my Parent Component
import { Component } from '#angular/core';
#Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
})
export class SettingsComponent {
private studyDesignText = 'Text';
constructor() {
}
public handleStudyDesignUpdated(designText: any) {
this.studyDesignText = designText;
}
}
It's html
<div class="section section-trials-settings-parent light rounded">
<div class="section section-trials-settings-child">
<div class="pure-g">
<div class="pure-u-1-1">
<app-settings-study-design
[studyDesignText]="studyDesignText">
</app-settings-study-design>
</div>
</div>
</div>
</div>
My child component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-settings-study-design',
templateUrl: './settings-study-design.component.html',
})
export class SettingsStudyDesignComponent implements OnInit {
#Input() studyDesignText: string;
constructor() {
}
ngOnInit() {
super.onInit();
loadControls();
}
loadControls(): void {
this.startAllTextAreas();
}
private startAllTextAreas() {
this.startTextArea('study-design-textarea');
}
private startTextArea(htmlId: string) {
// code to configure my text area; it's right...
}
If I change the value in the text area and send a signal with #Output so my parent component can be notified and console log the value, the printed value is the initial one. My friend did the same thing and it worked.
What am I missing?
#Input() is always one way binding from parent->child. Two way binding happens in this case, only when you have object as an input property. This is because, the reference for objects remain the same. And when one of the object updates, the other will also get updated. This is not true for string or number. It is always one way binding.
Related
I need to pass input's value from child component to parent component when user click on a submit button that exists in parent component.
childComp template
<input
type="password"
[(ngModel)]="userPasswordForm.inputId"
class="mr-password-field k-textbox"
/>
childComp TS file
export class PasswordInputComponent{
constructor() { }
#Output() inputValue = new EventEmitter<string>();
userPasswordForm:any={'input':''};
emitValue(value: string) {
this.inputValue.emit(value);
}
}
Parent Component Template
<child-component (inputValue)="" > </child-component>
<button (click)="getValueFromChild()"> </button>
Parent Component TS file
tempUserFormPasswords:any=[];
.
.
.
getValueFromChild(receivedVal){
this.tempUserFormPasswords.push(receivedVal);
}
It would easy to dio it if the button exists inside the child component. but in this case the value should be passed when the button in the parent component is clicked!
For single ChildComponent:
Use ViewChild
For multiple ChildComponent use: ViewChildren
Parent Component TS file
Single Child Component:
tempUserFormPasswords:any=[];
#ViewChild(ChildComponent) child: ChildComponent;
.
.
.
getValueFromChild(receivedVal){
var data = child.getData();
this.tempUserFormPasswords.push(data);
}
Multiple Child Component:
tempUserFormPasswords:any=[];
#ViewChildren(ChildComponent) child: ChildComponent;
#ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
.
.
.
getValueFromChild(receivedVal){
let data;
children.forEach(child => (data = this.updateData(child.data));
this.tempUserFormPasswords.push(data);
}
Create a BehaviorSubject in service file
#Injectable()
export class dataService {
data: BehaviorSubject<any> = new BehaviorSubject<any>(null);
public setData(data: any){
this.data.next(data);
}
public getData(): Observable<any> {
return this.data.asObservable();
}
}
You need to subscribe the data in your child component
PasswordInputComponent
export class PasswordInputComponent{
constructor(private service: dataService) {
this.service.getData().subscribe((data) => {
//Emit the event here
this.inputValue.emit(value);
});
}
#Output() inputValue = new EventEmitter<string>();
userPasswordForm:any={'input':''};
emitValue(value: string) {
this.inputValue.emit(value);
}
}
ParentComponent.ts
tempUserFormPasswords:any=[];
.
.
.
constructor(private service: dataService) { }
getValueFromChild(receivedVal){
this.service.setData('');
this.tempUserFormPasswords.push(receivedVal);
}
When a button clicked on the parent component we are setting the data behaviour subject, when a new value added to that it will automatically subscribed in child component.so, on that time we need to emit a event.
I think this will help you..
Read about Input and Output decorators in angular!
documentation: sharing-data.
Examples: examples
You can do it with ViewChild as already said in the other answer from #Raz Ronen. But keep in mind that depending on the Angular version, you might need to wait for the AfterViewInit lifecycle hook to be executed to interact with the child (or the child won't be available since it's not initialized).
Also, you can do it with a BehaviorSubject, like #Msk Satheesh just answered, and it's perfectly fine too. But it might be considered a bit overkill for such a simple use case.
(this is what we usually do when we don't have a relation between the components e.g one component is not children of the other one)
What I suggest is I think the simplest of all (again, their answers are not bad by any means);
It is basically the same of #Msk Satheesh (but under the hood), just a bit more Angular styled: Output + EventEmitter:
Parent component:
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
Children Component:
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "a string from child component"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
With the code, the parent will always be subscribed to the messageEvent that’s outputted by the child component, and it will run the function (the message function) after the child emits. Handling this with Angular has the advantage that we are sure that we don't have any memory leak in our app (e.g missing unsubscriptions).
When the component that is listening (the subscribed parent) gets destroyed, Angular will unsubscribe automatically to avoid potential memory leaks.
How can I access a string of text given within the tags of a component
<my-custom-component>THIS TEXT</my-custom-component>
Within a template, I can use ng-content, or if it is an instance of some other class I can access it within the component definition like demonstrated in these examples. However I am interested in detecting if there is a string of text there or not, which I believe would make providedText undefined. However, I am always getting undefined.
#ContentChild(Element, { static: true }) providedText: Text | undefined;
I have tried Text as the first element passed to #ContentChild. Passing any will not work (I don't know why).
StackBlitz
I am interested mostly in finding if there is a string or undefined, but am also curious why ContentChild(Text... isn't working.
Edit:
I have added a potential solution, but it seems pretty imperfect, so I hope something better comes along.
Edit 2:
I now understand that #ContentChild is not a mechanism for selecting whatever native HTML I want without wiring it up to Angular’s dependency graph with a ref, directive, etc.
I am still curious if my proposed solution below is a bad idea for any reason.
My solution for now (since I wish to capture all transcluded content) is to wrap ng-content in a containing element, then get its innerText.
#Component({
selector: "app-parent",
template: `
<span #transcludedContainerRef>
<ng-content></ng-content>
</span>
`
})
export class ParentComponent implements AfterViewInit {
#ViewChild("transcludedContainerRef", { static: false })
transcludedContainerRef: ElementRef | undefined;
buttonText: string;
ngAfterViewInit() {
const isButtonTextPresent = this.transcludedContainerRef.nativeElement
.innerText;
if (isButtonTextPresent) {
console.log(isButtonTextPresent); // successfully logs content
}else {
console.log('No text set');
}
}
}
It does feel hacky, but it works. I am holding out for something better.
it's difficult if I don't know about your <my-custom-component>
In general if your custom component it's only
<ng-content></ng-content>
You can inject in constructor the elementRef
constructor(public el:ElementRef){}
From a parent
<hello >
Start editing to see some magic happen :)
</hello>
You can use
#ViewChild(HelloComponent,{static:false}) helloComponent:HelloComponent
click()
{
console.log(this.helloComponent.el.nativeElement.innerHTML)
}
If your component has any variable -or ViewContent-, you can access this variables in a similar way
So the other way to read the inner text from the component is that child component emit the value whatever it get's as input from other component. See below:
hello.component.ts
import { Component, Input, Output, EventEmitter, OnInit } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit {
#Input() name: string;
#Output() innerText: EventEmitter<string> = new EventEmitter();
ngOnInit() {
this.innerText.emit(this.name);
}
}
app.component.ts
import { Component, ContentChild, AfterContentInit, OnInit } from "#angular/core";
#Component({
selector: "app-parent",
template: "content from <code>app-parent</code>"
})
export class ParentComponent implements AfterContentInit {
#ContentChild(Element, { static: true }) providedText: Text | undefined;
ngAfterContentInit() {
console.log("ngAfterContentInit Content text: ", this.providedText);
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular";
_innerText: string;
ngAfterContentInit() {}
get childContent(): string {
return this._innerText;
}
set childContent(text) {
this._innerText = text;
}
innerTextFn(innertext: string) {
this.childContent = innertext;
console.log('Event: ', innertext);
}
}
app.component.html
<hello name="{{ name }}" (innerText)="innerTextFn($event)"></hello>
<app-parent>This is the content text</app-parent>
Here is stackblitz url to check: https://stackblitz.com/edit/angular-bacizp
I hope this may helpful for you and if yes then accept this as correct answer.
I'd like to be able to access the SearchResults component, (when it has been clicked), in the root component (AppComponent) as I'm looking to set different properties on the SearchResults component such as;
I'd like to set an attribute on the SearchResults component so that it shows the "close" text
Also, I'd to set the click event on the SearchResults to redirect elsewhere or actually enable it as a multi-select so that it stays selected until a user proceeds to the next step for example.
I'm trying to make the SearchResults and SearchResult components as re-usable as possible so we're able to state in the parent component which would include the <app-searchresults> selector what action we'd like our SearchResults components to actually be when they are clicked.
The only way I can really see doing this is using EventEmitter to pass the event up once through the SearchResult component then onto the parent component and then a Service to hold selected values but I'm still stuck around enabling the SearchResults component as either a component which redirects when clicked or stays selected? Is this actually possible or do I need to create a different SearchResults component for each different state I'd like?!
export class AppComponent {
#ViewChildren(SearchresultComponent) components: QueryList<SearchresultComponent>;
name = 'Angular';
ngAfterViewInit() {
this.components.changes.subscribe((r) => { console.log(r) });
}
}
SearchResults.ts
#Component({
selector: 'app-searchresults',
templateUrl: './searchresults.component.html',
styleUrls: ['./searchresults.component.css']
})
export class SearchresultsComponent implements OnInit {
#ViewChildren(SearchresultComponent) components: QueryList<SearchresultComponent>;
constructor() { }
ngOnInit() {
}
}
SearchResults.html
<h1>Search Results<h1>
<app-searchresult result ="first"></app-searchresult>
<app-searchresult result ="second"></app-searchresult>
<app-searchresult result ="third"></app-searchresult>
SearchResult.ts
#Component({
selector: 'app-searchresult',
templateUrl: './searchresult.component.html',
styleUrls: ['./searchresult.component.css']
})
export class SearchresultComponent implements OnInit {
#Input()
result: string;
isSelected: boolean;
constructor() { }
ngOnInit() {
}
toggleClickedState(){
if(!this.isSelected){
this.isSelected = !this.isSelected;
}
}
}
SearchResult.html
<div>
<p (click)=toggleClickedState() [ngClass]="isSelected? 'selected' : '' "> Search Result : {{result}}</p>
<p *ngIf="isSelected" class="cross" (click)="isSelected = false;">close</p>
<div>
I've included a link to structure of an app that references the above;
https://stackblitz.com/edit/angular-cjhovx
I'm creating a 3d "card flip" using angular 2. A parent 'card-flip' component contains a nested 'card-flip-front' and 'card-flip-back' component.
<card-flip card-flip-id="demo-1" class="grid_col-6">
<card-flip-front class="card">
<div class="card__inner">
Card Front
</div>
</card-flip-front>
<card-flip-back class="card">
<div class="card__inner">
Card Back
</div>
</card-flip-back>
</card-flip>
I would like to create a "clone" of the card-flip-front component with content projection and data-binding in tact. The "clone" would be used for animating and the "original" would remain in it's original position hidden. That way I have a reference of where the "clone" should animate to when it returns to the original position (even if the user scrolls or resizes the window).
The main challenge I'm facing is that I need the content within the ng-content tag to also be projected in the "clone". The problem being that the first ng-content tag will be used by Angular for content projection and additional, unlabeled ng-content tags will be empty (which I know is the expected behavior).
One might ask, "why not just create a dumb, static copy of the element in the DOM?". I would like to avoid this so that nested components and data bindings that inject data (thereby modifying the dimensions of the element) will continue to work.
Here's my work so far which creates an instance of the CardFlipFront component via ComponentFactory to serve as the "clone" and simply inserts the innerHTML of the "original" CardFlipFront.
import {
Component,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
ContentChild,
Inject,
Input,
OnInit,
ViewChild,
ViewContainerRef
} from '#angular/core';
import { CardFlipFrontComponent } from './card-flip-front.component';
import { CardFlipBackComponent } from './card-flip-back.component';
import { CardFlipService } from './card-flip.service';
#Component({
selector: 'card-flip',
templateUrl: './card-flip.component.html',
styleUrls: ['./card-flip.component.css'],
entryComponents: [
CardFlipFrontComponent
]
})
export class CardFlipComponent implements OnInit {
#Input('card-flip-id') public id: string;
#ContentChild(CardFlipFrontComponent) private front: CardFlipFrontComponent;
#ContentChild(CardFlipBackComponent) private back: CardFlipBackComponent;
#ViewChild('frontCloneContainer', { read: ViewContainerRef }) private frontCloneContainer: ViewContainerRef;
private frontComponentRef: ComponentFactory<CardFlipFrontComponent>;
private frontClone: ComponentRef<CardFlipFrontComponent>;
constructor(
#Inject(CardFlipService) private _cardFlipService: CardFlipService,
private _componentFactoryResolver: ComponentFactoryResolver
) {
this.frontComponentRef = this._componentFactoryResolver.resolveComponentFactory(CardFlipFrontComponent);
}
ngOnInit() {
this._cardFlipService.register(this.id);
}
ngAfterViewInit() {
// Create a card-flip-front component instance to serve as a "clone"
this.frontClone = this.frontCloneContainer.createComponent(this.frontComponentRef);
// Copy the innerHTML of the "original" into the "clone"
this.frontClone.instance.el.nativeElement.innerHTML = this.front.el.nativeElement.innerHTML;
}
ngOnDestroy() {
this.frontClone.destroy();
}
}
<ng-content select="card-flip-front"></ng-content>
<ng-container #frontCloneContainer></ng-container>
<ng-content select="card-flip-back"></ng-content>
import {
Component,
ElementRef,
HostBinding,
Input,
OnInit,
Renderer
} from '#angular/core';
#Component({
selector: 'card-flip-front',
templateUrl: './card-flip-front.component.html',
styleUrls: ['./card-flip-front.component.css']
})
export class CardFlipFrontComponent implements OnInit {
constructor(private _el: ElementRef, private _renderer: Renderer) { }
public get el(): ElementRef {
return this._el;
}
public get renderer(): Renderer {
return this._renderer;
}
ngOnInit() { }
}
<ng-content></ng-content>
UPDATE:
Ok, so after reading about some similar challenges and the github issue here, I tried the following.
<ng-template #frontTemplate>
<ng-content select="card-flip-front"></ng-content>
</ng-template>
<ng-container *ngIf="isOpen == true" #front1>
<ng-container *ngTemplateOutlet="frontTemplate"></ng-container>
</ng-container>
<ng-container *ngIf="isOpen == false" #front2>
<ng-container *ngTemplateOutlet="frontTemplate"></ng-container>
</ng-container>
<ng-content select="card-flip-back"></ng-content>
Basically, we can get around the single projection issue with ng-content by placing it within a template and using two ng-container tags with an *ngIf statement that will only show one instance of the template based on a class property isOpen.
This doesn't solve the entire issue though because only one container will be rendered at any given time. So, I can't get the current position of "original" to figure out where to animate the "clone" during the return animation described above.
I think you can have an intermediary component <card-flip-content> inside <card-flip> template which is duplicated and which receives the <ng-content> of the <card-flip>.
Something like:
#Component(
selector = 'card-flip',
template = `
<card-flip-content #theOne>
<ng-content />
</card-flip-content>
<card-flip-content #theClone>
<ng-content />
</card-flip-content>
`)
Then bind data as needed to #theOne and #theClone and animate only #theClone.
This way can have #Input and #Output thus leaving the actions of one component to be interpreted by the parent in order to act on the other component.
Would that work?
I am trying to abstract out a tabular-data display to make it a child component that can be loaded into various parent components. I'm doing this to make the overall app "dryer". Before I was using an observable to subscribe to a service and make API calls and then printing directly to each component view (each of which had the tabular layout). Now I want to make the tabular data area a child component, and just bind the results of the observable for each of the parent components. For whatever reason, this is not working as expected.
Here is what I have in the parent component view:
<div class="page-view">
<div class="page-view-left">
<admin-left-panel></admin-left-panel>
</div>
<div class="page-view-right">
<div class="page-content">
<admin-tabs></admin-tabs>
<table-display [records]="records"></table-display>
</div>
</div>
</div>
And the component file looks like this:
import { API } from './../../../data/api.service';
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { TableDisplayComponent } from './../table-display/table-display.component';
#Component({
selector: 'account-comp',
templateUrl: 'app/views/account/account.component.html',
styleUrls: ['app/styles/app.styles.css']
})
export class AccountComponent extends TabPage implements OnInit {
private section: string;
records = [];
errorMsg: string;
constructor(private accountService: AccountService,
router: Router,
route: ActivatedRoute) {
}
ngOnInit() {
this.accountService.getAccount()
.subscribe(resRecordsData => this.records = resRecordsData,
responseRecordsError => this.errorMsg = responseRecordsError);
}
}
Then, in the child component (the one that contains the table-display view), I am including an #Input() for "records" - which is what the result of my observable is assigned to in the parent component. So in the child (table-display) component, I have this:
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'table-display',
templateUrl: './table-display.component.html',
styleUrls: ['./table-display.component.less']
})
export class TableDisplayComponent {
#Input() records;
constructor() {
}
}
Lastly, here's some of the relevant code from my table-display view:
<tr *ngFor="let record of records; let i = index;">
<td>{{record.name.first}} {{record.name.last}}</td>
<td>{{record.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record._id ]">{{record.name.first}} {{record.name.last}}</a></td>
When I use it with this configuration, I get "undefined" errors for the "records" properties I'm pulling in via the API/database. I wasn't getting these errors when I had both the table display and the service call within the same component. So all I've done here is abstract out the table-display so I can use it nested within several parent components, rather than having that same table-display show up in full in every parent component that needs it.
What am I missing here? What looks wrong in this configuration?
You need to protect against record being null until it comes in to your child component (and therefore it's view).
Use Elvis operators to protect your template:
<tr *ngFor="let record of records; let i = index;">
<td>{{record?.name?.first}} {{record?.name?.last}}</td>
<td>{{record?.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record?._id ]"> {{record?.name?.first}} {{record?.name?.last}}</a></td>
You can also assign your input to an empty array to help with this issue:
#Input() records = [];