5-6.Angular 入門教學 - 使用Service更優雅的進行元件間之通訊

Angular 入門教學


其實超過2-3個值以上的父子之間之溝通,且情境並不直覺時,就可以考慮使用Service來完成

這邊我們回到4-3的範例檔,重新進行撰寫

一樣先將todo、header、section和footer元件給建立出來

todo.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.scss']
})
export class TodoComponent implements OnInit {
  title = 'OneTodo';
 
  constructor() { }

  ngOnInit() {
  }

}

todo.component.html

<section class="todoapp">
    <header class="header">
        <app-header></app-header>
    </header>
    <section class="main"
             style="display: block;">
        <app-section></app-section>
    </section>
    <footer class="footer"
            style="display: block;">
        <app-footer></app-footer>
    </footer>
</section>

header.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { TodoService } from 'src/app/@services/todo.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
  @Input()
  title!: string;

  todoInputModel = '';
  constructor(private todoService: TodoService) { }

  ngOnInit(): void {
  }
  
  add() {
    this.todoService.add(this.todoInputModel);
    this.todoInputModel = '';
  }
}

header.component.html

<h1>
    <div>{{title}}</div>
</h1>
<input class="new-todo"
       [(ngModel)]="todoInputModel"
       (keyup.enter)="add()"
       placeholder="What needs to be done????"
       autofocus>

section.component.ts

import { Component, OnInit } from '@angular/core';
import { Todo } from 'src/app/@models/todo.model';
import { TodoService } from 'src/app/@services/todo.service';

@Component({
  selector: 'app-section',
  templateUrl: './section.component.html',
  styleUrls: ['./section.component.scss']
})
export class SectionComponent implements OnInit {
  get toggleAllBtn() {
    return this.todoService.toggleAllBtn;
  }

  get nowTodoList() {
    return this.todoService.nowTodoList;
  }
  
  constructor(private todoService: TodoService) { }

  ngOnInit(): void {
  }

  toggleAll() {
    this.todoService.toggleAll();
  }

  clickCheck(item: Todo) {
    this.todoService.clickCheck(item);
  }

  edit(item: Todo) {
    if (item.CanEdit) {
      item.Editing = true;
    }
  }

  update(item: Todo) {
    this.todoService.update(item);
  }

  delete(item: Todo) {
    this.todoService.delete(item);
  }

}

section.component.html

<input id="toggle-all"
       class="toggle-all"
       type="checkbox"
       [checked]="toggleAllBtn"
       (click)="toggleAll()">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
    <li *ngFor="let item of nowTodoList; let i=index"
        [class]="{completed:item.Status,editing:item.Editing}">
        <div class="view">
            <input (click)="clickCheck(item)"
                   class="toggle"
                   type="checkbox"
                   [checked]="item.Status"
                   *ngIf="item.CanEdit">
            <label (dblclick)="edit(item)">{{item.Thing}}</label>
            <button (click)="delete(item)"
                    *ngIf="item.CanEdit"
                    class="destroy"> </button>
        </div>
        <input *ngIf="item.Editing"
               #itemInput
               [(ngModel)]="item.Thing"
               (keyup.enter)="update(item)"
               (blur)="update(item)"
               (mouseenter)="itemInput.focus()"
               class="edit" />
    </li>
</ul>

footer.component.ts

import { Component, OnInit } from '@angular/core';
import { Todo, TodoStatusType } from 'src/app/@models/todo.model';
import { TodoService } from 'src/app/@services/todo.service';

@Component({
  selector: 'app-footer',
  templateUrl: './footer.component.html',
  styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
  TodoStatusType = TodoStatusType;

  get todoActive(): Todo[] {
    return this.todoService.todoActive;
  }

  get nowTodoStatusType() {
    return this.todoService.nowTodoStatusType;
  }  
  
  get todoCompleted(): Todo[] {
    return this.todoService.todoCompleted;
  }

  constructor(private todoService: TodoService) { }

  ngOnInit(): void {
  }

  clearCompleted() {
    this.todoService.clearCompleted();
  }


  setTodoStatusType(type: number) {
    this.todoService.setTodoStatusType(type);
  }
}

footer.component.html

<span class="todo-count"><strong>{{todoActive.length}}</strong> items left</span>
<ul class="filters">
    <li>
        <a [class.selected]="nowTodoStatusType===TodoStatusType.All"
           (click)="setTodoStatusType(TodoStatusType.All)"
           href="#/">All</a>
    </li>
    <li>
        <a [class.selected]="nowTodoStatusType===TodoStatusType.Active"
           (click)="setTodoStatusType(TodoStatusType.Active)"
           href="#/active">Active</a>
    </li>
    <li>
        <a [class.selected]="nowTodoStatusType===TodoStatusType.Completed"
           (click)="setTodoStatusType(TodoStatusType.Completed)"
           href="#/completed">Completed</a>
    </li>
</ul>
<button class="clear-completed"
        (click)="clearCompleted()"
        *ngIf="todoCompleted.length">Clear completed</button>

我們直接就切成三個部分,然後各自都依賴注入private todoService: TodoService

接著需要什麼參數,都直接從todoService裡面拿取

要使用什麼方法,也都使用todoService中的方法

由於todoService是註冊在root,所以這三個component取到的todoService會是同一隻

利用這個特性,三個component都操作這支todoService,所互動後取到的值都會是一致的

也就輕易的達成元件之間的互動,因此就不需要再把參數傳來傳去的作處理了,既麻煩,又不直覺

但利用Service的方式,就可以很直覺優雅的來做處理了

這邊的作法在官方文件父元件和子元件透過服務來通訊,有提到

但官方的例子對於新手來說非常難懂,我當初初學時,完全看不懂,其實主要原因是使用了Rxjs

並帶有情境的範例,一來不懂語法,二來完全不懂在表達什麼,所以那時直接跳過一陣子

等到後面學了差不多了才又回頭看

有關官方這個例子,我之後會特別示範一下到底在做什麼,有興趣的人可以看一下

那因為我會特別解說語法跟情境,所以不用擔心現在會看不懂,可以先備著了解一下,以後陸續都會用到Rxjs

其實HttpClient的subscribe就是Rxjs的語法之一

 

範例檔:下載

參考資料: 
父元件和子元件透過服務來通訊 




Copyright © 凱哥寫程式 2022 | Powered by TalllKai ❤