Angular: passing attributes to Dynamic component template - javascript

I have a component created, and I want to pass an attribute though the component to the template.
This is the component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-card-generator',
templateUrl: './card-generator.component.html',
styleUrls: ['./card-generator.component.css'],
inputs: ['id', 'collapseid', 'headingid','collapsehashid']
})
export class CardGeneratorComponent implements OnInit {
bindings: {
headingid:'#?',
collapseid:'#?',
collapsehashid
}
constructor() { }
ngOnInit() {}
and this is the template:
<button class="btn btn-link collapsed" style="text-decoration:none;" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
{{id}}
</button>
</h5>
</div>
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
<div class="card-body"></div>
</div>
created components are placed in home.component this way:
<div id="cardDivContainer" >
<app-card-generator id="Chart 1" collapsehashid="data-target='#collapseOne'" collapseid="aria-controls='collapseOne'" headingid="aria-labelledby='headingOne'"></app-card-generator>
<app-card-generator id="Chart 2" collapsehashid="data-target='#collapseTwo'" collapseid="aria-controls='collapseTwo'" headingid="aria-labelledby='headingTwo'"></app-card-generator>
</div>
I just need to set the "data-target", "aria-labelledby", and the "aria-controls" attributes for each component (depending on the component ID).
I am new to Angular, I hope the above make sense.

You can use #Input to reference the elements within the component. The elements can be passed as parameter outside. Something like:
import {
Component,
OnInit,
Input,
Output,
EventEmitter
} from '#angular/core';
#Component({
selector: 'app-card-generator',
styleUrls: ['./card-generator.component.css'],
templateUrl: './card-generator.component.html'
})
export class CardGeneratorComponent implements OnInit {
#Input() id: string;
#Input() collapseid: string;
#Input() headingid: string;
#Input() collapsehashid: string;
constructor() {
}
ngOnInit() {}
}
If the attributes do not exist in the element you are referencing, you can use attr.attribute and use the {{}} notation
<button class="btn btn-link collapsed" style="text-decoration:none;" type="button" data-toggle="collapse" attr.data-target="{{'#' + collapseid}}" aria-expanded="false" attr.aria-controls="collapseid">
</button>
<div id="{{collapseid}}" class="collapse" attr.aria-labelledby="{{headingid}}" data-parent="#accordionExample">
<div class="card-body"></div>
</div>
And finally you can access the attributes created in your component in the call from the outside
<div id="cardDivContainer" >
<app-card-generator id="Chart 1" collapsehashid="collapseOne" headingid="headingOne"></app-card-generator>
<app-card-generator id="Chart 2" collapsehashid="collapseTwo" headingid="headingTwo"></app-card-generator>
</div>
More #Input and #Output details in this link:
https://stackblitz.com/edit/card-generator-sample?file=src%2Fapp%2Fapp.component.html

Related

Change login button to logout in angular 13

I have a simple list with two buttons. I want to be able to show one or the other depending on whether I'm logged in.
<div>
<li class="nav-item">
<button *ngIf="token === ''" type="button" class="btn btn-dark btn-lg fs-4" (click)="login()">Inicia sesión</button>
<button *ngIf="token != ''" type="button" class="btn btn-dark btn-lg fs-4" (click)="logout()">Cerrar sesión</button>
</li>
</div>
I tried simply putting the ngIf but it doesn't make it instant, besides that since the log in is in another component I don't really know how to change that from there.
this is my component:
import { Component, ElementRef, ViewChild} from '#angular/core';
import { Router } from '#angular/router';
import { faHamburger } from '#fortawesome/free-solid-svg-icons';
import { UsersService } from './services/user.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'my-app';
token = this.userService.getToken();
#ViewChild('clickLogout')clickLogout:ElementRef;
faHamburger = faHamburger;
constructor(public userService: UsersService, public router: Router) { }
logout(){
this.userService.logout();
this.router.navigateByUrl('/login');
}
login(){
this.router.navigateByUrl('/login');
}
}
So as it is, I would need to reload the page every time I need one button or another and I need it to do it instantly when I log in or log out.
try to use that in html,
<div *ngIf="token === ''; else empty">
<button type="button" class="btn btn-dark btn-lg fs-4"
(click)="login()">Inicia
sesión</button>
</div>
<ng-template #empty>
<button type="button" class="btn btn-dark btn-lg fs-4"
(click)="logout()">Cerrar sesión</button>
</ng-template>
in ts call the token in the ngOnInit method and change the route (/login to /logout when you navigate) :
export class AppComponent implements OnInit {
//code...
token=''
ngOnInit(): void {
this.token = this.userService.getToken();}
You should use BehaviorSubject to store the token in the service, add:
public token$ = new BehaviorSubject<string>(null);
then use this.token$.next(valueFromRequest); when you will receive token value and
this.token$.next(null); to reset it.
In html code use ngIf in that way:
<button *ngIf="(userService.token$ | async)" type="button" [...]>Logout</button>
<button *ngIf="!(userService.token$ | async)" type="button"[...]>Login</button>

Need dynamically created buttons to work independently in Angular

In Angular i have dynamically created a group of buttons that all have the same action (to show text when clicked) everything works fine yet when I click one button they all perform the action. I need them to work independently of each other. I have dynamically made a different id for each button and was wondering if there was a way to us the id to have them work independently.
Button HTML and TS files:
<button id="{{index}}" class="btn btn-secondary" style="float: right;" (click)="onClick()">Discription</button>
import { Component, EventEmitter, OnInit, Output, Input } from '#angular/core';
#Component({
selector: 'app-discription-btn',
templateUrl: './discription-btn.component.html',
styleUrls: ['./discription-btn.component.css']
})
export class DiscriptionBtnComponent implements OnInit {
#Output() btnClick = new EventEmitter();
#Input() index!: number
constructor() { }
ngOnInit(): void { }
onClick() {
this.btnClick.emit();
}
}
Button Parent HTML and TS files:
<div class="card mb-4 h-100">
<img class="card-img-top-other" src= "{{ post.link }}" />
<div class="card-body">
<div class="small text-muted"> {{ post.created }} <app-discription-btn (btnClick) = "toggleDiscription()" [index] = index></app-discription-btn> </div>
<h2 class="card-title h4"> {{ post.title }} </h2>
<div *ngIf="showDiscription">
<p class="card-text"> {{ post.summary }} </p>
<a class="btn btn-primary" href="#!">Read More -></a>
</div>
</div>
</div>
import { Component, OnInit, Input } from '#angular/core';
import { Subscription } from 'rxjs';
import { BlogPost } from 'src/app/Post';
import { DiscriptionUiService } from 'src/app/services/discription-ui.service';
#Component({
selector: 'app-other-posts',
templateUrl: './other-posts.component.html',
styleUrls: ['./other-posts.component.css']
})
export class OtherPostsComponent implements OnInit {
#Input() post! : BlogPost
#Input() index! : number;
showDiscription : boolean = false;
subscription : Subscription;
constructor(private discritpionService: DiscriptionUiService) {
this.subscription = this.discritpionService.onToggle().subscribe((value) => (this.showDiscription = value));
}
ngOnInit(): void {
}
toggleDiscription(){
this.discritpionService.toggleDiscription();
}
}
Main HTML and TS files:
<div class="container">
<div class="row">
<div class="col-lg-8"><app-featured-post *ngFor="let post of posts; let i = index;" [post] = "post" [index] = "i"></app-featured-post></div>
<div class="col-lg-4"><app-side-widgets></app-side-widgets></div>
<app-other-posts *ngFor="let post of posts | myFilterPipe:filterargs; let i = index;" [post] = "post" [index] = "i" class="col-lg-4" style="padding-top: 10px;" ></app-other-posts>
<nav aria-label="Pagination">
<hr class="my-0" />
<ul class="pagination justify-content-center my-4">
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">Newer</a></li>
<li class="page-item active" aria-current="page"><a class="page-link" href="#!">1</a></li>
<li class="page-item"><a class="page-link" href="#!">Older</a></li>
</ul>
</nav>
</div>
</div>
import { Component, OnInit } from '#angular/core';
import { BlogPostService } from 'src/app/services/blog-post.service';
import { BlogPost } from '../../Post';
#Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
filterargs = {title: 'The Beginning'}
posts: BlogPost[] = [];
constructor(private postService: BlogPostService ) { }
ngOnInit(): void {
this.postService.getPosts().subscribe((posts) => (this.posts = posts));
}
}
Any ideas would be a great help. Thank you ahead of time!

How to open modal for multiple components from same parent component

I am using angular 9.
I have a home page which has four buttons. I want to open separate modal on each button's click at a time.
I did research a lot, here is my trial and effort.
Thanks in advance :)
parent component
#Component({
selector: 'app-link-budget-view',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
#ViewChild('childOne') callChild_OneFn: ChildOneComponent;
#ViewChild('childTwo') callChild_TwoFn: ChildTwoComponent;
...
ngOnInit(): void {
}
openModalOne() {
this.callChild_OneFn.OpenModalFunction();
}
openModalOne() {
this.callChild_TwoFn.OpenModalFunction();
...
}
}
Home Html
<button class="btn btn-primary mb-2 mr-2" (click)="openModalOne()">Modal 1</button>
<button class="btn btn-primary mb-2 mr-2" (click)="openModalTwo()">Modal 2</button>
...
<app-child-one #childOne></app-child-one>
<app-child-two #childTwo></app-child-two>
...
childOne Component
#Component({
selector: 'app-link-budget-view',
templateUrl: './child-one.component.html',
styleUrls: ['./chile-one.component.scss']
})
export class ChildOneComponent implements OnInit {
constructor( private modalService: NgbModal) { }
ngOnInit(): void {
}
OpenModalFunction() {
console.log("component One function running...")
this.modalService.open('#ModalOneId', { size: 'xl' });
}
...
}
similarly there is a function in component two
OpenModalFunction() {
console.log("component Two function running...")
this.modalService.open('#ModalTwoId', { size: 'xl' });
}
Component One Html
<ng-template #ModalOneId let-modal>
<div class="modal-header">
<h4 class="modal-title">This is modal one</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="card">
<div class=" card-body">
<div id="table" class="table-editable">
...
...
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="modal.close('Close click')">Close</button>
</div>
</ng-template>
similarly ng-templete is used in component two html
you didn't mention if what you wrote is working or not, or what errors you are getting. it looks like it should work to me. Although if you just want to open a modal, you can handle it completely in your html:
<button (click)="childOne.OpenModalFunction()">Modal 1</button>
<button (click)="childTwo.OpenModalFunction()">Modal 2</button>
<app-child-one #childOne></app-child-one>
<app-child-two #childTwo></app-child-two>
that should be the only thing you need in the parent. no code needed in the parent .ts file. if this is not working, there's something wrong with the code in your child class.
Finally I fixed it here is the code to help others
Parent ts
export class ParentComponent implements OnInit {
constructor(private modalService: CustomModalServiceService) { }
ngOnInit(): void {
}
openModal(modalName: string) {
this.modalService.openModalFunction(modalName);
}
}
Parent html
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('childOne')">Modal_1</button>
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('childTwo')">Modal_2</button>
child one or two ts code accordingly for one or two
export class ChildOneComponent implements OnInit {
constructor(private modalRef: NgbActiveModal) { }
ngOnInit(): void {
}
hideModalFunction() {
this.modalRef.close();
}
}
child one or two html for modal
<div class="modal-header">
<h4 class="modal-title">This is modal one</h4>
<button type="button" class="close" aria-label="Close" (click)="hideModalFunction()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="card">
<div class=" card-body">
<div id="table" class="table-editable">
...
...
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="hideModalFunction()">Close</button>
</div>
and you need a custom modal service
Custom modal service
export class CustomModalService {
...
private modalRef: NgbModalRef;
constructor( private modalService: NgbModal) { }
ngOnInit(): void {
}
openModalFunction(modalName: string) {
switch(modalName) {
case 'one':
console.log("component One function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildOneComponent, { size: 'xl' });
this.modalRef.componentInstance.testData = 'test';
break;
case 'two':
console.log("component Two function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildTwoComponent, { size: 'xl' });
break;
case default:
// do nothing
}
}
}
You are repeating too much code here. You could make it more re-usable and less repetitive. Try to extract most of the business logic to a service. Leave the component as dumb(no business logic) as possible for better re-usability.
Parent component
#Component({
selector: 'app-link-budget-view',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
...
constructor(private myModalService: MyModalService)
ngOnInit(): void {
}
openModal(modelName: string) {
this.myModalService.openModalFunction(modalName);
}
}
Home Html
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('one')">Modal 1</button>
<button class="btn btn-primary mb-2 mr-2" (click)="openModal('two')">Modal 2</button>
...
MyModal Service
#Injectable({
providedIn: 'root'
})
export class MyModalService {
...
private modalRef: NgbModalRef;
constructor( private modalService: NgbModal) { }
ngOnInit(): void {
}
openModalFunction(modalName: string) {
switch(modalName) {
case 'one':
console.log("component One function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildOneComponent, { size: 'xl' });
this.modalRef.componentInstance.testData = 'test';
break;
case 'two':
console.log("component Two function running...");
// do any execution before opening child component
this.modalRef = this.modalService.open(ChildTwoComponent, { size: 'xl' });
case default:
// do nothing
}
}
hideModalFunction() {
//do something before closing the modal
this.modalRef.close();
}
}
Now no OpenModalFunction() required inside the child components as you have extracted the modal opening logic into the MyModalService. This will also adhere to the Single Functionality principle means every component has only single functionality to be fulfilled. Furthermore, you have now full control via MyModalService to handle popups. If you want to execute a common code before/after opening/closing the windows like notifying another service, you could do that.
Now this could be further optimized as all software codes do. Like extracting common code from Child Components to a single interface/abstract class. But it totally depends upon your requirements.

Angular2 add HTML to dynamic elements

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>

Angular2 - pass dynamically loaded parameters to function while doing dropdown value selection

Please check that, I am passing the parameters of li tag click function of updateId method in the correct format or not ? The errors in the console shows that line only....
My app.component.html code is
<div class='form-group' style="width: 100%;">
<div class="btn-group" style="width: 100%;">
<button type="button" class="btn btn-default" style="width : 75%;text-align: left;">{{name}}</button>
<button type="button" data-toggle="dropdown" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>
<ul class="dropdown-menu" style="width: 75%;">
<li *ngFor="let result of jsonList" ><a class="dropdown-item" (click)="updateId('{{result.id}}', '{{result.name}}')" >{{result.name}}</a>
</li>
</ul>
</div>
</div>
My app.component.ts is ,
import { HomeService } from '../../services/home.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-home-component',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
id: string;
name : string;
jsonList : any;
constructor(private homeservice: HomeService) { }
ngOnInit() {
this.id = null;
this.name = "SELECT";
this.jsonList= [{"Id" : "F1", "name" : "FONE"}, {"id" : "F2", "name" : "FTWO"}]
}
updateId(id : string, name : string) : void {
this.id = id;
this.name = name;
}
}
It is not working. The error is on the li tag click function method passing the dynamic parameters from the jsonList. So pls help me to get resolve.
Don't use the handlebars {{ }} in event bindings such as the (click) attribute - they will be evaluated against the Component instance automatically. and the single quotes are not needed either. Use it like so:
<li *ngFor="let result of jsonList">
<a (click)="updateId(result.id, result.name)">{{result.name}}</a>
</li>
You don't need {{ }} while specifying arguments to an event handlers (ng-click). The correct syntax would be
<li *ngFor="let result of jsonList">
<a (click)="updateId(result.id, result.name)">{{result.name}}</a>
</li>

Categories

Resources