i want to show items in Tree Items in angular 9 and angular material .
my problem is here :
i want to show 3 or 4 level in tree . i send a request to server and get flat item , and i need to convert that to multi level model .
intialData(): any {
this.searchParam.page = 1;
this.searchParam.rows = 1000;
this.claimsManagerService.getAll(this.searchParam).subscribe(data => {
data['records'].forEach(element => {
let model = {} as FileNode;
if (element.parentId == null) {
model.id = element.id;
model.isChilde = element.isChilde;
model.parentId = element.parentId;
model.title = element.title;
data['records'].forEach(child => {
if (child.parentId == element.id) {
let childe = {} as FileNode;
childe.id = child.id;
childe.isChilde = child.isChilde;
childe.parentId = child.parentId;
childe.title = child.title;
if (!model.children) model.children = [] as FileNode[];
model.children.push(childe)
data['records'].forEach(childs => {
if (childs.parentId === child.id) {
let childe = {} as TreeNode;
childe.id = childs.id;
childe.isChilde = childs.isChilde;
childe.parentId = child.parentId;
childe.title = child.title;
let item = model.children.find(x => x.id == childs.parentId);
if (!item.children) item.children = [] as TreeNode[];
item.children.push(childe);
}
})
}
})
this.dataSource.data.push(model)
}
})
return this.dataSource.data
})
}
i write this code for show data in tree :
export interface FileNode {
id: number;
title: string;
parentId: number;
isChilde: boolean;
children?: FileNode[];
}
export interface TreeNode {
id: number;
title: string;
parentId: number;
isChilde: boolean;
level: number;
expandable: boolean;
}
#Component({
selector: 'kt-role-tree',
templateUrl: './tree.component.html',
styleUrls: ['./tree.component.css'],
providers: [TreeBuilder]
})
export class TreeComponent implements OnInit,AfterViewInit {
#Output() selectedList = new EventEmitter<any>();
/** The TreeControl controls the expand/collapse state of tree nodes. */
treeControl: FlatTreeControl<TreeNode>;
treeFlattener: MatTreeFlattener<FileNode, TreeNode>;
searchParam: TableSearch;
dataSource: MatTreeFlatDataSource<FileNode, TreeNode>;
treeModel :object;
constructor(private database: TreeBuilder,
private claimsManagerService: ClaimsManagerService,
private cdRef: ChangeDetectorRef,
private dialog: MatDialog) {
this.treeFlattener = new MatTreeFlattener(
this.transformer,
this.getLevel,
this.isExpandable,
this.getChildren);
this.searchParam = {
_search: true,
dateTimeType: 1,
page: 1,
rows: 2
};
this.treeControl = new FlatTreeControl<TreeNode>(this.getLevel, this.isExpandable);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
this.intialData();
}
ngAfterViewInit(): void {
this.cdRef.detectChanges();
}
ngOnInit(): void {
}
/** Transform the data to something the tree can read. */
transformer(node: FileNode, level: number) {
return {
id: node.id,
title: node.title,
parentId: node.parentId,
isChilde: node.isChilde,
level: level,
expandable: !!node.children
};
}
/** Get the level of the node */
getLevel(node: TreeNode) {
return node.level;
}
/** Return whether the node is expanded or not. */
isExpandable(node: TreeNode) {
return node.expandable;
};
/** Get the children for the node. */
getChildren(node: FileNode) {
return observableOf(node.children);
}
/** Get whether the node has children or not. */
hasChild(node: TreeNode) {
return node.expandable;
}
delete(id: number): void {
const title = 'Post Delete';
const itemName = `Post `;
const service = this.claimsManagerService;
const dialogRef = this.dialog.open(DeleteEntityDialogComponent, {
data: { id, title, itemName, service },
width: '440px'
});
dialogRef.afterClosed().subscribe(res => {
if (res) {
this.intialData();
}
});
}
openAdd(id, title): void {
let dialogRef;
if (typeof (id) === 'string') {
dialogRef = this.dialog.open(ClaimsManagerAddComponent, {
data: { id: null, isChilde: false, claimName: 'Main' }
});
} else {
dialogRef = this.dialog.open(ClaimsManagerAddComponent, {
data: { id: id, isChilde: true, claimName: title }
});
}
dialogRef.afterClosed().subscribe(() => {
this.intialData();
})
}
intialData(): any {
this.searchParam.page = 1;
this.searchParam.rows = 1000;
this.claimsManagerService.getAll(this.searchParam).subscribe(data => {
data['records'].forEach(element => {
let model = {} as FileNode;
if (element.parentId == null) {
model.id = element.id;
model.isChilde = element.isChilde;
model.parentId = element.parentId;
model.title = element.title;
data['records'].forEach(child => {
if (child.parentId == element.id) {
let childe = {} as FileNode;
childe.id = child.id;
childe.isChilde = child.isChilde;
childe.parentId = child.parentId;
childe.title = child.title;
if (!model.children) model.children = [] as FileNode[];
model.children.push(childe)
data['records'].forEach(childs => {
if (childs.parentId === child.id) {
let childe = {} as TreeNode;
childe.id = childs.id;
childe.isChilde = childs.isChilde;
childe.parentId = child.parentId;
childe.title = child.title;
let item = model.children.find(x => x.id == childs.parentId);
if (!item.children) item.children = [] as TreeNode[];
item.children.push(childe);
}
})
}
})
this.dataSource.data.push(model)
}
})
return this.dataSource.data
})
}
}
and this is the html code :
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
<button mat-icon-button disabled></button>
{{node.title}}
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.title">
<mat-icon class="mat-icon-rtl-mirror">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
{{node.title}} {{node.id}}
</mat-tree-node>
but it not show me any things in html .
whats the problem ? how can i solve this problem ????
Related
I want to check if there is a value in a certain variable I have. Below I put an example of a logic that I want to achieve.
No matter how hard I tried, I was able to write a very sketchy code as a result of 3 hours of work and research, but it has nothing to do with what I want to achieve.
My Code:
const Files = [
{
Name: 'System',
Type: 'directory',
Value: [
{
Name: 'Main',
Type: 'directory',
Value: [
{
Name: 'Drivers',
Type: 'directory',
Value: [
{
Name: 'Startup',
Type: 'file',
Value: new FileSystem.File('Startup', 0x1, 'test blah blah'),
},
],
},
],
},
],
},
];
BlahBlah.has(Files, 'System->Main->Drivers');
// [File]
BlahBlah.has(Files, 'System->Main->Drivers->Startup');
// File
BlahBlah.has(Files, 'System->Main->Drivers->AnyWhere');
// undefined
BlahBlah.has(Files, 'System->Main->AnyRandomDirectory');
// NaN
My Function:
function text2Binary(str: string, spliter: string = ' '): string {
return str
.split('')
.map(function (char) {
return char.charCodeAt(0).toString(2);
})
.join(spliter);
}
export function FileTypeFromNumber(e: number) {
if (typeof e != 'number')
try {
e = Number(e);
} catch (_) {
return null;
}
return {
0x1: {
Name: 'Executable File',
Extension: 'exe',
},
0x2: {
Name: 'Text Document',
Extension: 'txt',
},
}[e];
}
export type FileTypes =
| 0x1
| 0x2
| 0x3
| 0x4
| 0x5
| 0x6
| 0x7
| 0x8
| 0x9
| null;
export class File {
Name: string;
Type: {
Name: string;
Extension: string;
};
Content: string;
Size: number;
constructor(name: string, type: FileTypes, content: string) {
this.Name = name;
this.Type = FileTypeFromNumber(type);
this.Content = content;
this.Size = text2Binary(content, '').length;
}
}
export class Directory {
public Name: string;
public Files: (File | Directory)[] = [];
constructor(name: string) {
this.Name = name;
}
addFile(file: File | Directory) {
this.Files.push(file);
}
getFile(name: string): null | (File | Directory)[] {
if (typeof name != 'string')
try {
name = String(name);
} catch (_) {
return null;
}
const Result = this.Files.filter((e) => e.Name == name);
return Result.length == 0 ? null : Result;
}
getSize() {
return this.Files.map((e) =>
e instanceof Directory ? e.getSize() : e.Size
).reduce((a, b) => a + b, 0);
}
has(name) {
return this.Files.some((e) => e.Name == name);
}
getJSON() {
return this.Files.map((e) => ({ ...e }));
}
}
interface x {
Content: string;
Name: string;
Size: number;
Type: string;
}
export function ConvertFromJSONtoDirectory(json: any[]) {
return json.map((value) => {
const isDirectory = value.Type == 'directory';
if (!isDirectory) {
return value.Value;
}
const self = new Directory(value.Name);
ConvertFromJSONtoDirectory(value.Value).map((e) => self.addFile(e));
return self;
});
}
export default class DirectorySystem {
Memory: Map<any, any>;
Current: string | null;
constructor(Current = null) {
this.Memory = new Map();
this.Current = Current;
}
addDirectory(directory: Directory): null | true {
if (!(directory instanceof Directory)) return null;
if (this.Memory.has(directory.Name)) return null;
this.Memory.set(directory.Name, directory);
return true;
}
getDirectory(DirectoryName: string): boolean | Directory {
if (typeof DirectoryName != 'string')
try {
DirectoryName = String(DirectoryName);
} catch (_) {
return null;
}
const Result = this.Memory.has(DirectoryName);
return Result ? this.Memory.get(DirectoryName) : Result;
}
getDirectoryCurrent() {
if (this.Current == null) return this;
}
changeDirectory(by: -1 | 1, value: string) {
if (by == -1) {
if (this.Current == null) return null;
if (this.Current.includes('->')) {
this.Current = this.Current.split('->').slice(0, -1).join('->');
} else {
this.Current = null;
}
return this.Current;
} else if (by == 1) {
let Position = [this.Current, value].join('->');
if (this.Current == null) {
Position = Position.split('->').slice(1).join('->');
}
let Result = this.has(Position);
console.log(Result);
}
}
has(query: string) {
try {
return query.split('->').reduce((a, b) => {
if (Array.isArray(a)) {
const f = a.filter((e) => e['Name'] == b);
if (a.length > 0) {
return f['Files'];
} else {
return a;
}
}
return a['Files'];
}, this.getJSON());
} catch (_) {
return false;
}
}
getJSON(): x[][] {
return [...this.Memory.values()].reduce((a, b) => {
a[b.Name] = b.getJSON();
return a;
}, {});
}
}
Result: (Thanks Michael M. and chill 389cc for helping me understand the error)
has(
query: string,
overwrite = null
) {
// If overwrite argument is not null, we are going use it.
let files = overwrite == null ? this.getJSON() : overwrite;
// Split string for getting more usable type with converting string to Array.
const QueryParams = query.split('->').filter(String);
// If we dont have no query, we can return current status.
if (QueryParams.length == 0) return overwrite;
if (Array.isArray(files)) {
const SearchFor = QueryParams.shift();
const Result = files.filter((e) => {
if (e instanceof Directory) {
const x = e.Name == SearchFor;
return x ? e : false;
}
return e.Name == SearchFor;
})[0];
// If we cant find any indexing result
if (!Result) return false;
// We found a file and if we dont have any query this is mean we found it!
if (Result instanceof File) return QueryParams.length == 0;
// We found a Directory and we doesnt have any Query now, so we can return true.
if (Result instanceof Directory && QueryParams.length == 0) return true;
if (
Result.Name != SearchFor ||
(QueryParams.length != 0 && Result.Files.length == 0)
)
// If name not suits or still we has Query and not enough file for indexing.
return false;
// If nothing happens on upper section, return rescyned version of this function.
return this.has(QueryParams.join('->'), Result.Files);
} else {
// If value is Object, Try Search param in object, and return it.
const Result = files[QueryParams.shift()];
return !Result ? false : this.has(QueryParams.join('->'), Result);
}
}
I can't replicate all of your code, but does this help?
interface Entry {
Name: string,
Type: string,
Value: Array<Entry> | any,
};
const Files = [
{
Name: "System",
Type: "directory",
Value: [
{
Name: "Main",
Type: "directory",
Value: [
{
Name: "Drivers",
Type: "directory",
Value: [
{
Name: "Startup",
Type: "file",
Value: "test", // change this to anything
},
],
},
],
},
],
},
];
function getEl(files: Array<Entry>, path: String) {
let path_walk = path.split("->");
let obj = files;
for (let part of path_walk) {
let found = false;
for (let entry of obj) {
if (entry.Name == part) {
obj = entry.Value;
found = true;
}
}
if (!found) return undefined;
}
return obj;
}
console.log(getEl(Files, "System->Main->Drivers")); // => [ { Name: 'Startup', Type: 'file', Value: 'test' } ]
console.log(getEl(Files, "System->Main->Drivers->Startup")); // => "test"
console.log(getEl(Files, "System->Main->Drivers->AnyWhere")); // => undefined
console.log(getEl(Files, "System->Main->AnyRandomDirectory")); // => undefined
There are some obvious problems, such as the fact that your example shows .has() being called with two arguments but it is defined in the class to only take in one. That being said, here is a function that, given a string query as you have and an array of objects like you have, would read the query and return if the array works for that query.
function has(fileSystem, query) {
const arrayOfArgs = query.split('->')
if (Array.isArray(fileSystem)) {
for (let i = 0; i < fileSystem.length; i++) {
if (fileSystem[i]['Name'] === arrayOfArgs[0]) {
if (arrayOfArgs.length === 1) {
// element found
return true; // replace this to return an actual value if that is desired.
}
if (fileSystem[i]['Type'] === 'directory') {
// if not, recurse in if it is a directory
return has(fileSystem[i]['Value'], arrayOfArgs.slice(1).join('->'));
} else {
// if it isn't a directory, don't try to recurse in
return false;
}
}
}
}
return false;
}
console.log(has(Files, 'System->Main->Drivers')); // true
console.log(has(Files, 'System->Main->Drivers->Startup')); // true
console.log(has(Files, 'System->Main->Drivers->AnyWhere')); // false
console.log(has(Files, 'System->Main->AnyRandomDirectory')); // false
You'll have to add your own types to get it back to TypeScript and obviously I pulled it out of the class for easier testing but it should be pretty easy to re-implement.
I want to access value present in JSON object with configurable targetPath for response received.
Below is my component class which will bind the data present in list to the autocomplete drop down in HTML code
the configurable key is 'path', which value I want to provide as a key in custom JSON object key 'field'.
import { Component, ViewChild } from '#angular/core';
import { MenuItem } from 'primeng/api';
import { SelectItem } from 'primeng/api';
import { SelectItemGroup } from 'primeng/api';
import { FilterService } from 'primeng/api';
import { AutoComplete } from 'primeng/autocomplete';
import { CountryService } from './countryservice';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
providers: [CountryService, FilterService]
})
export class AppComponent {
userDetails: any[];
selectedUserDetails: any[];
selectedValue: any;
selectedUserDetail: any;
path: string = 'contactMedium[0].characteristic.emailAddress';
testVal: string;
constructor() {}
ngOnInit() {
this.userDetails = [
{
id: 'cont-609',
contactMedium: [
{
characteristic: {
emailAddress: 'test#gmail.com'
}
}
]
},
{
id: 'cont-610',
contactMedium: [
{
characteristic: {
emailAddress: 'test#gmail.com'
}
}
]
},
{
id: 'cont-611',
contactMedium: [
{
characteristic: {
emailAddress: 'test#gmail.com'
}
}
]
},
{
id: 'cont-612',
contactMedium: [
{
characteristic: {
emailAddress: 'test#gmail.com'
}
}
]
},
{
id: 'cont-614',
contactMedium: [
{
characteristic: {
emailAddress: 'test#gmail.com'
}
}
]
}
];
}
filterUserDetails(event) {
const filteredNew: any[] = [];
this.getUserDetails().then(response => {
for (let resp of response) {
this.testVal = resp[this.path];
filteredNew.push({
id: resp.id,
field: resp[this.path]
});
}
});
this.selectedUserDetails = filteredNew;
}
getUserDetails(): Promise<any[]> {
return Promise.resolve(this.userDetails);
}
chooseItem(event) {
this.selectedUserDetail =
event.contactMedium[0].characteristic.emailAddress;
}
}
From the response received from method getUserDetails(), I am building a custom JSON object array with fields 'id' and 'field', the key for id is known which itself is a 'id' but key for field is configurable which is path in my case.
But looks like above logic where key is accessed is not working as expected i.e I am not getting value for
filterUserDetails(event) {
const filteredNew: any[] = [];
this.getUserDetails().then(response => {
for (let resp of response) {
this.testVal = resp[this.path];
filteredNew.push({
id: resp.id,
field: resp[this.path]
});
}
});
this.selectedUserDetails = filteredNew;
}
Below is my HTML code
<h5>Dropdown Testing</h5>
<p>selectedUserDetail : {{selectedUserDetail}}</p>
<p>TestVal : {{testVal}}</p>
<p-autoComplete [(ngModel)]="selectedUserDetail" [suggestions]="selectedUserDetails"
(completeMethod)="filterUserDetails($event)" [dropdown]="true" field="field">
<ng-template let-userDetails pTemplate=" item">
<div>{{userDetails.field}}</div>
</ng-template>
</p-autoComplete>
If I change the usage of assignment like below everything works fine
field: resp.contactMedium[0].characteristic.emailAddress
Link of my code is here : https://stackblitz.com/edit/primeng-autocomplete-demo-dyihrs?file=src%2Fapp%2Fapp.component.html
Expectation here is to assign value of key: contactMedium[0].characteristic.emailAddress received from getUserDetails() to custom JSON object which is getting build in filterUserDetails() to 'field' key.
You should be aware that your field can be very complex but in the case you mentioned, can be resolved with this:
resolveField(data: any, field: string): any {
if (data && field) {
let fields: string[] = field.split('.');
let value = data;
for(let i = 0, len = fields.length; i < len; ++i) {
if (value == null) {
return null;
} else {
const pos: number = fields[i].match(/\d+/g)?.[0];
if(pos != null) {
const property = fields[i].match(/^\w+/g);
value = value[property][pos];
} else {
value = value[fields[i]];
}
}
}
return value;
} else {
return null;
}
}
You can use the resolveField function as part of your logic. You can modify it, as you want or required, for example: here is considering only letters as part of the property names.
Here is the solution for your code.
You can use a variable that has the dynamic key
var obj = {
...
}
var key = ...;
obj[key] = somevalue;
or to get the value
var somevalue = obj[key];
I would like to use a BehaviorSubject to store an Array of objects and have a way to easily update (next?) a single item of that array without having to update the whole array.
I would also like for an easy way to subscribe to changes to an specific item of that array. I know it could be done with filter, but an easier way would be nice...
Is that possible?
I am currently using this version I created (which I don't know if it is the best way or not) that also persists its contents to localstorage:
export class LocalStorageBehaviorSubject<T, Y = T> {
private _data: BehaviorSubject<T>;
public asObservable() {
return this._data.asObservable();
}
public next(data: T) {
if(this.expirationFn !== null) {
data = this.expirationFn(data);
}
localStorage.setItem(this.key, JSON.stringify(data));
this._data.next(data);
}
public nextItem(item: Y) {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
let dados: any = (<any>this._data.getValue()).slice();
if (dados.some(r => r[this.id] === item[this.id])) {
dados = dados.map(r => r[this.id] === item[this.id] ? item : r);
} else {
dados.push(item);
}
if(this.expirationFn !== null) {
dados = this.expirationFn(dados);
}
localStorage.setItem(this.key, JSON.stringify(dados));
this._data.next(<any>dados);
}
public removeItem(id) {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
let dados: any = (<any>this._data.getValue()).slice();
dados = dados.filter(r => r[this.id] !== id);
localStorage.setItem(this.key, JSON.stringify(dados));
this._data.next(<any>dados);
}
public removeExpiredData(){
let data = this.loadFromStorage();
if (data) {
if(this.expirationFn !== null) {
data = this.expirationFn(data);
}
this._data.next(data);
}
}
public getValue() {
this.removeExpiredData();
return this._data.getValue();
}
public getItem(id): Y {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
this.removeExpiredData();
return (<any>this._data.getValue()).slice().find(t => t[this.id] == id);
}
constructor(private key: string, private id: string, defaultValue: any = null, private expirationFn: (dados: T) => T = null) {
this._data = new BehaviorSubject<T>(defaultValue);
this.removeExpiredData();
}
private loadFromStorage(): T {
let dadosStr = localStorage.getItem(this.key);
if (dadosStr) {
return JSON.parse(dadosStr);
}
return null;
}
}
I hoped that would be an simpler way...
Thanks
I would also like for an easy way to subscribe to changes to an
specific item of that array. I know it could be done with filter, but
an easier way would be nice...
You can use map operator and inside lambda array.find
Example
const mockStorage = {
values: {},
setItem(key, value) {
this.values[key] = value;
},
getItem(key) {
return this.values[key]
},
clearItem(key) {
this.values[key] = undefined;
}
}
class LocalStorageBehaviorSubject {
constructor(key, defaultValue) {
this.key = key;
this._data = new rxjs.BehaviorSubject(defaultValue);
}
nextItem(item) {
const list = this._data.value;
const itemIndex = list.findIndex(pr => pr.id === item.id);
this._data.next([
...list.slice(0, itemIndex),
{
...(list[itemIndex] || {}),
...item
},
...list.slice(itemIndex + 1)
]);
}
removeItem(id) {
this._data.next(this._data.value.filter(pr => pr.id !== id));
}
getItem(id) {
return this.asObservable()
.pipe(
rxjs.operators.map(values => values.find(pr => pr.id === id) || null),
rxjs.operators.distinctUntilChanged());
}
asObservable() {
return this._data.asObservable().pipe(
rxjs.operators.tap(values => {
if (values && values.length) {
mockStorage.setItem(this.key, JSON.stringify(values));
}
else {
mockStorage.clearItem(this.key);
}
}))
}
}
const localStorageBehaviorSubject = new LocalStorageBehaviorSubject('items', []);
localStorageBehaviorSubject
.getItem(1)
.subscribe(item => {
console.log(item);
})
localStorageBehaviorSubject.nextItem({id: 1, value: 'test'})
localStorageBehaviorSubject.nextItem({id: 1, value: 'test1'})
localStorageBehaviorSubject.nextItem({id: 2, value: 'test2'})
localStorageBehaviorSubject.nextItem({id: 3, value: 'test3'})
localStorageBehaviorSubject.removeItem(2);
localStorageBehaviorSubject.removeItem(1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
I have a function below from which I am calling setModelData.
My query is I want to make template variable dynamic for both getGlobalList and getNonGlobalList function.
For Example
1) if getGlobalList is running it will set template: this.CustomTableItemTemplate
inside the setModelData function.
2) if getNonGlobalList is running it will pass template: this.NonGlobalCustomTableItemTemplate
inside the setModelData function.
Thanks for help
Code
#ViewChild('CustomTableItemTemplate') CustomTableItemTemplate: TemplateRef<any>;
#ViewChild('NonGlobalCustomTableItemTemplate') NonGlobalCustomTableItemTemplate: TemplateRef<any>;
ngOnInit() {
this.getGlobalList();
this.getNonGlobalList();
}
getGlobalList() {
this.globalSchemamodel.data.length = 0;
this.Service.getGlobalList(
this.constructQueryParam(this.globalSchemamodel, 'global'))
.subscribe((response: any) => {
const globalSchemas = response ? response.data : [];
if (globalSchemas.records) {
this.setModelData(globalSchemas, this.globalSchemamodel);
}
});
}
getNonGlobalList() {
this.nonGlobalSchemamodel.data.length = 0;
this.Service.getList(
this.constructQueryParam(this.nonGlobalSchemamodel, 'nonglobal'))
.subscribe((response: any) => {
const nonglobalschemaslist = response ? response.data : [];
if (nonglobalschemaslist.records) {
this.setModelData(nonglobalschemaslist, this.nonGlobalSchemamodel);
}
});
}
setModelData(globalSchemas, globalSchemamodel) {
for (const schema of globalSchemas.records) {
const tableModel = [
new TableItem({ data: schema.schema_id }),
this.isAdminRole ? new TableItem({
data:[{ 'schemaId': schema.schema_id }],
**template: this.CustomTableItemTemplate**
}) : null
];
globalSchemamodel.data.push(tableModel);
}
}
setModelData function needs another template param that's for sure.
Additionally you can extract similar code from getNonGlobalList and getGlobalList
ngOnInit() {
this.getList(
this.globalSchemamodel,
this.Service.getGlobalList,
'global',
this.CustomTableItemTemplate
);
this.getList(
this.nonGlobalSchemamodel,
this.Service.getList',
'nonglobal',
this.NonGlobalCustomTableItemTemplate
);
}
getList(model: any, functionToCall: any, paramName: string, template: TemplateRef<any>) {
model.data.length = 0;
functionToCall(
this.constructQueryParam(model, paramName))
.subscribe((response: any) => {
const schemas = response ? response.data : [];
if (schemas.records) {
this.setModelData(schemas.records, model);
}
});
}
setModelData(schemas: any[], schemaModel: any, template: TemplateRef<any>) {
for (const { schema_id } of schemas) {
const tableModel = [
new TableItem({
data: schema_id
}),
this.isAdminRole ? new TableItem({
data: [
{
'schemaId': schema_id
}
],
template
}) : null
];
schemaModel.data.push(tableModel);
}
}
this is my question:
i have a custom component which has a template with a ngModel inside.
import { Component, Input, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
const listaMesi = [
{
value: '01',
text: 'Gennaio'
}, {
value: '02',
text: 'Febbraio'
}, {
value: '03',
text: 'Marzo'
}, {
value: '04',
text: 'Aprile'
}, {
value: '05',
text: 'Maggio'
}, {
value: '06',
text: 'Giugno'
}, {
value: '07',
text: 'Luglio'
}, {
value: '08',
text: 'Agosto'
}, {
value: '09',
text: 'Settembre'
}, {
value: '10',
text: 'Ottobre'
}, {
value: '11',
text: 'Novembre'
}, {
value: '12',
text: 'Dicembre'
}
]
const annoCorrente = new Date().getFullYear();
#Component({
selector: 'seg-month-picker',
templateUrl: './month-picker.component.html',
styleUrls: ['./month-picker.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MonthPickerComponent),
multi: true
}]
})
export class MonthPickerComponent implements ControlValueAccessor {
private propagateChange: Function;
private checkedValue: string;
private isDisabled = true;
public meseSelezionato: string;
public annoSelezionato: string;
public yearList = [];
public monthList = listaMesi;
private _min: string;
private _max: string;
#Input() set min(value: string) {
this._min = value;
if (value) {
const [ year, month ] = value.split('-');
const maxYear = this._max ? +this._max.slice(0, 4) : annoCorrente;
this.yearList = Array.from({ length: maxYear + 1 - +year }).map((_, index) => +year + index);
}
}
get min() {
return this._min;
}
#Input() set max(value: string) {
this._max = value;
if (value) {
const [ maxYear, month ] = value.split('-');
const year = this._min ? +this.min.slice(0, 4) : annoCorrente;
this.yearList = Array.from({length: +maxYear - year + 1}).map((_, index) => year + index);
}
}
get max() {
return this._max;
}
updateYear(year: string) {
this.annoSelezionato = year;
this.updateValue();
}
updateMonth(month: string) {
this.meseSelezionato = month;
this.updateValue();
}
updateValue() {
if (this.annoSelezionato && this.meseSelezionato && this.propagateChange) {
this.propagateChange(`${this.annoSelezionato}-${this.meseSelezionato}`);
}
}
writeValue(yearMonth: string): void {
const [ year, month ] = yearMonth.split('-');
this.annoSelezionato = year;
this.meseSelezionato = month;
}
registerOnChange(fn: Function): void {
this.propagateChange = fn;
}
registerOnTouched(fn: Function): void { }
setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
}
<div>
<select id="anno" name="anno" [ngModel]="annoSelezionato" (ngModelChange)="updateYear($event)">
<option *ngFor="let anno of yearList" [value]="anno.value">{{anno.value}}</option>
</select>
<select id="mese" name="mese" [ngModel]="meseSelezionato" (ngModelChange)="updateMonth($event)">
<option *ngFor="let mese of monthList" [value]="mese.value">{{mese.text}}</option>
</select>
</div>
The problem is: how i track ngModule changing in my unit test? i'll paste my unit test which is not working; i tried with #viewChild() but i surely got something wrong.
import { Component, ViewChild } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { FormsModule } from '#angular/forms';
import { MonthPickerComponent } from './month-picker.component';
#Component({
selector: 'seg-month-picker',
template: `<seg-month-picker>`
})
export class TestComponent {
#ViewChild(MonthPickerComponent) picker;
}
function getComponent(): Promise<TestComponent> {
const fixture = TestBed
.createComponent(TestComponent);
fixture.detectChanges();
return fixture.whenStable().then(() => fixture.componentInstance);
}
describe('MonthPickerComponent', async() => {
let component: MonthPickerComponent;
let fixture: ComponentFixture<MonthPickerComponent>;
let element: HTMLElement;
const mockedComponent = await getComponent();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MonthPickerComponent, mockedComponent],
imports: [FormsModule]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MonthPickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
describe('setMin', () => {
it('deve impostare la lista di anni in base al minimo dato', () => {
component.min = '2014-03';
fixture.detectChanges();
expect(component.yearList).toEqual([2014, 2015, 2016, 2017]);
})
});
describe('getMin', () => {
it('deve restituire l\'anno minimo della lista', () => {
component.min = '2014-03';
fixture.detectChanges();
const result = component.min;
expect(result).toBe('2014-03');
});
it('deve restituire null se non ho un valore minimo', () => {
component.min = undefined;
fixture.detectChanges();
const result = component.min;
expect(result).toBe(undefined);
})
});
describe('setMax', () => {
it('deve restituire la lista di anni in base al massimo dato', () => {
component.max = '2018-01';
fixture.detectChanges();
expect(component.yearList).toEqual([2017, 2018]);
});
it('deve restituire la lista di anni in base al range dato', () => {
component.max = '2018-01';
component.min = '2014-01';
fixture.detectChanges();
expect(component.yearList).toEqual([2014, 2015, 2016, 2017, 2018]);
});
});
describe('getMax', () => {
it('deve restituire l\'anno massimo della lista', () => {
component.max = '2018-01';
fixture.detectChanges();
const result = component.max;
expect(result).toBe('2018-01');
});
it('deve restituire null se non ho un valore massimo', () => {
component.max = undefined;
fixture.detectChanges();
const result = component.max;
expect(result).toBe(undefined);
});
});
describe('writeValue', () => {
fit('deve modificare il valore all\'ngModel del componente', async () => {
console.log(mockedComponent.picker);
mockedComponent.picker.writeValue('2016-03');
fixture.detectChanges();
const result = component.max;
expect(result).toBe('2018-01');
});
it('deve restituire null se non ho un valore massimo', () => {
component.max = undefined;
fixture.detectChanges();
const result = component.max;
expect(result).toBe(undefined);
});
});
});
The problem is that i have to test the writeValue() function and the others from the component controller, tracking which values the ngModel assumes. i don't really know how to resolve this problem.
Thanks for the help guys!
--- EDIT ---
At least i could take an html reference to the <select> in my component view and change its value 'programmatically', but i think there's a better approach.
try this:
import { Component, Input, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
const listaMesi = [
{
value: '01',
text: 'Gennaio'
}, {
value: '02',
text: 'Febbraio'
}, {
value: '03',
text: 'Marzo'
}, {
value: '04',
text: 'Aprile'
}, {
value: '05',
text: 'Maggio'
}, {
value: '06',
text: 'Giugno'
}, {
value: '07',
text: 'Luglio'
}, {
value: '08',
text: 'Agosto'
}, {
value: '09',
text: 'Settembre'
}, {
value: '10',
text: 'Ottobre'
}, {
value: '11',
text: 'Novembre'
}, {
value: '12',
text: 'Dicembre'
}
]
const annoCorrente = new Date().getFullYear();
#Component({
selector: 'seg-month-picker',
templateUrl: './month-picker.component.html',
styleUrls: ['./month-picker.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MonthPickerComponent),
multi: true
}]
})
export class MonthPickerComponent implements ControlValueAccessor {
private propagateChange: Function;
private checkedValue: string;
private isDisabled = true;
public meseSelezionato: string;
public annoSelezionato: string;
public yearList = [];
public monthList = listaMesi;
private _min: string;
private _max: string;
#Input() set min(value: string) {
this._min = value;
if (value) {
const [ year, month ] = value.split('-');
const maxYear = this._max ? +this._max.slice(0, 4) : annoCorrente;
this.yearList = Array.from({ length: maxYear + 1 - +year }).map((_, index) => +year + index);
}
}
get min() {
return this._min;
}
#Input() set max(value: string) {
this._max = value;
if (value) {
const [ maxYear, month ] = value.split('-');
const year = this._min ? +this.min.slice(0, 4) : annoCorrente;
this.yearList = Array.from({length: +maxYear - year + 1}).map((_, index) => year + index);
}
}
get max() {
return this._max;
}
updateYear(year: string) {
this.annoSelezionato = year;
this.updateValue();
}
updateMonth(month: string) {
this.meseSelezionato = month;
this.updateValue();
}
updateValue() {
if (this.annoSelezionato && this.meseSelezionato && this.propagateChange) {
this.propagateChange(`${this.annoSelezionato}-${this.meseSelezionato}`);
}
}
writeValue(yearMonth: string): void {
const [ year, month ] = yearMonth.split('-');
this.annoSelezionato = year;
this.meseSelezionato = month;
}
registerOnChange(fn: Function): void {
this.propagateChange = fn;
}
registerOnTouched(fn: Function): void { }
setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
}