Type 'xx' is not assignable to type 'any[] | Iterable<any> | > (Iterable<any> & any[]) | (any[] & Iterable<any>)' - javascript

I am getting the following error. I have been trying to resolve this issue for sometime now but with no luck. Can someone please help me out.
Type 'Student' is not assignable to type 'any[] | Iterable |
(Iterable & any[]) | (any[] & Iterable)'. Type 'Student' is
not assignable to type 'any[] & Iterable'. Type 'Student' is not
assignable to type 'any[]'.
The Code can be found here.
app.component.html
<div *ngFor="let stu of studentSelected; let i = index;">
<tr>
<td>{{stu.f}} :</td>
<ng-container *ngFor="let s of stu.ff">
<td>{{s.s_name}}</td>
</ng-container>
</tr>
</div>
app.component.ts
import { Component, VERSION } from '#angular/core';
import { Student } from './student.model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
studentSelected: Student | any = {
f: 'dfd',
ff: [
{
s_name: 'nswq'
}
]
};
}
School.model.ts
export class School {
s_name: string;
constructor(s_name: string = '') {
this.s_name = s_name;
}
}
student.model.ts
import { School } from './School.model';
export class Student {
f: string;
ff: School[];
constructor(f: string = '', ff: [] = []) {
this.f = f;
this.ff = ff;
}
}

As mentioned in comment, selectedStudent is an object, so remove the outer ngFor. Also, remove all any from the code. There is no use to code with TypeScript if you are going to use any. So, remove any and just declare the variable as a type of Student:
studentSelected: Student = {
f: 'dfd',
ff: [
{
s_name: 'nswq'
}
]
};
I see you are using classes with constructor. Please note that how you are declaring your studentSelected will NOT be an instance of your class. I like to use interfaces, unless there is a specific reason needed to have a class, like class specific methods. You don't seem to have that, so I suggest you just use interfaces:
export interface School {
s_name: string;
}
export interface Student {
f: string;
ff: School[];
}
If you want to have your data as classes though, remember when creating an instance of your class, you need to call new Student({.....})
As for the template... as earlier said, remove the outer *ngFor from template:
<tr>
<td>{{studentSelected.f}} :</td>
<ng-container *ngFor="let s of studentSelected.ff">
<td>{{s.s_name}}</td>
</ng-container>
</tr>
Your StackBlitz

Related

How do you filter an Observable with form input?

I have a component with this "countries$" variable:
countries$!: Observable<Country[]>;
that I'm populating with this data in an "ngOnInit" like this:
ngOnInit(){
this.countries$ = this.apiService.getAllCountries();
}
and I'm accessing this variable/Observable in the html template like this:
<div>
<app-country-card *ngFor="let country of countries$ | async" [country]="country"></app-country-card>
</div>
I want to include a search bar that filters the countries down to whatever is typed in.
I thought I could use the filter function inside a pipe like this:
searchFilterCountries(searchTerm: string){
this.countries$.pipe(filter((country: any) => country.name.common.toLowerCase().includes(searchTerm.toLowerCase())))
}
and put the input in the html template like this:
<input type="text" class="form-control" (input)="searchFilterCountries($event.target.value)"/>
so that the filter function would fire every time theres an input, narrowing down the list of countries on display.
This doesn't work however. I'm getting the typescript error:
Object is possibly 'null'.ngtsc(2531)
Property 'value' does not exist on type 'EventTarget'.ngtsc(2339)
Then I found a "sample" of a working filtered list here on Material UI
https://material.angular.io/components/autocomplete/examples (The FILTER one)
I attempted to implement this and came up with this code:
export class HomeComponent {
countries$!: Observable<Country[]>;
myControl = new FormControl('');
constructor(private apiService: ApiService) { }
ngOnInit(){
this.countries$ = this.apiService.getAllCountries();
}
private _filter(value: string): Observable<Country[]> {
const filterValue = value.toLowerCase();
return this.countries$.pipe(filter(option => option.name.common.toLowerCase().includes(filterValue))) <----ERROR #2
}
}
It doesn't work however. I think because the values are observables, not the data inside the observable.
I have squiggly lines showing a TS error under the under the "name" property in "option.name.common" saying:
option.name.common TS error
Property 'name' does not exist on type 'Country[]'
If I do this instead though:
option => option[0].name.common.toLowerCase().includes(filterValue)))
the error goes away, but I wouldn't be able to search all the values if I did that.
Am I on the right track here? Am I using the right operators? How do I fix the TS errors? I'm new to angular and don't know all the operators available. If I use mergeMap/switchMap will that solve my problem? If I do fix the typescript errors would it even work? Or is my approach wrong?
Can somebody help me get this working?
I would like to expand on your current code and suggest some changes like this:
export class HomeComponent {
allCountries: Country[] = [];
countries$!: Observable<Country[]>;
myControl = new FormControl('');
constructor(private apiService: ApiService) {}
ngOnInit() {
this.apiService
.getAllCountries()
.subscribe((countries) => (this.allCountries = countries));
this.countries$ = combineLatest({
searchTerm: this.myControl.valueChanges.pipe(startWith('')),
countries: this.apiService
.getAllCountries()
.pipe(tap((countries) => (this.allCountries = countries))),
}).pipe(map(({ searchTerm }) => this._filter(searchTerm)));
}
private _filter(value: string | null): Country[] {
if (value === null) {
return this.allCountries;
}
const filterValue = value?.toLowerCase();
return this.allCountries.filter((country) =>
country.name.common.toLowerCase().includes(filterValue)
);
}
}
So we're keeping the original country list in a separate variable, and we are using the form control's valueChange event to filter the countries that we need to display.
The template should look like this:
<input type="text" [formControl]="myControl" />
<div *ngFor="let country of countries$ | async">
<div>Name: {{ country.name.common }}</div>>
</div>
Example pipe
import { Pipe, PipeTransform } from '#angular/core';
import { Country } from './country';
#Pipe({
name: 'filterList',
})
export class FilterListPipe implements PipeTransform {
transform(countries: Country[]|null, searchText: string): Country[] {
if(!countries) return []
return countries.filter(country=>country.name.indexOf(searchText) != -1);
}
}
app.component.html
<form [formGroup]="controlsGroup">
<input type="text" formControlName="searchInput"/>
<div *ngFor="let country of countries | async | filterList:searchText">
<div>Name: {{country.name}}</div>
<div>Ranking: {{country.ranking}}</div>
<div>Metric: {{country.metric}}</div>
</div>
</form>
app.component.ts
import { Component } from '#angular/core';
import { FormBuilder, FormControl, FormGroup } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { Country } from './country';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'piper-example-app';
searchText = ''
controlsGroup: FormGroup
constructor(public fb:FormBuilder){
this.controlsGroup = fb.group({
searchInput: new FormControl('')
})
this.controlsGroup.get('searchInput')?.valueChanges.subscribe(value => this.searchText=value)
}
countries: Observable<Country[]> = of([{
name: 'United States of America',
ranking: 1,
metric: 'burgers per capita'
},
{
name: 'China',
ranking: 9000,
metric: 'power level lower bound'
}])
}
Admittedly I'm doing a few things that are "dirty" here where filtering the incoming observable stream of arrays of countries might be a bit more efficient. Also note you'd need to still expand the filter function to check all the properties (can use for(prop in obj) type loop to iterate over all properties to see if any of them matches the searchText or adjust the criteria as see fit.
Bit more of a complete example showing the filter part with different types of properties being filtered slightly differently:
filter-list.pipe.ts (alternative)
import { Pipe, PipeTransform } from '#angular/core';
import { Country } from './country';
#Pipe({
name: 'filterList',
})
export class FilterListPipe implements PipeTransform {
transform(countries: Country[]|null, searchText: string): Country[] {
if(!countries) return []
return countries.filter(country => {
let foundMatch = false;
let property: keyof typeof country
for(property in country) {
if(typeof country[property] === 'string') {
if((country[property] as string).indexOf(searchText) != -1)
foundMatch = true
}else {
if((country[property] as number) == parseInt(searchText))
foundMatch = true
}
}
return foundMatch
});
}
}

Angular2, evaluate template from string inside a component

It's possible evaluate template from string in a variable?. I need place the string in the component instead of the expression,
e.g.
template: "<div>{{ template_string }}</div>"
template_string contains: <b>{{ name }}</b>
and all should be evaluated to <div><b>My Name</b></div>
but I see <div>{{ template_string }}</div>
I need something like {{ template_string | eval }} or something else to evaluate the content of the variable on current context.
It's possible? I need something to use this approach because template_string can be changed when the component is used.
Edit1:
Angular Version: 4.0.3
E.g.
#Component({
selector: 'product-item',
template: `
<div class="product">{{ template }}</div>`,
})
export class ProductItemComponent {
#Input() name: string;
#Input() price: number = 0;
#Input() template: string = `{{ name }} <b>{{ price | currency }}</b>`;
}
Usage:
<product-item [name]="product.name" [price]="product.price"></product-item>
Expected: Product Name USD3.00
Output: {{ name }} <b>{{ price | currency }}</b>
You can create your own directive that will do it:
compile.directive.ts
#Directive({
selector: '[compile]'
})
export class CompileDirective implements OnChanges {
#Input() compile: string;
#Input() compileContext: any;
compRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler) {}
ngOnChanges() {
if(!this.compile) {
if(this.compRef) {
this.updateProperties();
return;
}
throw Error('You forgot to provide template');
}
this.vcRef.clear();
this.compRef = null;
const component = this.createDynamicComponent(this.compile);
const module = this.createDynamicModule(component);
this.compiler.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories: ModuleWithComponentFactories<any>) => {
let compFactory = moduleWithFactories.componentFactories.find(x => x.componentType === component);
this.compRef = this.vcRef.createComponent(compFactory);
this.updateProperties();
})
.catch(error => {
console.log(error);
});
}
updateProperties() {
for(var prop in this.compileContext) {
this.compRef.instance[prop] = this.compileContext[prop];
}
}
private createDynamicComponent (template:string) {
#Component({
selector: 'custom-dynamic-component',
template: template,
})
class CustomDynamicComponent {}
return CustomDynamicComponent;
}
private createDynamicModule (component: Type<any>) {
#NgModule({
// You might need other modules, providers, etc...
// Note that whatever components you want to be able
// to render dynamically must be known to this module
imports: [CommonModule],
declarations: [component]
})
class DynamicModule {}
return DynamicModule;
}
}
Usage:
#Component({
selector: 'product-item',
template: `
<div class="product">
<ng-container *compile="template; context: this"></ng-container>
</div>
`,
})
export class ProductItemComponent {
#Input() name: string;
#Input() price: number = 0;
#Input() template: string = `{{ name }} <b>{{ price | currency }}</b>`;
}
Plunker Example
See also
Angular 2.1.0 create child component on the fly, dynamically
not sure how you're building the template string
import { ..., OnInit } from '#angular/core';
#Component({
selector: 'product-item',
template: `
<div class="product" [innerHtml]='template_string'>
</div>`,
})
export class ProductItemComponent implements OnInit {
#Input() name: string;
#Input() price: number = 0;
#Input() pre: string;
#Input() mid: string;
#Input() post: string;
template_string;
ngOnInit() {
// this is probably what you want
this.template_string = `${this.pre}${this.name}${this.mid}${this.price}${this.post}`
}
}
<product-item [name]="name" [price]="price" pre="<em>" mid="</em><b>" post="</b>"></product-item>
the string can be built from outside the component, would still recommend something like ngIf to control dynamic templates though.
In Angular double curly braces {{}} are used to evaluation an expression in a component's template. and not work on random strings or dynamically added DOM elements. So one way of doing this is to use typescript string interpolation using ${}. check the rest of code to understand
#Component({
selector: 'product-item',
template: `
<div class="product" [innerHTML]="template"></div>`,
})
export class ProductItemComponent {
#Input() name: string;
#Input() price: number = 0;
#Input() template: string = `${ this.name } <b>${ this.price }}</b>`;
}

How to create pipe to filter list in Angular2

I'm facing problem with creating the pipe to filter list from input.
What I would like to have is something like this: http://www.w3schools.com/howto/howto_js_filter_lists.asp
Is there anybody who can help me with creating pipe for this?
Update, I changed the code with one proposition and it is still not working with my part of code.
Some parts from my code:
component.html:
<input id="desc" type="text" placeholder="Alarm name" [(ngModel)]="desc">
<ul>
<li *ngFor="let name of names" [style.display]="(name | search : desc) ? 'block' : 'none'">
{{ name }}
</li>
</ul>
<div class="alarm-list-item" *ngFor="let alarm of alarmsList" [style.display]="(alarm.Description | search : desc) ? 'block' : 'none'">
{{alarm.Description }}
</div>
alarmList is an array:
enter image description here
search.pipe.ts also I had to change the pipe code as "contains" doesn't work and I change type to any:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'search'
})
export class SearchPipe implements PipeTransform {
transform(list: any, searchText: any): any {
for(let i=0; i<list.length; i++){
if(list[i].includes(searchText)){
return true;
}
}
return false;
}
}
You're not supposed to do that with a Pipe I believe.
What you want is to perform a pipe operation on your itemssource 'alarmlist' which angular 2 won't allow.
What you could do though is something like this, which I'd not advise since it's ugly code:
https://plnkr.co/edit/3e6cFSBFIf0uYYIgKNZ8?p=preview
My advice would be to make another property: filteredAlarmList which you would edit in the (change) event of the <input> or the setter of your desc property and then rewrite your ngFor like *ngFor="let alarm of filteredAlarmList"
If you need this kind of filtering multiple times you could extract all of this to a seperate components. e.g.: FilterListComponent which will take 2 inputs: searchText and List (in your case AlarmList)
something like this could help
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'search',
pure: true
})
export class SearchPipe implements PipeTransform {
transform(value: any, args:string=''): any {
console.log(value);
if(!value) return value;
else {
if(value.toLowerCase().indexOf(args.toLowerCase())!= -1) {
return value;
}
}
}
}
In your component
#Component({
selector: 'my-app',
template: `
<input type="text" [(ngModel)]="filterValue">
<ul>
<li *ngFor="let name of names">{{name | search:filterValue}}</li>
</ul>`,
})
export class AppComponent {
names:Array<string> = ['Agnes','Adele','Binny','Bob'];
constructor(){
}
}

Angular 2 RC1: DataBinding and ComponentResolver

I've recently updated my project from beta.15 to RC1. Now, DynamicComponentLoader is deprecated. So I've re-writed my code using ComponentResolver.
My component is correctly loaded into another but I'm experiencing an issue : data-binding seems to doesn't works.
Here is my code :
#Component({
selector: 'browser-pane',
styleUrls: ['src/modules/component#browser/src/styles/pane.css'],
template: `
<li class="pane">
<ul class="nodes">
<li *ngFor="let node of nodes; let i = index" id="node-{{i}}"></li>
</ul>
</li>
`
})
export class PaneComponent implements OnInit {
#Input() nodeId: number;
#Input() nodes: any[];
#Input() fillerComponent: any;
#Input() mapComponentData: any;
constructor(private _injector: Injector, private _cr: ComponentResolver) {}
ngOnInit(): any {
this.nodes.forEach((node: any, i: number) => {
this._cr.resolveComponent(this.fillerComponent)
.then((factory: ComponentFactory) => factory.create(this._injector, null, `#node-${i}`))
.then((ref: ComponentRef<any>) => this.mapComponentData(ref.instance, node));
})
}
}
mapComponentData is just a function which map data to the component ref. In my case, the component that I'm creating dynamically needs an #Input() named 'media'. This function will do the following instruction: myComponentInstance.media = media;.
Here is the filler component (simplified) :
#Component({
selector: 'cmp-media-box',
styleUrls: ['src/modules/component#media-node/src/style.css'],
pipes: [ MediaCountPipe ],
template: `
<div class="media-node"
(click)="onClick()"
(dragover)="onDragover($event)"
(drop)="onDrop($event)"
[class.dropDisable]="!drop">
<span class="title">{{ media.title }}</span>
<div class="box" [class.empty]="isEmpty()">{{ media | mediaCount:'playlists':'channels' }}</div>
</div>
`
})
export class MediaNodeComponent {
#Input() media: Media;
private _OSelection:Observable<Media[]>;
private _selectionSubscription: Subscription;
private _removeAllFromSelection;
drop: boolean = false;
constructor(private _store:Store<AppStore>, private _mediaService:MediaService) {
setTimeout(() => this.initialize(), 0);
}
initialize():any {
if(this.media.type === MEDIA) {
this._OSelection = this._store.select(s => s['data#media'].selected);
this._removeAllFromSelection = mediaRemoveAll;
} else if(this.media.type === CHANNEL) {
this._OSelection = this._store.select(s => s['data#channel'].selected);
this._removeAllFromSelection = channelRemoveAll;
}
this.subscribeToSelection();
}
// ...
}
So what's the problem ? Inside thisfillerComponent, this.media is defined. If i put a console.log(this.media) inside initialize, I can see it. But I can't use it inside the template. I've tried many things :
use {{media?.title}}
use {{media.title | async}}
remove the #Input() in front of the media declaration
stop passing media and use a hard-coded variable (just in case)
use DynamicComponentLoader : same result. But I think that dcl uses ComponentResolver behind (not sure about that, I'm checking this point)
...
In other words : I can't use variables inside my template.
What am I doing wrong ?
Speaking about this code, there is another thing that I don't understand. I can't use OnInit on the filler component: ngOnInit() will never be triggered. That's why I'm using this horrible setTimeout().

Angular 2: component with more than one dependency

The following code:
...
#Component({
selector: 'sitelink',
properties: ['route: route, title: title, isHome: home, href: href']
})
#View({
template: ''
})
export class SiteLink {
constructor(#Optional() #Host() #SkipSelf() parentFrame: AppFrame, #Optional() #Host() #SkipSelf() parentLink: SiteLink) {
}
...
gives me the following error during the translation from TypeScript to JavaScript
at line 32, file app-frame.ts Argument of type 'typeof SiteLink' is not assignable to parameter of type 'Type'.
Component({
selector: 'sitelink',
properties: ['route: route, title: title, isHome: home, href: href']
})
at line 36, file app-frame.ts Argument of type 'typeof SiteLink' is not assignable to parameter of type 'Type'.
View({
template: ''
})
Using only one constructor parameter works as expected. When removing the annotations from the parameter list, it does not work, too. After removing the #Component and #View annotations, the code compiles. So the error must be related to the #Component annotation.
Edit: Even if I replace the SiteLink parameter with another type (for example AppFrame), I get the same error.
I'm using alpha 36 and the latest d.ts files from the DefinitelyTyped repository.
Its possible to inject an instance of the class itself in the constructor by using a forward reference:
constructor(#Inject(forwardRef(() => SiteLink)) siteLink) {
this.name = nameService.getName();
}
See here for more details.
As Jesse Good wrote, this is an issue with angular's d.ts file, see https://github.com/angular/angular/issues/3972.
Replacing
interface Type extends Function {
new(args: any): any;
}
with
interface Type extends Function {
new(...args): any;
}
fixed it for me.

Categories

Resources