Here i wrote a popup function and one Boolean flag is their in my component class. My flag will return true or false based on my conditions. In template class popup function needs to fire when flag becomes true, then immediately my popup dialog box will come. But i am not aware to call right approach, If any one knows please help me the correct approach.
<ng-template #sessionSuccessModal let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Include Criteria Error</h4>
<button type="button" class="close" aria-label="Close" (click)="closeModel()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" class="bg-light text-dark">
<p>{{ alertMessage }}!</p>
</div>
<div style="text-align: center" class="bg-light text-dark">
<button type="button" (click)="closeModel()">Ok</button>
</div>
</ng-template>
intially commaSeparation will be false, this.commaSeparation = this.genericValidator.validateMultiComma(this.orderUnitForm); this function is returning either true or false. If it is true then i need to call my displayModel() alert method. Now popup is working fine, calling in ngAfterViewInit() but getting error in console like.
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-untouched: true'. Current value: 'ng-untouched: false'
ngAfterViewInit() {
let controlBlurs: Observable<any>[] = this.formControls
.map((formControl: ElementRef) => Observable.fromEvent(formControl.nativeElement, 'blur'));
// debounceTime(1000)/
Observable.merge(this.orderUnitForm.valueChanges, ...controlBlurs).subscribe(value => {
this.displayMessage = this.genericValidator.processMessages(this.orderUnitForm);
// this.valid = this.genericValidator.validateControls(this.orderUnitForm);
});
this.orderUnitForm.valueChanges.debounceTime(1000).subscribe(value => {
this.valid = this.genericValidator.validateControls(this.orderUnitForm);
this.commaSeparation = this.genericValidator.validateMultiComma(this.orderUnitForm);
if(this.commaSeparation == true){
this.displayModel();
}
});
}
This is my dispalyModel() function:
displayModel() {
this.alertMessage = 'You cannot enter more than one multiple at the same time ';
this.successErrorModalBlock = this.modalService.open(this.sessionSuccessModalref);
}
You can achieve this implementing the interface OnChanges that is a lifecycle hook of the Angular.
import { Component, OnChanges, SimpleChanges } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnChanges {
// the flag property
commaSeparation: boolean;
ngOnChanges(changes: SimpleChanges){
if (changes['commaSeparation'] && changes['commaSeparation'].currentValue){
this.showPopup();
}
}
public showPopup(){
alert('Replace this alert by the code that shows your popup');
}
}
Reference: https://angular.io/guide/lifecycle-hooks#onchanges
In general, it's a bad idea to call functions with side effects inside an angular expression. Angular is free to call those functions however often it likes to make sure the result is still the same. You should use functions in these expressions to return a result, not to cause an action.
I would suggest instead calling popupAlert from your controller, e.g.
$scope.$watch('commaSeparation', function(commaSeparation) {
// The value of commaSeparation has just changed. Alert if it changed to true!
if (commaSeparation) {
popupAlert();
}
});
If commaSeparated is an input property, you can add a property change watcher using the ngOnChanges lifecycle hook and then call popupAlert if commaSeparation was changed (link to docs).
#Component({
...
})
export class MyComponent implements OnChanges {
// ...
#Input()
commaSeparation = false;
// ...
ngOnChanges(changes: SimpleChanges) {
const commaSeparationChanges = changes.commaSeparation;
if (commaSeparationChanges && commaSeparationChanges.currentValue) {
this.popupAlert();
}
}
}
If commaSeparated is only changed inside the component, then you can just make it private and work with a getter/setter pair to trigger the popup:
#Component({
...
})
export class MyComponent implements OnChanges {
// ...
private _commaSeparation = false;
// ...
get commaSeparation() {
return this._commaSeparation;
}
set commaSeparation(value) {
this._commaSeparation = value;
if (value) {
this.popupAlert();
}
}
}
Related
i have an issue while click binding on dynamic html.I tried setTimeout function but click event not binding on button.i have also tried template referance on button and get value with #ViewChildren but #ViewChildren showing null value.
Typscript
export class AddSectionComponent implements OnInit {
sectionList: any = [];
constructor(private elRef: ElementRef,private _httpService: CommonService ,private sanitized: DomSanitizer) { }
ngOnInit(): void {
this.getSectionList();
}
ngAfterViewInit() {
let element = this.elRef.nativeElement.querySelector('button');
if (element) {
element.addEventListener('click', this.bindMethod.bind(this));
}
}
bindMethod() {
console.log('clicked');
}
sanitizeHtml(value: string): SafeHtml {
return this.sanitized.bypassSecurityTrustHtml(value)
}
getSectionList() {
//API request
this._httpService.get('/Section/GetSectionList').subscribe(res => {
if (res) {
this.sectionList = res.json();
//sectionList is returning below HTML
//<div class="wrapper">
// <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
//</div>
}
})
}
}
Template
<ng-container *ngFor="let item of sectionList">
<div [innerHTML]="sanitizeHtml(item?.sectionBody)">
</div>
//innerHTML after rendering showing this
//<div class="wrapper">
// <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
//</div>
</ng-container>
Short Answer, you are binding functions inside your templates, which means you have a new html content every time change detection runs, and change detection runs everytime a function is called, which means your button keeps on being updated infinitely, that's why it never works, Read more here please.
Now on how to do this, I would listen to ngDoCheck, and check if my button has a listener, if not, I will append the listener. I will also make sure to use on Push change detection, because if not, this will ngDoCheck will be called a lot, and maybe the button will be replaced more often, not quite sure about it.
Here is how the code would look like.
html
<!-- no more binding to a function directly -->
<div #test [innerHTML]='sanitizedHtml'></div>
component
import { HttpClient } from '#angular/common/http';
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, ElementRef, OnDestroy, ViewChild } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements DoCheck {
name = 'Angular';
people: any;
//now we are getting the div itself, notice the #test in the html part
#ViewChild('test')
html!: ElementRef<HTMLDivElement>;
//a property to hold the html content
sanitizedHtml!: SafeHtml;
constructor(private _http: HttpClient, private sanitized: DomSanitizer,private change: ChangeDetectorRef ) {}
ngDoCheck(): void {
//run with every change detection, check if the div content now has a button and attach the click event
if (this.html != undefined) {
let btn = this.html.nativeElement.querySelector('button');
if (btn && btn.onclick == undefined) {
btn.onclick = this.bindMethod.bind(this);
}
}
}
ngOnInit() {
this.peoples();
}
peoples() {
this._http.get('https://swapi.dev/api/people/1').subscribe((item: any) => {
const people = `<div class="wrapper">
<p>${item['name']}</p>
<button type='button' class='btn btn-primary btn-sm'>Click Me</button>
</div>`;
//assign the html content and notify change detection
this.sanitizedHtml = this.sanitized.bypassSecurityTrustHtml(people);
this.change.markForCheck();
});
}
bindMethod() {
console.log('clicked');
}
}
I don't like the approach because of the need to listen to ngDoCheck, this can run a lot, especially if you don't use onpush change detection.
I hope this helped.
I have a problem with Anglular 8 and binding input parameters from parent component to child component.
I have the following setup:
-app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'parent-child-binding';
showTitle: boolean = true;
childData = [];
onBoolean(){
this.showTitle = !this.showTitle;
}
onComplexAdd(){
this.childData.push("data 4");
this.childData.push("data 5");
}
onComplexEdit(){
this.childData[0] = "data 1 edited";
this.childData[1] = "data 2 edited";
}
onComplexNew(){
this.childData = [
"data 1",
"data 2",
"data 3"
]
}
}
-app.component.html
<button (click)="onBoolean()">Boolean Bind</button>
<button (click)="onComplexNew()">Complex Object New</button>
<button (click)="onComplexEdit">Complex Object Edit</button>
<button (click)="onComplexAdd">Complex Object Add</button>
<app-child [data] = "childData" [showTitle]="showTitle"></app-child>
-child.component.ts
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges {
#Input() showTitle : boolean = true;
#Input() data : Array<any>;
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
}
ngOnInit(): void {
}
}
-child.component.html
<h3 *ngIf="showTitle">Hello from child</h3>
<p *ngFor="let item of data">{{item}}</p>
So when I start I see the following:
and the console:
When I click on the first button, as expected the title Hello from child shows and disappears.
When I click on the second button, as expected I see:
and the console:
When I click on the third and forth buttons, nothing happens, not in the UI or console (the onChanges method seems that is not firing).
An I doing something wrong, or this that I want to achieve is not possible?
Best regards,
Julian
EDIT: After a comment and an answer from #MBB and #Apoorva Chikara, I've edited the code.
<button (click)="onBoolean()">Boolean Bind</button>
<button (click)="onComplexNew()">Complex Object New</button>
<button (click)="onComplexEdit()">Complex Object Edit</button>
<button (click)="onComplexAdd()">Complex Object Add</button>
<app-child [data] = "childData" [showTitle]="showTitle"></app-child>
The edition made the buttons to act (do something), but it is not what I expect.
What I mean:
When I click on the Complex Object Edit button in the UI I see:
But in the console, there is no ngOnChanges callback firing, but the binded object has changed, as we can see on the print screen (<p *ngFor="let item of data">{{item}}</p>) fired and printed out the new values.
The same happens when I click on the Complex Object Add button. In the UI I see:
But in the console the ngOnChanges callback is not firing, but the UI is containing the new added data.
I'm confused, can anyone advice please?
You have a very simple fix, you are not calling a function instead assigning its definition :
<button (click)="onComplexEdit()">Complex Object Edit</button> // call it as a function
<button (click)="onComplexAdd()">Complex Object Add</button>// call it as a function
The issue, you are facing for NgonChanges is due to the arrays passed by reference, this has a good explanation why this happens.
I'm trying to update boolean value when mouseover/mouseout (it should change dynamically), to use it later with if else statement and assign some functions based on true/false. But it shows only false and never true. Can someone help me out?
ts:
mouseEv: boolean;
mouseOut(e) {
this.mouseEv = false;
}
mouseOver(e) {
this.mouseEv = true;
}
ngOnInit(): void {
if(this.mouseEv == false){ func(); }
else if(this.mouseEv == true) { otherFunc();};
}
html:
<div (mouseover)=" mouseOver($event)" (mouseout)="mouseOut($event)"></div>
EDIT:
I need to change boolean value dynamically, because I will use it with object that has functions in it and I can't call them from another function.
Create a function for example MouseHandlerEv in wich you recive the boolean value:
.HTML file
<div (mouseover)="mouseEvHandler(true)" (mouseout)="mouseEvHandler(false)"></div>
.TS file
mouseEvHandler(status){
status ? FunctionTrue() : FunctionFalse();
}
Example:
function mouseEvHandler(status){
status ? sayHi() : sayBye();
}
function sayHi() {
console.log('HI');
}
function sayBye() {
console.log('Bye');
}
<div onmouseover="mouseEvHandler(true)" onmouseout="mouseEvHandler(false)">MESSAGE ON CONSOLE</div>
Extrapolate it to angular
You can simply use it like below without creating a function for event:
<div (mouseover)="mouseEv=true" (mouseout)="mouseEv=false"></div>
You can also try passing true and false directly as method argument in mouseOver(true) and receive its value in component.
At the moment, the values are checked only once at beginning of the component since you're checking the values in ngOnInit() hook. You could instead try to make it reactive using RxJS fromEvent and use it to trigger events.
Try the following
Template
<div #mouseControl></div>
import { Component, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
#Component({ ... })
export class AppComponent implements AfterViewInit {
#ViewChild('mouseControl') mouseControl: ElementRef<any>;
ngAfterViewInit() {
fromEvent(this.mouseControl.nativeElement, 'mouseover').subscribe(
_ => otherFunc();
);
fromEvent(this.mouseControl.nativeElement, 'mouseout').subscribe(
_ => func();
);
}
}
I have a dialog box that is displaying a separate child component. The child component is below:
#Component({
selector: 'userEdit',
templateUrl: './edituser.component.html',
styleUrls: ['./edituser.component.css']
})
export class EditUserComponent implements OnInit {
public theName: string;
public theAddress: string;
constructor() {
this.theName = '';
this.theAddress = '';
}
ngOnInit() {
}
}
The dialog box code and template are below:
#Component({
selector: 'app-userdialog',
templateUrl: './userdialog.component.html',
styleUrls: ['./userdialog.component.css']
})
export class UserDialogComponent implements OnInit {
#ViewChild('userEdit', {static: false})
userEdit: EditUserComponent;
constructor(
public dlgRef: MatDialogRef<UserDialogComponent>,
#Inject(MAT_DIALOG_DATA) public theData: UsrStuff) { }
ngOnInit() {
}
ngAfterViewInit() {
console.log('Name: ' + this.userEdit.theName);
}
addUser() {
// TODO: implement adding a user
}
closeBox() {
this.dlgRef.close();
}
}
and
<div id="attribdlg">
<h3>Add New User</h3>
<userEdit theName="" theAddress=""></userEdit>
<mat-dialog-actions>
<button mat-raised-button color="primary" (click)="addUser()">Add User</button>
<button mat-raised-button color="primary" (click)="closeBox()">Done</button>
</mat-dialog-actions>
</div>
Based on the documentation and examples Ihave seen, this setup should enable me to print to the console the value pf userEdit's theName property in the ngAfterViewInit() function.
Unfortunately, this does not appear to be working.When the console log is called, I get the following failure message:
null: TypeError: Cannot read property 'theName' of undefined
Obviously, there is some kind of initialization of the child component that is supposed to happen, but I do not see this being done anywhere in the documentation! I am missing something.
How can I get this child component and its properties available to my dialog?
Two options:
Set an id to the component you wish to have with ViewChild():
TypeScript:
#ViewChild('userEdit', {static: false})
HTML:
<userEdit #userEdit theName="" theAddress=""></userEdit>
Select by directive or component:
TypeScript:
#import { EditUserComponent } from '...';
#ViewChild(EditUserComponent, {static: false})
HTML:
<userEdit theName="" theAddress=""></userEdit>
I highly recommend you to use app perfix for the component's selector!!!
#Component({
...
selector: 'app-user-edit',
...
})
In angular1, we call the function ng-init based on ng-if condition. The following code represents If first div match with date && time then it will check the second condition, checked or not checked. If checked then it's automatically call play() function and if it's not check then call the pause function.
<div ng-if="ramadan.date === date && ramadan.time === clock">
<div ng-if="ramadan.checked" ng-init="play()"></div>
<div ng-if="!ramadan.checked" ng-init="pause()"></div>
</div>
I want to convert this code by angular2 typescript. Is there any way to automatically call the function based on condition in angular 2?
There is no ng-init in Angular2. You can easily create such a directive yourself though.
import { Directive, Input } from '#angular/core';
#Directive({
selector: '[ngInit]'
})
export class NgInit {
#Input() ngInit;
ngOnInit() {
if (this.ngInit) {
this.ngInit();
}
}
}
and then use it like
<div *ngIf="ramadan.date === date && ramadan.time === clock">
<div *ngIf="ramadan.checked" [ngInit]="play"></div>
<div *ngIf="!ramadan.checked" [ngInit]="pause"></div>
</div>
Working code,
Custom directive:
import { Directive, Input } from '#angular/core';
#Directive({ selector: '[myCondition]' })
export class ngInitDirective {
constructor() { }
#Input() set myCondition(condition: boolean) {
if (!condition) {
console.log("hello")
} else {
console.log("hi")
}
}
}
In template:
<ion-label *ngIf="setting1.date === current_date && setting1.time === current_time">
<div *myCondition="setting1.checked === condition" >Play</div>
<div *myCondition="!setting1.checked === !condition">pause</div>
</ion-label>
You can use ngOnInit method in your component class but remember to implements the OnInit interface.
Here's an example:
#Component({
selector: 'ramadan',
template: `<div>Ramadan</div>`
})
class Ramadan implements OnInit {
ngOnInit() {
this.play();
}
}
You can fin more about ngOnInit here.