docs(aio): update migrated content from anguar.io
This commit is contained in:

committed by
Pete Bacon Darwin

parent
ff82756415
commit
fd72fad8fd
@ -2,23 +2,18 @@
|
||||
Master/Detail
|
||||
|
||||
@intro
|
||||
We build a master/detail page with a list of heroes.
|
||||
Build a master/detail page with a list of heroes.
|
||||
|
||||
@description
|
||||
Our story needs more heroes.
|
||||
We’ll expand our Tour of Heroes app to display a list of heroes,
|
||||
allow the user to select a hero, and display the hero’s details.
|
||||
In this page, you'll expand the Tour of Heroes app to display a list of heroes, and
|
||||
allow users to select a hero and display the hero's details.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||
|
||||
Let’s take stock of what we’ll need to display a list of heroes.
|
||||
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
||||
so we’ll need a way to do that.
|
||||
|
||||
## Where We Left Off
|
||||
Before we continue with Part 2 of the Tour of Heroes,
|
||||
let’s verify we have the following structure after [Part 1](tutorial/toh-pt1).
|
||||
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||
## Where you left off
|
||||
Before you continue with this page of the Tour of Heroes,
|
||||
verify that you have the following structure after [The Hero Editor](tutorial/toh-pt1) page.
|
||||
If your structure doesn't match, go back to that page to figure out what you missed.
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
@ -70,7 +65,7 @@ If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
@ -84,273 +79,313 @@ If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
## Keep the app transpiling and running
|
||||
Enter the following command in the terminal window:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm start
|
||||
|
||||
</code-example>
|
||||
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
|
||||
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
|
||||
|
||||
## Displaying Our Heroes
|
||||
### Creating heroes
|
||||
Let’s create an array of ten heroes.
|
||||
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
|
||||
|
||||
## Displaying heroes
|
||||
To display a list of heroes, you'll add heroes to the view's template.
|
||||
|
||||
### Create heroes
|
||||
Create an array of ten heroes.
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='hero-array'}
|
||||
<code-example path="toh-2/src/app/app.component.ts" region="hero-array">
|
||||
|
||||
The `HEROES` array is of type `Hero`, the class defined in part one,
|
||||
to create an array of heroes.
|
||||
We aspire to fetch this list of heroes from a web service, but let’s take small steps
|
||||
first and display mock heroes.
|
||||
</code-example>
|
||||
|
||||
### Exposing heroes
|
||||
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||
The `HEROES` array is of type `Hero`, the class defined in the previous page.
|
||||
Eventually this app will fetch the list of heroes from a web service, but for now
|
||||
you can display mock heroes.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='hero-array-1'}
|
||||
|
||||
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
||||
We could have defined the heroes list here in this component class.
|
||||
But we know that ultimately we’ll get the heroes from a data service.
|
||||
Because we know where we are heading, it makes sense to separate the hero data
|
||||
from the class implementation from the start.### Displaying heroes in a template
|
||||
Our component has `heroes`. Let’s create an unordered list in our template to display them.
|
||||
We’ll insert the following chunk of HTML below the title and above the hero details.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-template-1'}
|
||||
|
||||
Now we have a template that we can fill with our heroes.
|
||||
|
||||
### Listing heroes with ngFor
|
||||
|
||||
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
||||
and display them individually.
|
||||
We’ll need some help from Angular to do this. Let’s do this step by step.
|
||||
|
||||
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-ngfor-1'}
|
||||
### Expose heroes
|
||||
Create a public property in `AppComponent` that exposes the heroes for binding.
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-critical}
|
||||
<code-example path="toh-2/app/app.component.1.html" region="hero-array-1">
|
||||
|
||||
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
|
||||
</code-example>
|
||||
|
||||
The `heroes` type isn't defined because TypeScript infers it from the `HEROES` array.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The hero data is separated from the class implementation
|
||||
because ultimately the hero names will come from a data service.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Display hero names in a template
|
||||
To display the hero names in an unordered list,
|
||||
insert the following chunk of HTML below the title and above the hero details.
|
||||
|
||||
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
|
||||
|
||||
|
||||
<code-example path="toh-2/app/app.component.1.html" region="heroes-template-1" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now you can fill the template with hero names.
|
||||
|
||||
### List heroes with ngFor
|
||||
|
||||
The goal is to bind the array of `heroes` in the component to the template, iterate over them,
|
||||
and display them individually.
|
||||
|
||||
Modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||
|
||||
|
||||
<code-example path="toh-2/app/app.component.1.html" region="heroes-ngfor-1">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
The (`*`) prefix to `ngFor` is a critical part of this syntax.
|
||||
It indicates that the `<li>` element and its children
|
||||
constitute a master template.
|
||||
|
||||
The `ngFor` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
||||
and stamps out instances of this template.
|
||||
The `ngFor` directive iterates over the component's `heroes` array
|
||||
and renders an instance of this template for each hero in that array.
|
||||
|
||||
The quoted text assigned to `ngFor` means
|
||||
“*take each hero in the `heroes` array, store it in the local `hero` variable,
|
||||
and make it available to the corresponding template instance*”.
|
||||
The `let hero` part of the expression identifies `hero` as the template input variable,
|
||||
which holds the current hero item for each iteration.
|
||||
You can reference this variable within the template to access the current hero's properties.
|
||||
|
||||
The `let` keyword before "hero" identifies `hero` as a template input variable.
|
||||
We can reference this variable within the template to access a hero’s properties.
|
||||
|
||||
Learn more about `ngFor` and template input variables in the
|
||||
[Displaying Data](guide/displaying-data) and
|
||||
[Template Syntax](guide/template-syntax) chapters.
|
||||
Now we insert some content between the `<li>` tags
|
||||
that uses the `hero` template variable to display the hero’s properties.
|
||||
Read more about `ngFor` and template input variables in the
|
||||
[Showing an array property with *ngFor](guide/displaying-data) section of the
|
||||
[Displaying Data](guide/displaying-data) page and the
|
||||
[ngFor](guide/template-syntax) section of the
|
||||
[Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='ng-for'}
|
||||
~~~
|
||||
|
||||
When the browser refreshes, we see a list of heroes!
|
||||
Within the `<li>` tags, add content
|
||||
that uses the `hero` template variable to display the hero's properties.
|
||||
|
||||
### Styling our heroes
|
||||
Our list of heroes looks pretty bland.
|
||||
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
|
||||
|
||||
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
||||
|
||||
<code-example path="toh-2/app/app.component.1.html" region="ng-for" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
When the browser refreshes, a list of heroes appears.
|
||||
|
||||
### Style the heroes
|
||||
Users should get a visual cue of which hero they are hovering over and which hero is selected.
|
||||
|
||||
To add styles to your component, set the `styles` property on the `@Component` decorator
|
||||
to the following CSS classes:
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='styles'}
|
||||
<code-example path="toh-2/src/app/app.component.ts" region="styles" linenums="false">
|
||||
|
||||
Notice that we again use the back-tick notation for multi-line strings.
|
||||
|
||||
That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component.
|
||||
We'll do this in a later chapter. For now let's keep rolling.
|
||||
|
||||
When we assign styles to a component they are scoped to that specific component.
|
||||
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
|
||||
|
||||
Our template for displaying the heroes should now look like this:
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-styled'}
|
||||
|
||||
|
||||
## Selecting a Hero
|
||||
We have a list of heroes and we have a single hero displayed in our app.
|
||||
The list and the single hero are not connected in any way.
|
||||
We want the user to select a hero from our list, and have the selected hero appear in the details view.
|
||||
This UI pattern is widely known as "master-detail".
|
||||
In our case, the master is the heroes list and the detail is the selected hero.
|
||||
|
||||
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
||||
|
||||
### Click event
|
||||
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='selectedHero-click'}
|
||||
|
||||
Focus on the event binding
|
||||
<code-example>
|
||||
(click)="onSelect(hero)"
|
||||
</code-example>
|
||||
|
||||
The parenthesis identify the `<li>` element’s `click` event as the target.
|
||||
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
||||
passing the template input variable `hero` as an argument.
|
||||
That’s the same `hero` variable we defined previously in the `ngFor`.
|
||||
Learn more about Event Binding in the
|
||||
[User Input](guide/user-input) and
|
||||
[Templating Syntax](guide/template-syntax) chapters.### Add the click handler
|
||||
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
||||
We’ll add that method to our component now.
|
||||
Remember to use the backtick notation for multi-line strings.
|
||||
|
||||
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
||||
Adding these styles makes the file much longer. In a later page you'll move the styles to a separate file.
|
||||
|
||||
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
||||
When you assign styles to a component, they are scoped to that specific component.
|
||||
These styles apply only to the `AppComponent` and don't affect the outer HTML.
|
||||
|
||||
### Expose the selected hero
|
||||
|
||||
We no longer need the static `hero` property of the `AppComponent`.
|
||||
**Replace** it with this simple `selectedHero` property:
|
||||
The template for displaying heroes should look like this:
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='selected-hero'}
|
||||
|
||||
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
||||
we won’t initialize the `selectedHero` as we were doing with `hero`.
|
||||
<code-example path="toh-2/app/app.component.1.html" region="heroes-styled" linenums="false">
|
||||
|
||||
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='on-select'}
|
||||
|
||||
We will be showing the selected hero's details in our template.
|
||||
At the moment, it is still referring to the old `hero` property.
|
||||
Let’s fix the template to bind to the new `selectedHero` property.
|
||||
</code-example>
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='selectedHero-details'}
|
||||
## Selecting a hero
|
||||
The app now displays a list of heroes as well as a single hero in the details view. But
|
||||
the list and the details view are not connected.
|
||||
When users select a hero from the list, the selected hero should appear in the details view.
|
||||
This UI pattern is known as "master/detail."
|
||||
In this case, the _master_ is the heroes list and the _detail_ is the selected hero.
|
||||
|
||||
Next you'll connect the master to the detail through a `selectedHero` component property,
|
||||
which is bound to a click event.
|
||||
|
||||
### Add a click event
|
||||
Add a click event binding to the `<li>` like this:
|
||||
|
||||
|
||||
|
||||
<code-example path="toh-2/app/app.component.1.html" region="selectedHero-click" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The parentheses identify the `<li>` element's `click` event as the target.
|
||||
The `onSelect(hero)` expression calls the `AppComponent` method, `onSelect()`,
|
||||
passing the template input variable `hero`, as an argument.
|
||||
That's the same `hero` variable you defined previously in the `ngFor` directive.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Learn more about event binding at the
|
||||
[User Input](guide/user-input) page and the
|
||||
[Event binding](guide/template-syntax) section of the
|
||||
[Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Add a click handler to expose the selected hero
|
||||
You no longer need the `hero` property because you're no longer displaying a single hero; you're displaying a list of heroes.
|
||||
But the user will be able to select one of the heroes by clicking on it.
|
||||
So replace the `hero` property with this simple `selectedHero` property:
|
||||
|
||||
|
||||
<code-example path="toh-2/src/app/app.component.ts" region="selected-hero">
|
||||
|
||||
</code-example>
|
||||
|
||||
The hero names should all be unselected before the user picks a hero, so
|
||||
you won't initialize the `selectedHero` as you did with `hero`.
|
||||
|
||||
Add an `onSelect` method that sets the `selectedHero` property to the `hero` that the user clicks.
|
||||
|
||||
<code-example path="toh-2/src/app/app.component.ts" region="on-select" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The template still refers to the old `hero` property.
|
||||
Bind to the new selectedHero property instead as follows:
|
||||
|
||||
|
||||
|
||||
<code-example path="toh-2/app/app.component.1.html" region="selectedHero-details" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Hide the empty detail with ngIf
|
||||
|
||||
When our app loads we see a list of heroes, but a hero is not selected.
|
||||
The `selectedHero` is `undefined`.
|
||||
That’s why we'll see the following error in the browser’s console:
|
||||
When the app loads, the `selectedHero` is undefined and won't be defined until you click a hero's name.
|
||||
Angular can't display properties of the undefined `selectedHero` and throws the following error,
|
||||
visible in the browser's console:
|
||||
|
||||
<code-example format="nocode">
|
||||
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
||||
|
||||
</code-example>
|
||||
|
||||
Remember that we are displaying `selectedHero.name` in the template.
|
||||
This name property does not exist because `selectedHero` itself is undefined.
|
||||
Although `selectedHero.name` is displayed in the template,
|
||||
you must keep the hero detail out of the DOM until there is a selected hero.
|
||||
|
||||
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
|
||||
|
||||
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
|
||||
Wrap the HTML hero detail content of the template with a `<div>`.
|
||||
Then add the `ngIf` built-in directive and set it to the `selectedHero` property of the component.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='ng-if'}
|
||||
<code-example path="toh-2/app/app.component.1.html" region="ng-if" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-critical}
|
||||
|
||||
Remember that the leading asterisk (`*`) in front of `ngIf` is
|
||||
a critical part of this syntax.
|
||||
Don't forget the asterisk (`*`) in front of `ngIf`.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The app no longer fails and the list of names displays again in the browser.
|
||||
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
|
||||
There will be no hero detail elements and no bindings to worry about.
|
||||
There are no hero detail elements or bindings to worry about.
|
||||
|
||||
When the user picks a hero, `selectedHero` becomes "truthy" and
|
||||
When the user picks a hero, `selectedHero` becomes defined and
|
||||
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
|
||||
`ngIf` and `ngFor` are called “structural directives” because they can change the
|
||||
structure of portions of the DOM.
|
||||
In other words, they give structure to the way Angular displays content in the DOM.
|
||||
|
||||
Learn more about `ngIf`, `ngFor` and other structural directives in the
|
||||
[Structural Directives](guide/structural-directives) and
|
||||
[Template Syntax](guide/template-syntax) chapters.
|
||||
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
||||
The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined.
|
||||
When we click on a hero in the list, the selected hero displays in the hero details.
|
||||
Everything is working as we expect.
|
||||
|
||||
### Styling the selection
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
We see the selected hero in the details area below but we can’t quickly locate that hero in the list above.
|
||||
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
|
||||
For example, when we select Magneta from the heroes list,
|
||||
we can make it pop out visually by giving it a subtle background color as shown here.
|
||||
Read more about `ngIf` and `ngFor` in the
|
||||
[Structural Directives](guide/structural-directives) page and the
|
||||
[Built-in directives](guide/template-syntax) section of the
|
||||
[Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
### Style the selected hero
|
||||
|
||||
While the selected hero details appear below the list, it's difficult to identify the selected hero within the list itself.
|
||||
|
||||
In the `styles` metadata that you added above, there is a custom CSS class named `selected`.
|
||||
To make the selected hero more visible, you'll apply this `selected` class to the `<li>` when the user clicks on a hero name.
|
||||
For example, when the user clicks "Magneta", it should render with a distinctive but subtle background color
|
||||
like this:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/toh/heroes-list-selected.png' alt="Selected hero"> </img>
|
||||
</figure>
|
||||
|
||||
We’ll add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`.
|
||||
In the template, add the following `[class.selected]` binding to the `<li>`:
|
||||
|
||||
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
||||
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
||||
<code-example path="toh-2/app/app.component.1.html" region="class-selected-1" linenums="false">
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='class-selected-1'}
|
||||
</code-example>
|
||||
|
||||
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
|
||||
This is the syntax for a **property binding**, a binding in which data flows one way
|
||||
from the data source (the expression `hero === selectedHero`) to a property of `class`.
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='class-selected-2'}
|
||||
When the expression (`hero === selectedHero`) is `true`, Angular adds the `selected` CSS class.
|
||||
When the expression is `false`, Angular removes the `selected` class.
|
||||
|
||||
|
||||
Learn more about [property bindings](guide/template-syntax)
|
||||
in the Template Syntax chapter.
|
||||
The browser reloads our app.
|
||||
We select the hero Magneta and the selection is clearly identified by the background color.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Read more about the `[class]` binding in the [Template Syntax](guide/template-syntax) guide.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The final version of the `<li>` looks like this:
|
||||
|
||||
<code-example path="toh-2/app/app.component.1.html" region="class-selected-2" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
After clicking "Magneta", the list should look like this:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app"> </img>
|
||||
</figure>
|
||||
|
||||
We select a different hero and the tell-tale color switches to that hero.
|
||||
|
||||
Here's the complete `app.component.ts` as it stands now:
|
||||
Here's the complete `app.component.ts` as of now:
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts'}
|
||||
<code-example path="toh-2/src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
## The Road We’ve Travelled
|
||||
Here’s what we achieved in this chapter:
|
||||
## The road you've travelled
|
||||
Here's what you achieved in this page:
|
||||
|
||||
* Our Tour of Heroes now displays a list of selectable heroes
|
||||
* We added the ability to select a hero and show the hero’s details
|
||||
* We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template
|
||||
* The Tour of Heroes app displays a list of selectable heroes.
|
||||
* You added the ability to select a hero and show the hero's details.
|
||||
* You learned how to use the built-in directives `ngIf` and `ngFor` in a component's template.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown, but it’s far from complete.
|
||||
We can't put the entire app into a single component.
|
||||
We need to break it up into sub-components and teach them to work together
|
||||
as we learn in the [next chapter](tutorial/toh-pt3).
|
||||
## The road ahead
|
||||
You've expanded the Tour of Heroes app, but it's far from complete.
|
||||
You can't put the entire app into a single component.
|
||||
In the [next page](tutorial/toh-pt3), you'll split the app into sub-components and make them work together.
|
Reference in New Issue
Block a user