【问题标题】:Open/Close sidenav from another component从另一个组件打开/关闭 sidenav
【发布时间】:2018-06-12 20:56:39
【问题描述】:

我使用 angular(最新版本)和 angular 材质。

有 3 个组件:

  • header.component,其中有一个右侧导航的控制按钮
  • rigth-sidenav.component,其中是right-sidenav
  • sidenav.component(这是左侧主菜单),该组件调用header.component、right-sidenav.component和content

如何从另一个组件打开/关闭 sidenav ? (在我的例子中,按钮在 header.component 中)。

尝试了以下选项(但出现错误:TypeError: this.RightSidenavComponent.rightSidenav is undefined):

header.component.html

<button mat-button (click)="toggleRightSidenav()">Toggle side nav</button>

header.component.ts

import { Component } from '@angular/core';
import { RightSidenavComponent } from '../right-sidenav/right-sidenav.component';
@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent {

    constructor(public RightSidenavComponent:RightSidenavComponent) {   }
    toggleRightSidenav() {
        this.RightSidenavComponent.rightSidenav.toggle();
    }

}

sidenav.component.html

<mat-sidenav-container class="sidenav-container">
    <mat-sidenav #sidenav mode="side" opened="true" class="sidenav"
                 [fixedInViewport]="true"> Sidenav  </mat-sidenav>
    <mat-sidenav-content>
        <app-header></app-header>
        <router-outlet></router-outlet>
        <app-right-sidenav #rSidenav></app-right-sidenav>
    </mat-sidenav-content>
</mat-sidenav-container>

sidenav.component.ts

import { Component, Directive, ViewChild } from '@angular/core';
import { RightSidenavComponent } from '../right-sidenav/right-sidenav.component';

@Component({
  selector: 'app-sidenav',
  templateUrl: './sidenav.component.html',
  styleUrls: ['./sidenav.component.scss']
})

export class SidenavComponent {

    @ViewChild('rSidenav') public rSidenav;
    constructor(public RightSidenavComponent: RightSidenavComponent) {
        this.RightSidenavComponent.rightSidenav = this.rSidenav;
    }

}

right-sidenav.component.html

<mat-sidenav #rightSidenav mode="side" opened="true" class="rightSidenav"
             [fixedInViewport]="true" [fixedTopGap]="250">
    Sidenav
</mat-sidenav>

right-sidenav.component.ts

import { Component, Injectable } from '@angular/core';

@Component({
  selector: 'app-right-sidenav',
  templateUrl: './right-sidenav.component.html',
  styleUrls: ['./right-sidenav.component.scss']
})

@Injectable()
export class RightSidenavComponent {

    public rightSidenav: any;

    constructor() { }

}

Non-working sample with code stackblitz

【问题讨论】:

  • 在 right-sidenav.component.ts 中尝试将 public rightSidenav: any; 更改为 @ViewChild('rightSidenav') public rightSidenav: any;
  • 我认为如果你这样实现,你的左右侧导航(side nav)只需要一个组件
  • 我还建议您格式化您的问题。代码 sn-p 仅适用于纯 html 、 java 脚本或 css 。检查它是否在角度 2 中工作。所以我建议你删除并进行正确的格式设置。

标签: angular typescript angular-material


【解决方案1】:

为了更简单的解决方案,只需使用 @ouput() 事件发射器。

在你的父组件中

 <mat-sidenav
    #sidenav
    class="sidenav"
    fixedInViewport
    [opened]="opened"
    [mode]="mode"
  >
// catch the emitted output from child component 
<childcomponent (sidenav)="sidenav.toggle()"></childcomponent>
....
</mat-sidenav>

子组件

<mat-toolbar>
 <div>
  <button mat-button (click)="toggle()">
   <mat-icon>menu</mat-icon>
  </button>
 </div>
</mat-toolbar>

子组件.ts

import {Component, EventEmitter, Output} from '@angular/core';

@Component({
  selector: 'childcomponent',
  templateUrl: './childcomponent.component.html',
  styleUrls: ['./childcomponent.component.scss'],
})
export class ChildComponent {
 @Output() sidenav: EventEmitter<any> = new EventEmitter();

toggle() {
 this.sidenav.emit();
 }
}

【讨论】:

  • 这是一个优雅而简单的解决方案。谢谢;)
【解决方案2】:

我在 parent\ 另一个组件中使用 @Input() inputSideNav: MatSideNav 将 sideNav 对象作为子组件的目标属性传递。它按预期工作。顺便说一句,我喜欢@Eldho 的服务实现 :)

  1. 在您当前的实现中插入插件很简单
  2. 它可以扩展到作为参考传递到组件定义中的尽可能多的 sideNav 或其他 UI 控件
  3. 代码没有杂乱,看起来很简单。
  4. 如果您明白我们的意思,请投票 :)

Child.component.html

<app-layout-header [inputSideNav]="sideNav"></app-layout-header>
<mat-sidenav-container>
  <mat-sidenav #sideNav (click)="sideNav.toggle()" mode="side">
    <a routerLink="/">List Products</a>
  </mat-sidenav>
  <mat-sidenav-content>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

layout-header.component.html

<section>
  <mat-toolbar>
    <span (click)="inputSideNav.toggle()">Menu</span>
  </mat-toolbar>
</section>

layout-header.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { MatSidenav } from '@angular/material';

@Component({
  selector: 'app-layout-header',
  templateUrl: './layout-header.component.html',
  styleUrls: ['./layout-header.component.css']
})
export class LayoutHeaderComponent implements OnInit {
  @Input() inputSideNav: MatSidenav;

  constructor() { }

  ngOnInit() {
  }
}

【讨论】:

  • 我觉得这个方案比较简单
  • 我喜欢这个解决方案,但它不适用于我的应用组件结构。 header 组件和 sidenav 组件是兄弟组件,即我在 app.component 下同时调用 app-header 和 app-sidenav。这意味着我的 app-sidenave 中的“#sidenav”不能传递给我的 app-header,有什么想法吗?
  • @AshokanSivapragasam 我无法在简化的 StackBlitz 中重现该错误,所以它一定是我介绍的。如果我找到原因,我会回帖。干杯!
  • @AshokanSivapragasam 找到它:我通过组件控制器传递 ViewChild 引用,而不是直接通过模板传递#variable。
  • 如果应用这种风格,在不改变设计的情况下实现起来会更简单
【解决方案3】:

要扩展 eldho 服务响应,我在单击时出错 this.sidenav 未定义'我通过确保我的侧导航被标记为 #sidenav 以匹配 app.component.ts 文件上的选择器来修复它。

还有 mat-drawer-container 和 mat-drawer 似乎是 sidenav 的新名称

<mat-drawer-container autosize>
   <mat-drawer #sidenav class="example-sidenav" mode="side" position="end">
    
   </mat-drawer>

   <mat-drawer-content autosize="true">
   
   </mat-drawer-content>
</mat-drawer-container>

app.component.ts

@ViewChild('sidenav') public sidenav: MatSidenav;

【讨论】:

    【解决方案4】:

    虽然上面的人已经回答了,但是我相信我自己解决的时候我的解决方案会更简单。

    您需要在中间有一个服务来支持这两个组件。

    假设这样的服务是NavService,下面的代码进入NavService

    public toggleNav: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    
      public toggle(): void {
        this.toggleNav.next(!this.toggleNav.value);
      }
    

    然后你有一个组件,你想打开或关闭你的SideNav。就我而言,我在Navbar 中创建了按钮:

    button *ngIf="this.loginService.tokenSubject.value"  mat-mini-fab color="warn" (click)="onToggle()"><mat-icon>menu</mat-icon></button>
    

    然后在您为SideNav 创建的SideNavComponent 中,将其放入mat-sidenav 标记中:

    [opened]="this.sideNavService.toggleNav.value"
    

    当然,我在这个组件的构造函数中注入了NavService作为sideNavService。

    【讨论】:

      【解决方案5】:

      如果您在 Angular 9+ 中工作,请记住在 @ViewChild 中添加 static: true 的参数,例如:

      @ViewChild('rightSidenav', {static: true}) sidenav: MatSidenav;

      您可以在此处查看我在 Angular 9 上运行的代码:

      https://stackblitz.com/edit/angular-kwfinn-matsidenav-angular9

      【讨论】:

        【解决方案6】:

        添加到 Eldho 的答案中,您可以禁用动画以获得真正的隐藏效果。

            <mat-sidenav #leftbar opened mode="side" class="ng-sidenav" [@.disabled]="true">
        

        【讨论】:

          【解决方案7】:

          我在使用时遇到了同样的问题。我是这样解决的。

          SidenavService

          import { Injectable } from '@angular/core';
          import { MatSidenav } from '@angular/material';
          
          @Injectable()
          export class SidenavService {
              private sidenav: MatSidenav;
          
          
              public setSidenav(sidenav: MatSidenav) {
                  this.sidenav = sidenav;
              }
          
              public open() {
                  return this.sidenav.open();
              }
          
          
              public close() {
                  return this.sidenav.close();
              }
          
              public toggle(): void {
              this.sidenav.toggle();
             }
          

          您的组件

          constructor(
          private sidenav: SidenavService) { }
          
          toggleRightSidenav() {
             this.sidenav.toggle();
          }
          

          根据您的要求绑定您的 html toggle()。

          应用组件。

          @ViewChild('sidenav') public sidenav: MatSidenav;
          
          constructor(private sidenavService: SidenavService) {
          }
          
          ngAfterViewInit(): void {
            this.sidenavService.setSidenav(this.sidenav);
          }
          

          应用模块

          providers: [YourServices , SidenavService],
          

          Working sample with code stackblitz

          Angular 9+ 更新

          根据this answer on SO,“无法再通过@angular/material 导入组件。使用单独的辅助入口点,例如@angular/material/button.”因此,请确保像这样导入MatSidenav

          import { MatSidenav } from '@angular/material/sidenav';
          

          【讨论】:

          • 你必须在模块中添加服务,@Denis 我已经更新了app.module.ts,你需要在其中注册每个服务。立即尝试
          • 你能在这里创建一个项目吗stackblitz.com。所以我可以通过编辑来帮助你
          • stackblitz.com/edit/angular-tku8zp 请看这个。我已经实现了你可以调整它的基本方法
          • 唯一想到的是使用ngAfterViewInit() 而不是ngOnInit() - 从这篇文章blog.angular-university.io/angular-viewchild 初始化viewChild 确保“@ViewChild 注入的引用存在”。
          • 正如@NathanBeck 提到的,如果你得到“thissidenav is undefined”,请尝试使用 ngAfterViewInit 而不是 ngOnInit。这对我有用。
          【解决方案8】:

          首先,您不需要在 sidenav 组件中切换右侧 sidenav。 您的切换按钮位于标题组件中,因此您可以在标题组件中执行此操作。

          header.component.ts

          import { Component } from '@angular/core';
          import { RightSidenavComponent } from '../right-sidenav/right-sidenav.component';
          @Component({
            selector: 'app-header',
            templateUrl: './header.component.html',
            styleUrls: ['./header.component.scss']
          })
          export class HeaderComponent {
            rightSidenav: boolean;
            constructor(public RightSidenavComponent:RightSidenavComponent) { }
          
            toggleRightSidenav() {
              this.rightSidenav = !this.rightSidenav;
            }
          }
          

          在您的 header.component.html 中,使用以下代码:

          <app-right-sidenav *ngIf="rightSideNav"></app-right-sidenav>
          

          这将使您的工作变得轻松。

          【讨论】:

          • 要记住的一点是,使用此解决方案将不会呈现 sidenav 的标记。这可能会影响网站的 SEO,因为搜索引擎不会找到导航代码。
          猜你喜欢
          • 2022-01-25
          • 1970-01-01
          • 2021-12-19
          • 2020-05-12
          • 1970-01-01
          • 2023-01-31
          • 1970-01-01
          • 2013-01-04
          • 1970-01-01
          相关资源
          最近更新 更多