angular-cn/aio/content/guide/dynamic-component-loader.md

201 lines
6.1 KiB
Markdown
Raw Normal View History

# Dynamic Component Loader
2017-03-31 19:57:13 -04:00
Component templates are not always fixed. An application may need to load new components at runtime.
2017-03-31 19:57:13 -04:00
This cookbook shows you how to use `ComponentFactoryResolver` to add components dynamically.
See the <live-example name="dynamic-component-loader"></live-example>
2017-03-31 19:57:13 -04:00
of the code in this cookbook.
2017-03-31 19:57:13 -04:00
{@a dynamic-loading}
2017-03-31 19:57:13 -04:00
## Dynamic component loading
2017-03-31 19:57:13 -04:00
The following example shows how to build a dynamic ad banner.
2017-03-31 19:57:13 -04:00
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.
2017-03-31 19:57:13 -04:00
Instead, you need a way to load a new component without a fixed
reference to the component in the ad banner's template.
2017-03-31 19:57:13 -04:00
Angular comes with its own API for loading components dynamically.
2017-03-31 19:57:13 -04:00
{@a directive}
## The anchor directive
2017-03-31 19:57:13 -04:00
Before you can add components you have to define an anchor point
to tell Angular where to insert components.
2017-03-31 19:57:13 -04:00
The ad banner uses a helper directive called `AdDirective` to
mark valid insertion points in the template.
<code-example path="dynamic-component-loader/src/app/ad.directive.ts" header="src/app/ad.directive.ts" linenums="false">
</code-example>
2017-03-31 19:57:13 -04:00
`AdDirective` injects `ViewContainerRef` to gain access to the view
container of the element that will host the dynamically added component.
2017-03-31 19:57:13 -04:00
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.
2017-03-31 19:57:13 -04:00
{@a loading-components}
2017-03-31 19:57:13 -04:00
## Loading components
2017-03-31 19:57:13 -04:00
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.
2017-03-31 19:57:13 -04:00
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.
<code-example path="dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" header="src/app/ad-banner.component.ts (template)" linenums="false">
2017-03-31 19:57:13 -04:00
</code-example>
2017-03-31 19:57:13 -04:00
The `<ng-template>` element is a good choice for dynamic components
because it doesn't render any additional output.
2017-03-31 19:57:13 -04:00
{@a resolving-components}
## Resolving components
2017-03-31 19:57:13 -04:00
Take a closer look at the methods in `ad-banner.component.ts`.
2017-03-31 19:57:13 -04:00
`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
2017-03-31 19:57:13 -04:00
component.`AdService` returns the actual ads making up the ad campaign.
2017-03-31 19:57:13 -04:00
Passing an array of components to `AdBannerComponent` allows for a
dynamic list of ads without static elements in the template.
2017-03-31 19:57:13 -04:00
With its `getAds()` method, `AdBannerComponent` cycles through the array of `AdItems`
and loads a new component every 3 seconds by calling `loadComponent()`.
<code-example path="dynamic-component-loader/src/app/ad-banner.component.ts" region="class" header="src/app/ad-banner.component.ts (excerpt)" linenums="false">
</code-example>
2017-03-31 19:57:13 -04:00
The `loadComponent()` method is doing a lot of the heavy lifting here.
Take it step by step. First, it picks an ad.
<div class="alert is-helpful">
2017-03-31 19:57:13 -04:00
2017-03-31 19:57:13 -04:00
**How _loadComponent()_ chooses an ad**
2017-03-31 19:57:13 -04:00
The `loadComponent()` method chooses an ad using some math.
First, it sets the `currentAdIndex` by taking whatever it
2017-03-31 19:57:13 -04:00
currently is plus one, dividing that by the length of the `AdItem` array, and
using the _remainder_ as the new `currentAdIndex` value. Then, it uses that
2017-03-31 19:57:13 -04:00
value to select an `adItem` from the array.
2017-04-10 11:51:13 -04:00
</div>
2017-03-31 19:57:13 -04:00
After `loadComponent()` selects an ad, it uses `ComponentFactoryResolver`
to resolve a `ComponentFactory` for each specific component.
2017-03-31 19:57:13 -04:00
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
2017-03-31 19:57:13 -04:00
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.
2017-03-31 19:57:13 -04:00
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}
#### 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:
<code-example path="dynamic-component-loader/src/app/app.module.ts" region="entry-components" header="src/app/app.module.ts (entry components)" linenums="false">
</code-example>
2017-03-31 19:57:13 -04:00
{@a common-interface}
## The _AdComponent_ interface
2017-03-31 19:57:13 -04:00
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:
<code-tabs>
<code-pane header="hero-job-ad.component.ts" path="dynamic-component-loader/src/app/hero-job-ad.component.ts">
</code-pane>
<code-pane header="hero-profile.component.ts" path="dynamic-component-loader/src/app/hero-profile.component.ts">
</code-pane>
<code-pane header="ad.component.ts" path="dynamic-component-loader/src/app/ad.component.ts">
</code-pane>
</code-tabs>
2017-03-31 19:57:13 -04:00
{@a final-ad-baner}
## Final ad banner
2017-03-31 19:57:13 -04:00
The final ad banner looks like this:
docs(aio): image sweep (#16609) * fix(aio): allow code blocks to clear floated images Previously the negative margin on the code headings were causing floated images to overlay the start of a code block. Now all code block successfully clear all floated elements. * feat(aio): add a `.clear` class for clearing floating images * fix(aio): tidy up image styles The css rules for `img.right` and `img.left` allow authors easy access to floating an image on the left or right, respectively. The `.image-display` rule which was always found on a figure has been simplified so that all figures have this styling. It is very unlikely that a figure will be used outside the content area; and at this time it seems like `figure` is as good an indicator that we want this kind of styling as anything. Now that images are all tagged with width and height values, we cannot assume to modify these dimensions via CSS as it can cause the image to lose its correct proportions. Until we find a better solition we must set `height` to `auto` when the screen width is below 1300px to ensure that these images maintain their proportions as they get shrunk to fit. * docs(aio): general tidy up of image HTML in guides Previously, the guides have a lot of inline image styling and unnecessary use of the `image-display` css class. Images over 700px are problematic for guide docs, so those have been given specific widths and associated heights. * docs(aio): use correct anchor for "back to the top" link The `#toc` anchor does not work when the page is wide enough that the TOC is floating to the side. * build(aio): add `#top-of-page` to path variants for link checking Since the `#top-of-page` is outside the rendered docs the `checkAnchorLinks` processor doesn't find them as valid targets for links. Adding them as a `pathVariant` solves this problem but will still catch links to docs that do not actually exist. * fix(aio): ensure that headings clear floated images * fix(aio): do not force live-example embedded image to 100% size This made them look too big, generally. Leaving them with no size means that they will look reasonable in large viewports and switch to 100% width in narrow viewports.
2017-05-09 18:53:32 -04:00
<figure>
<img src="generated/images/guide/dynamic-component-loader/ads.gif" alt="Ads">
</figure>
2017-03-31 19:57:13 -04:00
See the <live-example name="dynamic-component-loader"></live-example>.