Conditional Templates

Note: If you skipped ahead to this section, click here to download a zip of the tutorial at this point.

In this step we are going to show or hide different parts of the app's UI depending the user's current state: either logged-in or anonymous.

Attribute Binding

As you may know, Angular 1 has ng-show and ng-hide attribute directives for controlling the visibility of content. We'll look at how visibility is handled differently in Angular 2.

Angular 2 binds an attribute to an element's property. As you already know, one can directly bind to the component attribute directives, for example:

<my-component [foo]="fooValue" />

The Angular 2 team went further and implemented the same direct binding support for the DOM element attributes, including additional attributes like hidden and disabled, and which seems logical.

[hidden]

The hidden attribute arrived in the DOM with HTML 5. It's essentially similar to the old and well-known disabled attribute, but only makes an element hidden. With the presence of the hidden attribute and direct attribute binding, it seems there is no further need for attribute directives like ng-hide. There is one exception, though.

The DOM property hidden is rather new, and not supported by older versions of Internet Explorer (less than 11). If you need to support older browsers, you must implement a new directive attribute similar to the ng-hide yourself or make use of an already existing directive. There are sure to be solutions in the future.

A user who hasn't logged-in does not have all the same permissions; we can hide functionality that anonymous users cannot access such as the "add party" form and the "remove" button for each party in the parties list.

Let's toggle on and off these components with the help of the hidden attribute, but first let's inject the user property into the PartiesList component, since this is what our attribute binding will depend on. User injection was already mentioned in step 8, so let's make practical use of it now:

16.1 Inject Meteor User and add isOwner method client/imports/app/parties/parties-list.component.ts
5
6
7
8
9
10
11
 
27
28
29
30
31
32
33
 
38
39
40
41
42
43
44
 
107
108
109
110
111
112
113
114
115
116
import { MeteorObservable } from 'meteor-rxjs';
import { PaginationService } from 'ng2-pagination';
import { Counts } from 'meteor/tmeasday:publish-counts';
import { InjectUser } from "angular2-meteor-accounts-ui";
 
import 'rxjs/add/operator/combineLatest';
 
...some lines skipped...
  selector: 'parties-list',
  template
})
@InjectUser('user')
export class PartiesListComponent implements OnInit, OnDestroy {
  parties: Observable<Party[]>;
  partiesSub: Subscription;
...some lines skipped...
  partiesSize: number = 0;
  autorunSub: Subscription;
  location: Subject<string> = new Subject<string>();
  user: Meteor.User;
 
  constructor(
    private paginationService: PaginationService
...some lines skipped...
    this.nameOrder.next(parseInt(nameOrder));
  }
 
  isOwner(party: Party): boolean {
    return this.user && this.user._id === party.owner;
  }
 
  ngOnDestroy() {
    this.partiesSub.unsubscribe();
    this.optionsSub.unsubscribe();

As you can see, we've added a new isOwner method to the component, thus, we allow only a party owner to remove the party.

Then, change the template to use the hidden attribute:

16.2 Use the hidden attribute in the PartiestList client/imports/app/parties/parties-list.component.html
1
2
3
4
5
 
19
20
21
22
23
24
25
<div>
  <parties-form [hidden]="!user" style="float: left"></parties-form>
  <input type="text" #searchtext placeholder="Search by Location">
  <button type="button" (click)="search(searchtext.value)">Search</button>
  
...some lines skipped...
      <a [routerLink]="['/party', party._id]">{{party.name}}</a>
      <p>{{party.description}}</p>
      <p>{{party.location}}</p>
      <button [hidden]="!isOwner(party)" (click)="removeParty(party)">X</button>
      <div>
        Who is coming:
        Yes - {{party | rsvp:'yes'}}

Now run the app.

The "add party" form and "remove" buttons should disappear if you are not logged-in. Try to log in: everything should be visible again.

Note: CSS's display property has priority over the hidden property. If one of the CSS classes of any element has this property set, hidden gets over-ruled. In this case, you'll have to wrap the element into a container element such as a <div> and assign CSS classes with the "display" on that parent container.

[disabled]

Next let's add the disabled attribute to the PartyDetails component. Currently, all users have access to the party details page and can change the values of the inputs, though they are still prohibited from saving anything (remember the parties security added in step 8?). Let's disable these inputs for users that are not owners.

We will get an isOwner property when the party owner matches the logged-in user id:

4
5
6
7
8
9
10
 
19
20
21
22
23
24
25
 
27
28
29
30
31
32
33
 
102
103
104
105
106
107
108
109
110
111
import { Subscription } from 'rxjs/Subscription';
import { Meteor } from 'meteor/meteor';
import { MeteorObservable } from 'meteor-rxjs';
import { InjectUser } from "angular2-meteor-accounts-ui";
 
import 'rxjs/add/operator/map';
 
...some lines skipped...
  selector: 'party-details',
  template
})
@InjectUser('user')
export class PartyDetailsComponent implements OnInit, OnDestroy {
  partyId: string;
  paramsSub: Subscription;
...some lines skipped...
  partySub: Subscription;
  users: Observable<User>;
  uninvitedSub: Subscription;
  user: Meteor.User;
 
  constructor(
    private route: ActivatedRoute
...some lines skipped...
    });
  }
 
  get isOwner(): boolean {
    return this.party && this.user && this.user._id === this.party.owner;
  }
 
  ngOnDestroy() {
    this.paramsSub.unsubscribe();
    this.partySub.unsubscribe();

isOwner can be used before the subscription has finished, so we must check if the party property is available before checking if the party owner matches.

Then, let's add our new [disabled] condition to the party details template:

16.4 Add disabled attribute bindings client/imports/app/parties/party-details.component.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form *ngIf="party" (submit)="saveParty()">
  <label>Name</label>
  <input [disabled]="!isOwner" type="text" [(ngModel)]="party.name" name="name">
 
  <label>Description</label>
  <input [disabled]="!isOwner" type="text" [(ngModel)]="party.description" name="description">
 
  <label>Location</label>
  <input [disabled]="!isOwner" type="text" [(ngModel)]="party.location" name="location">
 
  <button [disabled]="!isOwner" type="submit">Save</button>
  <a [routerLink]="['/']">Cancel</a>
</form>
 

Using ngIf

It's important to know the difference between the hidden attribute and ngIf directive. While hidden shows and hides a DOM element that is already rendered, ngIf adds or removes an element from the DOM, making it both heavier and slower. It makes sense to use ngIf if the decision to show or hide some part of the UI is made during page loading.

Regarding our party details page, we'll show or hide with the help of ngIf. We'll show or hide the invitation response buttons to those who are already invited, and the invitation list to the party owners and to everybody if the party is public.

We've already added our isOwner variable. Let's add two more: isPublic and isInvited.

16.5 Add isPublic and isInvited properties client/imports/app/parties/party-details.component.ts
81
82
83
84
85
86
87
88
 
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
      $set: {
        name: this.party.name,
        description: this.party.description,
        location: this.party.location,
        'public': this.party.public
      }
    });
  }
...some lines skipped...
    return this.party && this.user && this.user._id === this.party.owner;
  }
 
  get isPublic(): boolean {
    return this.party && this.party.public;
  }
 
  get isInvited(): boolean {
    if (this.party && this.user) {
      const invited = this.party.invited || [];
 
      return invited.indexOf(this.user._id) !== -1;
    }
 
    return false;
  }
 
  ngOnDestroy() {
    this.paramsSub.unsubscribe();
    this.partySub.unsubscribe();

Then, make use of the properties in the template:

16.6 Make use of properties in the template client/imports/app/parties/party-details.component.html
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  <a [routerLink]="['/']">Cancel</a>
</form>
 
<div *ngIf="isOwner || isPublic">
  <p>Users to invite:</p>
  <ul>
    <li *ngFor="let user of users | async">
      <div>{{user | displayName}}</div>
      <button (click)="invite(user)">Invite</button>
    </li>
  </ul>
</div>
 
<div *ngIf="isInvited">
  <h2>Reply to the invitation</h2>
  <input type="button" value="I'm going!" (click)="reply('yes')">
  <input type="button" value="Maybe" (click)="reply('maybe')">

Summary

In this step we've become familiar with the binding to the DOM attributes in Angular 2 and used two of the attributes to make our app better: hidden and disabled.

The difference between ngIf and hidden was highlighted, and based on that, ngIf was used to make the party details page securer and visually better.