Using #input and #output between Angular 2 child components - html

I have an ng-table which is a child component of my main page. When a row is clicked, it sends the information in that row via onCellClick using an EventEmitter. I'm trying to send this information to another child component. This happens to be a button which is the child of a Bootstrap 4 modal which pops up when a button on the main page is clicked. Just having trouble with the receiving and manipulation of that information.
HTML of child component table:
<ng-table [config]="config"
(tableChanged)="onChangeTable(config)"
(cellClicked)="onCellClick($event)"
[rows]="rows" [columns]="columns">
</ng-table>
HTML for the child component (this appears in the main page's HTML):
<app-datatable (row)="received($event)"></app-datatable>
Typescript for getting and sending the row's data (this.row is the EvenEmitter. data.row is the actual row that's clicked on):
#Output() row: EventEmitter<any> = new EventEmitter<any>();
public onCellClick(data: any): any {
let d = data.row.tDataPoint;
let i = data.row.tICCP;
let s = data.row.tStartDate;
let e = data.row.tEndDate;
let toSend:DataTable = new DataTable(d, i, s, e);
this.row.emit(toSend);
}
HTML for the button that is the child component of the Bootstrap 4 modal:
<button type="submit" class="btn" data-dismiss="modal" (click)="onClick($event)">Delete</button>
Typescript for the button child component:
selector: 'deletebutton'
#Input() receivedRow:DataTable;
onClick(message:DataTable){
this.sender.emit('This is from On Click Deletebutton');
console.log("On Click Deletebutton");
console.log(this.receivedRow);
for (let entry in DPS){
if (DPS[entry].tDataPoint===message.tDataPoint){
DPS.splice(parseInt(entry),1);
}
}
}
HTML of the button child component (this appears in the modal's HTML). This is what should actually be receiving the data from the clicked row as input.
<deletebutton [receivedRow]='row'></deletebutton>
Right now in my onClick method is saying receivedRow is undefined. I feel like what is missing is the coordination between [receivedRow]='row' where I have my deletebutton HTML and the onClick function call in the HTML for that child component. Overall, I just want to click a row, click the button to open the delete Boostrap Modal, and have the correct row be deleted I click the Delete button inside the modal. Let me know if something's not clear or more code is needed.
Is there actually a way to communicate between child components like this using #Input and #Output?

With angular2, your data flow should be :
- down to pass data
- up to send events
So if you really want to go this way, you should have something like that :
I think there's a better way tho :
For your app AND for your user, it'd be best to have a remove button on each line. This way, it avoid the user to be confused clicking on a row and then click on a remove button and within your code you'll be able to do something like that :
src/app.html :
<table class="table">
<tr *ngFor="let row of tableData">
<td *ngFor="let column of row.columns">
{{ column.name }}
</td>
<td (click)="deleteRow(row)"><button>X</button></td>
</tr>
</table>
<button (click)="addRow()">Add a row</button>
src/app.ts (troncated here to the class only) :
#Component({
selector: 'app',
templateUrl: `./src/app.html`,
})
export class App {
private tableData;
private cptRow = 1;
constructor() {
this.tableData = [
{
idRow: `idR${this.cptRow++}`,
columns: [
{idColumn: 'idR1C1', name: 'Column 1-1'},
{idColumn: 'idR1C2', name: 'Column 1-2'},
{idColumn: 'idR1C3', name: 'Column 1-3'}
]
},
{
idRow: `idR${this.cptRow++}`,
columns: [
{idColumn: 'idR2C1', name: 'Column 2-1'},
{idColumn: 'idR2C2', name: 'Column 2-2'},
{idColumn: 'idR2C3', name: 'Column 2-3'}
]
},
{
idRow: `idR${this.cptRow++}`,
columns: [
{idColumn: 'idR3C1', name: 'Column 3-1'},
{idColumn: 'idR3C2', name: 'Column 3-2'},
{idColumn: 'idR3C3', name: 'Column 3-3'}
]
}
];
}
deleteRow(row) {
// we can do this by reference ...
// this.tableData = this.tableData.filter(r => r !== row);
// or by ID
this.tableData = this.tableData.filter(r => r.idRow !== row.idRow);
}
addRow() {
this.tableData.push({
idRow: `idR${this.cptRow}`,
columns: [
{idColumn: `idR${this.cptRow}C1`, name: `Column ${this.cptRow}-1`},
{idColumn: `idR${this.cptRow}C2`, name: `Column ${this.cptRow}-2`},
{idColumn: `idR${this.cptRow}C3`, name: `Column ${this.cptRow}-3`}
]
});
this.cptRow++;
}
}
Here's a working Plunkr : http://plnkr.co/edit/hNhcdraoDNnI2C92TQvr?p=preview
Now, if you really want to use input/output properties, you should look for tutorials because the structure here seems a bit confused. I can help you to understand that (and it's important to understand it with angular2 !) but maybe you should give me a shout on Gitter/Angular instead of detailing Angular2 flow here :)

Somewhat of a work around is to place the Delete button component in the HTML for the table component like this:
<ng-table [config]="config"
(tableChanged)="onChangeTable(config)"
(cellClicked)="onCellClick($event)"
[rows]="rows" [columns]="columns">
</ng-table>
<deletebutton [receivedRow]='toSend'></deletebutton>
And still leave the table's tag in the main page's HTML like I had it:
<app-datatable (row)="received($event)"></app-datatable>
And now the row's data is being sent to that Delete button since it is technically a part of the child component of the main page.
Still not able to communicate between child components like I asked in my question though. But this is something close that works.

Related

Angular/Typescript Text with routerLink

Updated Question for more Clarity:
Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate
Also, How to add DomSanitizer from #ViewChild/ElementRef
Added all example in below code
Here is the updated stackblitz code
As shown in screenshot from angular.io some texts and some links
Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.
First:
You are not importing RouterModule
solution:
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([]) // this one
]
Second:
You can't bind Angular events through innerHTML property
fix:
Make use of #ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from
<div id="box" [innerHTML]="shouldbedivcontent" ></div>
to
<div #box id="box"></div>
Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:
#ViewChild('box') container: ElementRef;
Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.
export class AppComponent implements AfterViewInit {
and
ngAfterViewInit() {
this.container.nativeElement.innerHTML = this.shouldbedivcontent;
this.container.nativeElement.addEventListener('click',
() => this.goto('bar')
);
}
change shouldbedivcontent property from:
'1) this is a click
<a (click)="goto("bar")">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto("bar")">Click</a><br>
3)This only works with href
bar and test'
to
'1) this is a click
<a id="link_1">Click</a><br>
2)this is with routerlink
<a [routerLink]="" (click)="goto(\'bar\')">Click</a><br>
3)This only works with href
bar and test'
And even so you'd still not get the default anchor style unless you apply some styling yourself.
Third
You are not HTML sanitizing, which could be dangerous. read more here
MY SUGGESTION:
Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!
Move your html to your app.component.html:
<div id="box">
1) this is a click
<a (click)="goto('bar')">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto('bar')">Click</a><br>
3)This only works with href
bar and test
</div>
<p>Below is actual content</p>
You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.
EDIT:
Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.
Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):
public jsonHTML = [
{
tagName: '',
text: 'some text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'bar',
attributes: {
value: 'bar' // goto parameter
}
},
{
tagName: '',
text: ' some more text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'foo',
attributes: {
value: 'foo' // goto parameter
}
}
]
Once you have it, it's way easier to create all of those elements dynamically:
this is for the code in your Q1:
Inject Renderer2 with private r2: Renderer2
And replace the Q1 related code in AfterViewInit hook to:
const parent = this.r2.createElement('div'); // container div to our stuff
this.jsonHTML.forEach((element) => {
const attributes = Object.keys(element.attributes);
const el = element.tagName && this.r2.createElement(element.tagName);
const text = this.r2.createText(element.text);
if (!el) { // when there's no tag to create we just create text directly into the div.
this.r2.appendChild(
parent,
text
);
} else { // otherwise we create it inside <a></a>
this.r2.appendChild(
el,
text
);
this.r2.appendChild(
parent,
el
);
}
if (attributes.length > 0) {
attributes.forEach((name) => {
if (el) {
this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
if (name === 'value') {
this.r2.listen(el, 'click', () => {
this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
})
}
} else {
throw new Error('no html tag specified as element...');
}
})
}
})
this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point
Good Luck!
You have a css problem.
looks like a link
<a [routerLink]="something"></a> looks like a link, because if you inspect the HTML it actually gets an href property added because of routerLink
<a (click)="goTo()"></a> does NOT look like a link, because there is no href
Chrome and Safari default user agents css will not style <a> without an href (haven't confirmed Firefox but I'm sure its likely). Same thing for frameworks like bootstrap.
Updated stackblitz with CSS moved to global, not app.css
https://stackblitz.com/edit/angular-ivy-kkgmkc?embed=1&file=src/styles.css
This will style all links as the default blue, or -webkit-link if that browser supports it. It should be in your global.css file if you want it to work through the whole app.
a {
color: rgb(0, 0, 238);
color: -webkit-link;
cursor: pointer;
text-decoration: underline;
}
this works perfectly for me :D
#Directive({
selector: "[linkify]",
})
// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
#Input()
appStyle: boolean = true;
constructor(
private router: Router,
private ref: ElementRef,
#Inject(PLATFORM_ID) private platformId: Object
) {}
#HostListener("click", ["$event"])
onClick(e: any) {
e.preventDefault();
const href = e.target.getAttribute("href");
href && this.router.navigate([href]);
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
const href = a.getAttribute("href");
href &&
this.appStyle &&
a.classList.add("text-indigo-600", "hover:text-indigo-500");
});
}
}
}
HOW I USE IT
<p linkify
class="mt-3 text-lg text-gray-500 include-link"
[innerHtml]="apiSectionText"
></p>
result

Changing values of a counter variable in Angular

I have 2 buttons on my login page, Button1 and Button2. Both the buttons direct to the same URL page. But on clicking Button 2, I want to disable the functionality of Button 3 which is on the next URL page.
Button 3 should be accessed only when Button 1 was clicked on the main page.
Here's the HTML code of the main page. Button 1 is a part of the ngForm.
<button class="btn btn-primary" id="alert" type="submit">Login</button>
<button class="btn-primary" routerLink="/login/olduser" id="logins">Patient Login</button>
Here's the HTML code of Second page.
<button class="btn btn-primary" style ='margin-left: 700px;'routerLink="../../login/newuser">Register a new patient </button>
One possible solution I thought of was exporting a counter variable from the main page to the second page on clicking Button 2, which will inform to disable Button 3, but I failed to do so.
How can I implement this functionality?
Here's what I have tried till now :
<button class="btn-primary" (Click)="newUser()" id="logins">Patient Login</button>
public newUser(){
var status="success";
console.log(status);
this.router.navigateByUrl('/login/olduser');
}
I'm trying to print the value of "status" on console, to check if the method is being accessed but there's no output on console and also the url doesn't change.
I want to call this "status" variable in olduser.ts script.
It seems you're trying to limit the functionality of some sort of dashboard depending on user type (patient, non-patient).
I don't think you should rely on a referrer button at all here.
I'd send something like a list of permissions for user to client app after logging in and wrap it in a AuthorizationService of some kind. Then I'd check if the user has the permission to register a new patient and show/hide the corresponding button.
Of course, you shouldn't forget about server-side validation for registration requests.
UPD: if one of the user types doesn't distinguish between users and doesn't require server-side authentication, you can just generate some kind of default set of permissions in the service for those non-privileged users and keep the display logic for page 2 based on permission checks.
On Click of button pass a query parameter. Then on the next page read the value of the query parameter from URL and disable the button 3 based on the value.
Working Demo
Homepage HTML
<a routerLink='/page1' [queryParams]="{button: 'a'}"><button>button 1</button></a>
<a routerLink='/page1' [queryParams]="{button: 'b'}"><button>button 2 </button></a>
<router-outlet></router-outlet>
In the routed component .TS
import { Component, Input, OnInit } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "hello",
template: `
<h1>Hello {{ name }}!</h1>
<button [disabled]="isDisable">button3</button>
`,
styles: [
`
h1 {
font-family: Lato;
}
`
]
})
export class HelloComponent implements OnInit {
#Input() name: string;
isDisable: boolean;
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => {
this.isDisable = params.button === "a";
});
}
}
You can send the state of the button as a query parameter on button 2 click. Now, On the new page get the query params value and then apply property binding.
On Button 2 click :
this.router.navigate(['/newpage'], { queryParams: { state: "false"});
Now, on new page add as below :
import { ActivatedRoute } from '#angular/router'
export class newPage implements OnInit {
btnState
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.queryParams
.filter(params => params.state)
.subscribe(params => {
this.btnState = params
});
}
Now,apply property binding to the button
<button [disabled]="btnState">Button3</button>
There is multiple ways to achieve what you try to do:
With the click on Button 2, you can store in a service, a variable isActive to false and in your next url/Component, check from the service the variable to disabled or not your Button 3.
Navigate to your url with a params: my-new-url?ACTIVE=false, and in your new url/component, check the url to find the Params and disable your button according to the value

Angular 6: How to build a simple multiple checkbox to be checked/unchecked by the user?

I am writing this post after having read several threads concerning this topic but no one of them gives me what I need. This post seems to have the solution but I do not have to read the checked values from the json.
All I need is to:
read countries from an array of objects
build dinamically a list of checkbox representing each country
user should check and uncheck each checkbox
bonus:
get the value of the checked input and send it outside the component
I know It might be really dumb to do but all I have accomplished untile now is to have a list of uncheckable checkboxes and nothing more.
Here is the code:
Template:
<div class="form-group">
<div *ngFor="let country of countries">
<input type="checkbox"
name="countries"
value="{{country.id}}"
[(ngModel)]="country"/>
<label>{{country.name}}</label>
</div>
</div>
And TS:
countries = [
{id: 1, name: 'Italia'},
{id: 2, name: 'Brasile'},
{id: 3, name: 'Florida'},
{id: 4, name: 'Spagna'},
{id: 5, name: 'Santo Domingo'},
]
I tried to use the reactive forms but that gave me more issues then template driven (surely because of bad implementation of mine).
Please, help me, I do not know where to bump my head anymore
Here is a working example, where you can observe that an additional 'checked' value is added to each country, and bound to the value of each checkbox with [(ngModel)].
Stackblitz live example
template:
<p>
Test checkboxes
</p>
<div *ngFor="let country of countries; let i = index;">
<input type="checkbox" name="country{{country.id}}" [(ngModel)]="countries[i].checked">
<label for="country{{country.id}}">{{country.name}}</label>
</div>
<button type="button" (click)="sendCheckedCountries()" *ngIf="countries">Click to send the selected countries (see your javascript console)</button>
<p *ngIf="!countries">loading countries, please wait a second...</p>
<p *ngIf="countries">Debug info : live value of the 'countries' array:</p>
<pre>{{ countries | json }}</pre>
component :
//...
export class AppComponent implements OnInit {
public countries: Country[];
constructor(private countryService: CountryService) {}
public ngOnInit(): void {
// loading of countries, simulate some delay
setTimeout(() => {
this.countries = this.countryService.getCountries();
}, 1000);
}
// this function does the job of sending the selected countried out the component
public sendCheckedCountries(): void {
const selectedCountries = this.countries.filter( (country) => country.checked );
// you could use an EventEmitter and emit the selected values here, or send them to another API with some service
console.log (selectedCountries);
}
}
To use some proper TypeScript, I made an interface Country :
interface Country {
id: number;
name: string;
checked?: boolean;
}
I hope you get the idea now.
Note : the checked value is not "automatically there" at the beginning, but it doesn't matter.
When not there, it is the same as undefined, and this will be treated as false both in the checkbox and in the function that reads which country is checked.
For the "sending value" part :
The button will output the selected value to the browser's console, with some filter similar to what #Eliseo's answer suggests (I just used full country objects instead of ids)
For "real usecase" situation, you could use Angular's EventEmitters and have your component "emit" the value to a parent component, or call some service function that will make a POST request of your values to another API.
Your countries like
{id: 1, name: 'Italia',checked:false},
Your html like
<div *ngFor="let country of countries">
<input type="checkbox" [(ngModel)]="country.checked"/>
<label>{{country.name}}</label>
</div>
You'll get an array like, e.g.
[{id: 1, name: 'Italia',checked:false},{id: 2, name: 'Brasile',checked:tue}..]
you can do
result=this.countries.filter(x=>x.checked).map(x=>x.id)
//result becomes [2,...]
I had an error using [(ngModel)]
In case it serves anyone, I have solved the problem changing
[(ngModel)]
to:
[checked]="countries[i].checked" (change)="countries[i].checked= !countries[i].checked"

Angular 4 - How to add a material design HTML element into a file with Angular 4

I'm attempting to insert a md-button-toggle into a button toggle group programatically with Angular but Create Element doesn't seem to work well with non standard HTML tags.
HTML snippet i'm attempting to append:
<md-button-toggle-group class="toggle-box" [vertical]="true" id="button-toggle-group">
<dash-theme (onSelected)="setTheme($event);sidenav.close()"></dash-theme>
<md-button-toggle routerLink="/admin/dashboard" (click)=sidenav.close()>
Admin Dashboard
</md-button-toggle>
</md-button-toggle-group>
Angular attempt:
// adds a View Dashboard button to the side nav bar when
addViewDashboard(): void {
var node = document.createElement('<md-button-toggle routerLink="/' + this.dashboardName
+ '" (click)=sidenav.close() id="view-dashboard-button"> View Dashboard </md-button-toggle>');
document.getElementById("button-toggle-group").appendChild(node);
}
(this.dashboardName is just a string which will be passed into the router)
I realize CreateElement isn't supposed to work like this and can't really think of how else I can manage to do this.
The end product should be something like:
<md-button-toggle-group class="toggle-box" [vertical]="true" id="button-toggle-group">
<dash-theme (onSelected)="setTheme($event);sidenav.close()"></dash-theme>
<md-button-toggle routerLink="/admin/dashboard" (click)=sidenav.close()>
Admin Dashboard
</md-button-toggle>
<md-button-toggle routerLink="/[this.dashboardName Value]" (click)=sidenav.close() id="view-dashboard-button">
View Dashboard
</md-button-toggle>
</md-button-toggle-group>
I would just use an array to represent the button toggle info...
<md-button-toggle-group class="toggle-box" [vertical]="true" id="button-toggle-group">
<dash-theme (onSelected)="setTheme($event);sidenav.close()"></dash-theme>
<md-button-toggle *ngFor="let b of buttons" routerLink="b.route" (click)="b.clickAction()">{{b.title}}</md-button-toggle>
</md-button-toggle-group>
Here's an example of the component property you can use to drive the template.
buttons: any = [
{ title: 'button 1', route: '/admin/dashboard', clickAction: () => { alert('action 1'); } },
{ title: 'button 2', route: '/[this.dashboardName Value]', clickAction: () => { alert('action 2'); } }
]

Simply showing a component Only if another component is showing - Ext JS

So I have a hidden container item:
id: 'category_search', hidden: true, ...
And another hidden panel:
{ xtype: 'panel', id: 'mylist', hidden: true ...
Here i have a controller to show category search ONLY when mylist is Not hidden - handled by the click of a button categorized_search:
catSearch: function() {
var grid = Ext.getCmp('mylist');
if(grid.isHidden){ //checking to see if the component is hidden
console.log('Please enter a search');
}
else
{
Ext.getCmp('category_search').show(); //Shows category search
}
}
When I click my categorized_search button, it does not display when mylist is showing, and will display when mylist is not showing. How can I fix this?
Cheers!
AbstractComponent.isHidden() and AbstractComponent.isVisible() are functions, not properties. Add parentheses to your if statement.
http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.AbstractComponent-method-isHidden