I have a parent component which has two TABs. Each TAB hold one child component. Every child component has a form.
On the parent component there is a button. What I want is once two reactive forms are valid then enable the button.
The parent component likes
<Button (click)="Submit()" [disabled]="!isChild1FormValid || !isChild2FormValid">Submit</Button>
<kendo-tabstrip (tabSelect)="onTabSelect($event)">
<kendo-tabstrip-tab [title]="'Paris'" [selected]="true">
<ng-template kendoTabContent>
<app-child1 (isChild1FormValid)="trigger1($event)">
</app-child1>
</ng-template>
</kendo-tabstrip-tab>
<kendo-tabstrip-tab [title]="'New York City'">
<ng-template kendoTabContent>
<app-child2 (isChild2FormValid)="trigger2($event)">
</app-child2>
</ng-template>
</kendo-tabstrip-tab>
</kendo-tabstrip>
I parent's ts file. We have the methods.
trigger1(isValid: boolean) {
isChild1FormValid = isValid;
}
trigger2(isValid: boolean) {
isChild2FormValid = isValid;
}
In child1 component.
#Output() isChild1FormValid: EventEmitter<boolean> = new EventEmitter<boolean>();
In its `ngOnInit()`, we have
childForm1.statusChanges.subscribe(res => {
if(res == 'VALID') {
this.isChild1FormValid.emit(true);
}
});
}
Similar in child2 component,
#Output() isChild2FormValid: EventEmitter<boolean> = new EventEmitter<boolean>();
In its `ngOnInit()`, we have
childForm2.statusChanges.subscribe(res => {
if(res == 'VALID') {
this.isChild2FormValid.emit(true);
}
});
}
Let's say child1 form and child2 form have some textbox, which are required.(Omitted code here since it just Validators.required.
Now I typed some text in the controls and set breakpoint in the statusChanges event. Now the question is child2 statusChanges method is not called. But child1 is okay, the validation does work. I guess that is the TAB issue when I switch it, it may reload???
UPDATED:
Not sure why it works in stackblitz but failed in my application.
It turns out a lower level error, I didn't put the formcontrol's name in the element. I should be careful to check it.
Such as
<label>
First Name:
<input type="text" formControlName="firstName">
`
Related
I have a Grand Parent component in which I have a button , and basis on some code I want to show/hide that button so for that my code is as follow :
grandparent.component.html
<button
mat-raised-button
title="Send RFP to Suppliers"
(click)="RequestForBid()"
*ngIf="IsAllSuppliersGetRFP"
>
and grandparent.component.ts
this.IsAllSuppliersGetRFP=!this.SetSendButton();
and method SetSendButton as follow:
SetSendButton() {
let all_supplier_request_recived=true;
this.quoteDTO.lineitems.forEach(x=>{
x.item_quantity.forEach(y=>{
y.quote_lineitem_rfp.forEach(z=>{
z.rfp_suppliers.forEach(a=>{
if(!a.is_request_for_bid)
{
all_supplier_request_recived=a.is_request_for_bid;
}
})
})
})
});
return all_supplier_request_recived;
}
now I have another child and in child component I have another grand child component, that component is opening as pop up on parent box. On close of that pop-up I want to set that Grand Parent field IsAllSuppliersGetRFP to true.
I am not sure how i can get that property from grand parent to grand child and set it. I know about input and output but as this is grand children so I dont want to pass between Grand Parent -> Parent -> grand child way.
I read about the services as well to pass the values between multiple components but I am not sure how can I get the this.quoteDTO in service class ?
I read about the services as well to pass the values between multiple
components
Yes, you can do that with below approach.
In shared service add subject variable
public quoteSubject: Subject<any> = null;
public notifyWithType(type: string, message: any = null) {
this.quoteSubject.next({ type: type, text: message });
}
while closing the pop up set the value here..
this.sharedService.notifyWithType("all_suppliers_getRFP", 'boolean value' or 'DTO');
Add a method to bind the service subject to receive the value when event is triggered.
ngOnInit() {
this.sharedService.quoteSubject.subscribe(result => {
if (result.type === 'all_suppliers_getRFP') {
this.setAllSuppliers(result.text);
}
});
}
setAllSuppliers(value: any) {
// sent object (DTO or boolean)
this.quoteDTO = value;
this.IsAllSuppliersGetRFP = // 'value from DTO' as boolean;
}
You might need to change some things here as I am not able to understand your requirement exactly.
Happy Coding.. :)
I'm in trouble to create a component like mat-button-toggle-group of material
I create a simple container with an ng-content that wraps buttons and inside it some customized buttons. THe number of components-button can change...
<container-buttons-wrapper>
<component-button>Test 1</component-button>
<component-button>Test 2</component-button>
<component-button>Test 3</component-button>
</container-buttons-wrapper>
component-button has inside only a button tag
<button (click)="setActive()" [ngClass]="active? 'active-class' : 'no-active-class'"><ng-content></button>
I defined a function setActive() that toggle active value
setActive() {
this.active = !this.active
}
But I can't find a solution to control the other buttons into container. I want to reproduce exactly what mat-button-toggle-group. Is possible to define an eventEmitter inside the template html?
Problem: when a button changes its status to "active", container should change the state of the remaining buttons to "inactive".
Solution: Implement two-way communication between buttons and the container:
a button should be able to notify the container that it became active
a container should be able to set the remaining buttons to inactive state (or, a button should be able to know whether it is active or not from the container)
Basically, the container becomes a holder of the shared state for itself and all nested buttons. This state is made available to nested buttons via DI. State can be handled by a separate service, or it can be a part of the container component itself for simplicity (the latter approach is implemented in Material):
const CONTAINER = new InjectionToken<ContainerComponent>();
#Directive({
providers: [{provide: CONTAINER, useExisting: forwardRef(() => ContainerComponent)}]
})
class ContainerComponent {
private selectedButton: ButtonComponent | null = null
toggleButton(button: ButtonComponent) {
if (this.selectedButton = button) {
this.selectedButton = null
} else {
this.selectedButton = button;
}
}
isSelected(button: ButtonComponent): boolean {
return this.selectedButton = button
}
}
#Component({template: `
<button [class.selected]="isSelected()" (click)="onClick()">
<ng-content></ng-content>
</button>
`})
class ButtonComponent {
constructor(#Inject(CONTAINER) private container: ContainerComponent) {}
isSelected() {
return this.container.isSelected(this)
}
onClick() {
this.container.toggleButton(this)
}
}
Update: Pre-selecting a button
How do we set some button as "selected" initially?
Approach 1
One way is doing smth similar to what Material does.
Add "selected" input to a button componentn
In the Container we would read all buttons via ContentChildren
Whenever the input changes, button should update the state in container.
A challenge is that now we have two sources of truth for "selected" flag in a button (button's input and the state coming from the container) - so we need to reconcile them, and the overall code becomes more involved.
Approach 2
Alternatively, let's assume your toggle component has some sort of "value" property. Kind of like html <select> element - each option has value property, and the selected property of the <select> is derived based on that.
In this case, we would have an input in ContainerComponent that allows to set initial value:
class ContainerComponent {
#Input() selected: any
toggleButton(value: any) {
if (this.selected !== value) {
this.selected = value
} else {
this.selected = null
}
}
}
class ButtonComponent {
#Input() value: any
onClick() {
this.container.toggleButton(this.value)
}
}
// usage
<container selected="option-1">
<my-button value="option-1"><my-button>
<my-button value="option-2"><my-button>
</container>
Background: my main page opens up an external window (same origin) of another module within my project upon button click.
I also set up a BroadcastChannel so that these two windows can now communicate. Now, if this window is already open & the user clicks the triggering button once again, I want to communicate this to the window:
onAddNewFieldClick() {
if (this.window === null) {
this.window = window.open(window.location.origin + '/wizard', 'Field Wizard', 'resizable,scrollbar');
this.channel = new BroadcastChannel('edit-spec-wizard-channel');
} else {
this.channel.postMessage(1);
}
}
The new window listens on this channel and appends the message data to an array that is used in an ngFor. To be extra safe. I go ahead and create a brand new array each time a new value is pushed to cause a rebind. Here is the logic that powers the component in the new window.
export class EntryComponent implements OnInit, OnDestroy {
newFieldChannel: BroadcastChannel;
newFields: number[] = [];
constructor() { }
ngOnInit() {
this.newFieldChannel = new BroadcastChannel('edit-spec-wizard-channel');
this.newFieldChannel.onmessage = this.newFieldChannelOnMessage.bind(this);
this.newFields.push(1);
}
func() {
this.newFields.push(1);
this.newFields = this.newFields.slice();
}
private newFieldChannelOnMessage(event: MessageEvent) {
this.newFields.push(event.data as number);
this.newFields = this.newFields.slice();
}
ngOnDestroy() {
this.newFieldChannel.close();
}
}
And here is the template HTML:
<div class="row">
<div class="col" *ngFor="let newField of newFields">
<div style="width: 300px; height: 600px; background-color: white;">
NEW FIELD BOX
</div>
</div>
<button class="btn btn-primary" (click)="func()">Click me</button>
</div>
I have also included a button that triggers a function ("func()") that has the exact same logic as the postMessage handler.
Now, when I click the button in this window, I'll get the expected behavior: The correct number of "NEW FIELD BOX" divs will appear in this new window. However, when I press the original button from the main screen that posts a message over the BroadcastChannel, it WILL NOT update the UI to display the right number of "NEW FIELD BOX" divs. Using break points I can see that the array newFields does contain the right number of values, but ngFor does not re-render.
Example: I click the button on the main page that fires the onAddNewFieldClick(). It opens a new window which has one "NEW FIELD BOX" div. I click this button again which posts a message to add another. Still, only one remains on the window. I now click the button within the window that fires the function "func()." This will now render 3 "NEW FIELD BOX" divs (the original one from initialization, the one from the post message that didn't render, and the one from clicking this button).
Any ideas why change detection doesn't seem to happen from a postMessage?
The newFieldChannelOnMessage event handler is probably running outside of the Angular zone and does not trigger change detection. Try wrapping the code in NgZone.run():
import { NgZone } from "#angular/core";
constructor(private ngZone: NgZone) { ... }
private newFieldChannelOnMessage(event: MessageEvent) {
this.ngZone.run(() => {
this.newFields.push(event.data as number);
});
}
I have a service where I'm listening to browser print events.
#Injectable()
export class ApplicationSession {
printStream$: Observable<boolean>;
constructor() {
this._setupPrintListener();
}
private _setupPrintListener(): void {
if (this._window.matchMedia) {
const beforePrintEvent = fromEvent(this._window, 'beforeprint')
.pipe(mapTo(true));
const afterPrintEvent = fromEvent(this._window, 'afterprint')
.pipe(mapTo(false));
this.printStream$ = merge(beforePrintEvent, afterPrintEvent).pipe(startWith(false));
}
}
}
Then, in my Component, I'm binding property to the printStream$ property of the service instance. As in,
export class ReferralComponent {
printRequested$: Observable<boolean>;
constructor(session: ApplicationSession) {
this.printRequested$ = session.printStream$;
//Observing values here
this.printRequested$.subscribe(console.log);
}
}
I use the component's printRequested$ property to create and destroy an angular component asynchronously.
<generic-angular-component *ngIf="printRequested$ | async"></generic-angular-component>
I have a child component within the ReferralComponent that has a button which triggers window.print() function. As in,
#Component({
template: `<button (click)="print()">Print</button>`
})
export class ChildComponent {
print() {
window.print();
}
}
My problem is, when I press Command/Ctrl + P from the keyboard, I see angular creating and destroying the <generic-angular-component>. However, when I trigger the window.print() via the child component button click, I can see the true/false values being passed on the stream. However, Angular doesn't seem to care about it at all. I don't see the component at all.
Here's a stackblitz reproduction.
You can either click on Print button and see that there's no Toggle Me line at the bottom left of the print preview page. Or, you can press Command/Ctrl + P and see that it (Toggle Me) is there at the bottom left.
I have the following code and i wish to update the parent class when click on the image. The image will call "SelectVariation" method when clicked. Is there any way to do this?
component.html :
<div class="clr-row">
<ng-container *ngFor="let variOption of product.variOptions">
<div class="card clickable clr-col-2 variationCard"
*ngFor="let variOptionTwo of variOption.variOptionTwos"> //Update this class
<div class="card-img" (click)="selectVariation(variOptionTwo.id, $event)">
<clr-tooltip>
<img src="{{variOptionTwo.url}}" class="variationImgScale" clrTooltipTrigger>
<clr-tooltip-content clrPosition="top-right" clrSize="m" *clrIfOpen>
<span>{{variOption.optName}} - {{variOptionTwo.optName}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
component.ts :
selectVariation(id: number, event: any) {
//Update parent class
}
In the child component use
#Output() variableHere = new EventEmitter<>();
this.variableHere.emit(this.variableToSend);
Then in the parent associate the variable to a method in html child template definition:
<app-child (variableHere)="manageVariable($event)"></app-achild>
In the parent component define the method and do a variable equels the result of the method for example:
manageVariable(event) {
this.variableToUpdate = event;
}
If you have to check if the variable has changed his state call what you need to check in an ngDoCheck().
Take the advantage of the EventEmitter in angular with output
parent.component.html
<my-child-comp (onSelectVariation)="myVariation($event)" ></my-child-comp>
parent.component.ts
myVariation(myVars) {
console.log(myVars)
}
child.component.html
<button (click)="onSelectVariation.emit(myData)">trigger variation</button>
child.component.ts
#Output() onSelectVariation = new EventEmitter();
Name which you have defined in the output should be used as a event in it host element in parent