Showing and Hiding a child component from parent for specific seconds - javascript

I have notification component, which is a child component and being shown in parent component after a button click. After a custom seconds of time it will be hidden. So far I have come up with this code (Stackblits).
MyCode
Parent component:
<button (click)="handleClick()">Initiate Message</button>
<app-notification [message]="message" [duration]="3000" [show]="show"></app-notification>
<app-notification [message]="message" [duration]="6000" [show]="show"></app-notification>
export class AppComponent {
message:string;
show:boolean;
handleClick(){
this.message = 'Good Morning';
this.show=true;
}
}
Child comp:
<div class="container" *ngIf="show">
{{ message }}
</div>
#Input() message: string;
#Input() duration: number;
#Input() show = false;
constructor() {}
ngOnChanges(changes: SimpleChanges) {
console.log(this.show)
setTimeout(()=>{
this.show = false;
console.log(3)
}, this.duration)
}
This works perfectly, but after 3 seconds as it is in the example, If I click the button, the child component is not being showed. What am I doing wrong. I am new to Angular, please help me.
Also I want to handle the same for both instances of the child.

You have to move the code for the timeout out of ngOnChanges.
In your component setup a setter would do the trick.
#Component({
selector: 'app-notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.css'],
})
export class NotificationComponent {
#Input() message: string;
#Input() duration: number;
#Input() set show(value: boolean) {
this._show = value;
// Or find another failsafe if the values are not set as expected
if (value && this.duration) {
this._show = true;
setTimeout(() => {
this._show = false;
}, this.duration);
}
}
get show(): boolean {
return this._show;
}
private _show = false;
}
As well as an ugly hack to avoid Angular not realizing that this value needs to be reset:
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
message: string;
show: boolean;
duration: number;
handleClick() {
this.message = 'Good Morning';
this.show = true;
setTimeout(() => (this.show = false), 1);
}
}
Using RxJS Observables would solve this problem in a nice way, but it would also be more complicated if you are new to Angular.

Related

Renderer2.setProperty() doesn't update the value

I am trying to update the value using the rendered.setproperty() where value is updating the second time on listen event
these are the value that I am sending for the first time as empty in some widget
<ols-giftcard-payment-widget site-id="dddd" customer-last-name="" jwt-token="abcd"></ols-giftcard-payment-widget>
here I want to update the customer-last-name as a dynamic value
import { Component, Renderer2 } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
name = 'Angular';
constructor(private renderer: Renderer2) {}
createRecaptchaContainer() {
const recaptchaContainer = this.renderer.createElement(
'ols-giftcard-payment-widget'
);
this.renderer.setProperty(recaptchaContainer, 'siteId', '56711');
this.renderer.setProperty(
recaptchaContainer,
'jwtToken',
'abcd'
);
let cardClickEvent = this.renderer.listen(
recaptchaContainer,
'addCard',
(evt) => {
this.renderer.setProperty(
recaptchaContainer,
'customerLastName',
'anonymousLastName'
);
this.renderer.appendChild(
document.getElementById('payment-widget'),
recaptchaContainer
);
return recaptchaContainer;
debugger;
}
);
this.renderer.appendChild(
document.getElementById('payment-widget'),
recaptchaContainer
);
return recaptchaContainer;
}
ngOnInit(): void {
this.createRecaptchaContainer();
}
}
<div id="payment-widget"></div>
Here I am trying to add the customerLastname in this setProperty
this.renderer.setProperty(
recaptchaContainer,
'customerLastName',
'anonymousLastName'
);
it is updating on the second click, not on the first click
please help me to find here where I am wrong in the implementation
Your error message was Please provide the last name. Apparently recaptchaContainer needs a name to work.
When you click "Add card" for the first time customerLastName is empty. Look it up on localStorage under key cardDetails. anonymousLastName is added with click listener event from the component after data has been sent, that's why it works second time around.
{
"siteId": "56711",
"customerNumber": "",
"customerFirstName": "",
"customerLastName": "anonymousLastName",
"customerSession": "",
"companyId": "",
"primaryColor": "#2196F3"
}
To fix this, we could also setProperty for customerLastName outside of the event listener, so that customerLastName would be populated on first click. Either that or put something in customer-last-name="".
<hello name="{{ name }}"></hello>
<div id="payment-widget" (click)="setUsername('123')"></div>
<br>
<input [(ngModel)]="username" (input)="onChange()" #ctrl="ngModel" required>
<p>Value: {{ username }}</p>
<p>Valid: {{ ctrl.valid }}</p>
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
show: boolean = false;
jwtToknVal = of();
title = 'angular';
username: string;
recaptchaContainer: any;
constructor(private renderer: Renderer2) {}
createRecaptchaContainer() {
this.recaptchaContainer = this.renderer.createElement(
'ols-giftcard-payment-widget'
);
this.renderer.setProperty(this.recaptchaContainer, 'siteId', '56711');
this.renderer.setProperty(
this.recaptchaContainer,
'jwtToken',
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMXJ5ZzMwZjZnay5leGVjdXRlLWFwaS51cy13ZXN0LTIuYW1hem9uYXdzLmNvbS9hdXRoZW50aWNhdGUiLCJzY29wZSI6InNlbGYiLCJpYXQiOjE2MTkxMTIxMjcsImV4cCI6MTcxOTE5ODUyNywicm9sZXMiOlsiY29tbXVuaXR5X2FwcGxpY2F0aW9uX2FjY2Vzc3wxMTE4N3wxIl0sInVzZXIiOnsiaWQiOiIyNTc1NyIsIm5hbWUiOiJTaG9ydGN1dHMiLCJzdXJuYW1lIjoiUFBGIiwiZ2l2ZW5fbmFtZSI6IlNob3J0Y3V0cyIsImRpc3BsYXlfbmFtZSI6IlNob3J0Y3V0cyBQUEYiLCJlbWFpbCI6InNob3J0Y3V0c0BzaG9ydGN1dHNzb2Z0d2FyZWRldi5jb20ifX0.vll2mLmdtBuuz0mnlENEAs1xT0In0NmYKBWD67VfYdw'
);
this.renderer.appendChild(
document.getElementById('payment-widget'),
this.recaptchaContainer
);
this.renderer.setProperty(
this.recaptchaContainer,
'customerLastName',
this.username
);
this.renderer.listen(this.recaptchaContainer, 'addCard', (evt) => {
console.log(this.recaptchaContainer, evt);
this.renderer.setProperty(
this.recaptchaContainer,
'customerLastName',
this.username
);
return this.recaptchaContainer;
});
}
ngOnInit(): void {
this.createRecaptchaContainer();
}
onChange() {
console.log(this.username);
}
setUsername(name: string) {
this.renderer.setProperty(
this.recaptchaContainer,
'customerLastName',
name
);
}
}
Here is a working example: https://stackblitz.com/edit/angular-rkua7n?file=src%2Fapp%2Fapp.component.ts

Angular HTML Binding is not working when we removed HostListener

to make change detection lesser, we replace hostlistener with from event from RXJS and runoutside of angular.
this is how my angular code looks like
ngOnInit() {
this.windowKeyDown();
// this.subject$ = this.subject.asObservable();
}
constructor(private _ngZone: NgZone) {}
//#HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
console.log('handle key fired');
this.keypressed = event.key;
this.iskeyPressed = true;
}
windowKeyDown() {
console.log('windowKeyDown');
fromEvent(window, 'keydown')
.pipe(this.outsideZone(this._ngZone))
.subscribe(event => this.handleKeyboardEvent(<KeyboardEvent>event));
}
outsideZone<T>(zone: NgZone) {
return function(source: Observable<T>) {
return new Observable(observer => {
let sub: Subscription;
zone.runOutsideAngular(() => {
sub = source.subscribe(observer);
});
return sub;
});
};
}
and HTML binding is :
<h2>keypressed: {{keypressed}}</h2>
<h2>iskeyPressed: {{iskeyPressed}}</h2>
in this binding variable it self not updating now, can you please guide what's wrong with my code?
minimum step to repro : https://stackblitz.com/edit/keypress-example-vu3mej?file=app%2Fapp.component.html
I would recommend setting the ChangeDetectionStrategy for this component to OnPush. You can read more about it here. Do not put any logic in this component for which you want automatic change detection as changedetection is disabled for the component as a whole.
Below is a code sample that shows how you can subscribe to the keyboard events directly from the template (using the async pipe).
https://stackblitz.com/edit/angular-change-detection-strategy-onpush-g6mjkr?file=src%2Fapp%2Fchild%2Fchild.component.ts
import {
Component,
Input,
ChangeDetectionStrategy,
OnInit
} from '#angular/core';
import { fromEvent, Observable } from 'rxjs';
import { map, startWith, tap } from 'rxjs/operators';
#Component({
selector: 'child',
template: `
<ng-container *ngIf="keyboard$ | async as keyBoard">
<h2>keypressed: {{ keyBoard.keypressed }}</h2>
<h2>iskeyPressed: {{ keyBoard.iskeyPressed }}</h2>
</ng-container>
`,
styleUrls: [],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
keyboard$: Observable<{ keypressed: any; iskeyPressed: boolean }>;
ngOnInit() {
console.log('bla')
this.keyboard$ = fromEvent(window, 'keydown').pipe(
map(event => ({ keypressed: event.key, iskeyPressed: true })),
startWith({keypressed: null, iskeyPressed: null})
);
}
}

Angular - ngOnChanges() not getting executed on property change

#Component {
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: 'app.component.css'
}
export class App implements OnChanges, OnInit {
prop = '';
ngOnInit() {
this.prop = 'something';
}
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
}
}
I'm expecting to get the console log from ngOnChanges() when I change the prop property via ngOnInit() method. But it's not working. Any help?
The lifeCycle hook OnChanges is only triggered for an #Input() property when the value is changed in the parent component.
Edit regarding the comments :
In order to listen to a prop changes inside a component, you can change it to a Subject :
prop: Subject<string> = new Subject();
ngOnInit() {
this.prop.next('something');
this.prop.subscribe(value => console.log(value));
}
onClick(value: string) {
this.prop.next(value);
}

Initial counter value not displaying on ChangeDetectionPush strategy

I am writing a simple counter. It has start,stop, toggle functionality in parent (app) and displaying changed value in child (counter) component using ChangeDetectionStrategy.OnPush.
Issue I am facing is not able to display initial counter value in child component on load.
Below are screenshot and code.
app.component.ts
import { Component } from '#angular/core';
import {BehaviorSubject} from 'rxjs';
#Component({
selector: 'app-root',
template: `<h1>Change Detection</h1>
<button (click)="start()">Start</button>
<button (click)="stop()">Stop</button>
<button (click)="toggleCD()">Toggle CD</button>
<hr>
<counter [data]="data$" [notifier]="notifier$"></counter>`,
})
export class AppComponent {
_counter = 0;
_interval;
_cdEnabled = false;
data$ = new BehaviorSubject({counter: 0});
notifier$ = new BehaviorSubject(false);
start() {
if (!this._interval) {
this._interval = setInterval((() => {
this.data$.next({counter: ++this._counter});
}), 10);
}
}
stop() {
clearInterval(this._interval);
this._interval = null;
}
toggleCD(){
this._cdEnabled = !this._cdEnabled;
this.notifier$.next(this._cdEnabled);
}
}
counter.component.ts
import {Component, Input, ChangeDetectionStrategy, OnInit, ChangeDetectorRef} from '#angular/core';
import {Observable} from 'rxjs/index';
#Component({
selector: 'counter',
template: `Items: {{_data.counter}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
#Input() data: Observable<any>;
#Input() notifier: Observable<boolean>;
_data: any;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.data.subscribe((value) => {
/**
Below this._data.counter is showing 0 in console.log but
not in template
**/
this._data = value;
this.cd.markForCheck();
});
this.cd.detach();
this.notifier.subscribe((value) => {
if (value) {
this.cd.reattach();
} else {
this.cd.detach();
}
});
}
}
I'm using Angular 6.1.0
your AppComponent data$ is a BehaviorSubject, which you have given an initial value. your CounterComponent data expects an Observable, which you subscribe to. The defaulted BehaviorSubject does not fire until it changes. to get the value you have to query it upon load:
#Input() data: BehaviorSubject<any>;
ngOnInit() {
this._data = this.data.value; // get the initial value from the subject
this.data.subscribe((value) => {
this._data = value;
this.cd.markForCheck();
}
);
should do the trick.

Angular's `cdRef.detectChanges` doesn't prevent checked exception?

I have a situation where I have an inner component :
#Component({
selector: 'hello',
template: `<h1>Name = {{name}}!</h1> `
})
export class HelloComponent {
#Input() name: string;
#Output() ev: EventEmitter<string> = new EventEmitter<string>();
constructor(private cdRef: ChangeDetectorRef) { }
ngOnInit() {
this.ev.emit("new name");
this.cdRef.detectChanges();
}
}
Where in the parent component :
<hello name="{{ name }}" (ev)="changeName($event)"></hello>
#Component({
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
name = 'Angular 5';
changeName(e) {
this.name = e;
console.log(e)
}
}
So basically , when the inner component loads , it emits an event which in turn received by parent and re-set the value of its inner component.
But this code ( and I do know why) - yields an exception :
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has
changed after it was checked. Previous value: 'Angular 5'. Current
value: 'new name'.
But according to other answers in SO:
This line : this.cdRef.detectChanges(); should have caused a new detection and prevent the exception.
I already know that setTimeout()... does solve it in a hacky way.
Question:
Why doesn't detectChanges prevent the exception ?
Stackblitz

Categories