programing

Angular 2에서 템플릿 내의 유형 주조

iphone6s 2023. 8. 25. 23:23
반응형

Angular 2에서 템플릿 내의 유형 주조

Angular 프로젝트(Angular 4.0.0)를 진행하고 있는데 추상 클래스의 속성을 tongModel에 바인딩하는 데 문제가 있습니다. 속성에 액세스하려면 먼저 실제 클래스로 캐스팅해야 하기 때문입니다.

예를 들어, 저는 AbstractEvent 클래스가 있는데, 이 클래스는 확인란으로 설정하려면 vingModel을 통해 양방향 바인딩이 필요한 부울 속성 'accknowledged'를 가진 구체적인 구현 Event를 가지고 있습니다.

현재 DOM에 다음 요소가 있습니다.

<input type="checkbox" *ngIf="event.end" [(ngModel)]="(event as Event).acknowledged" 
                                          [disabled]="(event as Event).acknowledged">

안타깝게도 이로 인해 다음과 같은 오류가 발생하고 있습니다.

감지되지 않은 오류:[(이벤트로 이벤트)]의 8열에 있는 템플릿 구문 분석 오류: 파서 오류: Missing expected).인정된

검색해보니 템플릿 내에서 'as'를 사용할 때 지원되지 않기 때문일 수도 있다고 합니다.확실하진 않지만요.

또한 템플릿을 구동하는 내 타이프스크립트 파일에 이에 대한 함수를 작성하는 방법을 알아낼 수 없습니다. 이는 내가 필요로 하는 ngModel에 대한 양방향 바인딩이 끊어지기 때문입니다.

만약 누군가가 이 문제를 피하거나 각도가 있는 템플릿에서 활자 주조를 올바르게 수행할 방법이 있다면 저는 매우 감사할 것입니다!

만약 당신이 유형 조절에 신경 쓰지 않는다면.

Angular 8 이상 버전에서는

[(ngModel)]="$any(event).acknowledged"

공식 문서에서: https://angular.io/guide/template-typecheck#disabling-type-checking-using-any

@Component({
  selector: 'my-component',
  template: '{{$any(person).addresss.street}}'
})
class MyComponent {
  person?: Person;
}

그건 불가능해요 왜냐면Event템플릿 내에서 참조할 수 없습니다.

(as템플릿 바인딩 식에서도 지원되지 않음) 먼저 사용할 수 있도록 설정해야 합니다.

class MyComponent {
  EventType = Event;

그러면 이것은 효과가 있을 것입니다.

[(ngModel)]="(event as EventType).acknowledged"

갱신하다

class MyComponent {
  asEvent(val) : Event { return val; }

그러면 그것을 로 사용합니다.

[(ngModel)]="asEvent(event).acknowledged"

이 파이프는 다양한 입력에서 유형을 가져오는 데 사용할 수 있습니다.클래스, 이름이 지정된 유형/인터페이스 및 원시 클래스에서 매우 잘 작동합니다.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'as',
  pure: true,
})
export class AsPipe implements PipeTransform {

  transform<T>(value: any, _type: (new (...args: any[]) => T) | T): T {
    return value as T;
  }

}

_type인수는 사용되지 않지만 주요 목표에 부합합니다. 유형은 생성자/변수에서 유추됩니다.

다음 용도로 사용할 수 있습니다.

class ClassEvent {
  prop: string;
}

interface InterfaceEvent {
  prop: string;
}

export class MyComponent {

  MyClass = ClassEvent; // class constructor

  MyInterface: InterfaceEvent; // typed property

  propString: any; // primitive, string

  propNumber: any; // primitive, number

}
<td mat-cell *matCellDef="let row">
  Type from class constructor: {{ (row | as : MyClass).prop }}
  Type from interface: {{ (row | as : MyInterface).prop }}
  Type from primitive, string: {{ (propString | as : '').substr(1) }}
  Type from primitive, number: {{ (propString | as : 123).toFixed(2) }}
</td>

엄격한 템플릿과 Ivy가 필요합니다.

앞서 언급했듯이 베어본 메소드 호출을 사용하면 성능에 영향을 미칩니다.

더 나은 접근법은 파이프를 사용하는 것이고, 두 가지 모두의 장점을 가지고 있습니다.주형 파이프 정의:

@Pipe({
  name: 'cast',
  pure: true
})
export class CastPipe implements PipeTransform {  
  transform(value: any, args?: any): Event {
    return value;
  }
}

템플릿에서 다음을 사용합니다.event | cast깁스가 필요할 때.

이렇게 하면 변경 감지가 효율적으로 유지되고 입력이 안전합니다(요청된 유형 변경이 당연하다는 점을 고려할 때).

안타깝게도, 저는 이것을 가질 방법을 찾지 못합니다. 왜냐하면,name각 유형에 대해 새 파이프를 정의해야 합니다.

  • TypeSafe 제네릭을 사용하여 다음과 같이 답합니다.

  • 그리고 smnbbrv answer pass type에서 영감을 받아 유형을 추론할 곳이 없을 때 선택적 인수로 명시적으로 입력합니다.

     import { Pipe, PipeTransform } from '@angular/core';
    
     /**
      * Cast super type into type using generics
      * Return Type obtained by optional @param type OR assignment type.
      */
    
     @Pipe({ name: 'cast' })
     export class CastPipe implements PipeTransform {
         /**
          * Cast (S: SuperType) into (T: Type) using @Generics.
          * @param value (S: SuperType) obtained from input type.
          * @optional @param type (T CastingType)
          * type?: { new (): T }
          * type?: new () => T
          */
         transform<S, T extends S>(value: S, type?: new () => T): T {
             return <T>value;
         }
     }
    

    용도:

    template.vmdk

     <input
         type="checkbox"
         *ngIf="event.end"
         [(ngModel)]="(event | cast: Event).acknowledged"
         [disabled]="(event | cast: Event).acknowledged"
     />
    

    구성 요소.ts

     export abstract class AbstractEvent {
         end: boolean;
     }
     export class Event extends AbstractEvent {
         acknowledged: boolean;
     }
    
    
     export class MyComponent{
         event: AbstractEvent;
         Event = Event;
     }
    

@smnbbrv로 답변을 확장하려면 다음과 같이 인터페이스와 유사한 구문을 사용할 수 있습니다.

@Pipe({ name: 'as', pure: true })
export class AsPipe implements PipeTransform {
  transform<T>(input: unknown, baseItem: T | undefined): T {
    return (input as unknown) as T;
  }
}

따라서 올바른 유형의 "baseItem"을 제공해야 합니다.그러나 실제로 항목을 생성할 필요는 없으며 항목을 정의할 수 없으므로 선언만 하면 됩니다.즉, 다음과 같이 클래스에서 제안된 유형의 변수를 만들 수 있습니다.

export interface Person{
  name: string;
  age: number;
}

export class MyComponent {
  Person: Person;
}

참고로, 우리는 어떤 값도 할당하지 않습니다.baseItem우리는 단지 그것의 유형을 지정하는 것입니다.가지고 계신다면,strictPropertyInitialization활성화된 경우, 당신은 당신의 비선택적 주장을 추가해야 할 것입니다.baseItem

export class MyComponent {
  Person!: Person;
}

그런 다음 템플릿에서 다음과 같이 사용할 수 있습니다.

<td mat-cell *matCellDef="let row">
  {{ (row | as : Person).name }}
</td>

유형 술어를 반환하는 함수를 만들 수도 있습니다.

app.component.vmdk

<some-component *ngIf="isFoo(foo)" [foo]="foo"></some-component>

app.component.ts

isFoo(value: Foo | Bar): value is Foo {
    return value === 'Foo';
}

하면 템플릿 변수가 됩니다.foo타이핑하는Foo조합 유형과 관련된 모든 엄격한 템플릿 오류를 잠재웁니다.

면책 조항!저는 형식 주조 템플릿 변수에 대한 파이프 및 지침이 있는 ng-as Angular 라이브러리의 저자입니다.

직동형 주조(예:

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

// your interface, but also work with any typescript type (class, type, etc.)
interface Person {
  name: string;
}

@Component({
  selector: 'app-root',
  template: `
  <ng-container *ngTemplateOutlet="personTemplate; context: {$implicit: person}"></ng-container>
  <ng-template #personTemplate [ngAs]="Person" let-person>
    <span>Hello {{ person.name }}!</span>
  </ng-template>
  `,
})
export class AppComponent {
  // NOTE: If you have "strictPropertyInitialization" enabled, 
  // you will need to add a non-null assertion (!)
  public Person!: Person; // publish your interface into html template
  person: Person = { name: 'Simone' }; // the data
}

파이프를 사용한 주조(예:

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

// your interface, but also work with any typescript type (class, type, etc.)
interface Person {
  name: string;
}

@Component({
  selector: 'app-root',
  template: `
  <ng-container *ngTemplateOutlet="personTemplate; context: {$implicit: person}"></ng-container>
  <ng-template #personTemplate let-person>
    <span>Hello {{ (person | as: Person).name }}!</span>
  </ng-template>
  `,
})
export class AppComponent {
  // NOTE: If you have "strictPropertyInitialization" enabled, 
  // you will need to add a non-null assertion (!)
  public Person!: Person; // publish your interface into html template
  person: Person = { name: 'Simone' }; // the data
}

파이프 소스:

import { Pipe, PipeTransform } from "@angular/core";

@Pipe({ name: 'as', pure: true })
export class NgAsPipe implements PipeTransform {
  // eslint-disable-next-line no-unused-vars
  transform<T>(input: unknown, baseItem: T | undefined): T {
    return input as unknown as T;
  }
}

지시 출처:

import { Directive, Input } from "@angular/core";

interface NgAsContext<T> {
  ngLet: T;
  $implicit: T;
}

@Directive({ selector: '[ngAs]' })
export class NgAsDirective<T> {
  @Input() ngAs!: T;

  static ngTemplateContextGuard<T>(dir: NgAsDirective<T>, ctx: any): ctx is NgAsContext<Exclude<T, false | 0 | '' | null | undefined>> {
      return true;
  }
}

많은 정보: https://www.npmjs.com/package/ng-as

이것은 여전히 Angular에서 지원되지 않습니다.사용자 정의 파이프 또는 유형 지정할 함수를 만들 수 있습니다.

또는 $any()라는 구문을 사용하여 'any'로 캐스팅할 수 있습니다.

예:

{{$any(person).address.street}}

참조 문서: https://angular.io/guide/template-typecheck

언급URL : https://stackoverflow.com/questions/45964576/type-casting-within-a-template-in-angular-2

반응형