You might be wondering at this point how we would manage the data flow if we started adding more functionality to this app.
For instance, say we wanted to add a shopping cart view and then we would add items to our cart.
How could we implement this?
The only tools we’ve talked about are emitting output events. When we click add-to-cart do we simply bubble up anaddedToCartevent and handle at the root component? That feels a bit awkward.
Data architecture is a large topic with many opinions. Thankfully, Angular is flexible enough to handle a wide variety of data architectures, but that means that you have to decide for yourself which to use.
In Angular 1, the default option was two-way data binding. Two-way data binding is super easy to get started: your controllers have data, your forms manipulate that data directly, and your views show the data.
The problem with two-way data binding is that it often causes cascading effects throughout your application and makes it really difficult to trace data flow as your project grows.
Another problem with two-way data binding is that because you’re passing data down through components it often forces your “data layout tree” to match your “dom view tree”. In practice, these two things should really be separate.
One way you might handle this scenario would be to create aShoppingCartService, which would be a singleton that would hold the list of the current items in the cart. This service could notify any interested objects when an item in the cart changes.
The idea is easy enough, but in practice there are a lot of details to be worked out.
The recommended way in Angular, and in many modern web frameworks (such as React), is to adopt a pattern of one-way data binding. That is, your data flows only down through components. If you need to make changes, you emit events that cause changes to happen “at the top” which then trickle down.
One-way data binding can seem like it adds some overhead in the beginning but it saves a lot of complication around change detection and it makes your systems easier to reason about.
Thankfully there are two major contenders for managing your data architecture:
1. Use an Observables-based architecture like RxJS 2. Use a Flux-based architecture
Later in this book we’ll talk about how to implement a scalable data architecture for your app. For now, bask in the joy of your new Component-based application!
Introduction
Angular provides a number of built-in directives, which are attributes we add to our HTML elements that give us dynamic behavior. In this chapter, we’re going to cover each built-in directive and show you examples of how to use them.
By the end of this chapter you’ll be able to use the basic built-in directives that Angular offers.
How To Use This Chapter
Instead of building an app step-by-step, this chapter is a tour of the built-in directives in Angular. Since we’re early in the book, we won’t explain every detail, but we will provide plenty of example code.
Remember: at any time you can reference the sample code for this chapter to get the complete context.
If you’d like to run the examples in this chapter then see the folder
code/built-in-directivesand run:
1 npm install 2 npm start
And then openhttp://localhost:4200⁴³in your browser.
NgIf
ThengIfdirective is used when you want to display or hide an element based on a condition. The condition is determined by the result of the expression that you pass into the directive.
If the result of the expression returns a false value, the element will be removed from the DOM.
Some examples are:
⁴³http://localhost:4200
1 <div *ngIf="false"></div> <!-- never displayed -->
2 <div *ngIf="a > b"></div> <!-- displayed if a is more than b -->
3 <div *ngIf="str == 'yes'"></div> <!-- displayed if str is the string "yes" -->
4 <div *ngIf="myFunc()"></div> <!-- displayed if myFunc returns truthy -->
Note for AngularJS 1.x Users
If you’ve used AngularJS 1.x, you may have used thengIfdirective before. You can think of the Angular 4 version as a direct substitute.
On the other hand, Angular 4 offers no built-in alternative forng-show. So, if your goal is to just change the CSS visibility of an element, you should look into either thengStyleor theclassdirectives, described later in this chapter.
NgSwitch
Sometimes you need to render different elements depending on a given condition.
When you run into this situation, you could usengIfseveral times like this:
1 <div class="container">
2 <div *ngIf="myVar == 'A'">Var is A</div>
3 <div *ngIf="myVar == 'B'">Var is B</div>
4 <div *ngIf="myVar != 'A' && myVar != 'B'">Var is something else</div>
5 </div>
But as you can see, the scenario wheremyVaris neitherAnorBis verbose when all we’re trying to express is anelse.
To illustrate this growth in complexity, say we wanted to handle a new valueC.
In order to do that, we’d have to not only add the new element withngIf, but also change the last case:
1 <div class="container">
2 <div *ngIf="myVar == 'A'">Var is A</div>
3 <div *ngIf="myVar == 'B'">Var is B</div>
4 <div *ngIf="myVar == 'C'">Var is C</div>
5 <div *ngIf="myVar != 'A' && myVar != 'B' && myVar != 'C'">Var is something els\
6 e</div>
7 </div>
For cases like this, Angular introduces thengSwitchdirective.
If you’re familiar with theswitchstatement then you’ll feel very at home.
The idea behind this directive is the same: allow a single evaluation of an expression, and then display nested elements based on the value that resulted from that evaluation.
Once we have the result then we can:
• Describe the known results, using thengSwitchCasedirective
• Handle all the other unknown cases withngSwitchDefault Let’s rewrite our example using this new set of directives:
1 <div class="container" [ngSwitch]="myVar">
2 <div *ngSwitchCase="'A'">Var is A</div>
3 <div *ngSwitchCase="'B'">Var is B</div>
4 <div *ngSwitchDefault>Var is something else</div>
5 </div>
Then if we want to handle the new valueCwe insert a single line:
1 <div class="container" [ngSwitch]="myVar">
2 <div *ngSwitchCase="'A'">Var is A</div>
3 <div *ngSwitchCase="'B'">Var is B</div>
4 <div *ngSwitchCase="'C'">Var is C</div>
5 <div *ngSwitchDefault>Var is something else</div>
6 </div>
And we don’t have to touch the default (i.e. fallback) condition.
Having thengSwitchDefaultelement is optional. If we leave it out, nothing will be rendered when myVarfails to match any of the expected values.
You can also declare the same*ngSwitchCasevalue for different elements, so you’re not limited to matching only a single time. Here’s an example:
code/built-in-directives/src/app/ng-switch-example/ng-switch-example.component.html 1 <h4 class="ui horizontal divider header">
2 Current choice is {{ choice }}
3 </h4>
17 <button class="ui primary button" (click)="nextChoice()">
18 Next choice 19 </button>
20 </div>
In the example above when thechoiceis2, both the second and fifthlis will be rendered.
NgStyle
With the NgStyle directive, you can set a given DOM element CSS properties from Angular expressions.
The simplest way to use this directive is by doing[style.<cssproperty>]="value". For example:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html 5 <div [style.background-color]="'yellow'">
6 Uses fixed yellow background 7 </div>
This snippet is using theNgStyledirective to set thebackground-colorCSS property to the literal string'yellow'.
Another way to set fixed values is by using theNgStyleattribute and using key value pairs for each property you want to set, like this:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
13 <div [ngStyle]="{color: 'white', 'background-color': 'blue'}">
14 Uses fixed white text on blue background 15 </div>
Notice that in theng-stylespecification we have single quotes aroundbackground-color
but not around color. Why is that? Well, the argument tong-styleis a Javascript object and
coloris a valid key, without quotes. Withbackground-color, however, the dash character isn’t allowed in an object key, unless it’s a string so we have to quote it.
Generally I’d leave out quoting as much as possible in object keys and only quote keys when we have to.
Here we are setting both thecolorand thebackground-colorproperties.
But the real power of theNgStyledirective comes with using dynamic values.
In our example, we are defining two input boxes with an apply settings button:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
56 <div class="ui input">
57 <input type="text" name="color" value="{{color}}" #colorinput>
58 </div>
59
60 <div class="ui input">
61 <input type="text" name="fontSize" value="{{fontSize}}" #fontinput>
62 </div>
63
64 <button class="ui primary button" (click)="apply(colorinput.value, fontinput.val\
65 ue)">
66 Apply settings 67 </button>
And then using their values to set the CSS properties for three elements.
On the first one, we’re setting the font size based on the input value:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
It’s important to note that we have to specify units where appropriate. For instance, it isn’t valid CSS to set afont-sizeof 12- we have to specify a unit such as12pxor1.2em. Angular provides a handy syntax for specifying units: here we used the notation[style.font-size.px].
The.pxsuffix indicates that we’re setting thefont-sizeproperty value in pixels. You could easily replace that by[style.font-size.em] to express the font size in ems or even in percentage using [style.font-size.%].
The other two elements use the#colorinputto set the text and background colors:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.html
33 <h4 class="ui horizontal divider header">
34 ngStyle with object property from variable 35 </h4>
43 <h4 class="ui horizontal divider header">
44 style from variable
This way, when we click the Apply settings button, we call a method that sets the new values:
code/built-in-directives/src/app/ng-style-example/ng-style-example.component.ts 32 apply(color: string, fontSize: number): void {
33 this.color = color;
34 this.fontSize = fontSize;
35 }
And with that, both the color and the font size will be applied to the elements using theNgStyle directive.
NgClass
The NgClassdirective, represented by a ngClassattribute in your HTML template, allows you to dynamically set and change the CSS classes for a given DOM element.
The first way to use this directive is by passing in an object literal. The object is expected to have the keys as the class names and the values should be a truthy/falsy value to indicate whether the class should be applied or not.
Let’s assume we have a CSS class calledborderedthat adds a dashed black border to an element:
code/built-in-directives/src/styles.css 8 .bordered {
9 border: 1px dashed black;
10 background-color: #eee; }
Let’s add twodivelements: one always having theborderedclass (and therefore always having the border) and another one never having it:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html 2 <div [ngClass]="{bordered: false}">This is never bordered</div>
3 <div [ngClass]="{bordered: true}">This is always bordered</div>
As expected, this is how those twodivs would be rendered:
Simple class directive usage
Of course, it’s a lot more useful to use theNgClassdirective to make class assignments dynamic.
To make it dynamic we add a variable as the value for the object value, like this:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
5 <div [ngClass]="{bordered: isBordered}">
6 Using object literal. Border {{ isBordered ? "ON" : "OFF" }}
7 </div>
Alternatively, we can define aclassesObjobject in our component:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.ts
3 @Component({
4 selector: 'app-ng-class-example',
5 templateUrl: './ng-class-example.component.html' 6 })
7 export class NgClassExampleComponent implements OnInit { 8 isBordered: boolean;
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html 9 <div [ngClass]="classesObj">
10 Using object var. Border {{ classesObj.bordered ? "ON" : "OFF" }}
11 </div>
Again, be careful when you have class names that contains dashes, likebordered-box. JavaScript requires that object-literal keys with dashes be quoted like a string, as in:
1 <div [ngClass]="{'bordered-box': false}">...</div>
We can also use a list of class names to specify which class names should be added to the element.
For that, we can either pass in an array literal:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html 31 <div class="base" [ngClass]="['blue', 'round']">
32 This will always have a blue background and 33 round corners
34 </div>
Or assign an array of values to a property in our component:
1 this.classList = ['blue', 'round'];
And pass it in:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html 36 <div class="base" [ngClass]="classList">
37 This is {{ classList.indexOf('blue') > -1 ? "" : "NOT" }} blue 38 and {{ classList.indexOf('round') > -1 ? "" : "NOT" }} round 39 </div>
In this last example, the [ngClass] assignment works alongside existing values assigned by the HTMLclassattribute.
The resulting classes added to the element will always be the set of the classes provided by usual classHTML attribute and the result of the evaluation of the[class]directive.
In this example:
code/built-in-directives/src/app/ng-class-example/ng-class-example.component.html
31 <div class="base" [ngClass]="['blue', 'round']">
32 This will always have a blue background and 33 round corners
34 </div>
The element will have all three classes: base from the classHTML attribute and also blue and roundfrom the[class]assignment:
Classes from both the attribute and directive
NgFor
The role of this directive is to repeat a given DOM element (or a collection of DOM elements) and pass an element of the array on each iteration.
The syntax is*ngFor="let item of items".
• Thelet itemsyntax specifies a (template) variable that’s receiving each element of theitems array;
• Theitemsis the collection of items from your controller.
To illustrate, we can take a look at the code example. We declare an array of cities on our component controller:
1 this.cities = ['Miami', 'Sao Paulo', 'New York'];
And then, in our template we can have the following HTML snippet:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html
1 <h4 class="ui horizontal divider header">
2 Simple list of strings 3 </h4>
4
5 <div class="ui list" *ngFor="let c of cities">
6 <div class="item">{{ c }}</div>
7 </div>
And it will render each city inside thedivas you would expect:
Result of the ngFor directive usage
We can also iterate through an array of objects like these:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.ts
17 this.people = [
18 { name: 'Anderson', age: 35, city: 'Sao Paulo' }, 19 { name: 'John', age: 12, city: 'Miami' },
20 { name: 'Peter', age: 22, city: 'New York' }
21 ];
And then render a table based on each row of data:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html
9 <h4 class="ui horizontal divider header">
10 List of objects 11 </h4>
12
13 <table class="ui celled table">
14 <thead>
15 <tr>
16 <th>Name</th>
17 <th>Age</th>
18 <th>City</th>
19 </tr>
20 </thead>
21 <tr *ngFor="let p of people">
22 <td>{{ p.name }}</td>
23 <td>{{ p.age }}</td>
24 <td>{{ p.city }}</td>
25 </tr>
26 </table>
Getting the following result:
Rendering array of objects
We can also work with nested arrays. If we wanted to have the same table as above, broken down by city, we could easily declare a new array of objects:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.ts
22 this.peopleByCity = [
23 {
24 city: 'Miami',
25 people: [
26 { name: 'John', age: 12 }, 27 { name: 'Angel', age: 22 }
28 ]
29 },
30 {
31 city: 'Sao Paulo',
32 people: [
33 { name: 'Anderson', age: 35 }, 34 { name: 'Felipe', age: 36 }
35 ]
36 }
37 ];
38 }
And then we could useNgForto render oneh2for each city:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html
32 <div *ngFor="let item of peopleByCity">
33 <h2 class="ui header">{{ item.city }}</h2>
And use a nested directive to iterate through the people for a given city:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html
13 <table class="ui celled table">
14 <thead>
15 <tr>
16 <th>Name</th>
17 <th>Age</th>
18 <th>City</th>
19 </tr>
20 </thead>
21 <tr *ngFor="let p of people">
22 <td>{{ p.name }}</td>
23 <td>{{ p.age }}</td>
24 <td>{{ p.city }}</td>
25 </tr>
26 </table>
Resulting in the following template code:
code/built-in-directives/src/app/ng-for-example/ng-for-example.component.html
28 <h4 class="ui horizontal divider header">
29 Nested data 30 </h4>
31
32 <div *ngFor="let item of peopleByCity">
33 <h2 class="ui header">{{ item.city }}</h2>
34
35 <table class="ui celled table">
36 <thead>
37 <tr>
38 <th>Name</th>
39 <th>Age</th>
40 </tr>
41 </thead>
42 <tr *ngFor="let p of item.people">
43 <td>{{ p.name }}</td>
44 <td>{{ p.age }}</td>
45 </tr>
46 </table>
47 </div>
And it would render one table for each city: