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.
Related
I have two .ts files (editor.ts and editor_settings.ts), Corresponding to editor.ts i have creater editor.html file. Now what i am trying to call function inside editor_settings.ts on button click in editor.html.
editor.ts
import { EditorSetting } from '../app/editorSetting.component';
export class PadComponent implements OnInit, OnDestroy { ---- }
constructor(
private component: EditorSetting
) { }
submit() {
let userCode = this.component.editor.getValue();
console.log('Inside pad.componet.ts');
console.log(userCode);
}
editor.html
<button id="submit" type="button" class="btn btn-sm btn-run" (click)="submit()" [disabled]="loading"
style="background: #FF473A">
<i class="fa fa-play" aria-hidden="true"></i>
<span *ngIf="loading">Running</span>
<span else> Run </span>
</button>
Now, on button click in editor.html, i want to call function which is inside editor_settings.ts.
editor_settings.ts
export class EditorComponent implements OnInit, OnDestroy, OnChanges {--}
I am facing the following error:
inline template:0:0 caused by: No provider for EditorComponent!
To communicate two components that are not related to each other, you can use a service.
#Injectable({
providedIn: 'root',
})
export class YourService {
private yourVariable: Subject<any> = new Subject<any>();
public listenYourVariable() {
return this.yourVariable.asObservable();
}
public yourVariableObserver(value ?: type) {
this.yourVariable.next(value);
}
You import in yours components where you want use it this service.
import{ YourService } from ...
In Edit component :
submit(){
this.yourService.yourVariableObserver();
}
while in Editor_setting.ts
ngOnInit() {
this.sub=this.yourService.listenYourVariable().subscribe(
variable => {
this.callyourFunction();
}
)
}
Don't forget to unsubscribe to prevent memory leak
ngOnDestroy() {
this.sub.unsubscribe()
}
Another aproach valid if your editor is inside the editorSetting
<editor-setting>
<editor></editor>
</editor-setting>
Use Host in constructor
constructor(#Optional #Host() private component: EditorSetting) { }
I have a component (componentA) that is using another component (componentB) in its HTML.
ComponentB has a button where the title is something else, but I would like to change the name of the button in ComponentA.
ComponentA uses componentB button to navigate to another page, but ComponentB has button that opens up a form. The navigation works and everything, but I would just like to change the name of the buttons when its on different page.
ComponentA.html
<div>
<component-b (click)="buttonEdit()"></component-b>
</div>
ComponentA.ts
public buttonEdit(): void {
this.router.navigate(['/users']);
}
ComponentB.html
<button (click)="openModal()">Add Users</button>
ComponentB.ts
#Input() buttonEdit: () => void;
You can try this
Component B.tmpl:
<button (click)="openModal()">{{buttonName}}</button>
Component B.ts:
#Input() buttonEdit: () => void;
#Input() buttonName: string = 'Add Users';
Component A.ts:
public buttonEdit(): void {
this.router.navigate(['/users']);
}
public buttonName(): string {
...
return buttonName;
}
component A.tmpl:
<div>
<component-b (click)="buttonEdit()" [buttonName]="buttonName()"></component-b>
</div>
Try this
Component A.tmpl:
<div>
<component-b (click)="buttonEdit()" [buttonName]="buttonName()" [backgroundColor]="backgroundColor()"></component-b>
</div>
Component A.ts:
public backgroundColor(): string {
...
return backgroundColor;
}
Component B.tmpl:
<div [ngStyle]="{'background-color': backgroundColor}"></<div>
Component B.ts:
#Input() backgroundColor: string = 'green';
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">
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>
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.