docs(dyn-comp-loader): add explanations for methods, add doc structure, plunker (#3451)

This commit is contained in:
Kapunahele Wong 2017-04-01 14:31:05 -04:00 committed by Ward Bell
parent cd0c36340e
commit 4f87145170
3 changed files with 129 additions and 62 deletions

View File

@ -0,0 +1,9 @@
{
"description": "Dynamic Component Loader",
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js"
],
"tags":["cookbook component"]
}

View File

@ -11,11 +11,12 @@ import { AdComponent } from './ad.component';
template: `
<div class="ad-banner">
<h3>Advertisements</h3>
<template ad-host></template>
<ng-template ad-host></ng-template>
</div>
`
// #enddocregion ad-host
// #enddocregion ad-host
})
// #docregion class
export class AdBannerComponent implements AfterViewInit, OnDestroy {
@Input() ads: AdItem[];
currentAddIndex: number = -1;
@ -53,3 +54,4 @@ export class AdBannerComponent implements AfterViewInit, OnDestroy {
}, 3000);
}
}
// #enddocregion class

View File

@ -3,107 +3,158 @@ include ../_util-fns
:marked
Component templates are not always fixed. An application may need to load new components at runtime.
In this cookbook we show how to use `ComponentFactoryResolver` to add components dynamically.
This cookbook shows you how to use `ComponentFactoryResolver` to add components dynamically.
<a id="toc"></a>
:marked
## Table of contents
# Contents
[Dynamic Component Loading](#dynamic-loading)
* [Dynamic component loading](#dynamic-loading)
* [The directive](#directive)
* [Loading components](#loading-components)
[Where to load the component](#where-to-load)
* [Resolving Components](#resolving-components)
* [Selector References](#selector-references)
[Loading components](#loading-components)
* [A common _AdComponent_ interface](#common-interface)
* [Final ad banner](#final-ad-banner)
:marked
See the <live-example name="cb-dynamic-component-loader"></live-example>
of the code in this cookbook.
.l-main-section
<a id="dynamic-loading"></a>
:marked
## Dynamic Component Loading
## Dynamic component loading
The following example shows how to build a dynamic ad banner.
The hero agency is planning an ad campaign with several different ads cycling through the banner.
The following example shows how to build a dynamic ad banner.
New ad components are added frequently by several different teams. This makes it impractical to use a template with a static component structure.
Instead we need a way to load a new component without a fixed reference to the component in the ad banner's template.
The hero agency is planning an ad campaign with several different
ads cycling through the banner. New ad components are added
frequently by several different teams. This makes it impractical
to use a template with a static component structure.
Angular comes with its own API for loading components dynamically. In the following sections you will learn how to use it.
Instead, you need a way to load a new component without a fixed
reference to the component in the ad banner's template.
Angular comes with its own API for loading components dynamically.
.l-main-section
<a id="where-to-load"></a>
<a id="directive"></a>
:marked
## Where to load the component
## The directive
Before components can be added we have to define an anchor point to mark where components can be inserted dynamically.
Before you can add components you have to define an anchor point
to tell Angular where to insert components.
The ad banner uses a helper directive called `AdDirective` to mark valid insertion points in the template.
The ad banner uses a helper directive called `AdDirective` to
mark valid insertion points in the template.
+makeExample('cb-dynamic-component-loader/ts/src/app/ad.directive.ts',null,'src/app/ad.directive.ts')(format='.')
:marked
`AdDirective` injects `ViewContainerRef` to gain access to the view container of the element that will become the host of the dynamically added component.
`AdDirective` injects `ViewContainerRef` to gain access to the view
container of the element that will host the dynamically added component.
In the `@Directive` decorator, notice the selector name, `ad-host`;
that's what you use to apply the directive to the element.
The next section shows you how.
.l-main-section
<a id="loading-components"></a>
:marked
## Loading components
The next step is to implement the ad banner. Most of the implementation is in `AdBannerComponent`.
Most of the ad banner implementation is in `ad-banner.component.ts`.
To keep things simple in this example, the HTML is in the `@Component`
decorator's `template` property as a template string.
We start by adding a `template` element with the `AdDirective` directive applied.
+makeTabs(
`cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts,
cb-dynamic-component-loader/ts/src/app/ad.service.ts,
cb-dynamic-component-loader/ts/src/app/ad-item.ts,
cb-dynamic-component-loader/ts/src/app/app.module.ts,
cb-dynamic-component-loader/ts/src/app/app.component.ts`,
null,
`ad-banner.component.ts,
ad.service.ts,
ad-item.ts,
app.module.ts,
app.component`
)
:marked
The `template` element decorated with the `ad-host` directive marks where dynamically loaded components will be added.
Using a `template` element is recommended since it doesn't render any additional output.
The `<ng-template>` element is where you apply the directive you just made.
To apply the `AdDirective`, recall the selector from `ad.directive.ts`,
`ad-host`. Apply that to `<ng-template>` without the square brackets. Now Angular knows
where to dynamically load components.
+makeExample('cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts', 'ad-host' ,'src/app/ad-banner.component.ts (template)')(format='.')
:marked
### Resolving Components
The `<ng-template>` element is a good choice for dynamic components
because it doesn't render any additional output.
`AdBanner` takes an array of `AdItem` objects as input. `AdItem` objects specify the type of component to load and any data to bind to the component.
a#resolving-components
:marked
### Resolving components
The ad components making up the ad campaign are returned from `AdService`.
Passing an array of components to `AdBannerComponent` allows for a dynamic list of ads without static elements in the template.
Take a closer look at the methods in `ad-banner.component.ts`.
`AdBannerComponent` cycles through the array of `AdItems` and loads the corresponding components on an interval. Every 3 seconds a new component is loaded.
`AdBannerComponent` takes an array of `AdItem` objects as input,
which ultimately comes from `AdService`. `AdItem` objects specify
the type of component to load and any data to bind to the
component.`AdService` returns the actual ads making up the ad campaign.
`ComponentFactoryResolver` is used to resolve a `ComponentFactory` for each specific component. The component factory is need to create an instance of the component.
Passing an array of components to `AdBannerComponent` allows for a
dynamic list of ads without static elements in the template.
`ComponentFactories` are generated by the Angular compiler.
Generally the compiler will generate a component factory for any component referenced in a template.
With dynamically loaded components there are no selector references in the templates since components are loaded at runtime. In order to ensure that the compiler will still generate a factory, dynamically loaded components have to be added to their `NgModule`'s `entryComponents` array.
With its `getAds()` method, `AdBannerComponent` cycles through the array of `AdItems`
and loads a new component every 3 seconds by calling `loadComponent()`.
+makeExample('cb-dynamic-component-loader/ts/src/app/app.module.ts', 'entry-components' ,'src/app/app.module.ts (entry components)')(format='.')
+makeExample('cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts', 'class' ,'src/app/ad-banner.component.ts (excerpt)')(format='.')
:marked
Components are added to the template by calling `createComponent` on the `ViewContainerRef` reference.
`createComponent` returns a reference to the loaded component. The component reference can be used to pass input data or call methods to interact with the component.
The `loadComponent()` method is doing a lot of the heavy lifting here.
Take it step by step. First, it picks an ad.
In the Ad banner, all components implement a common `AdComponent` interface to standardize the api for passing data to the components.
.l-sub-section
:marked
**How _loadComponent()_ chooses an ad**
Two sample components and the `AdComponent` interface are shown below:
The `loadComponent()` method chooses an ad using some math.
First, it sets the `currentAddIndex` by taking whatever it
currently is plus one, dividing that by the length of the `AdItem` array, and
using the _remainder_ as the new `currentAddIndex` value. Then, it uses that
value to select an `adItem` from the array.
:marked
After `loadComponent()` selects an ad, it uses `ComponentFactoryResolver`
to resolve a `ComponentFactory` for each specific component.
The `ComponentFactory` then creates an instance of each component.
Next, you're targeting the `viewContainerRef` that
exists on this specific instance of the component. How do you know it's
this specific instance? Because it's referring to `adHost` and `adHost` is the
directive you set up earlier to tell Angular where to insert dynamic components.
As you may recall, `AdDirective` injects `ViewContainerRef` into its constructor.
This is how the directive accesses the element that you want to use to host the dynamic component.
To add the component to the template, you call `createComponent()` on `ViewContainerRef`.
The `createComponent()` method returns a reference to the loaded component.
Use that reference to interact with the component by assigning to its properties or calling its methods.
a#selector-references
:marked
#### Selector references
Generally, the Angular compiler generates a `ComponentFactory`
for any component referenced in a template. However, there are
no selector references in the templates for
dynamically loaded components since they load at runtime.
To ensure that the compiler still generates a factory,
add dynamically loaded components to the `NgModule`'s `entryComponents` array:
+makeExample('cb-dynamic-component-loader/ts/src/app/app.module.ts', 'entry-components' ,'src/app/app.module.ts (entry components)')(format='.')
a#common-interface
:marked
### A common _AdComponent_ interface
In the ad banner, all components implement a common `AdComponent` interface to
standardize the API for passing data to the components.
Here are two sample components and the `AdComponent` interface for reference:
+makeTabs(
`cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts,
@ -113,9 +164,14 @@ include ../_util-fns
`hero-job-ad.component.ts,
hero-profile.component.ts,
ad.component.ts`
)
)
a#final-ad-baner
:marked
### Final ad banner
The final ad banner looks like this:
figure.image-display
img(src="/resources/images/cookbooks/dynamic-component-loader/ads.gif" alt="Ads")
img(src="/resources/images/cookbooks/dynamic-component-loader/ads.gif" alt="Ads")
:marked
See the <live-example name="cb-dynamic-component-loader"></live-example>.