Cannot Bind Dynamic Data in Component Angular 8 - json

Error when component loading dynamic
DynamicBuilderComponent.ngfactory.js:198 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-pristine: true'. Current value: 'ng-pristine: false'.
Problem
after binding json in select2data to select2 component Angular throw exception.
component code
#Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'select2',
Imported changeDetection in component.
template: `
<div [formGroup]="form">
<ng-container>
<ng-select2
[data]="select2data"
[options]="options"
[width]="500"
[formControlName]="field.code"
(keyup)="changed($event.target.value)">
</ng-select2>
</ng-container>
</div>`
})
select2 component class
export class Select2Component implements OnInit {
#Input() field: any = {};
#Input() form: FormGroup;
public exampleData: Array<Select2OptionData>;
public options: Options;
public value: string[];
select2data: any;
public selected: string;
constructor(public cl: Services,private cd: ChangeDetectorRef) {
this.options = {
width: '258',
multiple: true,
tags: false
};
}
Problem Area After Binding subscribe data in ng select2 component
changed(search: any) {
//call service pass search text to service
return this.cl.searchFunc(search).subscribe(
res1 =>
this.select2data = res1.data;
this.cd.markForCheck(); // marks path
}
}
},
error => {
console.log('error = ', error);
});
}
}
i tried to print this.select2data in console.log its return me json.
Vendor.js
function expressionChangedAfterItHasBeenCheckedError(context, oldValue, currValue, isFirstCheck) {
var msg = "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '" + oldValue + "'. Current value: '" + currValue + "'.";
if (isFirstCheck) {
msg +=
" It seems like the view has been created after its parent and its children have been dirty checked." +
" Has it been created in a change detection hook ?";
}
return viewDebugError(msg, context);
}
Great Article
https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
Reference
Expression ___ has changed after it was checked
any suggestion is most welcome.

I believe that you put your component select2 inside another component which contains a form which you then pass to select2 for create another <form> tag, is that correct? I mean do you have something like that?
<form [formGroup]="form">
<!-- Some code -->
<select2 [field]="something" [form]="form"></select2>
</form>
If so, then your select2 component SHOULD NOT contain re-declaration of form, it should not contain anything related to forms at all. It should be a form control. Please read a post by Netanel Basal on how to create custom form controls. You will need to create ControlValueAccessor for your select2 and wire it up to Angular forms through a custom provider.
The issue you're facing is that since you include form object twice in the DOM data changes are propagated twice as well and you run into issues. There should be only one reference to a specific instance of FormGroup in your templates.

Solution that worked
#Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'select2',
export class Select2Component implements OnInit {
constructor(public cl: Services,private cd: ChangeDetectorRef) {
this.options = {
width: '258',
multiple: true,
tags: false
};
}
Binding function
changed(search: any) {
//call service pass search text to service
return this.cl.searchFunc(search).subscribe(
res1 =>
this.select2data = res1.data;
this.cd.markForCheck(); // marks path
this.cd.detectChanges();
}
}
},
error => {
console.log('error = ', error);
});
}

Related

Angular and JSON

I'm implementing a simple system to import JSON elements in Angular.
Everything works fine: I've used an interface, an observable and a directive. You can find the JSON here: http://jsonplaceholder.typicode.com/todos
Now, I want to use "completed", the boolean from JSON file, to display or not users when the page is loaded. There is a boolean "showUSer" and a method "displayUSer()" but I don't get it...
I cannot correctly retrieve this JSON data.
Any ideas ? :>
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
interface JSP {
"userId": string;
"id": string;
"title": string;
"completed": boolean
}
#Component({
selector: 'app-product',
template: `<div class="display" *ngFor="let todo of todos">
<div>User Id: {{todo.userId}}</div>
<div >id: {{todo.id}}</div>
<div *ngIf="showUser">Title: {{todo.title}}</div>
</div>`,
styles: ['.display {margin-top: 20px; margin-bottom: 20px;}']
})
export class ProductComponent implements OnInit {
title: string = "Products List";
todos: JSP[];
showUSer: boolean;
constructor(private http: HttpClient){
}
ngOnInit(){
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => this.todos = result);
}
displayUSer(): void {
this.showUSer = this.todos.completed;
}
}
Nitpicks: Your question says to display or not users but your code seems to be display or not the title. Also why do you capitalize the 'S' in 'USers'?
The problem is this function which seems to ignore your actual data layout:
displayUSer(): void {
this.showUSer = this.todos.completed;
}
todos is a property of your controller. This is an array from the api call you make and it doesn't contain a completed property, so this.todos.completed will always be false. I'm a little surprised that you don't get an error compiling your typescript.
It looks like you want this flag to be on a 'todo item' basis and not page-wide, so this.showUSer doesn't make sense. Also you don't seem to be calling displayUSer to set the value in any case.
Since you are looking at an individual todo item and the query is simple, why don't you just look at the flag?
<div *ngIf="todo.completed">Title: {{todo.title}}</div>
If you are wanting to set a page-wide flag based on some critieria, you can do that when you subscribe to the results. Here I'm assuming that you will set the showUSer flag if any of the todo items is marked as completed:
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => {
this.todos = result;
this.showUSers = result.reduce((previous, current) => previous || current.completed, false);
});
Your JSON hasn't any json.completed value, but json[_].completed.

Refresh datalist in view after user finishes typing in textbox Angular 7

I am trying to refresh a datalist in the view after waiting for user to finish typing in the textbox and updating results. Tried with angular directives, tried with Observable and various timeouts and debounces and no luck. I ran out of options.
In the html file:
<input type="text" class="form-control" id="Other"
(keyup)="onKeySearch($event)" list="dynamicList" formControlName="Other"/>
<datalist id="dynamicList">
<option *ngFor="let employee of employeesList" [value]="employee.Name">
{{employee.Name}}</option>
</datalist>
in the .ts file:
public employeesList: EmployeeData[] = [];
timeout: any = null;
getEmployeesList(name : string) {
let empList: EmployeeData[] = [];
// get employees list from service
this.employeeService.getEmployeesList(name).subscribe((data: any) => {
empList = data;
console.log(empList)
})
return empList;
}
public onKeySearch(event: any) {
let empListt: EmployeeData[] = [];
clearTimeout(this.timeout);
var $this = this;
this.timeout = setTimeout(() => {
empListt = $this.getEmployeesList(event.target.value);
console.log(empListt)
}, 1000);
this.employeesList = empListt;
}
The problem is that the datalist is not updates after retrieving the data an populating the list. After it exists the method the list is again empty, thus no data to display.
I have added stackblitz example code with similar code as above (same behavior):
.ts file:
import { Component, VERSION, OnInit } from "#angular/core";
import { FormControl } from "#angular/forms";
import { distinctUntilChanged, debounceTime, tap } from "rxjs/operators";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
searchControl = new FormControl("");
message = "";
public employeesList: string[] = [];
ngOnInit() {
this.searchControl.valueChanges
.pipe(
tap(() => (this.message = "User is typing...")),
distinctUntilChanged(),
debounceTime(1000)
)
.subscribe(res => {
this.message = "User finished typing!";
this.employeesList.push('1');
this.employeesList.push('2');
this.employeesList.push('3');
});
}
}
.html file:
<input [formControl]="searchControl" list="dynamicList">
<datalist id="dynamicList">
<option *ngFor="let employee of employeesList">
{{employee}}</option>
</datalist>
<span> {{message}} </span>
The dropdown will be filtered according to the text you have entered. So, in the given example, since you have pushed 1,2 and 3 values into the list, the drop down will only list with the filtered value.
Eg. if you input 1, the drop down list will have 1 (which is the required functionality)
You can see this behavior clearly if you slightly change the test input as :
ngOnInit() {
this.searchControl.valueChanges
.pipe(
tap(() => (this.message = "User is typing...")),
distinctUntilChanged(),
debounceTime(1000)
)
.subscribe(res => {
this.message = "User finished typing!";
this.employeesList.push('Employee 1');
this.employeesList.push('Employee 2');
this.employeesList.push('Employee 3');
});
}
Now, when you search 'Employee', it will list all 3, and if you search 'Employee 1', it will list the required one entry only. (Which is the expected behavior)
Your formControl (called 'Other', for some reason) has an event that is emitted as an Observable on each change. You can subscribe to that event, and use the RxJS operator debounceTime() to specify how many milliseconds should it wait for the user to stop typing until the event is emitted.
I have created a simple DEMO, maybe it's easier to understand.
Good luck!

How to make Angular 2 render HTML template after a promise in the component is resolved?

For my app, the ItemDetailComponent is where info of an item will be displayed. I have a service that retrieves all items using promise. I use ActivatedRoute to retrieve the item ID from the url path, then run the service, get all items, then find the item with the ID retrieved above, and assign it to selectedItem variable.
Here is item-detail.component.ts:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
console.log('Inside promise', this.selectedItem)
})
console.log('Outside promise', this.selectedItem)
}
}
And here is item-detail.component.html template so I could display my item, just an example:
<div>
<h1>{{title}}</h1>
<div *ngIf="selectedItem">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app returns nothing but the title unfortunately. I then added the two console.log() commands and found out that the one outside of the promise as well as the html template are rendered before the promise is fulfilled, and no selectedItem is available at that time. How could I force the app to execute them only after the promise is resolved in order to have the selectedItem in place for displayed?
EDIT: I added a new line in the html template to examine further:
<div>
<h1>{{title}}</h1>
<div><label>Item ID 1: </label>{{selectedItem.itemId}}</div>
<div *ngIf="selectedItem">
<div><label>Item ID 2: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app displays "Item ID 1:" label but with no actual id there. The console shows me an error saying that "Cannot read property 'itemId' of undefined", again confirming that the whole template is rendered before promise resolved and is not re-rendered after data is loaded. So weird.
You could create a Resolver for the route that fetches the desired data.
https://angular.io/api/router/Resolve
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
Add a boolean variable in to your class like
private dataAvailable:boolean=false;
and in the subscription to the promise,make this true when the data is available
then((selectedItem) => {
this.selectedItem = selectedItem;
this.dataAvailable=true;
console.log('Inside promise', this.selectedItem)
})
and in the template render when the data is available
<div>
<h1>{{title}}</h1>
<div *ngIf="dataAvailable">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
It should do the trick
Update
ngOnInit() seems to be just a event handler hook - returning anything won't affect anything it seems. Hence my old answer will not work.
There are other workarounds like using *ngIf or putting it in routes etc. but I wish there was something like resolvePromise(): Promise hook that would put a condition on resolution before rendering.
This is instead of developers putting the boilerplate in every component.
Old answer
Most likely that is because you are missing return statement in the second then.
then((selectedItem) => {
this.selectedItem = selectedItem
console.log():
return selectedItem;//
}
Is it possible that the ChangeDetection is set to OnPush somewhere up the component tree?
If that is the case, the template does not automatically rerender, because nothing triggers the ChangeDetection for this component.
Look out for a Component with the setting changeDetection: ChangeDetectionStrategy.OnPush
#Component({
selector: 'example',
template: `...`,
styles: [`...`],
changeDetection: ChangeDetectionStrategy.OnPush
})
Also you already have a valid solution by using a Resolver you could check if this helps:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService,
// the reference to the components changeDetector is needed.
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
// this triggers the changedetection and the template should be rerendered;
this.changeDetectorRef.detectChanges();
console.log('Inside promise', this.selectedItem)
});
console.log('Outside promise', this.selectedItem)
}
}
Here is a great article about Angulars ChangeDetection: https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

Angular2 functions in template and change detection

Im trying to build a method inside a service that checks whether a navigation button should be showed to the current user based on his permissions or not (this is just cosmetic "security" I know). Therefore this is the button placed inside the template
<button [routerLink]="['/some/where']"
*ngIf="AuthService.isAuthorized(['some', 'where'])">
Personen
</button>
The method AuthService.isAuthorized uses the provided array to run through all available routes and get the required permissions from the particular route's data object:
{
path: 'some',
component: SomeComponent,
data: {
permissions: [
"read:some",
"edit:some"
]
},
children: [
{
path: 'where',
component: SomeComponent,
data: {
permissions: [
"read:where"
]
}
},
]
}
so in this case the permissions ["read:some","edit:some","read:where"] are needed by the current signed in user so that the button would be displayed to him. Working so far!
But since the function is called inside the template it is called multiple times because of angular change detection. How could I change my code so that the function is called only once? Even better if it would only be called once after the authentication finished writing all permissions assigned to the authenticated user into AuthService.permissions
You can make AuthService.isAuthorized() method returns a promise:
#injectable()
export class AuthService {
...
isAuthorized(arr: string[]): Promise<boolean> {
return new Promise(resolve =>{
// your logic here
resolve(yourResult);
});
}
...
}
You can call this method on your ngOnInit of a component (Therefore it will be called once). You pass the return value to a new variable (e.g. isAuthorized) in the component and use this variable in the template instead.
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent implements OnInit {
isAuthorized: boolean;
constructor(private authService: AuthService) {}
ngOnInit() {
this.authService.isAuthorized(['some', 'where']).then(result => {
this.isAuthorized = result;
});
}
}
In the template you can just use isAuthorized variable.
<button [routerLink]="['/some/where']"
*ngIf="isAuthorized">
Personen
</button>
Edit:
If AuthService.isAuthorized() needed to be called only once but for more than one element, code like these may suits your need:
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent {
isObjectAuthorized = {} as {
isFirstAuthorized: boolean;
isSecondAuthorized: boolean;
};
constructor(private authService: AuthService) {}
checkForAuthorization(isElementAuthorized, arr: string[]) {
if (isElementAuthorized !== undefined) {
return;
}
this.authService.isAuthorized(arr).then(result => {
isElementAuthorized = result;
});
}
}
And in your template:
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isFirstAuthorized, ['some', 'where'])">
First
</button>
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isSecondAuthorized, ['some', 'where', 'else'])">
Second
</button>

How to iterate API data using ngFor in Angular 2

I am new in Angular 2 and I want to display all the API data in tabular form.
Here is my working code:
http://plnkr.co/edit/CB3oGppm4fvoEExfDSRc?p=preview
But when I am using this code in my files, I am having an error:
Type 'Response' is not assignable to type 'any[]'
test.component.html:
<h1>{{getData[0]?.name}}</h1>
<h1>{{getData[0]?.time}}</h1>
<div *ngFor="let item of getData">
<span>{{item?.name}}</span>
</div>
app.component.ts
import { Component } from '#angular/core';
import { ConfigurationService } from './ConfigurationService';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
getData = [];
constructor(private _ConfigurationService: ConfigurationService)
{
console.log("Reading _ConfigurationService ");
//console.log(_ConfigurationService.getConfiguration());
this._ConfigurationService.getConfiguration()
.subscribe(
(data)=> {
this.getData = data;
console.log(this.getData);
},
(error) => console.log("error : " + error)
);
}
}
This code is working in Plunkr but having error when I try it in my project. Please help to iterate the API values in my project. Thank you.
That's because you haven't assigned type to your getData variable. If you change getData = []; to getData: any = [];, your code should work. Other solution is to change compiler options in your tsconfig.json file:
"compilerOptions": {
"noImplicitAny": false
}
If you set noImplicitAny to false, you don't have to assign type to variables, if you don't implicitly set variable's type, it will be set to any automatically.
The signature of your service method getConfiguration is
(): Observable<Response>
But the return type is not correct, it should be the type that the Observable will materialize. Example:
(): Observable<any[]>
You can make any a specific type.