실제 각도 상속 예제

최근에 저는 on Bookmarks.dev 에 가능성을 추가했습니다. 코드 스니펫을 만들고 업데이트하는 코드가 너무 얽혀 있다는 것을 알게 되었습니다. 코드 중복을 피하기 위해 처음에는 코드 스니펫을 만들고 업데이트하는 데 하나의 구성 요소만 사용했습니다. 이제는 너무 많은 조건부 검사를 더 이상 참을 수 없어서 기능을 두 부분으로 나누기로 결정했습니다.

태그의 자동 완성 처리와 같은 두 가지 공통 기능이 여전히 있기 때문에 코드 중복을 피하기 위해 Angular 구성 요소 상속을 사용하기로 결정했습니다. 이 블로그 게시물에서는 각도 상속에 대한 코드 예제를 보여주고 일부 각도 특성에 이름을 지정합니다.

Source code for this post is available on Github



공통 기본 양식 구성요소



먼저 태그 로드 및 자동 완성을 처리하는 스니펫 기본 양식 클래스를 정의하고 초기 스니펫 메서드 및 탐색 메서드를 만듭니다.

import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { Observable } from 'rxjs';
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { snippet_common_tags } from '../shared/snippet-common-tags';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { Snippet, CodeSnippet } from '../core/model/snippet';
import { map, startWith } from 'rxjs/operators';
import { MatChipInputEvent } from '@angular/material/chips';
import { MatAutocompleteActivatedEvent, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { SuggestedTagsStore } from '../core/user/suggested-tags.store';
import { UserInfoStore } from '../core/user/user-info.store';
import { Params, Router } from '@angular/router';
import { textSizeValidator } from '../core/validators/text-size.validator';
import { HttpResponse } from '@angular/common/http';
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
import { PersonalSnippetsService } from '../core/personal-snippets.service';
import { ErrorService } from '../core/error/error.service';


@Component({
  template: ''
})
export class SnippetFormBaseComponent implements OnInit {

  snippetFormGroup: FormGroup;
  codeSnippetsFormArray: FormArray;
  userId = null;

  // chips
  selectable = true;
  removable = true;
  addOnBlur = true;

  autocompleteTagsOptionActivated = false;

  // Enter, comma, space
  separatorKeysCodes = [ENTER, COMMA];

  commonSnippetTags = snippet_common_tags;

  autocompleteTags = [];

  tagsControl = new FormControl();

  filteredTags: Observable<any[]>;

  @Input()
  snippet: Snippet;

  @ViewChild('tagInput', {static: false})
  tagInput: ElementRef;

  constructor(
    protected formBuilder: FormBuilder,
    protected personalSnippetsService: PersonalSnippetsService,
    protected suggestedTagsStore: SuggestedTagsStore,
    protected userInfoStore: UserInfoStore,
    protected router: Router,
    protected errorService: ErrorService
  ) {
  }

  ngOnInit(): void {
    this.userInfoStore.getUserInfo$().subscribe(userInfo => {
      this.userId = userInfo.sub;
      this.suggestedTagsStore.getSuggestedSnippetTags$(this.userId).subscribe(userTags => {

        this.autocompleteTags = userTags.concat(this.commonSnippetTags.filter((item => userTags.indexOf(item) < 0))).sort();

        this.filteredTags = this.tagsControl.valueChanges.pipe(
          startWith(null),
          map((tag: string | null) => {
            return tag ? this.filter(tag) : this.autocompleteTags.slice();
          })
        );
      });
    });
  }

  addTag(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    if ((value || '').trim() && !this.autocompleteTagsOptionActivated) {
      // if ((value || '').trim()) {
      this.formArrayTags.push(this.formBuilder.control(value.trim().toLowerCase()));
    }

    // Reset the input value
    if (input) {
      input.value = '';
    }

    this.tagsControl.setValue(null);
    this.formArrayTags.markAsDirty();
  }

  removeTagByIndex(index: number): void {
    if (index >= 0) {
      this.formArrayTags.removeAt(index);
    }
    this.formArrayTags.markAsDirty();
  }

  filter(name: string) {
    return this.autocompleteTags.filter(tag => tag.toLowerCase().indexOf(name.toLowerCase()) === 0);
  }

  optionActivated($event: MatAutocompleteActivatedEvent) {
    if ($event.option) {
      this.autocompleteTagsOptionActivated = true;
    }
  }

  selectedTag(event: MatAutocompleteSelectedEvent): void {
    this.formArrayTags.push(this.formBuilder.control(event.option.viewValue));
    this.tagInput.nativeElement.value = '';
    this.tagsControl.setValue(null);
    this.autocompleteTagsOptionActivated = false;
  }

  get formArrayTags() {
    return <FormArray>this.snippetFormGroup.get('tags');
  }

  createCodeSnippet(codeSnippet: CodeSnippet): FormGroup {
    return this.formBuilder.group({
      code: [codeSnippet.code, textSizeValidator(5000, 500)],
      comment: codeSnippet.comment
    });
  }

  createInitialCodeSnippet(): FormGroup {
    return this.formBuilder.group({
      code: ['', textSizeValidator(5000, 500)],
      comment: ['', textSizeValidator(1000, 30)]
    });
  }

  createEmptyCodeSnippet(): FormGroup {
    return this.formBuilder.group({
      code: ['', textSizeValidator(5000, 500)],
      comment: ['', textSizeValidator(1000, 30)]
    });
  }

  addEmptyCodeSnippet(index: number): void {
    this.codeSnippetsFormArray.insert(index + 1, this.createEmptyCodeSnippet());
  }

  removeCodeSnippet(index: number) {
    this.codeSnippetsFormArray.removeAt(index);
  }

  createSnippet(snippet: Snippet, copyToMine: boolean, popup: any) {
    snippet.userId = this.userId;
    const now = new Date();
    snippet.lastAccessedAt = now;
    if (copyToMine) {
      delete snippet['_id'];
      snippet.createdAt = now
    }

    this.personalSnippetsService.createSnippet(this.userId, snippet)
      .subscribe(
        response => {
          const headers = response.headers;
          // get the snippet id, which lies in the "location" response header
          const lastSlashIndex = headers.get('location').lastIndexOf('/');
          const newSnippetId = headers.get('location').substring(lastSlashIndex + 1);
          snippet._id = newSnippetId;
          const queryParmas = popup ? {popup: popup} : {};
          this.navigateToSnippetDetails(snippet, queryParmas)
        },
        (error: HttpResponse<any>) => {
          this.errorService.handleError(error.body.json());
          return observableThrowError(error.body.json());
        }
      );
  }

  navigateToSnippetDetails(snippet: Snippet, queryParams: Params): void {
    const link = [`./my-snippets/${snippet._id}/details`];
    this.router.navigate(link, {
      state: {snippet: snippet},
      queryParams: queryParams
    });
  }

}


구성 요소 상속


UpdateSnippetFormComponentCreateSnippetFormComponent 구성 요소는
그것에서 상속합니다.

예를 들어 CreateSnippetFormComponent 에서 그 중 하나를 살펴보고 특정 사항에 대해 논의해 보겠습니다.

// imports ignored for brevity

@Component({
  selector: 'app-save-snippet-form',
  templateUrl: './create-snippet-form.component.html',
  styleUrls: ['./create-snippet-form.component.scss']
})
export class CreateSnippetFormComponent extends SnippetFormBaseComponent implements OnInit {

  snippetFormGroup: FormGroup;
  codeSnippetsFormArray: FormArray;
  userId = null;

  @Input()
  snippet$: Observable<Snippet>;

  @ViewChild('tagInput', {static: false})
  tagInput: ElementRef;

  snippet: Snippet;

  @Input()
  code; // value of "desc" query parameter if present

  @Input()
  title; // value of "title" query parameter if present

  @Input()
  sourceUrl; // value of "url" query parameter if present

  @Input()
  tagsStr; // tags received - string with comma separated values

  @Input()
  comment; // comment received via query

  @Input()
  popup; // if it's popup window

  constructor(
    protected formBuilder: FormBuilder,
    protected personalSnippetsService: PersonalSnippetsService,
    protected suggestedTagsStore: SuggestedTagsStore,
    protected userInfoStore: UserInfoStore,
    private userDataStore: UserDataStore,
    private logger: Logger,
    protected router: Router,
    private route: ActivatedRoute,
    protected errorService: ErrorService,
    private webpageInfoService: WebpageInfoService,
    private stackoverflowHelper: StackoverflowHelper,
  ) {
    super(formBuilder, personalSnippetsService, suggestedTagsStore, userInfoStore, router, errorService);
  }

  ngOnInit(): void {
    super.ngOnInit();
    this.buildInitialForm();
    this.codeSnippetsFormArray = this.snippetFormGroup.get('codeSnippets') as FormArray;

    if (this.sourceUrl) {
      const stackoverflowQuestionId = this.stackoverflowHelper.getStackoverflowQuestionIdFromUrl(this.sourceUrl);
      if (stackoverflowQuestionId) {
        this.webpageInfoService.getStackoverflowQuestionData(stackoverflowQuestionId).subscribe((webpageData: WebpageInfo) => {
            if (webpageData.tags) {
              for (let i = 0; i < webpageData.tags.length; i++) {
                this.formArrayTags.push(this.formBuilder.control(webpageData.tags[i]));
              }

              this.tagsControl.setValue(null);
              this.formArrayTags.markAsDirty();
            }
          },
          error => {
            console.error(`Problems when scraping data for stackoverflow id ${stackoverflowQuestionId}`, error);
          });
      }
    }

    this.setTagsFromQueryParameter();
  }

  private setTagsFromQueryParameter() {
    if (this.tagsStr) {
      const tags: String[] = this.tagsStr.split(',');
      for (let i = 0; i < tags.length; i++) {
        this.formArrayTags.push(this.formBuilder.control(tags[i].trim()));
      }

      this.tagsControl.setValue(null);
      this.formArrayTags.markAsDirty();
    }
  }

  buildInitialForm(): void {
    this.snippetFormGroup = this.formBuilder.group({
      title: [this.title ? this.title : '', Validators.required],
      tags: this.formBuilder.array([], [tagsValidator, Validators.required]),
      codeSnippets: new FormArray([this.createInitialCodeSnippet()]),
      sourceUrl: this.sourceUrl ? this.sourceUrl : '',
      public: false
    });

  }

  createInitialCodeSnippet(): FormGroup {
    return this.formBuilder.group({
      code: [this.code ? this.code : '', textSizeValidator(5000, 500)],
      comment: [this.comment ? this.comment : '', textSizeValidator(1000, 30)]
    });
  }

}


  • extends 키워드는 상속을 표시하는 데 사용됩니다. - CreateSnippetFormComponent extends SnippetFormBaseComponent
  • 자식에서 부모 생성자의 모든 속성을 protected로 정의하고 생성자의 시작 부분에 super 키워드를 사용하여 생성자를 호출해야 합니다. - super(formBuilder, personalSnippetsService, suggestedTagsStore, userInfoStore, router, errorService);
  • 상위 구성 요소의 ngOnInit 기능을 트리거하려면 하위 구성 요소에서도 super 키워드를 사용하여 호출해야 합니다(그렇지 않으면 덮어쓰게 됩니다) - super.ngOnInit();

  • Typescript의 클래스 상속에 대한 자세한 설명은 TypeScript 핸드북의 "Classes" section을 참조하십시오.

    다음 gif에서 작동 중인 코드 스니펫 생성 양식을 볼 수 있습니다.


    If you have found this useful, please show some love and give us a star on Github

    좋은 웹페이지 즐겨찾기