I have 2 components: parent and child.
I want to send an array from parent to child when I click on a button(from parent-component) and call a function generate() for every object in array(in child-component).
I have tried something with #Output() EventEmitter but I am not sure of this approach.
parent component
export class ViewCalendarsComponent implements OnInit {
#ViewChildren(MonthHeaderComponent) months: any[];
#Output() monthsList: EventEmitter<Date[]> = new EventEmitter();
selectedMonths: any[];
test() {
this.monthsList.emit(this.selectedMonths);
}
}
child component
export class MonthHeaderComponent implements OnInit {
ngOnInit() {
}
generate(date: Date) {
// code here....
}
show() {
for (let i = 0; i <= monthsList.length; i++)
{
generate(monthsList[i]);
}
}
}
and in parent html
<button class="primary" (click)="test()"> Show </button>
<div class="right view-calendar">
<child *ngFor="let selectedMonth of selectedMonths" [monthsList]="show($event)"></child>
</div>
How can I send this array and use it as parameter in a method?
Just use the #input instead of output. And when the input value get modifies, use the ngOnChanges to catch the changes.
child component
export class MonthHeaderComponent implements OnInit {
#Input monthsList: Date[]
ngOnChanges(){
// catch changes
}
}
In the parent template, pass the array.
<child *ngFor="let selectedMonth of selectedMonths" [monthsList]="monthArr"></child>
Related
I have 3 components in this situation:
-OuterComponent
--MiddleComponent
---InnerComponent
I need to pass a function from OuterComponent to InnerComponent through MiddleComponent.
It is important to mention that the function I need to pass does take an input: DoSomething(node)
I don't know if it is relevant but I am already passing a NodeTree from the OuterComponent to the MiddleComponent and then I am unpacking the NodeTree into a Node and passing it InnerComponent. This Node is what I need to use as an input for the function being passed.
So, I need to be able to use an #Input as the input for the function being passed to the InnerCompoenent, which I assume will need to be an #output.
Method 1:
You can call the parent component function(OuterComponent) from the child component(InnerComponent) using #Output.
OuterComponent HTML:
<MiddleComponent (updateOuterComponent)="parentFunction($event)" />
OuterComponent TS:
export class OuterComponent implements OnInit {
constructor() {}
ngOnInit() {}
parentFunction(para) {
console.log(para);
// operations you want to do in parent component
}
}
MiddleWare HTML:
<InnerComponent (updateMiddleComponent)="middleFunction($event)" />
MiddleComponent TS:
export class MiddleComponent implements OnInit {
#Output() updateOuterComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
middleFunction(para) {
this.updateOuterComponent.emit(para);
}
}
InnerComponent HTML:
It can be whatever you want to write
InnerComponent TS:
export class InnerComponent implements OnInit {
#Output() updateMiddleComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
updateMiddleAndParent(para) {
this.updateMiddleComponent.emit(para);
}
}
Once you call updateMiddleAndParent function form Inner component using emitter, that will trigger middleFunction in the MiddleComponent. After triggering middleFunction, Similarly middleFunction will trigger parentFunction with the help of emitter.
Method 2:
You need to create a service and use it to call the parent function:
DataService:
import {BehaviorSubject} from "rxjs/BehaviorSubject"
export class DataService {
private state$ = new BehaviorSubject<any>('initialState');
changeState(myChange) {
this.state$.next(myChange);
}
getState() {
return this.state$.asObservable();
}
}
call DataService in both InnerComponent and OuterComponent:
In the OuterComponent call DataService and call getState(), this will return an observable whenever the value changes you can any function using data passed in observable response.
In the InnerComponent call DataService and use the changeState() to change the value.
once the value is changed in DataService, then in parent Component the value will be change as you are subscribed to the observable, You will get the updated data from there you can call any function in parent.
I'm trying to catch paste event occuring on input fields.
It works perfectly on textarea and input but not on dropdowns. Select.
Here is my directive, the console.log is never called.
import { Directive, HostListener } from '#angular/core';
#Directive({ selector: '[catchPasteEvents]' })
export class CatchPastEvent {
#HostListener('onpaste') onPaste(event) {
console.log('Paste', event);
}
}
I created this directive that set a listener on document paste (as suggested by Sergey) but only when the select element is focused.
#Directive({ selector: 'select[selectPaste]' })
export class SelectPasteDirective implements OnDestroy {
#Output()
public paste: EventEmitter<ClipboardEvent> = new EventEmitter();
private listener: (event: ClipboardEvent) => void = this.handlePaste.bind(
this,
);
public ngOnDestroy(): void {
document.removeEventListener('paste', this.listener);
}
#HostListener('focus')
public onFocusedItem() {
document.addEventListener('paste', this.listener);
}
#HostListener('blur')
public onBlurItem() {
document.removeEventListener('paste', this.listener);
}
private handlePaste(event: ClipboardEvent) {
this.paste.emit(event);
}
}
Goodday, This is probably a nooby question but I can't get it to work.
I have a simple service which toggles an boolean, if the boolean is true the class active should appear on my div and if false no class.. Simple as that. But the boolean gets updated, but my view doesn't react to it. Do I somehow have to notify my view that something has changed ?
Service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ClassToggleService {
public menuActive = false;
toggleMenu() {
this.menuActive = !this.menuActive;
}
}
View (left menu component):
<div id="mainContainerRightTop" [class.active]="classToggleService.menuActive == true">
Toggle point (top menu component):
<a id="hamburgerIcon" (click)="classToggleService.toggleMenu()">
This because you are changing a value on a service not on the component, so Angular don't need to update the component, because it did not change. If you want to update the view of your component when a service element is modified, you have to use an Observables and Subjects, and subscribe to them. In this way when the element is changed, it automatically notify all the subscribed components.
#Injectable({
providedIn: 'root'
})
export class ClassToggleService {
public menuSubject: Subject<boolean> = new BehaviorSubject<boolean>(false);
public menuActive = this.menuSubject.asObservable();
toggleMenu(val : boolean) {
this.menuSubject.next(val);
}
}
And in your component just implement OnInit interface and subcribe to the observable in the your service:
public localBool = false;
ngOnInit() {
this._myService.menuActive.subscribe(value => this.localBool = value);
}
ComponentToggleMenu() {
this._myService.toggleMenu(!this.localBool);
}
Then your html:
<div id="mainContainerRightTop" [class.active]="localBool">
<a id="hamburgerIcon" (click)="ComponentToggleMenu()">
Why we need service, this should be integrated with component class. As a general rule, you are not supposed to call service method in template file.
export class TestComponent implements OnInit{
public menuActive = false;
toggleMenu() {
this.menuActive = !this.menuActive;
}
}
Template:
<div id="mainContainerRightTop" [class.active]="menuActive">
We have a standard modal within our application.
<ng-template [ngIf]="true" #editDataModal>
<div class="modal-header">
<h5 class="modal-title">Edit Modal</h5>
<button type="button" class="close" aria-label="Close" (click)="onCancelClicked()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" #editDataModalBody>CUSTOM COMPONENT GOES HERE</div>
</ng-template>
We want to be able to pass in a custom component as our body. Is there a way in ngx bootstrap to do this?
It appears that the modal appears outside the main content so we can't find it using ViewChild.
We are calling it using the modal service. Like so:-
constructor(
private modalService: BsModalService
) { }
ngOnInit() {
this.modalConfig = {
ignoreBackdropClick: true
};
this.ModalRef = this.modalService.show(this.editModal, this.modalConfig);
}
The modal component could obtain and publish the ViewContainerRef. For example, it could use a BehaviorSubject. The parent component can create the custom component and add it to the modal when the viewContainerRef is published. I only did it this way instead of just a getter because ViewChild is not valid until afterViewInit so you need a way to handle that.
// EditModalComponent
export class EditModalComponent implements AfterViewInit {
#ViewChild("editDataModalBody", {read: ViewContainerRef}) vc: ViewContainerRef;
public bodyRefSubject: BehaviorSubject<ViewContainerRef> = new BehaviorSubject<ViewContainerRef>(null);
constructor(
public bsModalRef: BsModalRef,
public vcRef: ViewContainerRef
) {}
ngAfterViewInit() {
this.bodyRefSubject.next(this.vc);
}
onCancelClicked() {
console.log('cancel clicked');
this.bsModalRef.hide()
}
}
And in the parent component:
// Parent Component
export class AppComponent {
bsModalRef: BsModalRef;
bodyContainerRef: ViewContainerRef;
customComponentFactory: ComponentFactory<CustomComponent>;
modalConfig: ModalOptions;
constructor(
private modalService: BsModalService,
private resolver: ComponentFactoryResolver
) {
this.customComponentFactory = resolver.resolveComponentFactory(CustomComponent);
}
openModalWithComponent() {
this.modalConfig = {
ignoreBackdropClick: true,
}
this.bsModalRef = this.modalService.show(EditModalComponent, this.modalConfig);
this.bsModalRef.content.bodyRefSubject.subscribe((ref) => {
this.bodyContainerRef = ref;
if (this.bodyContainerRef) {
this.bodyContainerRef.createComponent(this.customComponentFactory);
}
})
}
}
Another way without using ViewChild is to place a directive on the div instead of #editDataModalBody and that directive can inject the ViewContainerRef and publish it using a service or similar.
I can pass a class object like Person into a child component from parent component without any problems. But I would like to also manipulate that object in child component and pass it back to parent component.
This is the child component class:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify: EventEmitter = new EventEmitter();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
This is the html of this component (excerpt):
<div class="row" (change)="onChange()">
<div class="col-xs-2">
<button md-icon-button >
<md-icon>skip_previous</md-icon>
</button>
</div>
This is the parent component class (excerpt):
public onNotify(company: Company):void {
this.company = company;
}
And the parent component html (excerpt):
<action-panel [company]="company" (notify)="onNotify($event)"></action-panel>
I am doing something wrong because I cannot pass my company object inside the .emit and nothing works.
What is the correct way of achieving two way object binding between components?
Thanks in advance!
You were missing the type on the initialization of the EventEmitter.
You could use the Output binding to implement the two way object binding:
Child component (ts)
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() companyChange: EventEmitter = new EventEmitter<Company>();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.companyChange.emit(this.company);
}
}
Parent component (html)
<action-panel [(company)]="company"></action-panel>
So like this you don't need to declare an extra function onNotify. If you do need the onNotify function, use another name for the output binding:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify: EventEmitter = new EventEmitter<Company>();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
Change it like this to tell TS which Type the EventEmitter should emit:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify = new EventEmitter<Company>(); //<---- On this line!
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
It is a workaround that worked for me, if it is helpful for anyone.
Your parent parent-component.ts would be like;
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'parent',
templateUrl:'./parent.component.html',
styleUrls: ['./parent.component.css']
})
export class Parent implements OnInit {
let parentInstance= this; //passing instance of the component to a variable
constructor() { }
parentMethod(var:<classtyepyourchoice>){
console.log(var);
}
ngOnInit() {
}
}
In you parent.component.html, you would have your child
<child [parent]="parentInstance" ></child>
This object will be available in the child component
Now, in your child component you will receive this like
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'child',
templateUrl:'./child.component.html',
styleUrls: ['./child.component.css']
})
export class Child implements OnInit {
#Input('parent') parent;
constructor() { }
childMethod(yourClassObject){
this.parent.parentMethod(yourClassObject);
}
ngOnInit() {
}
}
Thus, you can pass classobject from your child, like this, it worked for me.