I have a HTML list build with ngFor from AngularFire2 ObjectObservable.
Once a object(not last) is deleted from the Database, null appears in his position(index) and the HTML list tries to update but an error is thrown from the ngFor loop.
JSON from AngularFire2 DB:
let tasksList = {
"name": "Task list",
"items": [
{
"name": "Task 1"
},
{
"name": "Test 2"
},
{
"name": "Test 3"
}
]
}
HTML:
<ul *ngFor="let theTask of tasksList.items">
<li>{{theTask.name}}</li>
</ul>
After task 1 has been removed, the JSON looks like this:
let tasksList = {
"name": "Task list",
"items": [
null,
{
"name": "Test 2"
},
{
"name": "Test 3"
}
]
}
I think that the simplest solution would be to stop the null value from replacing the deleted object.
But is that possible?
Thank you
You can use angular 2's pipe (angular 1's filter) in order to filter all empty items:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'removeNull', pure: false})
export class RemoveNullPipe implements PipeTransform {
transform(items: any[]): any {
return items.filter(item => item);
}
}
then use it in HTML:
<ul>
<li *ngFor="let theTask of tasksList.items | removeNull">{{ theTask.name }}</li>
</ul>
I moved *ngFor from <ul> to <li> element, it seems better to repeat <li>s within <ul> and not repeat uls
plunker: http://plnkr.co/edit/IQq45kaJqqWRZOqhZdoe?p=preview
Related
Helloo !
i would use a json files in my angular-app.
I have a local json file 'client.json'
{
"T001": {
"name": "admin",
"type": "adm",
"id": "1",
"class": "viewer",
},
"T002": {
"name": "Customer",
"type": "usr",
"id": "2",
"class": "viewer",
},
"T003": {
"name": "DB450",
"type": "air",
"id": "3",
"class": "gateway",
},
"T004": {
"name": "DB620",
"type": "air",
"id": "4",
"class": "gateway",
}
}
Actually, i could read this file and i got access to the name of object (object of object) -- T003.name for example and using console.log to display the result but this is not what i'm looking for because i can't use this variable into html template.
import { Component, OnInit } from '#angular/core';
import * as tunnelsData from '../data/client.json'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{
objects: any;
ngOnInit(): void {
this.objects = (tunnelsData as any).default;
console.log(this.object);
}
}
I'm looking for loads this file into a variable, then using this variable on a html template with ngfor like this :
<my-app *ngFor="let T of objects"
[tunnel]="T"
[name]="T.name"><my-app>
tunnel and name are two variables of my second component
To display at the end an example like this :
Tunnel : T001 (which the first object of my file)
Name : admin (which an object of the first object)
.
.
.
I hope it's clear for you.
any idea please ?
Thank you.
The only problem is that you don't have an array in your json data. you have this data as an object
{
"T001": {
"name": "admin",
"type": "adm",
"id": "1",
"class": "viewer",
},
"T002": {
"name": "Customer",
"type": "usr",
"id": "2",
"class": "viewer",
},
"T003": {
"name": "DB450",
"type": "air",
"id": "3",
"class": "gateway",
},
"T004": {
"name": "DB620",
"type": "air",
"id": "4",
"class": "gateway",
}
}
And in your html you are trying to loop through your elements with ngFor. So ngFor directive always expects an array to loop in html.
So the simple solution you can have is that in your component you can do like this
app.component.ts
this.objects = (tunnelsData as any).default;
this.objectKeys = Object.keys(his.objects); // ['T001', 'T002', 'T003', 'T004']
And inside of your html you can do like this
<my-app *ngFor="let keyName of objectKeys" [tunnel]="objects[keyName]" [name]="objects[keyName]?.name"><my-app>
What i am doing here is that first i am getting all of the keys of your object as an array and then i am iterating over the array of keys. And in html i am accessing the individual object by it's key name which is coming from ngFor loop.
Generaly, angular does not support iterating over objects in ngFor.
Check your browser's dev tools console. There should be an error.
You can use pipe in ngFor to make it work KeyValuePipe.
For more details check this thread.
And to display the key (eg."T001"), you would need to pass it too.
*ngFor="let item of objects | keyvalue"
[tunnel]="item.value"
[name]="item.value.name"
[key]="item.key"
I have imported the following json file:
[
{
"case_id": "1234",
"thread": [
{
"t_id": "1111",
"text": "test"
},
{
"t_id": "2222",
"text": "test"
}
]
},
{
"case_id": "5678",
"thread": [
{
"t_id": "9999",
"text": "test"
},
{
"t_id": "8888",
"text": "test"
},
{
"t_id": "777",
"text": "test"
}
]
}
]
using the following:
import cases from '../cases.json'
The whole json dataset is available in cases variable and can be used in the template with the support of v-if and v-for.
How can I create a separate dataset (thecase) that contains only threads for a given case_id? In the template I would only like to use v-for to display all threads for a given case_id.
Below is my export default section:
export default {
name: "details",
props: {
case_id: {
required: true,
type: String
}
},
data () {
return {
cases,
thecase: ### THIS IS THE PART I CANNOT FIGURE OUT ###
}
}
};
You can remove thecase from data options and use a computed property instead for thecase. Inside the computed property, we will need to use array .find() method to find the case where case_id is same as the case_id passed in the prop:
data: {
cases,
},
computed: {
thecase: function() {
return this.cases.find(c => c.case_id === (this.case_id || ''))
}
}
and then you can use v-for on thecase.thread just like you would do for a data option like:
<li v-for="item in thecase.thread" :key="item.t_id">
{{ item.text }}
</li>
You can further modify it and use v-if & v-else to show a text like No cases were found with give case id in case there is no match found.
I have a UI where initially a User has to check some checkboxes. The checkboxes have sequential IDs. The JSON Structure for it is as follows:
{
"categories": [{
"name": "Product",
"labels": [{
"id": 1,
"name": "I work on an asset (capital good).",
"checked": false
}, {
"id": 2,
"name": "I work on a consumer product.",
"checked": false
}, {
"id": 3,
"name": "I am not sure what type of product I work on.",
"checked": false
}
]
}, {
"name": "Goal",
"labels": [{
"id": 4,
"name": "I want to improve the product's reliability.",
"checked": false
}, {
"id": 5,
"name": "I need information to identify root causes.",
"checked": false
}, {
"id": 6,
"name": "I need information about the product's environment.",
"checked": false
}, {
"id": 7,
"name": "I need information about customer requirements.",
"checked": false
}, {
"id": 8,
"name": "I need quantified information.",
"checked": false
}, {
"id": 9,
"name": "I am not sure what I need.",
"checked": false
}
]
}
]
}
I render it Angular using the following Code:
component.html
<div class="row mt-lg-auto" *ngFor="let filter of filters['categories']">
<div class="col-lg-12">
<h4>
{{filter['name']}}
</h4>
<div *ngFor="let label of filter['labels']">
<div class="form-check">
<input class="form-check-input"
type="checkbox"
value="{{label['id']}}"
id="{{label['id']}}"
[(ngModel)]="label['checked']"
(change)="changeCheck(label['id'], $event)"
>
<label class="form-check-label" for="{{label['id']}}">
{{label['name']}}
</label>
</div>
</div>
</div>
</div>
component.ts
I directly import the JSON file from src/assets/ folder and save the id to a Set in order to avoid duplicate values when the user selects a checkbox.
import { Component, OnInit } from '#angular/core';
import * as FilterFunc from 'src/assets/FilterFunction.json';
const Filters: any = FilterFunc;
#Component({
selector: 'explore-step1',
templateUrl: './explore-step1.component.html',
styleUrls: ['./explore-step1.component.css']
})
export class ExploreStep1Component implements OnInit {
filters = Filters.default;
selections = new Set();
constructor() {
}
ngOnInit(): void {
}
changeCheck(id: number, event: any) {
(event.target.checked) ?
this.selections.add(id):
this.selections.delete(id);
console.log(this.selections);
}
}
I am using ngx-treeview to render a tree view for a fixed JSON file that has the following structure:
GitHub Gist of the Complete Recursive JSON
Here on the children in the most depth have the following key-value pair:
"value": {
"label_ids": [relevant ids from the first json],
"description": "some text to render"
}
else the "value" is null.
I wish to compare the Set values to the above mentioned recursive JSON's label_ids and if one or more than one values in the label_ids match with the Set then change the checked value to true
How does one accomplish this in Typescript/Angular?
I solved it by creating a Recursion Parsing Function which takes in the JSON Structure.
Within the ngOnInit() I call the service and pass it to the parseTree function
I recursively parse it and compare the values with the Set
I add additional information like a Font-Awesome class text within the value structure to render it
pass the updated JSON to the respective items variable
component.ts:
parseTree(factors: TreeviewItem[]) {
factors.forEach(factor => {
// console.log(factor);
if (!isNil(factor.value)) {
let labels: number[] = factor.value['label_ids'];
labels.forEach(label => {
if(this.selected.findIndex(l => l == label) > -1) {
factor.value['highlighted'] = true;
factor.value['class'] = 'fas fa-flag';
}
});
}
if (!isNil(factor.children)) {
this.parseTree(factor.children);
}
});
this.items = factors;
}
here selections is a Set and within ngOnInit() I set it a fixed value:
ngOnInit() {
this.selections.add(15);
this.items = this.parseTree(this.service.getDataQualityFactors());
}
Based on ngx-treeview example I use the itemTemplate in the code and add the Font-Awesome fonts next to the selections as follows:
component.html
<label class="form-check-label" *ngIf="item.value !== null && item.value['highlighted']">
<i class="{{item.value['class']}}" aria-hidden="true" title="Highlighted" [ngClass]="{'marked': item.checked}"></i>
</label>
and use the CSS classes to manipulate the color change of the font:
.fa-flag {
color: red;
}
.fa-flag.marked {
color: green;
}
StackBlitz Example Code
I know Angular 2 is old, but I used the best course our library had, and most of it still works...
I have a list of data in a json file, which looks something like this:
{
"name" :"example one",
"source" :"",
"source_type" :"",
"text" :"A bumblebee in the field"
},
{
"name" :"example two",
"source" :"",
"source_type" :"",
"text" :"Two flowers with red petals"
},
I can display the whole list of names and can access the other data too.
Now I want to have a text field so the user can search.
I'd like a search option for name and one for text (even though the text doesn't display directly).
The problem is: I'd like users to be able to search for single words, like 'one' and get all results that contain the word 'one'.
Is this possible? Or would I be better of learning how to set up a database online and implement a search option from there?
This might give you the idea for the search using filter
var searchText = "three";
var data = [
{
name: "example one three",
source: "",
source_type: "",
text: "A bumblebee in the field"
},
{
name: "example two",
source: "",
source_type: "",
text: "Two flowers with red petals"
},
{
name: "example three",
source: "",
source_type: "",
text: "Two flowers with red petals"
}
];
let newObj = data.filter((value)=>{
return value.name.indexOf(searchText) != -1 ? value : null
});
console.log(newObj);
Well, you could do this using a Pipe. But using Filter Pipes is not really recommended by the Angular Team. So you can essentially listen to the keyup event on your input field and then call a function to filter the data.
Now, since this is a strict filter object by a key, you can simply use the filter method on Array and check if the indexOf the searchInput in the name or text is >-1
Here's how:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
filterText;
data = [{
"name": "example one",
"source": "",
"source_type": "",
"text": "A bumblebee in the field"
},
{
"name": "example two",
"source": "",
"source_type": "",
"text": "Two flowers with red petals"
}];
filteredData = [];
ngOnInit() {
this.filteredData = [...this.data];
}
onChange() {
console.log('ran');
this.filteredData = this.data.filter(
datum => (datum.name.indexOf(this.filterText) > -1 || datum.text.indexOf(this.filterText) > -1));
console.log(this.filteredData);
}
}
And in the template:
<input type="text" [(ngModel)]="filterText" (keyup)="onChange()">
<ul>
<li *ngFor="let datum of filteredData">
{{ datum.name }}
</li>
</ul>
Here's a Sample StackBlitz for your ref.
I am working on an App with Angular 4.
I am a beginner with Angular.
I need help with 2 things:
-First: when I click on a user name in the HTML, I want to match the name clicked with the same user name in all the objects it is present in.
Second: display the title of the books this user name is present in.
For example: when I click on Jose Miguel I want to see, on the HTML, the 2 books he has read.
json data:
books = [
{
"title": "title1",
"author": "author1",
"users": [
{
"id": 1,
"name": "Isidro"
},
{
"id": 4,
"name": "Jose Miguel"
},
{
"id": 3,
"name": "Trinidad"
}
]
},
{
"title": "title2",
"author": "author2",
"users": [
{
"id": 4,
"name": "Jose Miguel"
},
{
"id": 5,
"name": "Beatriz"
},
{
"id": 6,
"name": "Rosario"
}
]
},
html:
<div style="text-align:center">
<ul class="books">
<li *ngFor="let book of books" >
Título: {{book.title}} <br> Autor: {{book.author}}<br>
<ul style="text-align:center" class="recentUsers">
<li *ngFor="let user of book?.users" (mouseenter)="myEvent($event)" class="individualUsers">{{user.name}}</li>
</ul>
</li>
</ul>
</div>
Up to here I managed to display all books and the recent users that had read each particular book.
(mouseenter)="myEvent($event)" is only console logging, as you can predict, the name of the first user of the first book, but that is not what I need.
This is the part I need help with.
import { Component, OnInit } from '#angular/core';
import { BooksService } from '../books.service';
#Component({
selector: 'app-completo',
templateUrl: './completo.component.html',
styleUrls: ['./completo.component.css']
})
export class CompletoComponent implements OnInit {
constructor(private booksService: BooksService){
}
books = [];
ngOnInit(){
this.booksService.getBooks().subscribe(responseBooks => this.books =responseBooks);
}
myEvent(){
console.log(this.books[0].users[0].name)
event.preventDefault();
}
}
You can simply filter the book list in your event:
readBooks: Array;
// ...
showReadBooksForUser(userId: number) {
readBooks = books.filter(
(book) => book.users.findIndex((user) => user.id == userId) > -1
);
}
resetReadBooks() {
readBooks = null;
}
And maybe something like that in your HTML:
<div style="text-align:center">
<ul class="books">
<li *ngFor="let book of books" >
Título: {{book.title}} <br> Autor: {{book.author}}<br>
<ul style="text-align:center" class="recentUsers">
<li *ngFor="let user of book?.users"
(mouseenter)="showReadBooksForUser(user.id)"
(mouseleave)="resetReadBooks()"
class="individualUsers">{{user.name}}</li>
</ul>
</li>
</ul>
<!-- display book div with appropriate css? -->
<div *ngIf="readBooks" id="readBooksOverlay">
<ul>
<li *ngFor="let book of readBooks">{{book.title}}</li>
</ul>
</div>
</div>