65.9K
CodeProject 正在变化。 阅读更多。
Home

关于 Angular 2 容器组件的说明

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (3投票s)

2017年1月16日

CPOL

5分钟阅读

viewsIcon

13653

downloadIcon

125

这是关于 Angular 2 容器组件的说明。

引言

这是关于 Angular 2 容器组件的说明。在计算机科学术语中,这被称为transclusion

背景

Angular 通过其基于组件的编程鼓励代码重用。有时,您可能会发现创建容器组件并与其他内容共享它很方便。如果您熟悉 ASP.NET MVC,容器组件在功能上与 ASP.NET MVC 中的布局页相似。

附带的 ASP.NET MVC 项目是用 Visual Studio Community 2015 Update 3 编写的。该项目包含了下载“node_modules”的所有依赖项配置文件。它还具有编译和运行 Angular 2 应用程序的所有配置。但是,在 Visual Studio 中设置环境来编译 TypeScript 文件和运行应用程序并非易事。我强烈建议您参考我早期的说明,如果您想下载并运行此示例。除了 Angular 2 之外,此示例还使用了 jQuery。如果您想运行它,您将需要互联网连接,因为 jQuery 库链接到 CDN。

在 Angular 2 中,组件、模块以及“bootstrap”过程的组织并不简单。如果您不熟悉它,可以参考我早期的说明,其中包含此过程的一个小型示例。当然,您随时可以访问官方网站获取最新信息。在本说明中,我将直接进入主题,介绍如何创建容器组件以及如何使用它来插入内容。

容器组件

例如,“slide-container”文件夹中实现了一个幻灯片容器。该组件的 HTML 模板如下。

<div class="slide-container"
     [style.width]="Width" [style.height]="Height"
     style="position: relative; border-radius: 6px;
        box-shadow: 3px 3px 3px 3px #888888; overflow: hidden;">
    
    <div [style.width]="Width" [style.height]="ControlHeight"
         style="position: absolute; top: 0px; left: 0px">
    
        <div style="float: left; height: 100%; display: flex;
                align-items: center; margin-left: 5px">
    
            <!-- Title/header section-->
            <ng-content select="[title-header]"></ng-content>
        </div>
        <div style="float: right; height: 100%; display: flex;
                align-items: center; margin-right: 5px">
            <button style="height: 80%; border-radius: 3px"
                    (click)="Slide($event, 'left')">
    
                <!-- Button text on show left button -->
                <ng-content select="[left-button-text]"></ng-content>
            </button>
            <button style="height: 80%; border-radius: 3px"
                    (click)="Slide($event, 'right')">
    
                <!-- Button text on show right button -->
                <ng-content select="[right-button-text]"></ng-content>
            </button>
        </div>
    </div>
    
    <!-- Left side shows by default -->
    <div class="slide-left"
         [style.width]="Width" [style.height]="SlideHeight"
         [style.top]="ControlHeight"
         style="position: absolute; left: 0px;">
    
        <!-- Left content -->
        <ng-content select="[left-content]"></ng-content>
    </div>
    
    <div class="slide-right"
         [style.width]="Width" [style.height]="SlideHeight"
         [style.top]="ControlHeight" [style.left]="Width"
         style="position: absolute">
    
        <!-- Right content -->
        <ng-content select="[right-content]"></ng-content>
    </div>
</div>

创建容器组件的关键是“<ng-content>”标签。这是使用此容器组件的组件插入实际内容的位置。在容器组件中,您可能拥有多个“<ng-content>”标签。“select”属性允许将不同的内容插入到正确的位置。为了让这个容器组件执行一些操作,让我们看看 TypeScript 文件“slide-container.component.ts”。

declare let $;
import { Component, Input, OnInit } from '@angular/core';
    
@Component({
    moduleId: module.id,
    selector: 'slide-container',
    templateUrl: 'slide-container.component.html'
})
export class SlideContainer implements OnInit {
    @Input() Width;
    @Input() Height;
    
    private animation_speed = 500;
    public ControlHeight;
    public SlideHeight;
    
    constructor() { }
    ngOnInit() {
        let height = parseInt(this.Height);
        let controlHeight = 50;
    
        this.ControlHeight = controlHeight + 'px';
        this.SlideHeight = (height - controlHeight) + 'px';
    }
    
    public Slide(e, show: string) {
        let container = $(e.target).closest('.slide-container');
        let l = $('.slide-left', container);
        let r = $('.slide-right', container);
    
        let width = parseInt(this.Width);
        if (show == 'left') {
            l.animate({ left: 0 }, this.animation_speed);
            r.animate({ left: width }, this.animation_speed);
        }
        else {
            l.animate({ left: -1 * width }, this.animation_speed);
            r.animate({ left: 0 }, this.animation_speed);
        }
    
        return false;
    }
}

为了保持此示例简单,容器组件的功能并不多。它所做的只是将两个按钮绑定到“SlideContainer”类中的“Slide”方法。当单击“left”按钮时,它会滑动以显示左侧内容;当单击“right”按钮时,它会滑动以显示右侧内容。

将内容插入容器组件

将内容插入容器组件非常容易。让我们看看“slide-container-contents”文件夹中实现的组件。

<slide-container Width="400px" Height="200px">
    <span left-button-text>SHOW LEFT</span>
    <span right-button-text>SHOW RIGHT</span>
    
    <span title-header>NG2-Transclusion Example</span>
    
    <div left-content style="width: 100%; height: 100%;
        box-sizing: border-box; padding: 20px;
        color: white;
        background-color: green">
        This is the left content...
    </div>
    <div right-content style="width: 100%; height: 100%;
        box-sizing: border-box; padding: 20px;
        color: white;
        background-color: blue">
        This is the right content...
    </div>
</slide-container>

在此组件中,我将内容插入到“<slide-container>”中的每个“<ng-content>”。您可能需要稍微注意一下“select”属性的使用方式,以便将内容插入到所需位置。由于本说明是关于如何使用容器组件,因此我在“slide-container-contents”组件中没有添加任何功能,所以“slide-container-contents.component.ts”文件基本上是空的。

import { Component, Input, OnInit } from '@angular/core';
    
@Component({
    moduleId: module.id,
    selector: 'slide-container-contents',
    templateUrl: 'slide-container-contents.component.html'
})
export class SlideContainerContents implements OnInit {
    ngOnInit() {}
}

运行应用程序

在 Visual Studio 中编译 TypeScript 文件和运行 Angular 2 应用程序的环境并不简单。如果您遇到问题,可以参考我早期的说明。如果一切顺利,当您运行应用程序时,您应该会看到以下页面。

您可以看到所有内容都已正确插入到容器组件中。如果您单击“SHOW RIGHT”按钮,您会看到内容向右滑动。

在您的 Angular 2 应用程序中,如果您有多个地方需要这种滑动效果,创建一个容器组件并在这些地方使用它可以避免您编写重复的代码来实现多处的效果。

这个容器组件太糟糕了!

您已经看到容器组件可以工作,但它是一个糟糕的 Angular 组件。让我们对“slide-container.component.ts”文件进行一个小小的更改,看看它有多糟糕。

declare let $;
import { Component, Input, OnInit, DoCheck } from '@angular/core';
    
@Component({
    moduleId: module.id,
    selector: 'slide-container',
    templateUrl: 'slide-container.component.html'
})
export class SlideContainer implements OnInit, DoCheck {
    @Input() Width;
    @Input() Height;
    
    private animation_speed = 500;
    public ControlHeight;
    public SlideHeight;
    
    constructor() { }
    
    // Console.log() to see if change detection runs
    ngDoCheck() {
        console.log('Angular change detection runs!');
    }
    
    ngOnInit() {
        let height = parseInt(this.Height);
        let controlHeight = 50;
    
        this.ControlHeight = controlHeight + 'px';
        this.SlideHeight = (height - controlHeight) + 'px';
    }
    
    public Slide(e, show: string) {
        let container = $(e.target).closest('.slide-container');
        let l = $('.slide-left', container);
        let r = $('.slide-right', container);
    
        let width = parseInt(this.Width);
        if (show == 'left') {
            l.animate({ left: 0 }, this.animation_speed);
            r.animate({ left: width }, this.animation_speed);
        }
        else {
            l.animate({ left: -1 * width }, this.animation_speed);
            r.animate({ left: 0 }, this.animation_speed);
        }
    
        return false;
    }
}

唯一的更改是添加一个名为“ngDoCheck()”的函数,它会打印一条消息说“Angular change detection runs!”。 Angular 文档说明“ngDoCheck()”函数在每次更改检测时都会运行。

如果您运行示例并且保持开发者工具打开。如果您现在单击“SHOW RIGHT”按钮,您会看到对于这个简单的按钮单击,Angular 更改检测运行了 34 次。

Angular 更改检测对于 Angular 控制的任何 DOM 事件或回调函数都会运行。为了实现滑动效果,jQuery 安排了 33 个“setTimeout()”回调函数以滑动方式移动容器中的内容。每个回调都触发了一次更改检测。

将其从 Angular 中移除

Angular 更改检测是 Angular 使数据与 DOM 同步的方式。根据一些博客,Angular 更改检测运行得非常快。但是,我们希望将其从这个容器组件中移除是非常有道理的。

  • 这个容器组件没有任何数据供 Angular 绑定,它的唯一目的是滑动内容;
  • Angular 更改检测在全局级别运行。这意味着,如果我们从这个组件触发更改检测,Angular 可能会查看整个 Angular 应用程序中的整个组件树。虽然 Angular 声称更改检测运行得非常快,但对于一个简单的按钮单击来说,34 次更改检测是难以置信的。

为了将其从 Angular 中移除,我们需要将“NgZone”注入到组件中。

declare let $;
import { Component, Input, OnInit, DoCheck } from '@angular/core';
import { NgZone, ElementRef } from '@angular/core';
    
@Component({
    moduleId: module.id,
    selector: 'slide-container',
    templateUrl: 'slide-container.component.html'
})
export class SlideContainer implements OnInit, DoCheck {
    @Input() Width;
    @Input() Height;
    
    private animation_speed = 500;
    public ControlHeight;
    public SlideHeight;
    
    constructor(private zone: NgZone, private eRef: ElementRef) { }
    
    // Console.log() to see if change detection runs
    ngDoCheck() {
        console.log('Angular change detection runs!');
    }
    
    ngOnInit() {
        let height = parseInt(this.Height);
        let controlHeight = 50;
    
        this.ControlHeight = controlHeight + 'px';
        this.SlideHeight = (height - controlHeight) + 'px';
    
        // Hook up the button click events out of Angular
        this.zone.runOutsideAngular(() => {
            let container = $(this.eRef.nativeElement);
            let l = $('.slide-left', container);
            let r = $('.slide-right', container);
            let width = parseInt(this.Width);
            let speed = this.animation_speed;
    
            let slide = function (show) {
                if (show == 'left') {
                    l.animate({ left: 0 }, speed);
                    r.animate({ left: width }, speed);
                }
                else {
                    l.animate({ left: -1 * width }, speed);
                    r.animate({ left: 0 }, speed);
                }
            };
    
            // Show left button
            $('.left-btn', container).click(function () {
                slide('left');
                return false;
            });
    
            // Show right button
            $('.right-btn', container).click(function () {
                slide('right');
                return false;
            });

        });
    }
}

“NgZone.runOutsideAngular()”函数允许我们在 Angular 外部挂钩按钮单击事件,因此它们不会触发更改检测。我们还需要更改模板文件,以移除对已从“slide-container.component.ts”文件中移除的“Slide()”函数的绑定。

        <div style="float: right; height: 100%; display: flex;
                align-items: center; margin-right: 5px">
            <button class="left-btn" style="height: 80%; border-radius: 3px">
    
                <!-- Button text on show left button -->
                <ng-content select="[left-button-text]"></ng-content>
            </button>
            <button class="right-btn" style="height: 80%; border-radius: 3px">
    
                <!-- Button text on show right button -->
                <ng-content select="[right-button-text]"></ng-content>
            </button>
        </div>

如果您运行示例应用程序并单击按钮,您会发现按钮单击不再触发更改检测。

关注点

  • 这是关于 Angular 2 容器组件的说明。在 Angular 术语中,这被称为transclusion
  • 希望您喜欢我的博文,并希望这篇笔记能以某种方式帮助您。

历史

首次修订 - 2017 年 1 月 14 日

© . All rights reserved.