Array in service not found in method angular 2 - arrays

hi i have array in servcie and I have a strange problem in the saveData array function is visible but in removeItems no
my service.ts
export class FormService {
myArray = []
constructor(){
}
saveData(key, value) {
this.myArray.push({
key: key,
value: value
});
//HERE IS VISIBLE and i can add may elements
console.log('myArray')
console.log(this.myArray)
}
removeItems(myIndex) {
//here is not visible is empty
console.log(this.myArray)
}
}
UPDATE
my component
export class Step3AdditionalServicesComponent implements OnInit {
constructor(private formService: FormService) {
}
ngOnInit() {
this.stepsGuard.getAccess(false);
}
addToMyArray(key,value) {
this.formService.saveData(key,value)
}
removeItemFromArray(event) {
var myIndex = event.target.id;
console.log('myIndex ' + myIndex);
this.formService.removeItems(myIndex);
}
}
my html
<button (click)="addToMyArray('key1','simple1')">Add Simle1</button>
<button (click)="addToMyArray('key2','simple2')">Add Simle2</button>
<button (click)="addToMyArray('key3','simple3')">Add Simle3</button>
<button id="1" (click)="removeItemFromArray($event)">Remove 1</button>
<button id="2" (click)="removeItemFromArray($event)">Remove 2</button>
<button id="3" (click)="removeItemFromArray($event)">Remove 3</button>
And when i click removeItemFromArray($event) i see index in console
----SOLUTION----
I declare my service in component providers and in app.module
my.component
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css'],
providers: [FormService] <---delete this and will be working
})
#brijmcq Thanks for help

Most of the time, declare your service in the module level and not in the component level unless you know what you are doing. You are probably getting the wrong data because if you declare it in component level, you will always get a new instance of FormService and the myArray = [] will always be empty.
You probably know what happens next if you access an empty array by index :)

Here is how I remove and add elements in my array:
elementsArray = [];
addElementList(event) {
let element = event.data;
if (!this.elementsArray.includes(element)) {
this.elementsArray.push(element);
console.log("Elements in array = " + this.elementsArray);
}
}
removeElementList(event) {
let element = event.data;
let index = this.elementsArray.indexOf(element, 0);
if (index > -1) {
this.elementsArray.splice(index, 1);
console.log("Elements in array = " + this.elementsArray);
}
}

Related

Implementing Enqueue and Dequeue without built-in methods

I have two functions one to dequeue and one to enqueue a user-inputted item from/to an array respectively.
What I know about queues
What I know about queues is pretty limited, I know they are similar to stacks but rather than being Last-In-First-Out (LIFO) as stacks are they are First-In-First-Out (FIFO) so basically whatever elements go into the array first will be taken out of the array first.
What I am trying right now
What I am trying to do right now is with the Enqueue button I am adding items to an array while incrementing the variable count by 1 with each push of the button so as to add each new user input to the next array position. In the dequeue function, I am setting each element of the dequeue array equal to each element of the original array by incrementing the dequeueCount variable.
What is the problem
The problem here is this, when I push the dequeue button I basically need to reindex everything so that after I dequeue an element the element at index 1, now takes the position index 0 and basically I always want to dequeue the item at element 0.
queue.component.ts
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-queue',
templateUrl: './queue.component.html',
styleUrls: ['./queue.component.css']
})
export class QueueComponent implements OnInit {
#Input() userInput: String
array = []
arrayCount = 0
dequeueCount = 0
dequeueArray = []
dequeued = null
constructor() { }
ngOnInit() {
}
enQ() {
this.array[this.arrayCount++] = this.userInput;
}
deQ() {
deQ() {
if (this.arrayCount > 0) {
this.dequeueArray[this.dequeueCount++] = this.array[this.dequeueCount-1]
this.dequeued = this.dequeueArray[this.dequeueCount-1]
this.arrayCount--
}
else {
this.dequeued = "There is nothing else to dequeue"
}
}
}
and I am trying to show the current value of the array here
<div>
<label for="userInput">Input to Array:
<input [(ngModel)]="userInput" type="text">
</label><br>
<button (click)="enQ()">Enqueue</button>
<button (click)="deQ()">Dequeue</button>
<h3>Arrays</h3>
<p *ngFor = "let item of array"> {{ array }} </p>
<h3>Dequeued Item</h3>
<p> {{ dequeued}} </p>
</div>
when I press the enqueue function everything seems to work correctly and a value is added to the array at the correct position so no problems with the enqueue function, however with the dequeue function, I need to somehow be able to dequeue the items and also remove the first item from the array and redisplay {{ array }} with the dequeued item removed.
Try the snippet, it should solve the problem.
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
userInput: String;
array = [];
dequeued = [];
enQ() {
this.array.push(this.userInput);
}
deQ() {
if (this.array.length > 0) {
this.dequeued.push(this.array[this.array.length - 1]);
this.array.shift();
}
}
}
<div>
<label for="userInput">Input to Array:
<input [(ngModel)]="userInput" type="text">
</label><br>
<button (click)="enQ()">Enqueue</button>
<button (click)="deQ()">Dequeue</button>
<h3>Arrays</h3>
<p> {{ array |json}} </p>
<h3>Dequeued Item</h3>
<p> {{ dequeued|json}} </p>
</div>
In your code you missing the pipe on dequeue variable, so when it hold the array the values will not be rendered by angular
Demo
I think your approach is complicated and vague, I made a new one based on your intention.
Even if this code doesn't match your needs, at least you can start from this one.
queue.component.ts
import { Component, Input } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
#Input() userInput;
array = [];
arrayCount = 0;
dequeuedCount = 0;
dequeued;
get queue() {
const q = [];
for (let i = this.dequeuedCount; i < this.arrayCount; i++) {
q[i - this.dequeuedCount] = this.array[i];
}
return q;
}
enQ() {
this.array[this.arrayCount++] = this.userInput;
}
deQ() {
if (this.arrayCount > this.dequeuedCount) {
this.dequeued = this.array[this.dequeuedCount++];
} else {
this.dequeued = "There is nothing else to dequeue";
}
}
}
queue.component.html
<div>
<label for="userInput">Input to Array:
<input [(ngModel)]="userInput" type="text">
</label><br>
<button (click)="enQ()">Enqueue</button>
<button (click)="deQ()">Dequeue</button>
<h3>Arrays</h3>
<p> {{ queue | json }} </p>
<h3>Dequeued Item</h3>
<p> {{ dequeued | json }} </p>
</div>
stackblitz: https://stackblitz.com/edit/angular-bt8ufd

Angular 4 Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays

Here is the code below:
component.ts
by selecting match geting id in routing, then taking this id from URL
export class MatchComponent implements OnInit {
_postArrayMatch: match[];
constructor(public router:Router, private matchService: MatchService,
private route: ActivatedRoute) {}
ngOnInit(){
this.getMatchId();
}
getMatchId() :void {
this.route.params.forEach((params: Params)=> {
let id = +params['id'];
this.matchService.getMatch(id).subscribe(
resultArray => this._postArrayMatch = resultArray,
error => console.log("Error ::" + error))
})
}
component.html
just basic interpolation by doing ngFor loop
<div *ngFor="let post of _postArrayMatch">
<p>{{post.team1.name}}</p>
</div>
service.ts
passing the dynamic id
getMatch(id:number): Observable<match[]>{
return this.http.get<match[]>(`http://localhost:3000/match/${id}`)
}
interface
export interface match{
team1:{
name:string,
id:number
}
team2:{
name:string,
id:number
}
}
Try something like this where you create the object in your response
component.ts
export class MatchComponent implements OnInit {
_postArrayMatch: match[];
newArr: Array<Object> = [];
constructor(public router:Router, private matchService: MatchService,
private route: ActivatedRoute) {}
ngOnInit(){
this.getMatchId();
}
getMatchId() :void {
this.route.params.forEach((params: Params)=> {
let id = +params['id'];
this.matchService.getMatch(id).subscribe(
resultArray => {
this._postArrayMatch = resultArray;
const keys = Object.keys(this._postArrayMatch) // new stuff starts here
for(let i = 0; i < keys.length; i++) {
newArr.push(this._postArrayMatch[keys[i]]);
newArr[i]['team'] = keys[i];
}
},
error => console.log("Error ::" + error))
})
}
And then you can access ALL of your sub objects in your html
Component.html
<div *ngFor="let post of newArr">
<p> {{post.team}}: {{post.name}}</p>
</div>
Currently, with what you have you are hard coding for team 1, and if you want to do that then you shouldn't be using the *ngFor
Thats seems the easiest way i could find on how to get the data from objects.
<div *ngFor="let post of objectKeys(_postArrayMatch.team1)">
<div> Team1: {{ _postArrayMatch.team1[post]}}</div>
</div>
component.ts
objectKeys = Object.keys;

Angular 4 - binding not working when declaring variable as a property of an object

I have a page where I display a list with locations, and clicking on each of them I am displaying assets for that location. I have set up the template like this:
<li
*ngFor="let location of locations"
(click)="select(location)"
droppable
[dragOverClass]="'drag-target-border'"
(onDrop)="onItemDrop($event, location)"
>
{{ location.name }}
<ul *ngIf="currentLocation == location && assetsVisible">
<ng-container *ngIf="currentLocation.assets?.length > 0;else message">
<li
*ngFor="let asset of currentLocation.assets"
class="asset"
draggable
[dragData]="asset"
>
<p>{{ asset.title }}</p>
<p>{{ asset.address }}</p>
<p>{{ asset.value }}</p>
</li>
</ng-container>
<ng-template #message>No assets for {{ location.name }}</ng-template>
</ul>
</li>
And in the component, I have tried to first set the empty assets array, and an empty currentLocation object with a property assets:
locations: any[] = [];
currentLocation: any = {
assets: any[] = []
};
And then in the method select I am fetching the assets like this:
select(location){
if (this.currentLocation != location || !this.assetsVisible) {
this.assetsVisible = true;
}
else {
this.assetsVisible = false;
}
this.currentAddress = address;
this.locationService.getAssets(location.Id)
.subscribe(
assets => this.currentLocation.assets = assets
);
}
What I would like to do is to since I am using the drag and drop plugin for Angular I would like to make it possible for user to drag assets from one location to another. And then just temporarily push that asset to an array of the property currentLocation.assets where the asset was dropped to.
I have made a function for that:
onItemDrop(e: any, location) {
this.select(location);
this.currentLocation.assets.push(e.dragData);
}
That drags and drops the asset from one location to another, but I get an error:
ERROR TypeError: Cannot read property 'push' of undefined
I have done console.log(this.currentLocation.assets) in both select and onItemDrop method, and both times I get:
undefined
But, if I do console.log(this.currentLocation) in both functions, I get an object with an empty assets array as a property of it. And I can access every other property of that object, but I can't access assets array.
Also, when I do console.log in ngOnInit I get an empty array back in the console:
console.log(this.currentLocation.assets);
So, I am not sure why do I get this error, why do I get an object with an empty assets array as a property of it, but I can't access directly property of that object, when I can access all other properties of the same object?
This is the whole component:
import { Component, OnInit } from '#angular/core';
import { LocationService } from '../services/location.service';
#Component({
moduleId: module.id,
selector: 'sd-addresses',
templateUrl: 'location.component.html'
})
export class LocationsComponent implements OnInit {
errorMessage: string;
locations: any[] = [];
currentLocation: any = {
assets: any[] = []
};
assetsVisible: boolean = false;
constructor(public locationService: LocationService) {}
ngOnInit() {
this.getLocations();
}
getLocations() {
this.locationService.get()
.subscribe(
locations => this.locations = locations,
error => this.errorMessage = <any>error
);
}
select(location = {}) {
if (this.currentLocation != location || !this.assetsVisible) {
this.assetsVisible = true;
}
else {
this.assetsVisible = false;
}
this.currentLocation = location;
this.locationService.getAssets(location.Id)
.subscribe(
assets => this.currentLocation.assets = assets,
);
}
onItemDrop(e: any, location:any = {}) {
this.currentLocation.assets = [];
this.currentLocation.assets.push(e.dragData);
this.select(location);
}
}
In your declaration try this:
locations: any[] = [];
currentLocation: any = {
assets: []
};
This way you will be declaring currentLocation that is an object type {} and has a property assets that is an array type [].

How to use a regular for in Angular 2? [duplicate]

...for example...
<div class="month" *ngFor="#item of myCollection; #i = index">
...
</div>
Is possible to do something like...
<div class="month" *ngFor="#item of 10; #i = index">
...
</div>
...without appeal to a non elegant solution like:
<div class="month" *ngFor="#item of ['dummy','dummy','dummy','dummy','dummy',
'dummy','dummy','dummy']; #i = index">
...
</div>
?
Within your component, you can define an array of number (ES6) as described below:
export class SampleComponent {
constructor() {
this.numbers = Array(5).fill().map((x,i)=>i); // [0,1,2,3,4]
this.numbers = Array(5).fill(4); // [4,4,4,4,4]
}
}
See this link for the array creation: Tersest way to create an array of integers from 1..20 in JavaScript.
You can then iterate over this array with ngFor:
#Component({
template: `
<ul>
<li *ngFor="let number of numbers">{{number}}</li>
</ul>
`
})
export class SampleComponent {
(...)
}
Or shortly:
#Component({
template: `
<ul>
<li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>
</ul>
`
})
export class SampleComponent {
(...)
}
#OP, you were awfully close with your "non-elegant" solution.
How about:
<div class="month" *ngFor="let item of [].constructor(10); let i = index">
...
</div>
Here I'm getting the Array constructor from an empty array: [].constructor, because Array isn't a recognized symbol in the template syntax, and I'm too lazy to do Array=Array or counter = Array in the component typescript like #pardeep-jain did in his 4th example. And I'm calling it without new because new isn't necessary for getting an array out the Array constructor.
Array(30) and new Array(30) are equivalent.
The array will be empty, but that doesn't matter because you really just want to use i from ;let i = index in your loop.
Edit to respond to comments:
Q. How can I use a variable to set the length of the NgFor loop?
Here is an example on how to render a table with variable columns/rows
<table class="symbolTable">
<tr *ngFor="let f of [].constructor(numRows); let r = index">
<td class="gridCell" *ngFor="let col of [].constructor(numCols); let c = index">
{{gridCards[r][c].name}}
</td>
</tr>
</table>
export class AppComponent implements OnInit {
title = 'simbologia';
numSymbols = 4;
numCols = 5;
numRows = 5;
guessCards: SymbolCard[] = [];
gridCards: SymbolCard[][] = [];
ngOnInit(): void {
for (let c = 0; c < this.numCols; c++) {
this.guessCards.push(new SymbolCard());
}
for (let r = 0; r < this.numRows; r++) {
let row: SymbolCard[] = [];
for (let c = 0; c < this.numCols; c++) {
row.push(
new SymbolCard({
name: '' + r + '_' + c
}))
}
this.gridCards.push(row);
}
}
}
No there is no method yet for NgFor using numbers instead collections,
At the moment, *ngFor only accepts a collection as a parameter, but you could do this by following methods:
Using pipe
demo-number.pipe.ts:
import {Pipe, PipeTransform} from 'angular2/core';
#Pipe({name: 'demoNumber'})
export class DemoNumber implements PipeTransform {
transform(value, args:string[]) : any {
let res = [];
for (let i = 0; i < value; i++) {
res.push(i);
}
return res;
}
}
For newer versions you'll have to change your imports and remove args[] parameter:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'demoNumber'})
export class DemoNumber implements PipeTransform {
transform(value) : any {
let res = [];
for (let i = 0; i < value; i++) {
res.push(i);
}
return res;
}
}
html:
<ul>
<li>Method First Using PIPE</li>
<li *ngFor='let key of 5 | demoNumber'>
{{key}}
</li>
</ul>
Using number array directly in HTML(View)
<ul>
<li>Method Second</li>
<li *ngFor='let key of [1,2]'>
{{key}}
</li>
</ul>
Using Split method
<ul>
<li>Method Third</li>
<li *ngFor='let loop2 of "0123".split("")'>{{loop2}}</li>
</ul>
Using creating New array in component
<ul>
<li>Method Fourth</li>
<li *ngFor='let loop3 of counter(5) ;let i= index'>{{i}}</li>
</ul>
export class AppComponent {
demoNumber = 5 ;
counter = Array;
numberReturn(length){
return new Array(length);
}
}
#Working demo
I couldn't bear the idea of allocating an array for plain repeat of components, so I've written a structural directive. In simplest form, that doesn't make the index available to the template, it looks like this:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({ selector: '[biRepeat]' })
export class RepeatDirective {
constructor( private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
#Input('biRepeat') set count(c:number) {
this.viewContainer.clear();
for(var i=0;i<c;i++) {
this.viewContainer.createEmbeddedView(this.templateRef);
}
}
}
http://plnkr.co/edit/bzoNuL7w5Ub0H5MdYyFR?p=preview
you can also use like that
export class SampleComponent {
numbers:Array<any> = [];
constructor() {
this.numbers = Array.from({length:10},(v,k)=>k+1);
}
}
HTML
<p *ngFor="let i of numbers">
{{i}}
</p>
This can also be achieved like this:
HTML:
<div *ngFor="let item of fakeArray(10)">
...
</div>
Typescript:
fakeArray(length: number): Array<any> {
if (length >= 0) {
return new Array(length);
}
}
Working Demo
Use a pipe to transform the number to an array.
#Pipe({
name: 'enumerate',
})
export class EnumeratePipe implements PipeTransform {
transform(n: number): number[] {
return [...Array(n)].map((_,i) => i);
}
}
Then use the pipe in your template.
<p *ngFor="let i of 5 | enumerate">
Index: {{ i }}
</p>
https://stackblitz.com/edit/angular-ivy-pkwvyw?file=src/app/app.component.html
I solved it like this using Angular 5.2.6 and TypeScript 2.6.2:
class Range implements Iterable<number> {
constructor(
public readonly low: number,
public readonly high: number,
public readonly step: number = 1
) {
}
*[Symbol.iterator]() {
for (let x = this.low; x <= this.high; x += this.step) {
yield x;
}
}
}
function range(low: number, high: number) {
return new Range(low, high);
}
It can be used in a Component like this:
#Component({
template: `<div *ngFor="let i of r">{{ i }}</div>`
})
class RangeTestComponent {
public r = range(10, 20);
}
Error checking and assertions omitted on purpose for brevity (e.g. what happens if step is negative).
You can use lodash:
#Component({
selector: 'board',
template: `
<div *ngFor="let i of range">
{{i}}
</div>
`,
styleUrls: ['./board.component.css']
})
export class AppComponent implements OnInit {
range = _.range(8);
}
I didn't test code but it should work.
<div *ngFor="let number of [].constructor(myCollection)">
<div>
Hello World
</div>
</div>
This is a nice and quick way to repeat for the amount of times in myCollection.
So if myCollection was 5, Hello World would be repeated 5 times.
My-component.ts
numbers: number[] = [];
constructor() {
this.numbers = new Array<number>(10)
}
My-component.html
<div *ngFor="let num of numbers; let i = index">{{ i }}</div>
Since the fill() method (mentioned in the accepted answer) without arguments throw an error, I would suggest something like this (works for me, Angular 7.0.4, Typescript 3.1.6)
<div class="month" *ngFor="let item of items">
...
</div>
In component code:
this.items = Array.from({length: 10}, (v, k) => k + 1);
Using custom Structural Directive with index:
According Angular documentation:
createEmbeddedView Instantiates an embedded view and inserts it into this container.
abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef.
Param Type Description
templateRef TemplateRef the HTML template that defines the view.
context C optional. Default is undefined.
index number the 0-based index at which to insert the new view into this container. If not specified, appends the new view as the last entry.
When angular creates template by calling createEmbeddedView it can also pass context that will be used inside ng-template.
Using context optional parameter, you may use it in the component,
extracting it within the template just as you would with the *ngFor.
app.component.html:
<p *for="number; let i=index; let c=length; let f=first; let l=last; let e=even; let o=odd">
item : {{i}} / {{c}}
<b>
{{f ? "First,": ""}}
{{l? "Last,": ""}}
{{e? "Even." : ""}}
{{o? "Odd." : ""}}
</b>
</p>
for.directive.ts:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
class Context {
constructor(public index: number, public length: number) { }
get even(): boolean { return this.index % 2 === 0; }
get odd(): boolean { return this.index % 2 === 1; }
get first(): boolean { return this.index === 0; }
get last(): boolean { return this.index === this.length - 1; }
}
#Directive({
selector: '[for]'
})
export class ForDirective {
constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { }
#Input('for') set loop(num: number) {
for (var i = 0; i < num; i++)
this.viewContainer.createEmbeddedView(this.templateRef, new Context(i, num));
}
}
Please find attached my dynamic solution if you want to increase the size of an array dynamically after clicking on a button (This is how I got to this question).
Allocation of necessary variables:
array = [1];
arraySize: number;
Declare the function that adds an element to the array:
increaseArrayElement() {
this.arraySize = this.array[this.array.length - 1 ];
this.arraySize += 1;
this.array.push(this.arraySize);
console.log(this.arraySize);
}
Invoke the function in html
<button md-button (click)="increaseArrayElement()" >
Add element to array
</button>
Iterate through array with ngFor:
<div *ngFor="let i of array" >
iterateThroughArray: {{ i }}
</div>
A simplest way that i have tried
You can also create an array in your component file and you can call it with *ngFor directive by returning as an array .
Something like this ....
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-morning',
templateUrl: './morning.component.html',
styleUrls: ['./morning.component.css']
})
export class MorningComponent implements OnInit {
arr = [];
i: number = 0;
arra() {
for (this.i = 0; this.i < 20; this.i++) {
this.arr[this.i]=this.i;
}
return this.arr;
}
constructor() { }
ngOnInit() {
}
}
And this function can be used in your html template file
<p *ngFor="let a of arra(); let i= index">
value:{{a}} position:{{i}}
</p>
Here is something quite clean and simple for Angular:
In .ts:
max = 10;
In .html:
<div *ngFor="let dummy of ','.repeat(max).split(','); index as ix">
- {{ix + 1}}:
</div>
I think the simple and short solution is here..
Your my.component.ts file
setArrayFromNumber(i: number) {
return new Array(i);
}
Your my.component.html file
<li *ngFor='let in of setArrayFromNumber(5); let i = index'>{{ i }}</li>
That's it!!

Can TestComponentBuilder test an angular component within a component?

So, I'm trying to write a unit test for my component. My component is a dynamically created list, and when a user clicks certain items, I'd like my test to validate that the component generates outputs correctly.
To test this, I wrote a demo component that creates my component within its template, and then attempted to get TestComponentBuilder to create this demo component and click items on the lists. Unfortunately, my unit test cases can't find my component's html elements to click them. I can find the demo component's elements, but it seems like the test is having trouble accessing my nested component.
My question is:
Can I get this approach to work?
Or is there a better way to unit test my component's inputs and outputs?
Any help would be appreciated. Here's what I'm doing:
<== UPDATED per Gunter's Answer ==>
CustomListComponent.ts (My component):
import {Component, EventEmitter} from "angular2/core";
import {CustomListItem} from "./CustomListItem";
#Component({
selector: 'custom-list-component',
inputs: ['itemClass', 'items'],
outputs: ['onChange'],
template: `
<ul class="list-unstyled">
<li *ngFor="#item of items" [hidden]="item.hidden">
<div [class]="itemClass" (click)="onItemClicked(item)">
{{item.text}}
</div>
</li>
</ul>
`
})
export class CustomListComponent {
itemClass: String;
items: CustomListItem[];
onChange: EventEmitter<CustomListItem>;
constructor() {
this.items = new Array<CustomListItem>();
this.onChange = new EventEmitter();
}
onItemClicked(item: CustomListItem): void {
let clone = new CustomListItem(item.text, item.hidden);
this.onChange.emit(clone);
}
}
CustomListItem.ts:
export class CustomListItem {
text: String;
hidden: boolean;
constructor(text: String, hidden: boolean) {
this.text = text;
this.hidden = hidden;
}
}
CustomListSpec.ts (Here's where I'm struggling):
import {CustomListComponent} from './CustomListComponent';
import {ComponentFixture, describe, expect, fakeAsync, inject, injectAsync, it, TestComponentBuilder} from 'angular2/testing'
import {Component} from "angular2/core";
import {CustomListItem} from "./CustomListItem";
import {By} from "angular2/src/platform/dom/debug/by";
describe('CustomListComponent', () => {
var el;
var dropdownToggleBtn, dropdownListEl;
var regularListEl;
it('Event output properly when list item clicked', injectAsync([TestComponentBuilder], (tcb) => {
return createComponent(tcb).then((fixture) => {
console.log("el (" + el + ")"); // => [object HTMLDivElement]
console.log("dropdownToggleBtn (" + dropdownToggleBtn + ")"); // => [object HTMLButtonElement]
console.log("dropdownListContainer(" + dropdownListContainer + ")"); // => [object HTMLDivElement]
console.log("dropdownListEl(" + dropdownListEl + ")"); // => [object HTMLUListElement]
//console.log("regularListEl (" + regularListEl+ ")");
//...further testing...
});
}));
function createComponent(tcb: TestComponentBuilder): Promise<ComponentFixture> {
return tcb.createAsync(CustomListComponentDemo).then((fixture) => {
fixture.detectChanges();
el = fixture.debugElement.nativeElement;
dropdownToggleBtn = fixture.debugElement.query(By.css(".btn")).nativeElement;
dropdownListContainer = fixture.debugElement.query(By.css(".dropdown-menu")).nativeElement;
dropdownListEl = dropdownListContainer.children[0].children[0];
//regularListEl = fixture.debugElement.query(By.css(".list-unstyled")); //TIMES OUT unfortunately. Maybe because there are 2.
return fixture;
})
}
});
#Component({
directives: [CustomListComponent],
template: `
<div class="btn-group" dropdown>
<button id="dropdown-toggle-id" type="button" class="btn btn-light-gray" dropdownToggle>
<i class="glyphicon icon-recent_activity dark-green"></i> Dropdown <span class="caret"></span>
</button>
<div class="dropdown-menu" role="menu" aria-labelledby="dropdown-toggle-id">
<custom-list-component id="dropdown-list-id"
[items]="dropdownListItems" [itemClass]="'dropdown-item'"
(onChange)="onDropdownListChange($event)">
</custom-list-component>
</div>
<span class="divider"> </span>
</div>
<custom-list-component id="regular-list-id"
[items]="regularListItems"
(onChange)="onRegularListChange($event)">
</custom-list-component>
`
})
class CustomListComponentDemo {
dropdownListItems: CustomListItem[];
regularListItems: CustomListItem[];
constructor() {
//intialize the item lists
}
onDropdownListChange(item: CustomListItem): void {
//print something to the console logs
}
onRegularListChange(item: CustomListItem): void {
//print something to the console logs
}
}
Call detectChanges() before you query dynamically created elements. They are not created before change detection has been run.
function createComponent(tcb: TestComponentBuilder): Promise<ComponentFixture> {
return tcb.createAsync(CustomListComponentDemo).then((fixture) => {
fixture.detectChanges();
el = fixture.debugElement.nativeElement;
regularListEl = fixture.debugElement.query(By.css(".list-unstyled"));
dropdownListEl = fixture.debugElement.query(By.css(".dropdown-menu")).nativeElement;
dropdownToggleBtn = fixture.debugElement.query(By.css(".btn")).nativeElement;
return fixture;
})
}

Resources