Angular 8. My view is not updated on model change - javascript

In my application I'm having a big trouble 'refreshing' the view after model for that view was updated. Mainly when API call is resolved and its response's data should be published on that view.
This my component management.component.ts ts file (I've removed code not important to this issue):
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'app-service',
templateUrl: './service.component.html',
styleUrls: ['./service.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ServiceComponent implements OnInit {
serviceForm: FormGroup;
notificationStatus: boolean;
constructor(
private http: HttpClient
) { }
ngOnInit() {
this.buildServiceForm();
// Example endpoint response: { active: false }
this.getNotificationInfo().subscribe((data: object) => {
this.notificationStatus = data['active'];
})
}
submit()
{
this.notificationStatus = !this.notificationStatus;
}
buildServiceForm()
{
this.serviceForm = this.formBuilder.group({
message_de: ['', { validators: [Validators.required] }],
message_en: ['', { validators: [Validators.required] }]
});
}
getNotificationInfo() {
return this.http.get(this.apiPath + 'service/notifications');
}
}
And this is the part in HTML that is responsible for displaying that model (button tag):
<form [formGroup]="serviceForm" (ngSubmit)="submit()">
<mat-form-field class="service__form__textarea">
<textarea matInput formControlName="message_de"></textarea>
</mat-form-field>
<mat-form-field class="service__form__textarea">
<textarea matInput formControlName="message_en"></textarea>
</mat-form-field>
<button>
<span *ngIf="notificationStatus == false"> Service Off </span>
<span *ngIf="notificationStatus == true"> Service On </span>
</button>
</form>
Whenever the button is clicked a form is submitted and the button's text should be updated (changed with use of ngIf) right away. But it only happens when I click randomly other objects on website.
I tried already with use of changeDetection: ChangeDetectionStrategy.OnPush but with no luck.
Here animation how it looks like in practice - after clicking the button its text changes only after clicking in textarea and not right after clicking the button:
gif animation of the behavior
Any one have any idea how can I refresh my view or what is the reason why my view behaves like that?

When using ChangeDetectionStrategy.OnPush the view will only update if object references change (see angular 2 change detection and ChangeDetectionStrategy.OnPush).
You may need to run the update to notificationStatus inside of zone using zone.run if that service is not already inside zone (the default Http client for example is already run inside zone automatically).
You also need to manually update the view using ChangeDetectorRef, or use the async pipe and observables which do this automatically.

You have two way to solve this:
Use CDR (quick & dirty)
Inject in your costructor ChangeDetectorRef
Call "cdr.markForCheck();" at the end of the submit method.
Transform your notificationStatus in a subject
In the class:
private notificationStatus$:Subject = new Subject(true);
In the submit method:
this.notificationStatus$:Subject.next(false);
In the html:
<span *ngIf="(notificationStatus$ | async ) == false"> Service Off </span>
PS: The $ in the variable name is a convention for obserable/subject

Related

angular can not get user data form sessionstorage

sorry about my english.
I use sessionstorage for keeping data. In sessionstorage have data enter image description here
but in html, not showing data form sessionstorage. when I get only {{currentUser}} in html show like this enter image description here
mycode services
import { Injectable } from '#angular/core';
const USER_KEY = 'auth-user';
#Injectable({
providedIn: 'root'
})
export class TokenStorageService {
constructor() { }
signOut(): void {
window.sessionStorage.clear();
}
public saveUser(user: any): void {
window.sessionStorage.removeItem(USER_KEY);
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
}
public getUser(): any {
const user = window.sessionStorage.getItem(USER_KEY);
if (user) {
return JSON.parse(user);
}
return {};
}
}
html
<div class="container" *ngIf="currentUser; else loggedOut">
<header class="jumbotron">
<h3>
<strong>{{ currentUser.employee_code }}</strong> Profile
</h3>
</header>
<p>
<strong>Token:</strong>
{{ currentUser.accessToken.substring(0, 20) }} ...
{{ currentUser.accessToken.substr(currentUser.accessToken.length - 20) }}
</p>
<p>
<strong>Emp:</strong>
{{ currentUser }}
</p>
</div>
{{ currentUser }}
<ng-template #loggedOut>
Please login.
</ng-template>
and component
import { Component, OnInit } from '#angular/core';
import { TokenStorageService } from '../../../services/token-storage.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
currentUser: any;
constructor(private token: TokenStorageService) { }
ngOnInit(): void {
this.currentUser = this.token.getUser();
console.log(this.currentUser = this.token.getUser())
}
}
how i can do to use please help me
this image for {{ currentUser|json}}
{{ currentUser|json}}
In TokenStorageService change getUser() method.
public getUser(): any {
return JSON.parse(window.sessionStorage.getItem(USER_KEY));
}
In HTML you are printing {{ currentUser }} Which will be an object. You need to specify the property of object.
Note: If you want to see the object in html use json pipe. ({{ currentUser | json }})
if I could see it correctly in your attached image, It is an array not an object, so you will need to use it like currentUser[0], to check it on your own please use
{{ currentUser | json}} in HTML it will show the exact content.
Hi, I have created a similar app with the source code provided by you stackblitz link here. I have made some modifications to mimic the login scenario.
You should be able to see the data on the initial load of the data. As the component is being initialized for the first time. But whereas when you change the user with the change user button. Although the session storage data changes. You won't be able to see the new data. In order to see these kinds of dynamic changes, you need to make use of Observables/Subjects.
Edit 1: The issue here was that the key & value stored in the local storage as stored as strings. So while storing we have to do JSON.Stringify() and JSON.Parse() while extracting back. This is explained in detail here.

Remove button on list item/array in Angular 12

I'm trying to remove a list item with the click button, tried various options but it seems to not work. Hope you can help me out with this.
On click i want to remove a list item from my users array. I will link the Typescript code alongside with the HTML.
//Typescript code
import { UsersService } from './../users.service';
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { Iuser } from '../interfaces/iuser';
#Component({
selector: 'tr[app-table-row]',
templateUrl: './table-row.component.html',
styleUrls: ['./table-row.component.css']
})
export class TableRowComponent implements OnInit {
#Input() item!: Iuser;
#Output() userDeleted = new EventEmitter();
removeUser(item: any) {
this.userDeleted.emit(item);
}
constructor() {}
ngOnInit(): void {}
}
<th scope="row">{{item.id}}</th>
<td>{{item.name}}</td>
<td>{{item.lastname}}</td>
<td>{{item.city}}</td>
<td> <button class="btn btn-sm" (click)="removeUser(item)">remove</button></td>
As #Priscila answered, when the button is clicked, you should only emit the action and let the parent component control the respective method i.e. delete or add.
Because that way, it will be easy for the data to be manipulated and handle the component's lifecycle.
Never keep the dead ends running on the app.
Happy Coding :)

Angular; how to re-render component data when I update variable in script. Binding data is working, but not updating

I'm trying to update the text in the span, using the latest Angular. However, I do not understand clearly how lifecycle hooks and update work in Angular. Issue is with fileName - I bind the data and it gets the initial value when the page loads. However when the data variable updated, I can see changes in the console, but the component itself is not updated.
Shall I use some Lifecycle methods or something else?
I've read: https://angular.io/guide/lifecycle-hooks and didn't make clear for me.
<form (ngSubmit)="putToBucket()" class='form-class' >
<label for="image_uploads" >Select Image</label>
<input type='file' id="image_uploads" (change) ='onFileSelected($event)' class='input-button' multiple>
<span > {{fileName }} </span>
<button class='submit-button' type='submit' >Submit</button>
</form>
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
constructor(
private http: HttpClient,
private toastr: ToastrService) { }
urlApi = '//myuri api';
respond;
fileName: Array<any> =['Test']
onFileSelected(event) {
//console.log(event.target.files[0].name)
let name = event.target.files[0].name;
this.fileName.push(name)
console.log(this.fileName)
Example of what I see:
Your fileName is an array so to display it you have to iterate it in .html file or you can change fileName to string and do as shown below.
export class AppComponent {
fileName="Test";
ngOnInit(){
console.log(this.fileName);
}
onFileSelected(newName){
this.fileName=newName;
console.log(this.fileName);
}
}
.html file
<button (click)="onFileSelected('newFile')">change file name</button>
Working Demo : demo
fileName : Array<any> = [];
onFileSelected(event){
console.log(event.target.files[0].name)
let name = event.target.files[0].name;
this.fileName.push(name)
console.log(this.fileName)
}
<input type='file' id="image_uploads" (change) ='onFileSelected($event)' class='input-button' multiple>
<span *ngFor="let list of fileName">{{list}}</span>
Please try to implement onPush or ChangeDetectionStrategy in your component
Doing this will instruct Angular to run change detection on these components and their sub-tree only when new references are passed to them versus when data is simply mutated.
Run this.ref.markForCheck() or this.ref.detectChanges() when you update your variable and want it to reflect in html
Please check the following links for more information
https://angular.io/api/core/ChangeDetectionStrategy
https://alligator.io/angular/change-detection-strategy/

Listening to FormControl changes with ngOnChanges in the AppComponent?

I'm attempting to listen to changes on a reactive email form control like this:
import { Component, OnChanges } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnChanges {
form: FormGroup = new FormGroup({
email: new FormControl('',[ Validators.email ])
});
get emailInput() { return this.form.get('email'); }
ngOnChanges() {
this.form.get('email').valueChanges.subscribe(val => {
const formattedMessage = `Email is ${val}.`;
console.log(formattedMessage);
});
}
}
The form looks like this:
<form [formGroup]="form">
<input placeholder="Email" type="email" formControlName="email" >
</form>
When typing in the email field nothing gets logged. This is the Stackblitz. Thoughts?
This is the article the question implementation was based on.
Update
The accepted answer is to use the ngOnInitit lifecycle hook. I wanted if perhaps it should be ngAfterViewInit just to make sure the view is entirely initialized or will be form bindings always be complete in ngOnInit?
Didn't notice at first, but your ngOnChanges should not be where you are subscribing to the observable. ngOnChanges is for changes to input parameters to the current component (typically wrapped in []).
Setup your subscription to the observable in the ngOnInit like this and your code will work:
ngOnInit() {
this.emailSubscription = this.form.get('email').valueChanges.subscribe(val => {
const formattedMessage = `Email is ${val}.`;
console.log(formattedMessage);
});
}
Angular does not automatically unsubscribe so typically you'll want to save the value of the description, and then unsubscribe it in the ngOnDestroy:
ngOnDestroy() {
this.emailSubscription.unsubscribe();
}
Since you're writing this code in appComponent there's probably not an explicit need to do this outside it being generally good practice for every other component.
Edit: Updated stackblitz showing this working.
You're using onChanges wrong. OnChanges watches for changes performed on a child component so that the parent component can update information. You're doing this with a form, so nothing will send changes up to the component.
Since the input is an element on the component, you can do it with an (input) listener or a (keypress).

#input two-way binding does not work

I want to access the text I have in a text area in my child component to put it on the parent component and keep it updated.
I was told that #input in angular 4 is supposed to perform two-way binding. But I can't do that that way, and I don't understand why.
I found a workaround for this issue. It includes an #Output to send the info to the parent component. But if Input already does that (in some way I don't know), I want to avoid it.
For example, this is my Parent Component
import { Component } from '#angular/core';
#Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
})
export class SettingsComponent {
private studyDesignText = 'Text';
constructor() {
}
public handleStudyDesignUpdated(designText: any) {
this.studyDesignText = designText;
}
}
It's html
<div class="section section-trials-settings-parent light rounded">
<div class="section section-trials-settings-child">
<div class="pure-g">
<div class="pure-u-1-1">
<app-settings-study-design
[studyDesignText]="studyDesignText">
</app-settings-study-design>
</div>
</div>
</div>
</div>
My child component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-settings-study-design',
templateUrl: './settings-study-design.component.html',
})
export class SettingsStudyDesignComponent implements OnInit {
#Input() studyDesignText: string;
constructor() {
}
ngOnInit() {
super.onInit();
loadControls();
}
loadControls(): void {
this.startAllTextAreas();
}
private startAllTextAreas() {
this.startTextArea('study-design-textarea');
}
private startTextArea(htmlId: string) {
// code to configure my text area; it's right...
}
If I change the value in the text area and send a signal with #Output so my parent component can be notified and console log the value, the printed value is the initial one. My friend did the same thing and it worked.
What am I missing?
#Input() is always one way binding from parent->child. Two way binding happens in this case, only when you have object as an input property. This is because, the reference for objects remain the same. And when one of the object updates, the other will also get updated. This is not true for string or number. It is always one way binding.

Categories