Target a template reference variable from another component in Angular 2 - javascript

Is there a way to target a template reference variable in another component?
I would like to trigger the side nav by creating an event that calls the sidenav's open function through the TRV sidenav.
app.component.html
<md-sidenav-layout>
<md-sidenav #sidenav (open)="mybutton.focus()">
Start Sidenav.
<br>
<button md-button #mybutton (click)="sidenav.close()">Close</button>
</md-sidenav>
<top-bar></top-bar>
<main class="main" (openNav)="sidenav.open()">
<router-outlet></router-outlet>
</main>
</md-sidenav-layout>
topbar.component.html
<md-toolbar color="primary">
<button class="app-icon-button" (click)="openSideNav()">
<i class="material-icons app-toolbar-menu">menu</i>
</button>
<span [routerLink]="['']">Home</span>
<span [routerLink]="['test']" class="navlink">Test</span>
<span class="navlink" (click)="signout()">signout</span>
</md-toolbar>
topbar.component.ts
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'top-bar',
templateUrl: './topbar.component.html',
styleUrls: ['./topbar.component.css']
})
export class TopbarComponent implements OnInit {
constructor() { }
#Output() openNav = new EventEmitter();
openSideNav(){
console.log(this.openNav.emit());
this.openNav.emit();
}
ngOnInit() {
}
}
Is it possible for me to target the template variable in the app component from the topbar directive?

I see a couple options.
One, you can create a service that holds the sidenav instance. You can set the sidenav from the app component.
class SidenavService {
private _sidenav: MdSidenav;
set sidenav(nav: MdSidenav) {
this._sidenav = nav;
}
open() {
if (this._sidenav) this.sidenav.open();
}
close() {
if (this._sidenav) this._sidenav.close();
}
}
And in your app controller
class AppComponent implements AfterViewInit {
#ViewChild(MdSidenav) sidenav: MdSidenav;
constructor(private sidenavService: SidenavService) {}
ngAfterViewInit() {
this.sidenavService.sidenav = this.sidenav;
}
}
Then you can inject the SidenavService into whatever other component where you want to have access to it.
The other options is to simply have the sidenav as an input to the topbar.
<topbar [sidenav]="sidenav"></topbar>
class TopbarComponent {
#Input() sidenav: MdSidenav;
}
I personally don't like the second option, as I don't like exposing the sidenav unnecessarily. I'd rather just use the service and control what others can do with it.

Related

System dialog Angular Material

I work for system of dialog (Angular Material).
I create dialog-service for controll and container dialog. Dialog-service has methods for open/show different dialoges.
And I create dialog-component for contain data for dialog (it is single component of dialog). It is universal component.
I add StackBlitz
I have problem with closing dialoges after callback.
How I can close dialog after callback? I try using [mat-dialog-close] - but I was not able to somehow parameterize - enable and disable the [mat-dialog-close] of different buttons.
And a have little problem. How I can add dynamicly mat-button to button element?
(I add class 'mat-button', but this don't full imitation mat-button)
<div *ngIf="getButtons().length > 0 || getCloseButton()" mat-dialog-actions>
<ng-container *ngFor="let button of getButtons()">
<button [attr.class]="button.class"
(click)="button.callback(button.callbackItem || dialogForm)">{{button.title}}</button>
</ng-container>
</div>
In your dialog.html you must have something like this:
<button mat-stroked-button (click)="closeDialog()">Close</button>
and in your dialog.ts:
import { Component, OnInit, Inject } from '#angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '#angular/material';
#Component({
selector: 'dialog',
templateUrl: './dialog.component.html',
styleUrls: ['./dialog.component.scss']
})
export class DialogComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<DialogComponent>) { }
ngOnInit() {
}
closeDialog() {
this.dialogRef.close();
}
}

Viewchild undefined with material angular matsidenav

I'm using Material with angular, and I want to toggle the Sidenav from another component, using viewchild, but i get undefined when i try to get the mat-sidenav element. Heres mi code
sidenav.component.html
<mat-sidenav-container class="example-container">
<mat-sidenav #sidenav class="example-sidenav">
Sidenav content goes here!
</mat-sidenav>
<div class="sidenav-container">
123
</div>
</mat-sidenav-container>
sidenav.component.ts
import { Component, OnInit, ViewChild } from '#angular/core';
import {MatSidenav} from '#angular/material/sidenav';
#Component({
selector: 'app-sidemenu',
templateUrl: './sidemenu.component.html',
styleUrls: ['./sidemenu.component.scss']
})
export class SidemenuComponent implements OnInit {
#ViewChild('sidenav') sidenav;
constructor() { }
ngOnInit() {
}
openSideNav(){
console.log(this.sidenav)
this.sidenav.open();
}
}
And this is the component from where i try to toggle the sidenav
header.component.ts
import { Component, OnInit } from '#angular/core';
import { SidemenuComponent } from '../sidemenu/sidemenu.component';
#Component({
providers:[SidemenuComponent],
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
constructor(private sm: SidemenuComponent) { }
ngOnInit() {
}
openSideNav(){
this.sm.openSideNav()
}
}
header.component.html
<mat-toolbar color="primary">
<mat-toolbar-row>
<div class="navbar-brand">
<img class="img-fluid" src="./assets/icons/logo_femsa_blanco.png"/>
</div>
<button class="buton-menu" mat-icon-button (click)="openSideNav()">
<img class="img-fluid menu-icon" src="./assets/icons/icons8-menu-filled-50.png"/>
</button>
<span class="toolbar-spacer"></span>
<div class="font-weight-light text-right user-name">
Hola Mundo!
</div>
</mat-toolbar-row>
</mat-toolbar>
Hope someone can help. I's starting with angular. Sorry for bad english
base on your sample, i have create Service to manage communication between component.
Goal is to have, from anywhere on your code, API to ask menu to open / close / toggle.
Functional step :
1/ From anywhere (button click, custom things), you want to make action on your side-nav. You call public function from your service, exemple : this.menuService.open()
2/ if needed, your service will next new menu state by Observable.
3/ Component who manage your sidenav will subscribe to any change on this state and do "open/close" if needed.
State is manage by Subject and internal service flag
export class MenuService {
private menuIsOpen$ : Subject<boolean>;
private menuIsOpen: boolean = false;
constructor() {
this.menuIsOpen$ = new Subject<boolean>();
}
/**
* If menu is open, let close it
**/
public open() {
if(!this.menuIsOpen) {
this.menuIsOpen = true;
this.menuIsOpen$.next(false);
}
}
/**
* Both silence open and close is use by navbar output, to silence switch internal flag.
**/
public silenceOpen() {
this.menuIsOpen = true;
}
public silenceClose() {
this.menuIsOpen = false;
}
/**
* If menu is close, let open it
**/
public close() {
if(this.menuIsOpen) {
this.menuIsOpen = false;
this.menuIsOpen$.next(false);
}
}
public toggle() {
this.menuIsOpen = !this.menuIsOpen;
this.menuIsOpen$.next(this.menuIsOpen);
}
public asObservable()
{
return this.menuIsOpen$.asObservable();
}
}
then your Component who embed sidenav :
export class SidemenuComponent implements OnInit {
#ViewChild('sidenav') sidenav: MatSidenav;
constructor(private menuService: MenuService)
{
}
ngOnInit() {
/**
When you reveive order to open / close sidenav.
**/
this.menuService.asObservable().subscribe((isOpen: boolean) => {
if(isOpen) {
this.sidenav.close();
}
else{
this.sidenav.open();
}
});
}
onOpenedChange() {
this.menuService.silenceOpen();
}
onClosedChange() {
this.menuService.silenceClose();
}
}
Html side :
Sample : https://stackblitz.com/edit/angular-2faa8z

Angular 4 show popup onclick by other component

i'm struggling about this problem and can't figure out.
I simply need to show a popup div situated in the page clicking from a menu entry in my navbar.component.
I added a property "show" in my popup which prints the "show" class on my div using the ngClass (with if) directive. I can get this working if the action button is inside my popup component but i cannot print the show class clicking on another component. The property in the Object get updated but the class is not printed. I'm using angular 4 with ng-bootstrap. I tried both with services and with parent/child emit event.
This is is my situation:
app.component.html
<app-nav-bar></app-nav-bar>
<app-login></app-login>
<router-outlet></router-outlet>
<app-footer></app-footer>
navbar.component.html
...
<button class="dropdown-item" (click)="showPopup()">LOGIN</button>
...
navbar.component.ts
import {Component, EventEmitter, Input, OnInit, Output} from '#angular/core';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
#Output() show = new EventEmitter<boolean>();
ngOnInit() {
}
showPopup() {
this.show.emit(true);
}
}
login.component.html
<div id="wrapper-login-popup" class="fade-from-top" [(class.show)]="show">
<div id="container-login-popup">
<div class="row">
<div class="col-sm-12 text-center">
<img id="popup-bomb" src="assets/images/bomb.png" alt="bomb"/>
<img id="popup-close" class="close-icon" src="assets/images/close.png" alt="close"
(click)="closePopup()"/>
</div>
</div>
</div>
</div>
login.component.ts
import {Component, Input, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
import {IUser} from './user';
#Component({
selector: 'app-login',
templateUrl: 'login.component.html',
styleUrls: ['login.css']
})
export class LoginComponent implements OnInit {
private username: string;
private password: string;
#Input() show: boolean = false;
constructor(private AuthService: AuthService) {
}
ngOnInit() {
}
login() {
...
}
showPopup() {
console.log(this); //Show is false
this.show = true;
console.log(this); //Show is true but does not trigger the show class
}
closePopup() {
this.show = false;
}
}
The issue here is that your nav-bar and login components are siblings and can't directly communicate with each other. You have show as an output of navbar and as an input of login, but you haven't connected the dots.
You need to update your app.component to connect them.
export class AppComponent implements OnInit {
show = false;
onShow() { this.show = true; }
}
and in the template:
<app-nav-bar (show)="onShow()"></app-nav-bar>
<app-login [(show)]="show"></app-login>
There's a lot of two way binding going on here which works for something simple liek this, but generally it's a bad idea as it leads to unmaintainable code. You should choose one owner of the show variable and force all changes to it through him. In this case the app component is the most logical owner, so I'd change the login component to emit an event that changes the show variable in app component adn remove all 2 way bindings, but in a bigger app, you may even want a separate service that manages hiding/showing pop ups. This eliminates the need for the sending a message up and down your component tree, you can inject the service where it's needed.
As another commenter mentioned, you also should be using ngClass for class manipulation like
[ngClass]="{'show':show}"
a service based solution would look like
import {Subject} from 'rxjs/Subject';
#Injectable()
export class PopUpService {
private showPopUpSource = new Subject();
showPopUp$ = this.showPopUpSource.asObservable();
showPopUp() { this.popUpSource.next(true); }
closePopUp() { this.popUpSource.next(false); }
}
Then you provide in app module or at app component level:
providers:[PopUpService]
make sure you don't re provide this later, as you only want one copy to exist so everyone shares it.
then inject into both components, and have them call the services close or show pop up methods.
then in the login component you bind to the popUp$ observable like
constructor(private popUpSvc:PopUpService){}
show$;
ngOnInit() { this.show$ = this.popUpSvc.showPopUp$; }
showPopUp() { this.popUpSvc.showPopUp(); }
closePopUp() { this.popUpSvc.closePopUp(); }
and in the template subscribe w async pipe like
<div id="wrapper-login-popup" class="fade-from-top" [ngClass]="{'show': (show$ | async) }">
The reason for using the async pipe is garbage collection managemetn is simpler. If you don't use async, you need to garbage collect manually in ngOnDestroy by calling unsubscribe(), otherwise your subscriptions will keep stacking up. There is also a more nuanced benefit in that the async pipe triggers change detection, but this only becomes important if you start using onPush change detection for performance optimization.

Angular2 OverlayComponent not updating message variable

I have the below OverlayComponent that's serving as a processing spinner during async calls. The overlay pops up without issue but when I try and pass a message to it, the message doesn't stick.
Child Component
import {Component, OnInit} from '#angular/core';
import {OverlayComponent} from "../../shared/app.mysite.overlay.component";
#Component({
moduleId: module.id,
selector: 'tracker-component',
templateUrl: '/public/app/templates/pages/racker/mysite.tracker.component.html',
styleUrls: ['../../../scss/pages/tracker/tracker.css'],
providers: [OverlayComponent]
})
export class TrackerComponent implements OnInit{
constructor(private overlayComponent: OverlayComponent) {
}
ngOnInit(): void {
this.overlayComponent.showOverlay("Testing 123"); //<-- shows overlay but doesn't show the message in the overlay
}
}
Child Component HTML
<div id="TrackerContainer">
<div class="col-lg-12" class="container">
<div class="jumbotron jumbotron-header">
<div>
<div id="pageTitle">Tracker</div>
</div>
</div>
<div class="content-container container">
<div *ngFor="let item of tracker.activeMenu.items">
<card-component [item]="item"></card-component>
</div>
</div>
</div>
</div>
<overlay-component [message]="overlayMessage"></overlay-component>
OverlayComponent.ts
import { Component } from '#angular/core';
#Component({
selector: 'overlay-component',
templateUrl: '/public/app/templates/shared/mysite.overlay.component.html',
styleUrls: ['../../app/scss/shared/overlay.css']
})
export class OverlayComponent {
message: string;
//message: string = 'testing...'; <-- this updated the message just fine
showOverlay(msg: string) {
this.message = msg; // <-- the right value comes through in the msg variable but doesn't update in the page
$('.overlay-component-container').show();
}
hideOverlay() {
$('.overlay-component-container').hide();
this.message = '';
}
}
OverlayComponent.html
<div class="overlay-component-container">
<div class="overlay-component">
<div class="overlay-message">{{message}}</div>
<div>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
</div>
</div>
The issue could be caused by a few things:
You don't need this here, this is just for services, not for components
providers: [OverlayComponent]
This isn't the way to get a reference to another component, you may not have the correct instance.
constructor(private overlayComponent: OverlayComponent)
Is <overlay-component> actually inside the HTML of the Tracker component?
Also where is the overlayMessage property on the tracker component? (as used below)
<overlay-component [message]="overlayMessage"></overlay-component>
Just ensure that the over-component is inside the tracker component, remove the this.message = msg; from showOverlay() and use two-way data binding to change the message.
I.E <overlay-component [message]="overlayMessage"></overlay-component>
If you add overlayMessage: string; to your Tracker component (or parent component of the overlay) Then all you need to do is update it in your parent component and your overlay component will pick up the changes.

Angular2 - two way databinding on a component variable / component class property?

In Angular2 (Beta 6) I have a component for a main menu.
<mainmenu></mainmenu>
I want to bind a boolean for wide or narrow. So I made it into this:
<mainmenu [(menuvisible)]="true"></mainmenu>
But what I want (I think) is to bind to a javascript class property (as I may have other things to bind but want to be tidy by using a single class in the component).
I get an error
EXCEPTION: Template parse errors: Invalid property name
'menumodel.visible' ("
][(menumodel.visible)]="menumodel.visible">
If I try the same with a single variable instead of a class I get:
Template parse errors: Parser Error: Unexpected token '='
However this (one way binding?) does seem to work (but I might want to trigger the menu to go wide/narrow from another component so felt this should be a two-way data bound property):
<menu [vis]="true"></menu>
This is a bit of my menu component:
#Component({
selector: 'menu',
templateUrl: './app/menu.html',
providers: [HTTP_PROVIDERS, ApplicationService],
directives: [ROUTER_DIRECTIVES, FORM_DIRECTIVES, NgClass, NgForm]
})
export class MenuComponent implements OnInit {
mainmenu: MainMenuVM;
constructor(private _applicationService: ApplicationService) {
this.mainmenu = new MainMenuVM();
}
// ...ngOnInit, various functions
}
Here is my MainMenu View Model class
export class MainMenuVM {
public visible: boolean;
constructor(
) { this.visible = true; }
}
I'm trying to create a menu which has icons and text, but can go narrow to just show icons. I will emit this event upwards to a parent component to alter the position of the container next to the menu. Triggering a content container to maximised will trigger the menu to go narrow - I am not saying this is the best way, but I would like to resolve this particular question before going deeper.
Please note: I am not databinding to an input control here - just databinding to a component so I can then modify the UI.
This is from the Angular cheatsheet
<my-cmp [(title)]="name">
Sets up two-way data binding. Equivalent to: <my-cmp [title]="name" (titleChange)="name=$event">
Thanks in advance!
UPDATE
Integrating the code from the accepted answer and adapting for my particular use case here the final working code:
app.html
...header html content
// This is what I started with
<!--<menu [menuvisible]="true" (menuvisibleChange)="menuvisible=$event"></menu>-->
// This is two way data binding
// 1. Banana-in-a-box is the input parameter
// 2. Banana-in-a-box is also the output parameter name (Angular appends it's usage with Change in code - to follow shortly)
// 3. Banana-in-a-box is the short hand way to declare the commented out code
// 4. First parameter (BIAB) refers to the child component, the second refers the variable it will store the result into.
// 5. If you just need an input use the remmed out code with just the first attribute / value
<menu [(menuvisible)]="menuvisible"></menu>
.. div content start
<router-outlet></router-outlet>
.. div content end
app.component.ts (root)
export class AppComponent implements OnInit{
menuvisible: Boolean;
}
menu.component.ts (child of root)
export class MenuComponent implements OnInit {
// Parameters - notice the appending of "Change"
#Input() menuvisible: boolean;
#Output() menuvisibleChange: EventEmitter<boolean> = new EventEmitter<boolean>();
// Init
ngOnInit() {
// Populate menu - fetch application list
this.getApplications();
// Initially we want to show/hide the menu depending on the input parameter
(this.menuvisible === true) ? this.showMenu() : this.hideMenu();
}
//...more code
}
menu.html
<div id="menu" [ngClass]="menuStateClass" style="position: absolute; top:0px; left: 0px;z-index: 800; height: 100%; color: #fff; background-color: #282d32">
<div style="margin-top: 35px; padding: 5px 0px 5px 0px;">
<ul class="menuList" style="overflow-x: hidden;">
<li>IsMenuVisible:{{menuvisible}}</li>
<li style="border-bottom: 1px solid #3d4247"><a (click)="toggleMenu()"><i class="fa fa-bars menuIcon" style="color: white; font-size: 16px;"></i></a></li>
<li *ngFor="#app of applications">
<a [routerLink]="[app.routerLink]">
<i class="menuIcon" [ngClass]="app.icon" [style.color]="app.iconColour" style="color: white;"></i>
<span [hidden]="menuStateTextHidden">{{ app.name }}</span>
</a>
</li>
</ul>
</div>
</div>
Remember to import what you need e.g.
import {Component, EventEmitter, OnInit, Input, Output} from
'angular2/core';
Highly recommend this video on You Tube:
Angular 2 Tutorial (2016) - Inputs and Outputs
For two-way binding you need something like:
#Component({
selector: 'menu',
template: `
<button (click)="menuvisible = !menuvisible; menuvisibleChange.emit(menuvisible)">toggle</button>
<!-- or
<button (click)="toggleVisible()">toggle</button> -->
`,
// HTTP_PROVIDERS should now be imports: [HttpModule] in #NgModule()
providers: [/*HTTP_PROVIDERS*/, ApplicationService],
// This should now be added to declarations and imports in #NgModule()
// imports: [RouterModule, CommonModule, FormsModule]
directives: [/*ROUTER_DIRECTIVES, FORM_DIRECTIVES, NgClass, NgForm*/]
})
export class MenuComponent implements OnInit {
#Input() menuvisible:boolean;
#Output() menuvisibleChange:EventEmitter<boolean> = new EventEmitter<boolean>();
// toggleVisible() {
// this.menuvisible = !this.menuvisible;
// this.menuvisibleChange.emit(this.menuvisible);
// }
}
And use it like
#Component({
selector: 'some-component',
template: `
<menu [(menuvisible)]="menuVisibleInParent"></menu>
<div>visible: {{menuVisibleInParent}}</div>
`
directives: [MenuComponent]
})
class SomeComponent {
menuVisibleInParent: boolean;
}
I've created a short plunkr.
ngModel Like Two-Way-Databinding for components
You have at least two possibilities to to create a two way databinding for components
V1: With ngModel Like Syntax, there you have to create a #Output property with the same name line the #Input property + "Change" at the end of the #Output property name
#Input() name : string;
#Output() nameChange = new EventEmitter<string>();
with V1 you can now bind to the Child Component with the ngModel Syntax
[(name)]="firstname"
V2. Just create one #Input and #Output property with the naming you prefer
#Input() age : string;
#Output() ageChanged = new EventEmitter<string>();
with V2 you have to create two attributes to get the two way databinding
[age]="alter" (ageChanged)="alter = $event"
Parent Component
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `<p>V1 Parentvalue Name: "{{firstname}}"<br/><input [(ngModel)]="firstname" > <br/><br/>
V2 Parentvalue Age: "{{alter}}" <br/><input [(ngModel)]="alter"> <br/><br/>
<my-child [(name)]="firstname" [age]="alter" (ageChanged)="alter = $event"></my-child></p>`
})
export class AppComponent {
firstname = 'Angular';
alter = "18";
}
Child Component
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'my-child',
template: `<p>V1 Childvalue Name: "{{name}}"<br/><input [(ngModel)]="name" (keyup)="onNameChanged()"> <br/><br/>
<p>V2 Childvalue Age: "{{age}}"<br/><input [(ngModel)]="age" (keyup)="onAgeChanged()"> <br/></p>`
})
export class ChildComponent {
#Input() name : string;
#Output() nameChange = new EventEmitter<string>();
#Input() age : string;
#Output() ageChanged = new EventEmitter<string>();
public onNameChanged() {
this.nameChange.emit(this.name);
}
public onAgeChanged() {
this.ageChanged.emit(this.age);
}
}

Categories

Resources