From 30d5e25a289fa4bf6e1fea8a58a84816d2c2d1e9 Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Tue, 24 Nov 2020 08:49:34 -0500 Subject: [PATCH] docs: edit attribute-directives.md, move best practice to styleguide (#39849) PR Close #39849 --- .../src/app/app.component.1.html | 4 - .../src/app/highlight.directive.2.ts | 15 +- .../src/app/highlight.directive.3.ts | 2 - .../src/app/highlight.directive.ts | 2 - aio/content/guide/attribute-directives.md | 412 ++++-------------- aio/content/guide/styleguide.md | 9 + 6 files changed, 102 insertions(+), 342 deletions(-) diff --git a/aio/content/examples/attribute-directives/src/app/app.component.1.html b/aio/content/examples/attribute-directives/src/app/app.component.1.html index f05c4a1bd3..9a9bfccd73 100644 --- a/aio/content/examples/attribute-directives/src/app/app.component.1.html +++ b/aio/content/examples/attribute-directives/src/app/app.component.1.html @@ -4,11 +4,7 @@

Highlight me!

-

Highlighted in yellow

Highlighted in orange

- -

Highlighted with parent component's color

- \ No newline at end of file diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts index 0b31849ca5..b18eadbd95 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts @@ -1,8 +1,8 @@ /* tslint:disable:no-unused-variable member-ordering */ // #docplaster -// #docregion imports, +// #docregion imports import { Directive, ElementRef, HostListener } from '@angular/core'; -// #enddocregion imports, +// #enddocregion imports import { Input } from '@angular/core'; // #docregion @@ -10,9 +10,8 @@ import { Input } from '@angular/core'; selector: '[appHighlight]' }) export class HighlightDirective { - // #docregion ctor + constructor(private el: ElementRef) { } - // #enddocregion ctor // #docregion mouse-methods @HostListener('mouseenter') onMouseEnter() { @@ -26,16 +25,10 @@ export class HighlightDirective { private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; } - // #enddocregion mouse-methods, - - // #docregion color - @Input() highlightColor: string; - // #enddocregion color - + // #enddocregion mouse-methods // #docregion color-2 @Input() appHighlight: string; // #enddocregion color-2 -// #docregion } // #enddocregion diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts index 01c7cb679c..4f17bd4924 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts @@ -12,11 +12,9 @@ export class HighlightDirective { @Input('appHighlight') highlightColor: string; - // #docregion mouse-enter @HostListener('mouseenter') onMouseEnter() { this.highlight(this.highlightColor || 'red'); } - // #enddocregion mouse-enter @HostListener('mouseleave') onMouseLeave() { this.highlight(null); diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.ts index dd138ae09d..fbee354143 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.ts @@ -12,9 +12,7 @@ export class HighlightDirective { @Input() defaultColor: string; // #enddocregion defaultColor - // #docregion color @Input('appHighlight') highlightColor: string; - // #enddocregion color // #docregion mouse-enter @HostListener('mouseenter') onMouseEnter() { diff --git a/aio/content/guide/attribute-directives.md b/aio/content/guide/attribute-directives.md index 047d011a91..ef2264a3f0 100644 --- a/aio/content/guide/attribute-directives.md +++ b/aio/content/guide/attribute-directives.md @@ -1,408 +1,174 @@ # Attribute directives -An **Attribute** directive changes the appearance or behavior of a DOM element. +With attribute directives, you can change the appearance or behavior of DOM elements and Angular components. -Try the . +
-{@a directive-overview} +See the for a working example containing the code snippets in this guide. -## Directives overview +
-There are three kinds of directives in Angular: +## Building an attribute directive -1. Components—directives with a template. -1. Structural directives—change the DOM layout by adding and removing DOM elements. -1. Attribute directives—change the appearance or behavior of an element, component, or another directive. +This section walks you through creating a highlight directive that sets the background color of the host element to yellow. -*Components* are the most common of the three directives. -You saw a component for the first time in the [Getting Started](start "Getting Started with Angular") tutorial. +1. To create a directive, use the CLI command [`ng generate directive`](cli/generate). -*Structural Directives* change the structure of the view. -Two examples are [NgFor](guide/built-in-directives#ngFor) and [NgIf](guide/built-in-directives#ngIf). -Learn about them in the [Structural Directives](guide/structural-directives) guide. - -*Attribute directives* are used as attributes of elements. -The built-in [NgStyle](guide/built-in-directives#ngstyle) directive in the -[Built-in directives](guide/built-in-directives) guide, for example, -can change several element styles at the same time. - -## Build a simple attribute directive - -An attribute directive minimally requires building a controller class annotated with -`@Directive`, which specifies the selector that identifies -the attribute. -The controller class implements the desired directive behavior. - -This page demonstrates building a simple _appHighlight_ attribute -directive to set an element's background color -when the user hovers over that element. You can apply it like this: - - - -{@a write-directive} - -Please note that directives _do not_ support namespaces. - - - -### Write the directive code - -Create the directive class file in a terminal window with the CLI command [`ng generate directive`](cli/generate). - - + ng generate directive highlight -The CLI creates `src/app/highlight.directive.ts`, a corresponding test file `src/app/highlight.directive.spec.ts`, and _declares_ the directive class in the root `AppModule`. + The CLI creates `src/app/highlight.directive.ts`, a corresponding test file `src/app/highlight.directive.spec.ts`, and declares the directive class in the `AppModule`. + + The CLI generates the default `src/app/highlight.directive.ts` as follows: + + + + The `@Directive()` decorator's configuration property specifies the directive's CSS attribute selector, `[appHighlight]`. + +1. Import `ElementRef` from `@angular/core`. + `ElementRef` grants direct access to the host DOM element through its `nativeElement` property. + +1. Add `ElementRef` in the directive's `constructor()` to [inject](guide/dependency-injection) a reference to the host DOM element, the element to which you apply `appHighlight`. + +1. Add logic to the `HighlightDirective` class that sets the background to yellow. + +
-_Directives_ must be declared in [Angular Modules](guide/ngmodules) in the same manner as _components_. + Directives _do not_ support namespaces. -
- -The generated `src/app/highlight.directive.ts` is as follows: - - - -The imported `Directive` symbol provides Angular the `@Directive` decorator. - -The `@Directive` decorator's lone configuration property specifies the directive's -[CSS attribute selector](https://developer.mozilla.org/docs/Web/CSS/Attribute_selectors), `[appHighlight]`. - -It's the brackets (`[]`) that make it an attribute selector. -Angular locates each element in the template that has an attribute named `appHighlight` and applies the logic of this directive to that element. - -The _attribute selector_ pattern explains the name of this kind of directive. - -
- -#### Why not "highlight"? - -Though *highlight* would be a more concise selector than *appHighlight* and it would work, -the best practice is to prefix selector names to ensure -they don't conflict with standard HTML attributes. -This also reduces the risk of colliding with third-party directive names. -The CLI added the `app` prefix for you. - -Make sure you do **not** prefix the `highlight` directive name with **`ng`** because -that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose. +
-After the `@Directive` metadata comes the directive's controller class, -called `HighlightDirective`, which contains the (currently empty) logic for the directive. -Exporting `HighlightDirective` makes the directive accessible. - -Now edit the generated `src/app/highlight.directive.ts` to look as follows: - - - -The `import` statement specifies an additional `ElementRef` symbol from the Angular `core` library: - -You use the `ElementRef` in the directive's constructor -to [inject](guide/dependency-injection) a reference to the host DOM element, -the element to which you applied `appHighlight`. - -`ElementRef` grants direct access to the host DOM element -through its `nativeElement` property. - -This first implementation sets the background color of the host element to yellow. - {@a apply-directive} +## Applying an attribute directive -## Apply the attribute directive +1. To use the `HighlightDirective`, add a `

` element to the HTML template with the directive as an attribute. -To use the new `HighlightDirective`, add a paragraph (`

`) element to the template of the root `AppComponent` and apply the directive as an attribute. + - - -Now run the application to see the `HighlightDirective` in action. - - - -ng serve - - -To summarize, Angular found the `appHighlight` attribute on the **host** `

` element. -It created an instance of the `HighlightDirective` class and -injected a reference to the `

` element into the directive's constructor -which sets the `

` element's background style to yellow. +Angular creates an instance of the `HighlightDirective` class and injects a reference to the `

` element into the directive's constructor, which sets the `

` element's background style to yellow. {@a respond-to-user} -## Respond to user-initiated events +## Handling user events -Currently, `appHighlight` simply sets an element color. -The directive could be more dynamic. -It could detect when the user mouses into or out of the element -and respond by setting or clearing the highlight color. +This section shows you how to detect when a user mouses into or out of the element and to respond by setting or clearing the highlight color. -Begin by adding `HostListener` to the list of imported symbols. +1. Import `HostListener` from '@angular/core'. - + -Then add two event handlers that respond when the mouse enters or leaves, -each adorned by the `HostListener` decorator. +1. Add two event handlers that respond when the mouse enters or leaves, each with the `@HostListener()` decorator. - + -The `@HostListener` decorator lets you subscribe to events of the DOM -element that hosts an attribute directive, the `

` in this case. + With the `@HostListener()` decorator, you can subscribe to events of the DOM element that hosts an attribute directive, the `

` in this case. -

+ The handlers delegate to a helper method, `highlight()`, that sets the color on the host DOM element, `el`. -Of course you could reach into the DOM with standard JavaScript and attach event listeners manually. -There are at least three problems with _that_ approach: - -1. You have to write the listeners correctly. -1. The code must *detach* the listener when the directive is destroyed to avoid memory leaks. -1. Talking to DOM API directly isn't a best practice. - -
- -The handlers delegate to a helper method that sets the color on the host DOM element, `el`. - -The helper method, `highlight`, was extracted from the constructor. -The revised constructor simply declares the injected `el: ElementRef`. - - - -Here's the updated directive in full: +The complete directive is as follows: -Run the app and confirm that the background color appears when -the pointer hovers over the paragraph element and disappears as the pointer moves out. +The background color appears when the pointer hovers over the paragraph element and disappears as the pointer moves out. {@a bindings} +## Passing values into an attribute directive -## Pass values into the directive with an _@Input_ data binding +This section walks you through setting the highlight color while applying the `HighlightDirective`. -Currently the highlight color is hard-coded _within_ the directive. That's inflexible. -In this section, you give the developer the power to set the highlight color while applying the directive. +1. In `highlight.directive.ts`, import `Input` from `@angular/core`. -Begin by adding `Input` to the list of symbols imported from `@angular/core`. - + -Add a `highlightColor` property to the directive class like this: +1. Add an `appHighlight` `@Input()` property. - + -{@a input} + The `@Input()` decorator adds metadata to the class that makes the directive's `appHighlight` property available for binding. -### Binding to an `@Input()` property +1. In `app.component.ts`, add a `color` property to the `AppComponent`. -Notice the `@Input()` decorator. It adds metadata to the class that makes the directive's `highlightColor` property available for binding. + -It's called an *input* property because data flows from the binding expression _into_ the directive. -Without that `@Input()` metadata, Angular rejects the binding; see [below](guide/attribute-directives#why-input "Why add @Input?") for more information. +1. To simultaneously apply the directive and the color, use property binding with the `appHighlight` directive selector, setting it equal to `color`. -Try it by adding the following directive binding variations to the `AppComponent` template: + - + The `[appHighlight]` attribute binding performs two tasks: -Add a `color` property to the `AppComponent`. + * applies the highlighting directive to the `

` element + * sets the directive's highlight color with a property binding - +### Setting the value with user input -Let it control the highlight color with a property binding. +This section guides you through adding radio buttons to bind your color choice to the `appHighlight` directive. - +1. Add markup to `app.component.html` for choosing a color as follows: -That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this. + - +1. Revise the `AppComponent.color` so that it has no initial value. -The `[appHighlight]` attribute binding both applies the highlighting directive to the `

` element -and sets the directive's highlight color with a property binding. -You're re-using the directive's attribute selector (`[appHighlight]`) to do both jobs. -That's a crisp, compact syntax. + -You'll have to rename the directive's `highlightColor` property to `appHighlight` because that's now the color property binding name. +1. Serve your application to verify that the user can choose the color with the radio buttons. - - -This is disagreeable. The word, `appHighlight`, is a terrible property name and it doesn't convey the property's intent. - -{@a input-alias} - -### Bind to an _@Input_ alias - -Fortunately you can name the directive property whatever you want _and_ **_alias it_** for binding purposes. - -Restore the original property name and specify the selector as the alias in the argument to `@Input()`. - - - -_Inside_ the directive the property is known as `highlightColor`. -_Outside_ the directive, where you bind to it, it's known as `appHighlight`. - -You get the best of both worlds: the property name you want and the binding syntax you want: - - - -Now that you're binding via the alias to the `highlightColor`, modify the `onMouseEnter()` method to use that property. -If someone neglects to bind to `appHighlight`, highlight the host element in red: - - - -Here's the latest version of the directive class. - - - -## Write a harness to try it - -It may be difficult to imagine how this directive actually works. -In this section, you'll turn `AppComponent` into a harness that -lets you pick the highlight color with a radio button and bind your color choice to the directive. - -Update app.component.html as follows: - - - -Revise the `AppComponent.color` so that it has no initial value. - - - -Here are the harness and directive in action. - -

+ {@a second-property} -## Bind to a second property +## Binding to a second property -This highlight directive has a single customizable property. In a real app, it may need more. +This section guides you through configuring your application so the developer can set the default color. -At the moment, the default color—the color that prevails until -the user picks a highlight color—is hard-coded as "red". -Let the template developer set the default color. +1. Add a second `Input()` property to `HighlightDirective` called `defaultColor`. -Add a second **input** property to `HighlightDirective` called `defaultColor`: + - +1. Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`, then with the `defaultColor`, and falls back to `red` if both properties are `undefined`. -Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`, -then with the `defaultColor`, and falls back to "red" if both properties are undefined. + - +1. To bind to the `AppComponent.color` and fall back to "violet" as the default color, add the following HTML. + In this case, the `defaultColor` binding doesn't use square brackets, `[]`, because it is static. -How do you bind to a second property when you're already binding to the `appHighlight` attribute name? + -As with components, you can add as many directive property bindings as you need by stringing them along in the template. -The developer should be able to write the following template HTML to both bind to the `AppComponent.color` -and fall back to "violet" as the default color. + As with components, you can add multiple directive property bindings to a host element. - +The default color is red if there is no default color binding. +When the user chooses a color the selected color becomes the active highlight color. -Angular knows that the `defaultColor` binding belongs to the `HighlightDirective` -because you made it _public_ with the `@Input()` decorator. - -Here's how the harness should work when you're done coding. - - + {@a ngNonBindable} -## `ngNonBindable` -With the built-in template primitive `ngNonBindable`, Angular won't -evaluate expressions in elements. For example: +## Deactivating Angular processing with `NgNonBindable` + +To prevent expression evaluation in the browser, add `ngNonBindable` to the host element. +`ngNonBindable` deactivates interpolation, directives, and binding in templates. + +In the following example, the expression `{{ 1 + 1 }}` renders just as it does in your code editor, and does not display `2`. -The expression `{{ 1 + 1 }}` will render just as it does in your code editor, -and will not display `2`. This is helpful when you want to render code in the browser. - -When you apply `ngNonBindable` to an element, it stops any binding starting at that element, including child elements. However, `ngNonBindable` still allows -directives to work to the element where you apply `ngNonBindable`. In the following example, the `appHighlight` directive will still be active but Angular will not evaluate the expression `{{ 1 + 1 }}`. +Applying `ngNonBindable` to an element stops binding for that element's child elements. +However, `ngNonBindable` still allows directives to work on the element where you apply `ngNonBindable`. +In the following example, the `appHighlight` directive is still active but Angular does not evaluate the expression `{{ 1 + 1 }}`. -Additionally, if you apply `ngNonBindable` to a parent element, interpolation and binding of any sort, such as property binding, or event binding, is disabled for its children. - -## Summary - -This page covered how to: - -* [Build an **attribute directive**](guide/attribute-directives#write-directive) that modifies the behavior of an element. -* [Apply the directive](guide/attribute-directives#apply-directive) to an element in a template. -* [Respond to **events**](guide/attribute-directives#respond-to-user) that change the directive's behavior. -* [**Bind** values to the directive](guide/attribute-directives#bindings). -* [Prevent expression evaluation](guide/attribute-directives#ngNonBindable). - -The final source code follows: - - - - - - - - - - - - -You can also experience and download the . - -{@a why-input} - -### Appendix: Why add `@Input()`? - -In this demo, the `highlightColor` property is an `@Input()` property of -the `HighlightDirective`. You've seen it applied without an alias: - - - -You've seen it with an alias: - - - -Either way, the `@Input()` decorator tells Angular that this property is -_public_ and available for binding by a parent component. -Without `@Input()`, Angular refuses to bind to the property. - -You've bound template HTML to component properties before and never used `@Input()`. -What's different? - -The difference is a matter of trust. -Angular treats a component's template as _belonging_ to the component. -The component and its template trust each other implicitly. -Therefore, the component's own template may bind to _any_ property of that component, -with or without the `@Input()` decorator. - -But a component or directive shouldn't blindly trust _other_ components and directives. -The properties of a component or directive are hidden from binding by default. -They are _private_ from an Angular binding perspective. -When adorned with the `@Input()` decorator, the property becomes _public_ from an Angular binding perspective. -Only then can it be bound by some other component or directive. - -You can tell if `@Input()` is needed by the position of the property name in a binding. - -* When it appears in the template expression to the ***right*** of the equals (=), - it belongs to the template's component and does not require the `@Input()` decorator. - -* When it appears in **square brackets** ([ ]) to the **left** of the equals (=), - the property belongs to some _other_ component or directive; - that property must be adorned with the `@Input()` decorator. - -Now apply that reasoning to the following example: - - - -* The `color` property in the expression on the right belongs to the template's component. - The template and its component trust each other. - The `color` property doesn't require the `@Input()` decorator. - -* The `appHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`, - not a property of the template's component. - For security, the directive property must carry the `@Input()` decorator. +If you apply `ngNonBindable` to a parent element, Angular disables interpolation and binding of any sort, such as property binding or event binding, for the element's children. diff --git a/aio/content/guide/styleguide.md b/aio/content/guide/styleguide.md index 5d8da7825c..23e40e8f37 100644 --- a/aio/content/guide/styleguide.md +++ b/aio/content/guide/styleguide.md @@ -1026,6 +1026,15 @@ For example, the prefix `toh` represents **T**our **o**f **H**eroes and the pref **Do** spell non-element selectors in lower camel case unless the selector is meant to match a native HTML attribute. + + +
+ + + +**Don't** prefix a directive name with `ng` because that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose. + +