Angular Directives In-Depth PDF
Document Details
Uploaded by DynamicTeal3293
Todd Motto
Tags
Summary
This book provides a comprehensive guide to mastering Angular directives. The author, Todd Motto, covers NgIf, NgFor, NgSwitch, NgStyle, and NgClass, providing in-depth explanations and examples for each. The book is suitable for experienced developers.
Full Transcript
Angular Directives In-Depth by Todd Motto Google Developer Expert tter n s w it h eac t i ve pa y nc Pipe R s & A s v a b le Obser Master the built-in Directives: NgIf, NgFor, NgSwitch, NgStyle and NgClass Contents Introduction................
Angular Directives In-Depth by Todd Motto Google Developer Expert tter n s w it h eac t i ve pa y nc Pipe R s & A s v a b le Obser Master the built-in Directives: NgIf, NgFor, NgSwitch, NgStyle and NgClass Contents Introduction.................................................. 2 What Directives will we cover?.................................... 2 Structural and Attribute Directives.................................. 3 Directive API............................................... 3 NgIf........................................................ 4 NgIf..................................................... 4 NgIf, Else.................................................. 4 NgIf, Else, Then.............................................. 5 NgIf and........................................ 6 NgIf and Async Pipe........................................... 6 NgIf and Syntax.................................... 8 NgFor...................................................... 9 NgFor.................................................... 9 NgFor and TrackBy Expression.................................... 9 NgFor Variables - Index, Odd, Even.................................. 11 NgFor and Async Pipe.......................................... 12 NgFor and Syntax................................... 13 NgSwitch.................................................... 14 NgSwitch.................................................. 14 NgSwitch and Default Case...................................... 15 NgSwitch and..................................... 15 NgSwitch and Syntax................................. 16 NgStyle and Style Bindings........................................ 18 Style Bindings............................................... 18 Multiple Style Bindings......................................... 19 Style Bindings and Units........................................ 19 Style Bindings via Objects....................................... 19 Style Bindings from Class Methods.................................. 20 Style Bindings from Class Getters................................... 21 Style Bindings from Strings....................................... 21 NgStyle................................................... 22 CONTENTS NgClass and Class Bindings........................................ 23 Class Bindings.............................................. 23 Multiple Class Bindings......................................... 24 Class Bindings via Objects....................................... 24 Class Bindings from Class Methods.................................. 24 Class Bindings from Class Getters.................................. 25 Class Bindings from Strings...................................... 26 Class Bindings from Arrays...................................... 26 NgClass................................................... 27 CONTENTS 1 Hi there! I’m Todd Motto¹, a Google Developer Expert for Angular and founder of Ultimate Courses. This is my straightforward, comprehensive guide to mastering Angular’s Directives! Whether you’re just getting started or have been using Angular for years, I’m sure this book will give you valuable insights and practices to fully master the Directives that Angular has to offer. I’d love to hear how you’ve enjoyed this book as well, please let me know on Twitter via @toddmotto². Excited yet? Let’s get started! ¹https://ultimatecourses.com/about ²https://twitter.com/toddmotto Introduction If you’re new to Angular, the word “Directive” might also be a fairly new addition to your vocabulary. Directives are a core concept in Angular that allow us to add functionality, and behaviour, to either an element or component. They’re an integral part of what makes Angular a great choice to build web applications as they allow us to encapsulate and reuse logic easily. Angular provides various Directives for us out of the box, and this book is a deep-dive on the most common ones you’ll use on a daily basis. Prepare to understand how each Angular Directive works, why it was created and what problems it solves. Not only will you understand the Directives, but how they fit into the bigger picture when dealing with things such as State Management, Component Architecture, Observables and much more. What Directives will we cover? In order, we’re going to fully explore the use cases, best practices and advanced syntax for the following Directives: NgIf NgFor NgSwitch NgStyle NgClass For all other built-in Angular Directives, feel free to check out the documentation³ at any time. All Directives that we’re going to cover here are exported from Angular’s CommonModule⁴, which is a required import to use them. By default the BrowserModule will include this CommonModule for us. If you’re new to Angular, great! I’m going to assume that you’re comfortable with JavaScript and the DOM (Document Object Model), so we can dive straight in and get to the good stuff. There’s lots to cover! ³https://angular.io/api?type=directive ⁴https://angular.io/api/common/CommonModule Introduction 3 Structural and Attribute Directives I’d like to take a moment to set the scene and give you some insight into the two types of Angular Directives; structural and attribute. To use a Directive, we declare its name on an element or component. There are a few variations in how to declare, based on the type of Directive we are using. Structural Directives Structural Directives are responsible for managing how templates are rendered inside our compo- nents. Structural Directives begin with an asterisk * syntax and tell Angular to treat the element or component as a template. We can then instruct Angular what to do with that template. We can show or hide it, iterate over it alongside a data structure and much more. This thinking is similar to how Web Components⁵ work with the element. In Angular, we have that offers similar functionality, and I’ll uncover the more advanced use cases and reasons we should adopt this style. Attribute Directives Attribute Directives are simpler and change existing elements or components appearance or behaviour. The key difference is that they do not create new templates or render them, they alter existing elements and components. This could be by adding a class or setting an inline style. Directive API Angular exposes it’s Directive API⁶ to us, which enables us to create our own custom Directives. If you’d like to learn how to create a custom Directive I’d encourage you to read my technical article on Creating a Custom Directive in Angular⁷. This book is dedicated to covering Angular’s built-in Directives only. ⁵https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots ⁶https://angular.io/api/core/Directive ⁷https://ultimatecourses.com/blog/ Component Architecture. Immutable State Management. One-Way Data Flow. Tired of not really understanding what you're doing? You're not alone. That's why I made this course. Architecture patterns Component roles (smart/dumb) Feature modules and lazy-loading State and data flow Services and dependency injection HttpClient and Retries Observables and pipe operators Routing and navigation Forms and validation 16,781 reviews Styling encapsulation and CSS 1. Watch the free lessons now: ultimatecourses.com/learn/angular-basics 2. Get a 25% discount with coupon code: ULTIMATE_ANGULAR NgIf NgIf⁸ is a Structural Directive that allows us to conditionally render an element or component based on an expression. NgIf The standard NgIf syntax is *ngIf="expression" and provides us with the ability to render a template if the expression evaluates to true, and optionally supply a fallback if false. We declare NgIf and supply a condition that must pass if the element is to be rendered: 1 @Component({ 2 template: ` 3 4 We have donuts! 5 6 ` 7 }) 8 export class AppComponent { 9 donuts: Donut[] = [ 10 { name: 'Glazed Fudge', price: 119 } 11 ]; 12 } As donuts.length will be 1 this will render the element, because 1 evaluates to true in JavaScript (and Angular expressions behave the same way). Angular is a reactive framework and will also manage these updates if and when new data becomes available, therefore if donuts.length changes to 0, evaluating to false, the NgIf Directive will respond accordingly and remove the element entirely. NgIf, Else NgIf also supports an else clause, much like if and else in JavaScript statements. This allows us to render a template should the expression evaluate false. ⁸https://angular.io/api/common/NgIf NgIf 5 To create an else block we first declare an and a Template Reference Variable⁹ to create a local reference to the template. This reference variable called #nothing is then passed into NgIf as part of the expression: 1 2 We have donuts! 3 4 5 6 No donuts here. 7 Angular will never render an unless you instruct it to via a Directive or Template Reference Variable.It is there to register a template partial and will remain in virtual memory until needed. If you create an and never specify a Directive, you will never see it rendered (much like a Web Component element). NgIf, Else, Then As component templates grow it may be useful to consider the then syntax with NgIf. This syntax allows us to declare where the templates will render, but abstract where we create them. This lets us break things out into two places, with the then and else clauses both expecting a Template Reference Variable from each : 1 2 This is always ignored. 3 4 5 6 We have donuts! 7 8 9 10 No donuts here. 11 This approach allows more separation, however our is always rendered which creates an additional element every single time. If only there was a way to declare a Directive without rendering an additional element… That’s where comes in. ⁹https://ultimatecourses.com/blog NgIf 6 NgIf and To avoid rendering an extra element, such as a , we can turn to to wrap elements and provide a spot to bind Directives: 1 2 3 4 We have donuts! 5 6 7 8 No donuts here. 9 My personal preference is to use this approach, which uses an alongside just an else clause: 1 2 We have donuts! 3 4 5 6 No donuts here. 7 I prefer this approach because it allows me to see directly what is rendered at the declaration site. Although the then syntax is powerful and clever, I don’t feel it justifies the additional overhead to work out what’s happening, nor does it make the template any cleaner. The combination of and is a great solution to many real-world problems and scenarios. It also handles Observables very well and allows us to expand a result as a variable. NgIf and Async Pipe Angular is a reactive framework, which means it supports and implements Observables. We can subscribe to Observables directly inside our templates using the async pipe. We can also combine the async pipe with NgIf. Using the Async Pipe with NgIf¹⁰ at the level subscribes our Observable and stores the result on a variable that we can reference: ¹⁰https://ultimatecourses.com/blog/angular-ngif-async-pipe NgIf 7 1 @Component({ 2 template: ` 3 4 {{ donuts }} 5 6 `, 7 }) 8 export class AppComponent { 9 donuts$: Observable = of([ 10 { name: 'Glazed Fudge', price: 119 } 11 ]); 12 } Notice here how we are using as donuts alongside the async expression. Once the donuts$ Observable has resolved, the result is stored on the donuts local variable, which can then be passed down to child components or consumed inside the template. Our is only being used to subscribe to our Observable, we have removed the else clause. This would now need to live inside the template to check if donuts.length was true or false to render our components. Another approach we could take is to pass an async pipe subscription directly to a component @Input: 1 @Component({ 2 template: ` 3 4 `, 5 }) 6 export class AppComponent { 7 donuts$: Observable = of([ 8 { name: 'Glazed Fudge', price: 119 } 9 ]); 10 } This means inside the component, an NgIf check can be made on the donuts.length property and can conditionally render the list or fallback. If the same Observable needs to be passed into multiple components you will still need to adopt the initial approach and unwrap the Observable via. This is to avoid duplicate subscriptions that would cause memory leaks, state management problems as well as debugging issues. NgIf 8 NgIf and Syntax The * syntax alongside NgIf is there to provide shorthand sugar syntax for a more complex template definition. To complete our picture, it’s imperative for us to learn what Angular translates our *ngIf declaration into. When we declare *ngIf two things are happening; a property binding and template declaration. For more advanced use cases, we can instead use directly, which is what Angular translates our code into behind-the-scenes when compiling our components. This means that the two examples below are essentially equivalent to eachother: 1 2 3 We have donuts! 4 5 6 7 8 We have donuts! 9 10 11 12 No donuts here. 13 My preference is to use *ngIf consistently and only introduce the approach when you have a more complex requirement or strictly need. NgFor NgFor¹¹ is a Structural Directive that allows us to iterate over a collection of data and access each value, index and more. NgFor The standard NgFor syntax is *ngFor="let x of iterable" and provides us with the ability to iterate over a data structure, such as an Array or Iterable¹². We declare NgFor on an element or component that we wish to repeat for each iteration of our data structure. 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donuts: Donut[] = [ 10 { id: 'x4f0ae', name: 'Glazed Fudge', price: 119 }, 11 { id: 'o2k95v', name: 'Sour Supreme', price: 129 } 12 ]; 13 } As donuts contains 2 items, the with our {{ donut.name }} interpolated values will be printed twice, once for each iteration of the data structure. Each value of the loop is stored on the variable donut for each iteration. Angular’s NgFor is essentially a Directive implementation of JavaScript’s ES2015 for...of construct, with some additional features. NgFor and TrackBy Expression Time to talk about immutable data structures and performance. In JavaScript two object identities are never the same, despite the fact the may look the same: ¹¹https://angular.io/api/common/NgForOf ¹²https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols NgFor 10 1 { name: 'Glazed Fudge' } === { name: 'Glazed Fudge' } // false 2 ['Sour Supreme'] === ['Sour Supreme'] // false This is a good thing, and you’ve now understood the concept of “object identity”. Angular favours State Management principles that follow immutable data structures and one-way data flow. The fact that two objects are never the same means we can easily check if our state has changed, because the identities are different. Combined with ChangeDetection.OnPush inside our components, it is a predictable way for us to manage state. Not only this, but it is a crucial pattern to adopt alongside one-way data flow. So what does this have to do with NgFor? A lot, actually. Let’s assume we have a large list, and our data changes. Angular uses an internal diffing algorithm that now has to check each identity before we can re-render our UI updates. Once it’s done that, Angular will re-render the entire DOM collection as it thinks all of our data has changed (due to the identities changing). So what if we could tell Angular what’s changed so it could re-render more intelligently? That would save vital DOM node destruction and creation time. With the trackBy clause, we can do exactly this. This would then only re-render the changes necessary, instead of triggering a cascade of DOM manipulation. It’s all about performance, and DOM manipulation is costly and kept to a minimum where possible. It’s likely each item we iterate over will contain a unique id, so we can pass this to Angular instead of it’s default differ, which Angular can then associate each NgFor item with: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donuts: Donut[] = [ 10 { id: 'x4f0ae', name: 'Glazed Fudge', price: 119 }, 11 { id: 'o2k95v', name: 'Sour Supreme', price: 129 } 12 ]; 13 NgFor 11 14 trackByFn(index: number, value: Donut) { 15 return value.id; 16 } 17 } Now Angular will know if a piece of data has changed, and update the UI accordingly. This is a far better approach to heavy and blind DOM manipulation, especially with how easy the trackBy clause is to implement. Although the index is available as the first parameter, I would avoid using this as it will instruct Angular to never re-render that element, which means any lifecycle hooks on components are never going to fire again. NgFor Variables - Index, Odd, Even Our “for loop” functionality wouldn’t be complete without the ability to access the index of each iteration. Angular also makes some extra utility variables available to us as well to determine if we’re dealing with an odd and even iteration. To start let’s access our index. This is done via an as syntax similar to that seen in NgIf: 1 2 {{ i }} {{ donut.name }} 3 To access odd and even variables, which are just boolean values, perhaps to set an alternating class or style property the approach is very similar: 1 2 {{ o }} {{ e }} {{ donut.name }} 3 To set an alternating class called highlighted for only our even numbers, we could implement as follows: NgFor 12 1 4 {{ donut.name }} 5 NgFor and Async Pipe It’s common to pass an Observable to a template, we’ve learned this with NgIf. We can also pass an Observable directly to an NgFor Directive via the async pipe. Whilst possible, it’s likely that there’s a slightly better pattern to adopt. We’ll explore that after. An Observable can be passed into the async pipe with NgFor as follows, which creates a subscription: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 `, 7 }) 8 export class AppComponent { 9 donuts$: Observable = of([ 10 { id: 'x4f0ae', name: 'Glazed Fudge', price: 119 }, 11 { id: 'o2k95v', name: 'Sour Supreme', price: 129 } 12 ]); 13 } If we need to access the donuts$ data elsewhere in the template, we’ll either create another subscription (which is generally an anti-pattern, so we’ll avoid that) or have to refactor our code. Which leads us to a better solution that uses the best of both NgFor and NgIf: 1 2 3 {{ donut.name }} 4 5 This allows our NgFor to deal with just the raw data structure instead of wiring up an Observable subscription. We then use an to unwrap our Observable data source and expand the result as a variable of donuts. NgFor 13 NgFor and Syntax The * syntax alongside NgFor is there to provide shorthand sugar syntax for a more complex template definition. To complete our picture, it’s imperative for us to learn what Angular translates our *NgFor declaration into. When we declare *NgFor two things are happening; a property binding and template declaration. For more advanced use cases, we can instead use directly, which is what Angular translates our code into behind-the-scenes when compiling our components. This means that the two examples below are essentially equivalent to eachother: 1 2 3 {{ donut.name }} 4 5 6 7 9 {{ donut.name }} 10 The above examples are equivalent, however the first example will render a around the contents and the will not as it contains no element. This example works by declaring ngFor as the Directive. Then we pass in the “of” by binding our donuts array directly to the ngForOf property. The magical piece here is let-donut which holds no value, therefore Angular sets this value as “implicit”, which means we use this variable for each iteration value. We also have let-i="index" which allows us to ask for the index, and by declaring let-i we create a local variable called i and assign it the value. Finally, we bind a trackByFn to an ngForTrackBy property. You can see why the short hand syntax here is nice and effective. My preference is to use *ngFor consistently and only introduce the approach when you have a more complex requirement or strictly need. NgSwitch NgSwitch¹³ is a Structural Directive that allows us to render a specific element or component based on a value. NgSwitch The standard NgSwitch syntax is [ngSwitch]="expression" and provides us with the ability to switch template partials by declaring a case via *ngSwitchCase. We declare NgSwitch on an element or component that we wish to provide conditional rendering and pass in an expression to be evaluated by a child *ngSwitchCase: 1 @Component({ 2 template: ` 3 4 New Arrival 5 Limited Edition 6 7 ` 8 }) 9 export class AppComponent { 10 donut: Donut = { 11 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 12 }; 13 } The value of donut.promo will is evaluated by each *ngSwitchCase, which then matches and renders the appropriate template. Also note here that the *ngSwitchCase is a template and not the [ngSwitch] binding. As we are using the * sugar syntax, we are creating a template and binding at the same time and therefore need to specify the strings as literals, i.e. 'new' instead of new. Angular’s NgSwitch is essentially a Directive implementation of JavaScript’s switch and case. ¹³https://angular.io/api/common/NgSwitch NgSwitch 15 NgSwitch and Default Case If no match is found, we have the opportunity to supply a default case with the *ngSwitchDefault Directive. A default case will be applied any time a match doesn’t exist, so is the perfect place to add fallback content, if you require. 1 2 New Arrival 3 Limited Edition 4 Shown if no matches! 5 At this point, we have a fully declarative switch case implementation. NgSwitch and The opportunity presents itself very nicely with NgSwitch to once again turn to the element if we do not require a parent element to be rendered. In our example, the we bind [ngSwitch] with is also rendered. Using we can keep the generated DOM lighter and simply render the contents only: 1 2 New Arrival 3 Limited Edition 4 Shown if no matches! 5 I always opt for this approach, and turn to an element for the initial binding if needed. We could also introduce an on the child element level, though. Why? In the example above we are creating the same tag each time. If we use an for the child elements, we can simply swap the text out instead and use the single tag as the parent: NgSwitch 16 1 2 New Arrival 3 Limited Edition 4 Shown if no matches! 5 This approach would work well if you always required a tag to be rendered and the markup remained the same each time. NgSwitch and Syntax The * syntax alongside NgSwitch (*ngSwitchCase and *ngSwitchDefault) is there to provide shorthand sugar syntax for a more complex template definition. To complete our picture, it’s imperative for us to learn what Angular translates our *NgSwitch declaration into. When we declare *NgSwitch two things are happening; a property binding and template declaration. The two examples below are essentially equivalent to eachother: 1 2 3 New Arrival 4 Limited Edition 5 Shown if no matches! 6 7 8 9 10 11 New Arrival 12 13 14 Limited Edition 15 16 17 Shown if no matches! 18 19 The above examples are equivalent, however, the first example minimises our template footprint by creating fewer elements. NgSwitch 17 This example works by declaring [ngSwitchCase] as the Directive. Given the simplicity of NgSwitch, there is little difference between the sugar syntax and fully expanded version besides the requirement to create a child tag inside each. One small thing to note is that we can opt to not bind to [ngSwitchCase] and simply declare it as an attribute instead. This approach allows us to specify string values more easily: 1 2 3 New Arrival 4 5 6 Limited Edition 7 8 9 Shown if no matches! 10 11 My preference is to use *ngSwitchCase consistently and only introduce the approach when you have a more complex requirement or strictly need. NgStyle and Style Bindings NgStyle¹⁴ is an Attribute Directive that allows us to apply inline styles to an element or component. There are two ways to add inline styles with Angular; using NgStyle or via a [style] property binding. Traditionally, NgStyle was the first choice for applying inline styles as it supported applying styles via an object. Using [style] had this limitation, until recently. That meant [style] was mostly used to add a single style binding at a time. But now [style] supports objects too, do we even need NgStyle? Despite this feature parity, NgStyle is not currently going anywhere. It may be removed in the future but there are currently no plans. Another reason to adopt [style] over NgStyle is that it offers a special inline style property and unit binding. This gives us a lot of power and a consistent approach to work with. That said, we’re still going to cover NgStyle - just not like before. And we’ll start with [style] bindings. While inline styles are possible, there is also the moral aspect of doing so. It’s not always a great approach, and a better solution may be to use a class and simply toggle it. We’ll come onto this next with [class] and NgClass though. Style Bindings Create a single inline style by binding to the style property along with the property name: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 } ¹⁴https://angular.io/api/common/NgStyle NgStyle and Style Bindings 19 We are applying a border if the donut.promo property evaluates true. As it is a string and has a length it will result in a true value when evaluated, therefore the style is added. I’m using a ternary statement to demonstrate how you can apply different styles based on your condition. Multiple Style Bindings It’s possible to also specify multiple single styles on the same element: 1 4 {{ donut.name }} 5 This gives us a flexible approach which is clean, readable, and concise. Style Bindings and Units A lesser known feature of [style] bindings is to specify the unit of the property as well. This can be achieved by extending the syntax to include the unit. An example using font-size: 1 3 {{ donut.name }} 4 There are two very great things happening here. First, we are passing 12 as a number, not "12" as a string. Why is this good? Dynamic styles. It’s highly unlikely you will send back "12px" from the server when a simple 12 is perfect. This would also let the user easily change units, if you implemented a custom font-size feature that stored the user preference. Second, note font-size and not fontSize. Other libraries and frameworks follow different conven- tions such as fontSize in React. This means your code is less portable from.css file to component and would require changing to camelCase. Style Bindings via Objects The most powerful feature of [style] is the ability to pass an object. This adds multiple styles at once: NgStyle and Style Bindings 20 1 6 {{ donut.name }} 7 This object definition is what NgStyle was famous for. Now that style supports this too, NgStyle no longer provides any significant benefit. Passing an object is clean and concise and can also be abstracted away into the component class as well. Style Bindings from Class Methods When styles grow larger, they become more difficult to manage. To avoid littering a template with more complexity, we can break out styles into component methods and pass a function as the expression: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 13 getStyles() { 14 return { 15 border: this.donut.promo ? '2px solid #eee' : 'none', 16 color: this.donut.id ? 'red' : 'blue' 17 }; 18 } 19 } This approach cleans things up nicely and can offer more complex and dynamic logic through means of further methods to manage changes to styles. NgStyle and Style Bindings 21 Style Bindings from Class Getters By using a TypeScript getter¹⁵ we can also specify styles to be passed from the component to the template: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 13 get styles() { 14 return { 15 border: this.donut.promo ? '2px solid #eee' : 'none', 16 color: this.donut.id ? 'red' : 'blue' 17 }; 18 } 19 } I personally like to use the get syntax and pass in a property to the template. Style Bindings from Strings Inline styles may also be set via a string that is formatted like valid CSS property and value: ¹⁵https://ultimatecourses.com/blog/typescript-setters-getter NgStyle and Style Bindings 22 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 13 get styles() { 14 return ` 15 border: ${this.donut.promo ? '2px solid #eee' : 'none'}; 16 color: ${this.donut.id ? 'red' : 'blue'}; 17 `; 18 } 19 } This approach may feel nicer to you if you prefer to not use an object definition. Just keep in mind that you won’t get any TypeScript benefit here like you would with objects. NgStyle So what about NgStyle? Everything you’ve learned above with the object syntax can also be applied to NgStyle, however there is no real reason to adopt it over [style] bindings now. That said, the following will work just the same as everything above: 1 6 {{ donut.name }} 7 NgStyle does not support the same features or provide any reasonable benefit over [style], it actually lacks some of the quick, short and powerful functionality that [style] offers. My preference is to use [style] consistently for both single, multiple and object configuration styles. NgClass and Class Bindings NgClass¹⁶ is an Attribute Directive that allows us to apply classes to an element or component. There are two ways to add classes with Angular; using NgClass or via a [class] property binding. Traditionally, NgClass was the first choice for applying classes as it supported applying classes via an object. Using [class] had this limitation, until recently. That meant [class] was mostly used to add a single class at a time. But now [class] supports objects too, do we even need NgClass? Despite this feature parity, NgClass is not currently going anywhere. It may be removed in the future but there are currently no plans. Another reason to adopt [class] over NgClass is that it offers a special class binding. This gives us a lot of power and a consistent approach to work with. That said, we’re still going to cover NgClass - just not like before. And we’ll start with [class] bindings. Class Bindings Add a single class by binding to the class property along with the property name: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 `, 7 styles: [`.promo { border: 2px solid #eee; }`] 8 }) 9 export class AppComponent { 10 donut: Donut = { 11 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 12 }; 13 } We are applying a promo class if the donut.promo property evaluates true. As it is a string and has a length it will result in a true value when evaluated, therefore the class is added. Should donut.promo evaluate false no class is added. ¹⁶https://angular.io/api/common/NgClass NgClass and Class Bindings 24 Multiple Class Bindings It’s possible to also specify multiple single class bindings on the same element: 1 4 {{ donut.name }} 5 This gives us a flexible approach which is clean, readable, and concise. Class Bindings via Objects The most powerful feature of [class] is the ability to pass an object. This adds multiple classes at once: 1 6 {{ donut.name }} 7 This object definition is what NgClass was famous for. Now that class supports this too, NgClass no longer provides any significant benefit. Passing an object is clean and concise and can also be abstracted away into the component class as well. Class Bindings from Class Methods When classes grow larger, they become more difficult to manage. To avoid littering a template with more complexity, we can break out classes into component methods and pass a function as the expression: NgClass and Class Bindings 25 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 13 getClasses() { 14 return { 15 new: this.donut.promo === 'new', 16 limited: this.donut.promo === 'limited' 17 }; 18 } 19 } This approach cleans things up nicely and can offer more complex and dynamic logic through means of further methods to manage changes to classes. Class Bindings from Class Getters By using a TypeScript getter¹⁷ we can also specify classes to be passed from the component to the template: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 ¹⁷https://ultimatecourses.com/blog/typescript-setters-getter NgClass and Class Bindings 26 13 get classes() { 14 return { 15 new: this.donut.promo === 'new', 16 limited: this.donut.promo === 'limited' 17 }; 18 } 19 } I personally like to use the get syntax and pass in a property to the template. Class Bindings from Strings Classes may also be set via a string that is whitespace separated: 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 13 get classes() { 14 return ` 15 ${this.donut.promo === 'new' ? 'new' : ''} 16 ${this.donut.promo === 'limited' ? 'limited' : ''} 17 `; 18 } 19 } This approach may feel nicer to you if you prefer to not use an object definition. Just keep in mind that you won’t get any TypeScript benefit here like you would with objects. Class Bindings from Arrays Classes may also be set via a string array: NgClass and Class Bindings 27 1 @Component({ 2 template: ` 3 4 {{ donut.name }} 5 6 ` 7 }) 8 export class AppComponent { 9 donut: Donut = { 10 id: 'x4f0ae', name: 'Glazed Fudge', price: 119, promo: 'new' 11 }; 12 13 get classes() { 14 return [ 15 `${this.donut.promo === 'new' ? 'new' : ''}`, 16 `${this.donut.promo === 'limited' ? 'limited' : ''}` 17 ].filter(Boolean); 18 } 19 } This method uses a ternary statement to declare the class or not. Note the addition of.filter(Boolean) to remove any empty strings from the array, this is because the classList API that Angular uses under-the-hood will error on empty strings so we must remove them prior to setting. The filter(Boolean) trick works because it returns any truthy values from the array. An empty string is a falsy value and therefore is removed. NgClass So what about NgClass? Everything you’ve learned above with the object syntax can also be applied to NgClass, however there is no real reason to adopt it over [class] bindings now. That said, the following will work just the same as everything above: 1 6 {{ donut.name }} 7 NgClass and Class Bindings 28 NgClass does not support the same features or provide any reasonable benefit over [class], it actually lacks some of the quick, short and powerful functionality that [class] offers. My preference is to use [class] consistently for both single, multiple and object configuration classes. Component Architecture. Immutable State Management. One-Way Data Flow. Tired of not really understanding what you're doing? You're not alone. That's why I made this course. Architecture patterns Component roles (smart/dumb) Feature modules and lazy-loading State and data flow Services and dependency injection HttpClient and Retries Observables and pipe operators Routing and navigation Forms and validation 16,781 reviews Styling encapsulation and CSS 1. Watch the free lessons now: ultimatecourses.com/learn/angular-basics 2. Get a 25% discount with coupon code: ULTIMATE_ANGULAR