I'm new to Angular 2 and my nested components are not visible or even processed.
Can you please help? I think i might haven't declared any directive....
My main.ts :
import {bootstrap} from 'angular2/platform/browser';
import {MatchListComponent} from "./match/match.component";
import { HTTP_PROVIDERS } from 'angular2/http';
bootstrap(
MatchListComponent,
[ HTTP_PROVIDERS ]
);
My MatchListComponent looks like that :
#Component({
selector: "matches",
template: `
<table class="table">
<match-row *ngFor="#list of matches" match="list" >
</match-row>
</table>
`,
providers: [MatchService]
})
export class MatchListComponent implements OnInit...
No match-row becomes displayed, but about 150 of them are present in the dom
Edit: match-row
#Component({
selector: "match-row",
template: `
<tr >
<td>
{{match.matchday.number}}
</td>
<td>
{{match.displayValue}}
</td>
</tr>
`,
providers: [TrainerService, MatchdayService]
})
export class MatchRowComponent ...
You should use let instead of # in your *ngFor and I'm assuming your match is an input on match-row. So your
<match-row *ngFor="#list of matches" match="list"</match-row>
should be
<match-row *ngFor="let list of matches" [match]="list" ></match-row>
To pass in variables on ()Inputs you must wrap the name in square brackets. Just like outputs are wrapped in parentheses. Inputs can be left without the square brackets when passing in string literals, not variables.
Hope that helps.
EDIT:
With the code for match-row added, there is a much simpler way to do this. There isn't really a reason to make this a separate component. Move your rows into the main component to iterate on it this way:
<table class="table">
<tr *ngFor="let match of matches">
<td>{{match.matchday.number}}</td>
<td>{{match.displayValue}}</td>
</tr>
</table>
If you insist on having the display data in another component, try moving the <tr> back to the main component since it is each row you want separated out. It would look something like this:
<table class="table">
<tr *ngFor="let list of matches">
<match-row [match]="list"></match-row>
</tr>
</table>
Related
I have angular 8 application. And I have a service for retrieving courses from a api call.
So the service method looks like this:
loadCourseById(courseId: number) {
return this.http.get<Course>(`/api/courses/${courseId}`)
.pipe(
shareReplay()
);
}
and the component looks like this:
#Component({
selector: 'course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {
course$: Observable<Course>;
lessons$: Observable<Lesson[]>;
constructor(private route: ActivatedRoute, private courseService: CoursesService) {
}
ngOnInit() {
const courseId = parseInt(this.route.snapshot.paramMap.get('courseId'));
this.course$ = this.courseService.loadCourseById(courseId);
console.log(courseId);
}
}
and template looks like this:
<ng-container *ngIf = "(course$ | async) as course">
<div class="course">
<h2>{{course?.description}}</h2>
<img class="course-thumbnail" [src]="course?.iconUrl">
<table class="lessons-table mat-elevation-z7">
<thead>
<th>#</th>
<th>Description</th>
<th>Duration</th>
</thead>
</table>
</div>
</ng-container>
So I have checked the api call. And I see in the console.log that I get the correct data back.
But in the html page it is empty so no content is visible.
So what I have to change?
Thank you
You're miss a small change in your code
remove this pipe from your service
.pipe(shareReplay());
and for your template there are 2 possible change to proceed:
1 - remove () inside your ngIf statement => *ngIf="course$ | async as course"
<h2>{{course.description}}</h2>
<img class="course-thumbnail" [src]="course.iconUrl">
....
2 - your ngIf statement will look like this => *ngIf="course$ | async"
<h2>{{(course$ | async)?.description}}</h2>
<img class="course-thumbnail" [src]="(course$ | async)?.iconUrl">
....
I hope that is helpfull
I have a simple data which I am populating through ngFor into view. I am getting all the objects and pushing into array and then from there I am populating into html.Here its working fine.But in my project the scenario is like I need to get into this page by selecting a event from a previous page.When I am frequently going and selecting events and coming to this page, some cases my data pushing into array but not populating into view.Is there any solution for this.Here is the code below
home.component.html
<div>
<table>
<tr *ngFor="let x of groupList">
<td ><span>{{x.vehicle_number}}</span></td>
<td ><span>{{x.vehicle_name}}</span></td>
<td ><span>{{x.status}}</span></td>
</tr>
</table>
</div>
home.component.html
import { Component, OnInit } from '#angular/core';
import { CurrencyPipe } from './../pipes/currency.pipe';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
imageSource :any;
statusdata1: any;
moreThanTen:boolean = false;
showit:boolean = false;
groupList:any = [];
constructor() {}
ngOnInit() {
/* First data */
let response =
{"vehicle_number":1,"vehicle_name":"car","status":"yellow"}
let response1 = {"vehicle_number":0,"vehicle_name":"car","status":"yellow"}
let response2 = {"vehicle_number":2,"vehicle_name":"car","status":"yellow"}
this.groupList.push(response,response1,response2);
console.log(this.groupList);
}
}
Please try to implement onPush or ChangeDetectionStrategy in your component
Doing this will instruct Angular to run change detection on these components and their sub-tree only when new references are passed to them versus when data is simply mutated.
Run this.ref.markForCheck() or this.ref.detectChanges() when you update your variable and want it to reflect in html
Please check the following links for more information
https://angular.io/api/core/ChangeDetectionStrategy
https://alligator.io/angular/change-detection-strategy/
I have quite complex infrastructure in my project which contains of
host component
structural directive used in host component's template (MyDir)
another component used in structural directive (MyComp)
Simplified version looks like the following.
host component
#Component({
selector: 'my-app',
template: `
<table>
<tr *myDir="let item of data">
<td>{{item.text}}</td>
</tr>
</table>`
})
export class AppComponent {
data = [ { text: 'item 1' }, { text: 'item 2' } ];
}
structural directive
import { MyComp } from './myComp';
#Directive({ selector: '[myDir][myDirOf]' })
export class MyDir implements OnInit {
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {
}
#Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const templateView = this.templateRef.createEmbeddedView({});
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = this.viewContainer.createComponent(
compFactory, undefined, this.viewContainer.injector, [templateView.rootNodes]
);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
}
}
structural directive's component
#Component({
selector: '[my-comp]',
template: `
<tr><td>custom td</td></tr>
<ng-template *ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
}
The output is
custom td
item 1
item 2
which is fine except the markup which is
<table>
<div my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</div>
</table>
Problem
I want to remove intermediate <div my-comp> from the result view or at least replace it with <tbody>. To see the whole picture I prepared Stackblitz DEMO, hope it will help... Also, it might be obvious the example is artificial, but this is what I came with trying to reproduce the issue with minimal code. So the problem should have a solution in the given infrastructure.
Update
#AlexG found simple way to replace intermediate div with tbody and stackblitz demo showed a good result at first. But when I tried to apply it to my project locally I faced new issue: browser arranges its own tbody before the dynamic contents of the table are ready to render, which results in two nested tbody in the end view, which seems inconsistent per html specs
<table>
<tbody>
<tbody my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</tbody>
</tbody>
</table>
Stackblitz demo has no such problem, only tbody my-comp is present. But exactly the same project in my local dev environment does. So I'm still trying to find a way how to remove intermediate my-comp container.
Update 2
The demo had been updated in accordance with the solution suggested by #markdBC.
My answer is inspired by Slim's answer to a similar question found here: https://stackoverflow.com/a/56887630/12962012.
You can remove the intermediate <div my-comp> by
Creating a TemplateRef object representing the template of the MyComp component inside the MyComp component.
Accessing this TemplateRef object from the structural directive.
Creating an embedded view inside the view container with the TemplateRef object from the structural directive.
The resulting code looks something like:
MyComp component
#Component({
selector: "[my-comp]",
template: `
<ng-template #mytemplate>
<tr>
<td>custom td</td>
</tr>
<ng-template
*ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>
</ng-template>
`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
#ViewChild("mytemplate", { static: true }) mytemplate: TemplateRef<any>;
}
MyDir directive
export class MyDir implements OnInit {
private version: string;
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {}
#Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = compFactory.create(this.viewContainer.injector);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
this.viewContainer.createEmbeddedView(componentRef.instance.mytemplate);
}
}
The resulting HTML looks something like:
<table>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</table>
I've prepared a StackBlitz demo at https://stackblitz.com/edit/table-no-div-wrapper.
Change the selector of your component from [my-comp] to tbody [my-comp] and your will have a <tbody my-comp> instead of a <div my-comp> which would be sufficient if I understood you correctly.
I know there are a few questions similar to this one but they aren't quite the same. I'm building a nested list and I want to display a custom html content in each grandchild along side common html. When I add the to ListComponent outside of the loop works, but if I pass it inside the loop to the inner child, it doesn't work like the example bellow. The html I pass in the in the code bellow isn't shown. I'm probably trying to solve this the wrong way but I couldn't get it to work any way I tried. Any of you guys know how to make this work?
Thanks!
export class Model {
title: string;
children?: Model[] = [];
}
#Component({
selector: 'list',
template: `
<ul>
<li *ngFor="let item of items">
<list-item [item]="item">
<div main-content>
<ng-content select="[main-content]"></ng-content>
</div>
</list-item>
<list [items]="item.children"></list>
</li>
</ul>
`
})
export class List {
#Input() items: Model[];
}
#Component({
selector: 'list-item',
template: `
<h1>{{ item.title }}</h1>
<div class="content">
<ng-content select="[main-content]"></ng-content>
</div>
`
})
export class ListItem {
#Input() item: Model;
}
#Component({
selector: 'app-main',
template: `
<list [items]="items">
<div main-content>
<h1>Test</h1>
</div>
</list>
`
})
export class AppMainComponent {
}
After much testing and going further through the duplicate question that was mentioned and its inner-links, it doesn't quite solve my issue, because the template I'm not trying to duplicate the content as it is in plain. I'm trying to inject into another component with other common html inside it, so if I just iterate through I'll just replicate the the template that I'm passing but I'll lose all other html that is inside.
I know it's a little too late but I've recently encountered a similar problem and an answer here would have helped me.
From what I understood you wish to define the template for your list items in the component in which you are using the list.
To do that you need to use ng-template in both the child component(the list) and also in the grandchild(the item).
The grandchild should be something like this:
#Component({
selector: 'app-item',
template:`
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: data}">
</ng-container>
`
})
export class ItemComponent {
#Input() public data;
#ContentChild('itemTemplate') public itemTemplate;
}
And the child:
#Component({
selector: 'app-list',
template:`
<app-item [data]="dataItem" *ngFor="let dataItem of data">
<ng-template #itemTemplate let-item>
<ng-container *ngTemplateOutlet="itemTemplate1; context: {$implicit: item}">
</ng-container>
</ng-template>
</app-item>
`
})
export class ListComponent {
#Input() public data;
#ContentChild('itemTemplate') public itemTemplate1;
}
And the component that uses the list:
<app-list [data]="data">
<ng-template #itemTemplate let-item>
<h1>--{{ item?.value}}--</h1>
</ng-template>
</app-list>
In the item you use an ng-template received as content and pass it the input as context. Inside this template you can define how the item will look inside of your list and though the context you have access to the data of that item. Because you can't just skip a level, you must also expect an ng-template in the list. Inside the list you define the ng-template for the item and inside that ng-template you are just using the ng-template received from above, through an ng-container. And finally at the highest level you can simply define the template for the item.
I am struggling to find a way to do this. In a parent component, the template describes a table and its thead element, but delegates rendering the tbody to another component, like this:
<table>
<thead>
<tr>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody *ngFor="let entry of getEntries()">
<my-result [entry]="entry"></my-result>
</tbody>
</table>
Each myResult component renders its own tr tag, basically like so:
<tr>
<td>{{ entry.name }}</td>
<td>{{ entry.time }}</td>
</tr>
The reason I'm not putting this directly in the parent component (avoiding the need for a myResult component) is that the myResult component is actually more complicated than shown here, so I want to put its behaviour in a separate component and file.
The resulting DOM looks bad. I believe this is because it is invalid, as tbody can only contain tr elements (see MDN), but my generated (simplified) DOM is :
<table>
<thead>
<tr>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<my-result>
<tr>
<td>Bob</td>
<td>128</td>
</tr>
</my-result>
</tbody>
<tbody>
<my-result>
<tr>
<td>Lisa</td>
<td>333</td>
</tr>
</my-result>
</tbody>
</table>
Is there any way we can get the same thing rendered, but without the wrapping <my-result> tag, and while still using a component to be sole responsible for rendering a table row ?
I have looked at ng-content, DynamicComponentLoader, the ViewContainerRef, but they don't seem to provide a solution to this as far as I can see.
You can use attribute selectors
#Component({
selector: '[myTd]'
...
})
and then use it like
<td myTd></td>
You need "ViewContainerRef" and inside my-result component do something like this:
.html:
<ng-template #template>
<tr>
<td>Lisa</td>
<td>333</td>
</tr>
</ng-template>
.ts:
#ViewChild('template', { static: true }) template;
constructor(
private viewContainerRef: ViewContainerRef
) { }
ngOnInit() {
this.viewContainerRef.createEmbeddedView(this.template);
}
you can try use the new css display: contents
here's my toolbar scss:
:host {
display: contents;
}
:host-context(.is-mobile) .toolbar {
position: fixed;
/* Make sure the toolbar will stay on top of the content as it scrolls past. */
z-index: 2;
}
h1.app-name {
margin-left: 8px;
}
and the html:
<mat-toolbar color="primary" class="toolbar">
<button mat-icon-button (click)="toggle.emit()">
<mat-icon>menu</mat-icon>
</button>
<img src="/assets/icons/favicon.png">
<h1 class="app-name">#robertking Dashboard</h1>
</mat-toolbar>
and in use:
<navigation-toolbar (toggle)="snav.toggle()"></navigation-toolbar>
Attribute selectors are the best way to solve this issue.
So in your case:
<table>
<thead>
<tr>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody my-results>
</tbody>
</table>
my-results ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-results, [my-results]',
templateUrl: './my-results.component.html',
styleUrls: ['./my-results.component.css']
})
export class MyResultsComponent implements OnInit {
entries: Array<any> = [
{ name: 'Entry One', time: '10:00'},
{ name: 'Entry Two', time: '10:05 '},
{ name: 'Entry Three', time: '10:10'},
];
constructor() { }
ngOnInit() {
}
}
my-results html
<tr my-result [entry]="entry" *ngFor="let entry of entries"><tr>
my-result ts
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: '[my-result]',
templateUrl: './my-result.component.html',
styleUrls: ['./my-result.component.css']
})
export class MyResultComponent implements OnInit {
#Input() entry: any;
constructor() { }
ngOnInit() {
}
}
my-result html
<td>{{ entry.name }}</td>
<td>{{ entry.time }}</td>
See working stackblitz: https://stackblitz.com/edit/angular-xbbegx
Use this directive on your element
#Directive({
selector: '[remove-wrapper]'
})
export class RemoveWrapperDirective {
constructor(private el: ElementRef) {
const parentElement = el.nativeElement.parentElement;
const element = el.nativeElement;
parentElement.removeChild(element);
parentElement.parentNode.insertBefore(element, parentElement.nextSibling);
parentElement.parentNode.removeChild(parentElement);
}
}
Example usage:
<div class="card" remove-wrapper>
This is my card component
</div>
and in the parent html you call card element as usual, for example:
<div class="cards-container">
<card></card>
</div>
The output will be:
<div class="cards-container">
<div class="card" remove-wrapper>
This is my card component
</div>
</div>
Another option nowadays is the ContribNgHostModule made available from the #angular-contrib/common package.
After importing the module you can add host: { ngNoHost: '' } to your #Component decorator and no wrapping element will be rendered.
Improvement on #Shlomi Aharoni answer. It is generally good practice to use Renderer2 to manipulate the DOM to keep Angular in the loop and because for other reasons including security (e.g. XSS Attacks) and server-side rendering.
Directive example
import { AfterViewInit, Directive, ElementRef, Renderer2 } from '#angular/core';
#Directive({
selector: '[remove-wrapper]'
})
export class RemoveWrapperDirective implements AfterViewInit {
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit(): void {
// access the DOM. get the element to unwrap
const el = this.elRef.nativeElement;
const parent = this.renderer.parentNode(this.elRef.nativeElement);
// move all children out of the element
while (el.firstChild) { // this line doesn't work with server-rendering
this.renderer.appendChild(parent, el.firstChild);
}
// remove the empty element from parent
this.renderer.removeChild(parent, el);
}
}
Component example
#Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss'],
})
export class PageComponent implements AfterViewInit {
constructor(
private renderer: Renderer2,
private elRef: ElementRef) {
}
ngAfterViewInit(): void {
// access the DOM. get the element to unwrap
const el = this.elRef.nativeElement; // app-page
const parent = this.renderer.parentNode(this.elRef.nativeElement); // parent
// move children to parent (everything is moved including comments which angular depends on)
while (el.firstChild){ // this line doesn't work with server-rendering
this.renderer.appendChild(parent, el.firstChild);
}
// remove empty element from parent - true to signal that this removed element is a host element
this.renderer.removeChild(parent, el, true);
}
}
This works for me and it can avoid ExpressionChangedAfterItHasBeenCheckedError error.
child-component:
#Component({
selector: 'child-component'
templateUrl: './child.template.html'
})
export class ChildComponent implements OnInit {
#ViewChild('childTemplate', {static: true}) childTemplate: TemplateRef<any>;
constructor(
private view: ViewContainerRef
) {}
ngOnInit(): void {
this.view.createEmbeddedView(this.currentUserTemplate);
}
}
parent-component:
<child-component></child-component>