Access another component's from another component in angular 4 - javascript

I need to scroll the tag in app.module.ts from another products component
app.module.ts
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<nav-header></nav-header>
<main class="mdl-layout__content" #scrollMe>
<router-outlet></router-outlet>
<app-footer [otherType]="'siteSetting'" [type]="'getAllPages'"></app-footer>
</main>
</div>
<cart-quantity-icon></cart-quantity-icon>
main tag i.e #scrollMe needs to be scrolled using following component
products.components.ts
import { Component, OnInit,ViewChild,ElementRef,ContentChild } from '#angular/core';
import { ActivatedRoute, NavigationEnd ,Router, Event, NavigationStart, RoutesRecognized,RouteConfigLoadStart,
RouteConfigLoadEnd, NavigationCancel, NavigationError} from '#angular/router';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
scrollTopButton = false;
#ViewChild('infinite') private myScrollContainer : ElementRef;
#ContentChild('scrollMe') private myScroll : ElementRef;
scrollToTop() {
console.log(this.scrollTopButton);
this.myScrollContainer.nativeElement.scrollTo(0, 0);
this.myScroll.nativeElement.scrollTo(0, 0);
window.scrollTo(0,0);
//this.content.scrollToTop(700);
this.scrollTopButton = false;
}}
<div style=" max-height: 750px;
overflow: auto;" #infinite infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="1000"
(scrolled)="getProducts()" [scrollWindow]="false" class="mdl-grid mdl-cell mdl-cell--top mdl-cell--9-col mdl-cell--8-col-tablet mdl-cell--4-col-phone">
<!-- new card -->
<product *ngFor="let p of products" [data]="p" [type]="'normal'"></product>
<div >
</div>
<button *ngIf="scrollTopButton" (click)="scrollToTop()" id="return-to-top" mat-fab color="primary"><i class="material-icons" style="font-size: 24px;">keyboard_arrow_up</i></button>
When I click the return-to-top button then I need to scroll #infinite and # scrollMe. I have tried following .Also instead of contentChild tried viewChild
#ViewChild('infinite') private myScrollContainer : ElementRef;
#ContentChild('scrollMe') private myScroll : ElementRef; //Also ViewChild here but none works!!
scrollToTop() {
console.log(this.scrollTopButton);
this.myScrollContainer.nativeElement.scrollTo(0, 0);
this.myScroll.nativeElement.scrollTo(0, 0);
window.scrollTo(0,0);
//this.content.scrollToTop(700);
this.scrollTopButton = false;
}}
infinite works but scrollMe does not.

This does not work because ContentChild from app-products does not have access to parent elements in DOM. You must use service for communication.
ComService to create and and to provide.
import { Injectable, EventEmitter } from '#angular/core';
import { Observable, Subject } from 'rxjs';
#Injectable()
export class ComService {
messageStream = new Subject();
getMessageSubscribe() {
return Observable.from(this.messageStream);
}
sendMessage() {
this.messageStream.next(this.filteredOrderList);
}
}
In component main component subscribe messages:
#ViewChild('scrollMe') private myScroll : ElementRef;
constructor(
private comService: ComService
) {
this.subscribeMes = this.comService.getComunication().subsribe(element => {
if(element) {
this.myScroll.nativeElement.scrollTo(0, 0);
//when element is true you got info from child to scroll
}
})
ngOnDestroy() {
this.subscribeMes.unsubscribe();
}
}
And #scrollMe insert in top element in template of main component.
In app-products:
constructor(
private comService: ComService
) {}
#ViewChild('infinite') private myScrollContainer : ElementRef;
scrollToTop() {
console.log(this.scrollTopButton);
this.myScrollContainer.nativeElement.scrollTo(0, 0);
this.comService.sendMessage(true);
window.scrollTo(0,0);
//this.content.scrollToTop(700);
this.scrollTopButton = false;
}}

Related

How to use same functionalities of function present in one component to another based on click to use same class binding in both components in angular

I'm using class binding to add and remove classes based on click of sidebar button to minimize and maximize the sidebar.
I need same functionality to add and remove class in footer component also to maximize and minimize width of the footer based on click made in sidebar component.
sidebar.component.html
<nav class="sidebar-container" [ngClass]="minimize ? 'minimize' : ''">
<ul>
<li class="sidebar-minimize">
<a (click)="clickEvent()">Minmax</a>
</li>
</ul>
</nav>
sidebar.component.ts
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent implements OnInit {
minimize: boolean = false;
ngOnInit(): void {
}
clickEvent() {
this.minimize = !this.minimize;
}
}
But i need same actions to be performed in footer component to add another CSS class through class binding followed by same clickEvent() method because in my situation sidebar component and footer components are interdependent on this clickEvent() function
For eg,
<footer (click)="clickEvent()" [ngClass]="minimize ? 'minimize':''">
<p>footer works</p>
</footer>
Is it possible to use same clickEvent() function in footer component for class binding
So in this case you can use a service. For example in your service you have a property like below:
#Injectable({
providedIn: 'root'
})
export class TestService {
private minimize: boolean;
public minimizeSubject: Subject<void>;
constructor() {
this.minimizeSubject = new Subject<void>();
this.minimize = false;
}
set setMinimize(minimize: boolean) {
this.minimize = minimize;
this.minimizeSubject.next();
}
Sidebar component should look like below:
export class SidebarComponent{
public minimize: boolean;
constructor(private testService: TestService) {
this.minimize = false;
}
public onClicked(): void {
this.minimize = !this.minimize;
this.testService.setMinimize = this.minimize;
}
In the end the footer component:
export class FooterComponent implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private testService: TestService) {
this.subscription = new Subscription();
}
ngOnInit(): void {
this.subscription = this.testService.minimizeSubject.subscribe(minimize => {
this.minimize = minimize;
});
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}

Angular - Structural directive with embedded view does not pass children to ng-template

I've got a structural directive that creates an embedded view by looking up a template ref using ng-template. My problem is that from this parent component (with structural directive), I cannot pass down children.
Parent component with structural directive
import { ViewChild, Component, OnInit, ElementRef } from "#angular/core";
import { TestJsonService } from "../../services/test-json.service";
#Component({
selector: "xfr-json-renderer",
template: `
<template-lookup></template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
<div>Why can't i pass this down to the child?</div>
</div>
`,
styleUrls: ["./json-renderer.component.css"],
})
export class JsonRendererComponent implements OnInit {
#ViewChild("childTemplate") childTemplate;
constructor(el: ElementRef, json: TestJsonService) {}
ngOnInit(): void {}
ngAfterViewInit() {}
}
Child component
import { Injectable, TemplateRef, Component, ViewChild } from "#angular/core";
#Injectable()
export class TemplateStore {
templates = new Map<string, TemplateRef<any>>();
}
#Component({
selector: "template-lookup",
template: `
<ng-template #flexRow></ng-template>
<ng-template #flexCol><xfr-flex-col>
// I want to pass the children into here
</xfr-flex-col></ng-template>
`,
})
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(private service: TemplateStore) {}
ngAfterViewInit() {
this.service.templates.set("flexRow", this.flexRowTemplate);
this.service.templates.set("flexCol", this.flexColTemplate);
}
}
Structural directive
import { ViewContainerRef } from "#angular/core";
import { TemplateStore } from "./../services/composite-template.service";
import { Directive, Input } from "#angular/core";
#Directive({
selector: "[replaceWith]",
})
export class CompositeTemplateDirective {
#Input() replaceWith: "flex-col" | "flex-row";
constructor(private service: TemplateStore, private view: ViewContainerRef) {}
ngAfterViewInit() {
this.view.createEmbeddedView(this.service.templates.get(this.replaceWith));
}
}
The problem is that you need to use internal API for that, what is not the best thing. I would use it until I stay with the same angular version and would test it before every update - then should work stable.
I was able to do the injection with Angular 9, quite sure a similar solution (but different internal API) can be applied for other angular versions.
The main thing for the injection - where to inject the content, in components we could use ng-content, but here it wouldn't work, because we have different component contexts. In this case we could use <ng-template [ngTemplateOutlet]></ng-template> to tell the script where we want the injection.
here you can find a live demo: https://codesandbox.io/s/nifty-wright-335bm?file=/src/app/json-renderer.component.ts
CompositeTemplateDirective
import {NgTemplateOutlet} from '#angular/common';
import {AfterViewInit, Directive, Input, TemplateRef, ViewContainerRef} from '#angular/core';
import {TemplateStore} from 'src/app/TemplateLookup/TemplateLookup';
#Directive({
selector: '[replaceWith]',
})
export class CompositeTemplateDirective implements AfterViewInit {
#Input() replaceWith: 'flex-col' | 'flex-row';
constructor(
private service: TemplateStore,
private view: ViewContainerRef,
private templateRef: TemplateRef<any>,
) {
}
public ngAfterViewInit(): void {
const wrapper = this.service.templates.get(this.replaceWith);
const source = this.templateRef;
const view: any = this.view.createEmbeddedView(wrapper);
let directive: NgTemplateOutlet;
const nodes: Array<any> = view._lView ? view._lView : view._view && view._view.nodes ? view._view.nodes : [];
for (const node of nodes) {
if (typeof node !== 'object') {
continue;
}
if (node instanceof NgTemplateOutlet) {
directive = node;
}
if (typeof node.instance === 'object' && node.instance instanceof NgTemplateOutlet) {
directive = node.instance;
}
}
if (directive) {
directive.ngTemplateOutlet = source;
directive.ngOnChanges({
ngTemplateOutlet: {
previousValue: null,
currentValue: source,
firstChange: true,
isFirstChange: () => true,
},
});
}
}
}
TemplateLookup
import {AfterViewInit, Component, Injectable, TemplateRef, ViewChild} from '#angular/core';
#Injectable()
export class TemplateStore {
templates = new Map<string, TemplateRef<any>>();
}
#Component({
selector: 'template-lookup',
template: `
<ng-template #flexRow>
<div>
flexRow template
</div>
</ng-template>
<ng-template #flexCol>
<div>
<div>wrap</div>
<ng-template [ngTemplateOutlet]></ng-template>
<div>wrap</div>
</div>
</ng-template>
`,
})
export class TemplateLookup implements AfterViewInit {
#ViewChild('flexRow', {static: false}) flexRowTemplate;
#ViewChild('flexCol', {static: false}) flexColTemplate;
constructor(
private service: TemplateStore,
) {
}
ngAfterViewInit() {
console.log('TemplateLookup:ngAfterViewInit');
this.service.templates.set('flexRow', this.flexRowTemplate);
this.service.templates.set('flexCol', this.flexColTemplate);
}
}
so the most pragmatic thing here seems to be to just put the child you want to pass as a child of the template-lookup component and use ng-content...
do this in the parent:
<template-lookup>
<div>I will pass to child</div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
</div>
and this in the child:
<ng-template #flexRow></ng-template>
<ng-template #flexCol>
<xfr-flex-col>
<ng-content></ng-content>
</xfr-flex-col>
</ng-template>
and that will solve your problem / fulfill the stated requirements.
You could also consider a rewrite to your service to solve timing problems between templates being set and gotten once and for all:
import { Injectable, TemplateRef } from "#angular/core";
import {ReplaySubject} from 'rxjs';
import {map, filter, distinctUntilChanged} from 'rxjs/operators';
#Injectable({providedIn: 'root'}) // provide appropriately, root for example
export class TemplateStore {
private templates = new Map<string, TemplateRef<any>>();
private tmpSource = new ReplaySubject<Map<string, TemplateRef<any>>>(1);
setTemplate(key: string, template: TemplateRef<any>) {
this.templates.set(key, template);
this.tmpSource.next(this.templates)
}
getTemplate(key: string) {
return this.tmpSource.pipe(
map(tmpMap => tmpMap.get(key)),
filter(tmp => !!tmp),
distinctUntilChanged()
)
}
}
and make the associated changes in the directive and child components...
export class CompositeTemplateDirective implements OnInit, OnDestroy {
#Input() replaceWith: "flex-col" | "flex-row";
private sub: Subscription;
constructor(private service: TemplateStore, private viewContainer: ViewContainerRef) { }
ngOnInit() {
this.sub = this.service.getTemplate(this.replaceWith).subscribe(t => {
this.viewContainer.clear()
this.viewContainer.createEmbeddedView(t)
})
}
ngOnDestroy() {
this.sub.unsubscribe()
}
}
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(private service: TemplateStore) {}
ngAfterViewInit() {
this.service.setTemplate("flexRow", this.flexRowTemplate);
this.service.setTemplate("flexCol", this.flexColTemplate);
}
}
functioning example: https://stackblitz.com/edit/angular-ygdveu
it's been pointed out that this doesn't support nesting... so make the following adjustments and you can nest. in template lookup, you'll need to use the SkipSelf modifier in your constructor, and also provide the TemplateStore... in the case of no nesting, this will have no effect, SkipSelf just tells the injector to start looking for the service at the parent rather than at the component:
#Component({
selector: "template-lookup",
template: `
<ng-template #flexRow>FLEX ROW</ng-template>
<ng-template #flexCol>
FLEX COL
<div class="flex-col">
<ng-content></ng-content>
</div>
</ng-template>
`,
providers: [TemplateStore]
})
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(#SkipSelf() private service: TemplateStore) {}
ngAfterViewInit() {
this.service.setTemplate("flexRow", this.flexRowTemplate);
this.service.setTemplate("flexCol", this.flexColTemplate);
}
}
then you can nest to your hearts content like so:
<template-lookup>
<div>I can pass this to the child!</div>
<template-lookup>NESTED</template-lookup>
<div class="nested-content" *replaceWith="'flexCol'"></div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
</div>
which is a little ugly, as you need to repeat the template-lookup component, but it does get the job done. This works by allowing the directive and template lookup to communicate with a different copy of the TemplateStore so you can nest different content.
working example of this variant: https://stackblitz.com/edit/angular-lpner2

How to make NgbModal draggable with #angular/cdk

I have some difficulties with figuring out how to make my modals draggable. I have reusable modals with its own service which is called to create one inside components.
confirm.modal.service.ts
import { Injectable } from "#angular/core";
import { NgbModal } from "#ng-bootstrap/ng-bootstrap";
import { Observable, from, EMPTY, throwError } from "rxjs";
import { catchError, tap } from "rxjs/operators";
import { ConfirmModalComponent } from "./confirm-modal.component";
export interface ConfirmOptions {
title: string;
subtitle?: string;
errorOnClose?: boolean;
}
#Injectable({ providedIn: "root" })
export class ConfirmModalService {
constructor(private modalService: NgbModal) {}
confirm(options: ConfirmOptions): Observable<boolean> {
const modalRef = this.modalService.open(ConfirmModalComponent, {
centered: true
});
modalRef.componentInstance.title = options.title || "Are you sure?";
modalRef.componentInstance.subtitle = options.subtitle || null;
return from(modalRef.result).pipe(
tap(),
catchError(err =>
options.errorOnClose
? throwError(err || "not confirmed")
: EMPTY
)
);
}
}
confirm.modal.module.ts
import { NgModule } from "#angular/core";
import { CommonModule } from "#angular/common";
import { DragDropModule } from "#angular/cdk/drag-drop";
import { ConfirmModalComponent } from "./confirm-modal.component";
#NgModule({
imports: [
CommonModule,
DragDropModule
],
declarations: [ConfirmModalComponent],
exports: [ConfirmModalComponent]
})
export class ConfirmModalModule {}
confirm.modal.component.ts
import { Component, Input } from "#angular/core";
import { NgbActiveModal } from "#ng-bootstrap/ng-bootstrap";
#Component({
selector: "app-confirm-modal",
templateUrl: "./confirm-modal.component.html",
styleUrls: ["./confirm-modal.component.scss"]
})
export class ConfirmModalComponent {
#Input() title: string;
#Input() subtitle: string;
constructor(public activeModal: NgbActiveModal) {}
public accept(): void {
this.activeModal.close(true);
}
public dismiss(): void {
this.activeModal.close(false);
}
}
confirm.modal.component.html
<div class="modal-body">
<div class="modal-body__header">
<span>{{ title }}</span>
</div>
<div *ngIf="subtitle" class="modal-body__text">
<span>{{ subtitle }}</span>
</div>
<div class="modal-body__button-row">
<button class="btn btn-primary" (click)="accept()">Yes</button>
<button class="btn btn-light" (click)="dismiss()">Cancel</button>
</div>
</div>
So I want to make the whole modal be draggable with Angular built-in DragDropModule, hence I should add cdkDrag inside element with class='modal-content' but I don't how to achieve that with current setup. NgbModalOptions provides functionality to add class only but not attribute directive.
I know that there is easier solution with JQuery draggable, but I would like to avoid that.
I was thinking about using #ViewChildren for each page but it doesn't seem to the best solution for me.
Thanks for any help!
Just wrap your modal inside a container and add the cdkDragRootElement to it as per the documentation. You will also have to add this class as an option when you open the dialog from the component.ts.
<ng-template #content
let-modal>
<div
cdkDrag
cdkDragRootElement=".your-custom-dialog-class">
<div class="modal-header">
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
</div>
</div>
</ng-template>
The code for the component.ts
const options: NgbModalOptions = {
windowClass: 'your-custom-dialog-class'
};
this.modalService.open(this.content, options);

Toggle active/inactive class angular from different component

I have a parent component HeaderComponent with two children, NavComponent and BurgerComponent! At the momemnt there is a class that toggles the active inactive state of the burger when clicked on it! Below the first image when the burger is inactive, the second with active burger and NavComponent visible.
When clicked on the NavComponent area I have managed that the NavComponent closes.
The issue: The the NavComponent is closed by click on nav area, but I also need the burger to go to the inactive state(image 1).
What I have so far:
NavCompoent HTML with click event
<nav class="nav-menu {{ menuStatus }}" (click)="collapseMenu($event)">
NavComponent TS:
export class NavComponent implements OnInit {
title: string;
closeMenu: boolean;
#Output() sendTitle = new EventEmitter < string > ();
#Output() menuClose = new EventEmitter<any>();
#Input() menuStatus: boolean;
active = false;
constructor() {}
ngOnInit() {}
getText(event) {
this.title = event.target.innerText;
this.sendTitle.emit(this.title)
console.log("title sent", this.title);
}
collapseMenu($event) {
this.menuStatus = false;
this.menuClose.emit(this.menuStatus);
}
}
HeaderComponent HTML (this is the parent component)
<header class="sticky">
<div class="header-container">
<div class="header-left">
<h1>{{pageTitle}}</h1>
</div>
<div class="header-right">
<app-burger (opened)="burgerStatus($event)" [menuCloseBurger]="menuClose"></app-burger>
</div>
</div>
</header>
<app-nav (sendTitle)="getTitle($event)" [menuStatus]="burger" (menuClose)="sendingMenuClose($event)"></app-nav>
Header Component TS:
export class HeaderComponentComponent implements OnInit {
route: string;
pageTitle: string;
burger: string;
menuClose: string;
constructor(location: Location, router: Router) {
router.events.subscribe(val => {
this.pageTitle = location.path();
this.pageTitle = this.pageTitle.substring(1);
});
}
ngOnInit() {
this.pageTitle = this.route;
console.log(this.pageTitle);
}
getTitle($event) {
console.log(this.route);
this.pageTitle = $event;
}
burgerStatus($event) {
this.burger = $event;
console.log($event);
}
sendingMenuClose($event) {
this.menuClose = $event;
console.log("menu close at parent", this.menuClose);
}
}
BurgerComponent TS:
export class BurgerComponent implements OnInit {
active: boolean;
#Output() opened = new EventEmitter<any>();
#Input() menuCloseBurger: string;
constructor() { }
ngOnInit() {
this.active = false;
}
onBurgerClicked() {
this.active = !this.active;
this.opened.emit(this.active);
}
}
BurgerComponent HTML:
<div class="burger-menu" [class.active]="active" (click)="onBurgerClicked()">
<div class="burger-container">
<div class="burger-inner"></div>
</div>
</div>
collapseMenu() send a boolean value false, I need to get this into the BurgerCompnent class somehow so the value of that is false and the burger close. I am so close, I can't figure out the last step!
As per #PitchBlackCat's recommendation I have create a service NavStatusService!
import { Injectable } from '#angular/core';
import { EventEmitter } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class NavStatusService {
constructor() { }
public readonly isNavCollapsed$ = new EventEmitter<boolean>();
}
I have implemented it in the BurgerComponent:
onBurgerClicked() {
this.active = !this.active;
this.state.isNavCollapsed$.emit(this.active);
}
Now I am stuck as to how the communication between in the two component, Burger and Nav supposed to work!
You need a separate service to manage the shared variables.
You could try a framework like ngrx or create your own service along the lines of the example provided below.
Notice how the application-state.service has become the owner of the data that is shared between multiple components. This way, components that live in seperate parts of your layout can share data, whithout knowing about eachother.
application-state.service
export class ApplicationStateService implements OnInit {
public isNavCollapsed: boolean = false;
}
burger.component
<button class="burger" [class.active]="!state.isNavCollapsed" (click)="onBurgerClicked()">
</button>
import { Component, OnInit } from '#angular/core';
import { ApplicationStateService } from '../application-state.service';
#Component({
selector: 'app-burger',
templateUrl: './burger.component.html',
styleUrls: ['./burger.component.css']
})
export class BurgerComponent implements OnInit {
constructor(public state: ApplicationStateService) { }
ngOnInit() {
}
onBurgerClicked() {
this.state.isNavCollapsed = !this.state.isNavCollapsed;
}
}
nav.component
<ul *ngIf="!state.isNavCollapsed">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
<li>
<button (click)="state.isNavCollapsed = true">close</button>
</li>
</ul>
import { Component, OnInit } from '#angular/core';
import { ApplicationStateService } from '../application-state.service';
#Component({
selector: 'app-nav',
templateUrl: './nav.component.html',
styleUrls: ['./nav.component.css']
})
export class NavComponent implements OnInit {
constructor(public state: ApplicationStateService) { }
ngOnInit() {
}
}
Check out this example on StackBlitz to see it in action

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

Categories

Resources