I have an angular component template like this.
<my-app>
<nav id="nav-bar">
<ul>
<li id="item-1"></li>
<li>
<div id="item-2"></div>
</li>
<li id="item-3"></li>
<div id="middle-div">
</div>
<div id="side-div">
</div>
</nav>
</my-app>
Now here I want to take the div with id, 'item-2' and append it to nav-bar so that it appears below the div with id "side-div".
I can easily do this with javascript's appendChild function like this.
var item = document.getElementById("item-2");
var nav = document.getElementById("nav-bar");
//change position of searchform in DOM
nav.appendChild(item);
However, I want to be able to do this in component's typescript file. Is ElementRef an ideal solution to this? or is there any other way to achieve this?
the component looks like this:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: '/app/Templates/nav.html',
providers: [MyService]
})
export class MyComponent implements OnInit {
constructor(){
}
ngOnInit(){
}
}
You can use appendChild using ElementRef. Here is an example
#Component({
selector: 'app',
template: `
<div>
<button type="button" (click)="appendToChild()">Append Item to Child</button>
<div #item>Item</div>
<div #appendToChildEl></div>
</div>
`
})
export class App {
#ViewChild('item') item: ElementRef;
#ViewChild('appendToChildEl') appendToChildEl: ElementRef;
appendToChild() {
this.appendToChildEl.nativeElement.appendChild(this.item.nativeElement);
}
}
A plunker example:
https://embed.plnkr.co/7DJ1nWN6sl563hcR71FV/
You could also do something like this if you don't want to use ElementRef
User a service or component variable to control the position:
<my-app>
<nav id="nav-bar">
<ul>
<li id="item-1"></li>
<li>
<div id="item-2" *ngIf="defaultPos"></div>
</li>
<li id="item-3"></li>
<div id="middle-div">
</div>
<div id="side-div">
</div>
<div id="item-2" *ngIf="newPos"></div>
</nav>
</my-app>
Related
I cannot make this eventemitter work. Can you please help? I am a beginner and it should be quite simple code for you.
I have a parent component, reading two different emitters from two different children:
<app-van [vans]="vans"></app-van>
<app-modal *ngIf="modalOpen" (closed)="onClick()" (openModal)="onClickTwo($event)"></app-modal>
import { Component, OnInit } from '#angular/core';
import { Van } from '../../interface';
#Component({
selector: 'app-fleet-home',
templateUrl: './fleet-home.component.html',
styleUrls: ['./fleet-home.component.css']
})
export class FleetHomeComponent implements OnInit {
modalOpen = true;
vans: Van [] = [
{ name: 'Ubeddu', description: 'Mercedes Sprinter', plate: 'NH55GKA' },
{ name: 'Abbestia', description: 'Ford Transit', plate: 'DK66HHR' },
{ name: 'Eumulu', description: 'Citroen Berlingo', plate: 'DR55MKL' }
];
constructor( ) { }
ngOnInit() {
}
onClick() {
this.modalOpen = !this.modalOpen;
console.log('modalOpen changed');
}
onClickTwo(event) {
this.modalOpen = event;
console.log('modalOpen changed');
}
}
the parent listened to this child:
<div (click)="onCloseClick()" class="ui dimmer visible active">
<div (click)="$event.stopPropagation()" class="ui modal visible active">
<div class="asuca">
<form class="ui form" >
<h4 class="ui dividing huge header">Van</h4>
<div class="required field">
<label class="ui header">Van Name</label>
<input type="text" placeholder="Van NickName">
</div>
<div class="field">
<label class="ui header">Description</label>
<input type="text"placeholder="Description">
</div>
<div class="field">
<label class="ui header">Plate</label>
<input type="text"placeholder="License Plate">
</div>
<button (click)="onCloseClick()" class="ui button" type="submit">Submit</button>
</form>
</div>
</div>
</div>
import { Component, OnInit, ElementRef, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {
#Output() closed = new EventEmitter();
constructor(private el: ElementRef) { }
ngOnInit() {
document.body.appendChild(this.el.nativeElement);
}
// tslint:disable-next-line: use-lifecycle-interface
ngOnDestroy() {
this.el.nativeElement.remove();
}
onCloseClick() {
this.closed.emit();
}
}
and doesnt listen to the second child:
<div class="ui fluid four black cards">
<div *ngFor="let van of vans" class="card">
<div class="content">
<div class="header">
{{ van.name }}
</div>
<div class="meta">
{{ van.description }}
</div>
<div class="description">
{{ van.plate }}
</div>
</div>
<div class="extra content">
<div class="ui two buttons">
<div (click)="onEditClick(true)" class="ui basic black button">Edit</div>
<div class="ui basic red button">Delete</div>
</div>
</div>
</div>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-van',
templateUrl: './van.component.html',
styleUrls: ['./van.component.css']
})
export class VanComponent implements OnInit {
#Input() vans = [];
#Output() openModal = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
onEditClick(event: boolean) {
this.openModal.emit(event);
}
}
the whole thing is to hide the modal clicking around the screen and show it again clicking a button.
On the console.log, the object emitter by the first child has got an observer, where the object emitter by the second child as none; no idea what means though.
thanks in advance for the help. I can provide the whole folder if needed. I am just trying to learn :)
Seems like you have missed binding the output event in parent template. Please correct like below:
<app-van [vans]="vans" (openModal)="onEditClick($event)"></app-van>
I have a component whose template allows for 2 content areas: Text and "read more" text. If the consumer of the component adds the area for the "read more" text, I want to show the "read more" link the end-user would click to show the text. If they don't include/need any "read more" text I don't want to show the link.
How do I detect the presence of the template area, and act accordingly with an ngIf?
For example, the html might be:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<div readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</div>
</app-promohero-message-unit>
Obviously, they might not need readmoretext, so if they've omitted it I should not show the readmore link.
The component code is, so far:
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
readMore = false;
showReadMore() {
this.readMore = true;
}
}
In Angular 8 you dont have to use the ngAfterViewInit life cycle hook. You can use the ngOnInit as long as you set the "static" value of the viewchild to true.
import { Component, OnInit, ViewChild, TemplateRef, ElementRef } from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
#ViewChild('content', { read: ElementRef, static: true }) content: ElementRef;
constructor() { }
ngOnInit() {
console.log(!!this.content.nativeElement.innerHTML); // return true if there is a content
}
}
Note that you must wrap the ng-content directive with html tag (such as div, span etc) and to set the templateRef on this outer tag.
<div #content>
<ng-content></ng-content>
</div>
I putted it on stackblitz: https://stackblitz.com/edit/angular-8-communicating-between-components-mzneaa?file=app/app.component.html
You can get a reference to the ng-content (Template Variable) and then access that variable in your component to check the length on the content of that ng-content using ViewChild
Then you can use the ngAfterViewInit life cycle hook to check for ng-content length
Your code will be like this:
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content #readMoreContent select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
#ViewChild('readMoreContent') readMoreContent: ElementRef;
readMore = false;
ngAfterViewInit() {
if (this.readMoreContent.nativeElement.childNodes.length.value == 0){
this.readMore = false
}
}
showReadMore() {
this.readMore = true;
}
}
You can use the ContentChild decorator for this, but will need to use an ng-template with a defined id as your content:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<ng-template #readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</ng-template>
</app-promohero-message-unit>
Then in your component, you can use the ContentChild annotation like this:
export class PromoheroMessageUnitComponent {
#ContentChild('readmoretext')
readMoreContent: TemplateRef<any>;
// ...snip
}
Then finally in the HTML for your component:
<!-- snip -->
<p class="text-white" *ngIf="readMoreContent">
<ng-container *ngTemplateOutlet="readMoreContent"></ng-container>
</p>
I have 3 components, they are nested.
But the ID on the last child is not rendering using id = {{myId}}.
I tried [id]=myID as well.
Something like this:
GrandFather:
<app-middle-section #middleSection
myTitle="Title"
myID="my_id_creation" >
</app-middle-section>
Father:
<div class="section rounded">
<div class="title">{{myTitle}}</div>
<app-list #glist
myID= {{myID}}
</app-list>
</div>
Child
<div class="">
<div id="{{myID}}" class=""></div>
</div>
EDIT:
If I use console log on the last component it prints my "my_id_creation".
For the Father and Child I'm using the:
#Input() myID: string;
Are you getting an error? In any case, your father and child components need to define those id's as input, something along the lines of this:
#Component({
selector: 'father-component',
template: `
<div class="section rounded">
<div class="title">{{myTitle}}</div>
<app-list #glist
[id]="id"
</app-list>
</div>
`
})
export class FatherComponent {
#Input() id: number;
}
I have this code:
import { Component, ElementRef, Renderer2 } from '#angular/core';
#Component({
selector: 'my-app',
template: '<button (click)="runR()">Run</button>
<div class="testme">
<div class="somediv">
<div class="dynamically_created_div unique_identifier"></div>
<div class="dynamically_created_div unique_identifier"></div>
<div class="dynamically_created_div unique_identifier"></div>
</div>
</div>',
})
export class AppComponent{
hostEl: any;
constructor(
private el:ElementRef,
private renderer:Renderer2,
) {
this.hostEl = el.nativeElement;
}
runR(){
let change_this;
change_this= this.renderer.createElement('span');
this.renderer.addClass(change_this, 'change_this');
this.renderer.appendChild(this.hostEl, change_this);
}
}
Is there any way in Angular2 to add HTML to the .dynamically_created_div?
Because the above only adds to the end of the HTML of the component.
I also tried with:
import { Component, ElementRef, ViewChild, Renderer, AfterViewInit } from '#angular/core';
#Component({
selector: 'my-app',
template: `<button (click)="runR()">Run</button>
<div class="testme">
<div class="somediv">
<div class="dynamically_created_div">
</div>
</div>
</div>
`,
})
export class AppComponent {
constructor(private renderer:Renderer) {}
runR() {
#ViewChild('dynamically_created_div') d1:ElementRef;
this.renderer.invokeElementMethod(this.d1.nativeElement, 'insertAdjacentHTML', ['beforeend', '<div class="new_div">new_div</div>'] );
}
}
But it's not working because the #ViewChild directive must be outside the function and I can't have control over it anymore
I also tried like this:
<div class="dynamically_created_div" [innerHtml]="newHTML"></div>
this.newHTML = '<div class="new_div">new_div</div>';
Thing I cannot do because my content is dynamic and uses unique IDs and I cannot use [innerHtml] dynamically ( it only works for what I put in themplate for the first time, then anything else that changes can't use innerHtml anymore.
I checked Angular2: Insert a dynamic component as child of a container in the DOM but there is the same problem, the placeholders aren't dynamic
UPDATE:
My code is a little bit more complex:
TS:
import { AfterContentInit, Component, OnInit, OnDestroy, ViewEncapsulation } from '#angular/core';
import { NgForm, FormsModule, ReactiveFormsModule, FormGroup, FormControl, FormBuilder, Validators } from '#angular/forms';
import { SFService } from '../services/sf.service';
import { Injectable, Pipe, PipeTransform } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
providers: [ SFService ],
})
export class AppComponent implements OnInit {
constructor(
private sfservice: SFService,
) {}
ngOnInit(){
this.sfservice.getMembers().subscribe(members => {
this.members = members.members;
});
}
members: Member[];
member_selector: Member[];
member_each: Member;
member_selector_each: Member[];
cases: Case;
runR(){
this.members.forEach(member_each => {
this.member_selector.forEach(member_selector_each => {
if(member_each.Id === member_selector_each.Id){
console.log(member_selector_each.Id);
this.sfservice.getCaseHistory(member_selector_each.Id, "2017-04-25T00:00:00", "2017-04-28T23:59:59").subscribe(cases => {
this.member_each['cases'] = cases;
console.log(this.member_each);
});
}
})
})
}
}
HTML:
<form #myForm="ngForm" novalidate>
<select name="member_selector_name" [(ngModel)]="member_selector" multiple ng-model="selectedValues" style="height:200px;">
<option *ngFor="let member of members" [ngValue]="member">{{member.Name}}</option>
</select>
<button (click)="runR()">Run</button>
</form>
<div id="results">
<div *ngFor="let mem of members" class="member-card-{{mem.Id}}">
<div class="card-container">
<div *ngFor="let case of mem.Cases" class="case-card" id="{{case.Id}}">{{case.Number}}
</div>
</div>
</div>
</div>
I was trying to use only ngFor but now I get
Cannot set property 'cases' of undefined
What's the problem with this approach?
export class AppComponent{
#ViewChild('d1') d1:ElementRef;
#ViewChild('d2') d2:ElementRef;
#ViewChild('d3') d3:ElementRef;
constructor(private renderer:Renderer2) { }
runR(){
let change_this;
change_this= this.renderer.createElement('span');
this.renderer.addClass(change_this, 'change_this');
this.renderer.appendChild(this.d1, change_this);
}
}
Template:
<div class="dynamically_created_div unique_identifier" #d1></div>
<div class="dynamically_created_div unique_identifier" #d2></div>
<div class="dynamically_created_div unique_identifier" #d3></div>
you can use ngfor and create you elements inside it and using index you can create different ids and names.
I do something like this i dont know if you want to do the same but here's my code to create some input's dynamically and add or access their values
<div *ngFor="let comp of templateVals | async;let i=index">
<md-input-container class="example-90" *ngIf="comp.type=='code'">
<textarea rows="4" mdInput name="desc{{i}}" [(ngModel)]="comp.data" placeholder="Description"></textarea>
</md-input-container>
<md-input-container class="example-90" *ngIf="comp.type=='text'">
<textarea rows="4" mdInput name="text{{i}}" [(ngModel)]="comp.data" placeholder="Text"></textarea>
</md-input-container>
<md-input-container class="example-90" *ngIf="comp.type=='title'">
<input mdInput name="title{{i}}" [(ngModel)]="comp.data" placeholder="Title">
</md-input-container>
<span class="example-90" *ngIf="comp.type=='upload'">
<input-file *ngIf="!comp.data" [acceptId]="comp.id" (onFileSelect)="addedFileInfo($event)"></input-file>
<span *ngIf="comp.data">{{comp.data}}</span>
</span>
<span class="example-10">
<button md-mini-fab (click)="removeThis(comp)"><md-icon>remove circle</md-icon></button>
</span>
</div>
Working through an Angular2 tutorial. The challenge was to create a 'tweet' component that used a 'tweet' service to retrieve its posts.
The service is to return an array of hard-coded tweets. This is just for the sake of exercise; obviously not efficiency. However, the service can only return an array of strings so I am not sure how to return an array of HTML that can then be rendered as HTML rather than text in the view. I've read up and seen that there are of course better ways of accomplishing this than through a service, perhaps through a directive. However this is what the challenge is.
This is what I've come up with so far. The view renders the array of tweets from the service as text. Everything else works fine as I tested it with the service returning an array of strings which rendered as planned.
app.component.ts:
import {Component} from 'angular2/core';
import {TweetComponent} from './tweet.component'
#Component({
selector: 'my-app',
template: `
<tweet></tweet>
`,
directives: [TweetComponent]
})
export class AppComponent {
}
tweet.component.ts:
import {Component} from 'angular2/core'
import {TweetService} from './tweet.service'
#Component ({
selector: 'tweet',
template: `
<li *ngFor="#tweet of tweets">
{{tweet}}
</li>
`,
providers: [TweetService]
})
export class TweetComponent {
tweets;
constructor(tweetService: TweetService){
this.tweets = tweetService.getTweets();
}
}
tweet.service.ts:
export class TweetService {
getTweets() {
return [`
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="http://lorempixel.com/100/100/people/?1" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Tweet 1<br>Media Title 1?<br><like></like></h4>
</div>
</div>
`,
`
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="http://lorempixel.com/100/100/people/?2" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Tweet 2<br>Media Title 2<br><like></like></h4>
</div>
</div>`
];
}
}
In tweet.component.ts use innerHTML:
import {Component} from 'angular2/core'
import {TweetService} from './tweet.service'
#Component ({
selector: 'tweet',
template: `
<li *ngFor="#tweet of tweets">
<span [innerHTML]="tweet"></span>
</li>
`,
providers: [TweetService]
})
by the way, if you are using more recent angular2 version you should change
<li *ngFor="#tweet of tweets">
to
<li *ngFor="let tweet of tweets">