Skip to content

Latest commit

 

History

History
234 lines (187 loc) · 7.06 KB

step-8.md

File metadata and controls

234 lines (187 loc) · 7.06 KB

Quick Jump

Step 8 task:

In this step we're going to add functionality that allows users to edit existing talks through a dialog. Angular Material comes with a MatDialog service that can be used to show dialogs that follow the Material Design specifications.

Since we want to use the MatDialog service, we first need to create a new Angular component that will serve the content of the dialog.

For CLI users: You can run ng generate component edit-talk. Otherwise, if we want to create the component manually, we need to create the following files:

  • src/app/edit-talk/edit-talk.component.ts
  • src/app/edit-talk/edit-talk.component.html
  • src/app/edit-talk/edit-talk.component.scss

Once these files have been created, we can start setting up the EditTalkComponent by creating a new Angular component that injects data which comes from the MatDialog service.

Note: Data for a dialog is usually provided through the MAT_DIALOG_DATA injection token

src/app/edit-talk/edit-talk.component.ts

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material';
import { Talk } from '../data.service';

 @Component({
  selector: 'ng-trello-edit-talk',
  templateUrl: './edit-talk.component.html',
  styleUrls: ['./edit-talk.component.scss'],
})
export class EditTalkComponent {

   constructor(@Inject(MAT_DIALOG_DATA) public talk: Talk) {}
}

Now that we have the basic component set up, we should add some form controls that allow the user to edit a talk. In order to do this, we add the following HTML to the template.

src/app/edit-talk/edit-talk.component.html

<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
  <img *ngIf="talk.image" [src]="talk.image" alt="Talk image preview">
   <mat-form-field>
    <input matInput placeholder="Talk description" required formControlName="text">
  </mat-form-field>
   <mat-form-field>
    <input matInput placeholder="Speaker" required formControlName="speaker">
  </mat-form-field>
   <mat-form-field>
    <input matInput placeholder="Image URL" formControlName="image">
  </mat-form-field>
   <mat-dialog-actions align="end">
    <!--
      mat-dialog-close is a directive from the `MatDialogModule` which can be used
      to close the current dialog in a declarative way on element click.
    -->
    <button mat-button mat-dialog-close>Cancel</button>
    
    <button mat-button type="submit" [disabled]="formGroup.invalid">Update</button>
  </mat-dialog-actions>
</form>

Once we have set up the dialog template, you might have realized that we are using multiple directives which are not imported in our app module.

The next task is to import the given modules in our app module, as well as adding the new EditTalkComponent to the app declarations. Note that EditTalkComponent will be rendered dynamically and therefore needs to be added to the entryComponents section.

src/app/app.module.ts

  ...
  MatDialogModule,
  MatInputModule,
} from '@angular/material';
import { ReactiveFormsModule } from '@angular/forms';
import { EditTalkComponent } from './edit-talk/edit-talk.component';

@NgModule({
  declarations: [
    ...
    EditTalkComponent,
  ],
  imports: [
    ...
    ReactiveFormsModule,
    MatDialogModule,
    MatInputModule,
  ],
  entryComponents: [EditTalkComponent],
})

At this point, the edit component is already declared in our app module but does not work yet. We still need to set up the form group and handle the dialog close.

In order to programmatically close a dialog, developers need to inject the MatDialogRef class that refers to the current dialog and provides a method for closing it.

src/app/edit-talk/edit-talk.component.ts

export class EditTalkComponent {

   formGroup: FormGroup;
   
   constructor(@Inject(MAT_DIALOG_DATA) public talk: Talk,
              private dialogRef: MatDialogRef<EditTalkComponent>,
              formBuilder: FormBuilder) {

     this.formGroup = formBuilder.group({
      text: [talk.text, Validators.required],
      speaker: [talk.speaker, Validators.required],
      image: [talk.image, Validators.required],
    });
  }
  
  onSubmit() {
    this.dialogRef.close(this.formGroup.value);
  }
}

Even though the edit dialog is technically ready now, it still lacks some custom styles in order to make the user experience better.

src/app/edit-talk/edit-talk.component.scss

$dialog-default-padding: 24px;
$edit-talk-dialog-width: 500px;

img {
  width: $edit-talk-dialog-width;
  
  // Shift the image to ignore the default margin of the dialog. We want to
  // show the talk image as a full-width dialog header.
  margin: (-$dialog-default-padding) (-$dialog-default-padding) 0px;
  padding-bottom: 8px;
}

.mat-form-field {
  display: block;
}

.mat-button {
  text-transform: uppercase;
}

Finally, our edit talk component is finished and we can start using the MatDialog service to open the edit talk dialog. In order to launch the dialog, we need to inject the MatDialog service in our AppComponent.

While we're at it, we also implement the editTalk method that should be called whenever we want to edit a given talk.

src/app/app.component.ts

import { MatDialog } from '@angular/material';
import { EditTalkComponent } from './edit-talk/edit-talk.component';

export class AppComponent {
  ...
  
  constructor(private _boardService: BoardService, private _dialog: MatDialog) {}
  
  editTalk(talk: Talk) {
    // Use the injected dialog service to launch the previously created edit-talk
    // component. Once the dialog closes, we assign the updated talk data to
    // the specified talk. 
    this._dialog.open(EditTalkComponent, {data: talk, width: '500px'})
      .afterClosed()
      .subscribe(newTalkData => Object.assign(talk, newTalkData));
  }

At this point, we've fulfilled all the prerequisites for opening dialog, and we only need to call the editTalk method whenever the EDIT button in the talk card has been pressed.

Since the edit button is part of a child component, we need to notify the parent component (AppComponent) which holds the data, about the button click.

We achieve this by using the @Output concept which is commonly used for communication between components.

src/app/card/card.component.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';

export class CardComponent {
  ...

  @Output() edit = new EventEmitter<void>();
}

Now that we have our output defined, we need to emit a value whenever the edit button is pressed.

src/app/card/card.component.html

<button mat-button (click)="edit.next()">EDIT</button>

To complete this step, we now need to call the editTalk method whenever the (edit) output of an <ng-trello-card> component fires.

src/app/app.component.html

<ng-trello-card
  ...
  (edit)="editTalk(talk)"></ng-trello-card>

Continue to the next step