typescript - Angular AoT tilpasset dekoratørfeil oppstod ved statisk løsning av symbolverdier
Jeg laget en dekoratør for å hjelpe meg med å håndtere skrivebords-/mobilhendelser
import { HostListener } from '@angular/core';
type MobileAwareEventName =
| 'clickstart'
| 'clickmove'
| 'clickend'
| 'document:clickstart'
| 'document:clickmove'
| 'document:clickend'
| 'window:clickstart'
| 'window:clickmove'
| 'window:clickend';
export const normalizeEventName = (eventName: string) => {
return typeof document.ontouchstart !== 'undefined'
? eventName
.replace('clickstart', 'touchstart')
.replace('clickmove', 'touchmove')
.replace('clickend', 'touchend')
: eventName
.replace('clickstart', 'mousedown')
.replace('clickmove', 'mousemove')
.replace('clickend', 'mouseup');
};
export const MobileAwareHostListener = (
eventName: MobileAwareEventName,
args?: string[],
) => {
return HostListener(normalizeEventName(eventName), args);
};
Problemet med det er at når jeg prøver å kompilere med --prod, får jeg følgende feil
typescript error
Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing
the function or lambda with a reference to an exported function (position 26:40 in the original .ts file),
resolving symbol MobileAwareHostListener in
.../event-listener.decorator.ts, resolving symbol HomePage in
.../home.ts
Error: The Angular AoT build failed. See the issues above
Hva er galt? Hvordan kan jeg fikse det?
Svar #1
Dette betyr nøyaktig det feilen sier. Funksjonskall støttes ikke på et sted der du gjør dem. Utvidelsen av oppførselen til Angulars innebygde dekoratører støttes ikke.
AOT-kompilering (utløst av --prod-alternativet) gjør det mulig å statisk analysere eksisterende kode og erstatte noen deler med forventede resultater av deres evaluering. Dynamisk oppførsel på disse stedene betyr at AOT ikke kan brukes for applikasjonen, noe som er en stor ulempe for applikasjonen.
Hvis du trenger tilpasset oppførsel, bør ikke HostListener brukes. Siden det i praksis setter opp en lytter på elementet, bør dette gjøres manuelt med renderer-leverandør, som er den foretrukne Angular-abstraksjonen over DOM.
Dette kan løses med en tilpasset dekoratør:
interface IMobileAwareDirective {
injector: Injector;
ngOnInit?: Function;
ngOnDestroy?: Function;
}
export function MobileAwareListener(eventName) {
return (classProto: IMobileAwareDirective, prop, decorator) => {
if (!classProto['_maPatched']) {
classProto['_maPatched'] = true;
classProto['_maEventsMap'] = [...(classProto['_maEventsMap'] || [])];
const ngOnInitUnpatched = classProto.ngOnInit;
classProto.ngOnInit = function(this: IMobileAwareDirective) {
const renderer2 = this.injector.get(Renderer2);
const elementRef = this.injector.get(ElementRef);
const eventNameRegex = /^(?:(window|document|body):|)(.+)/;
for (const { eventName, listener } of classProto['_maEventsMap']) {
// parse targets
const [, eventTarget, eventTargetedName] = eventName.match(eventNameRegex);
const unlisten = renderer2.listen(
eventTarget || elementRef.nativeElement,
eventTargetedName,
listener.bind(this)
);
// save unlisten callbacks for ngOnDestroy
// ...
}
if (ngOnInitUnpatched)
return ngOnInitUnpatched.call(this);
}
// patch classProto.ngOnDestroy if it exists to remove a listener
// ...
}
// eventName can be tampered here or later in patched ngOnInit
classProto['_maEventsMap'].push({ eventName, listener: classProto[prop] });
}
}
Og brukt som:
export class FooComponent {
constructor(public injector: Injector) {}
@MobileAwareListener('clickstart')
bar(e) {
console.log('bar', e);
}
@MobileAwareListener('body:clickstart')
baz(e) {
console.log('baz', e);
}
}
IMobileAwareDirective-grensesnittet spiller en viktig rolle her. Det tvinger en klasse til å ha injector-egenskapen, og på denne måten får den tilgang til sin injector og egne avhengigheter (inkludert ElementRef, som er lokal og åpenbart ikke tilgjengelig på roten injector). Denne konvensjonen er den foretrukne måten for dekoratører å samhandle med klasseinstansens avhengigheter på. class ... implements IMobileAwareDirective kan også legges til for uttrykksfullhet.
MobileAwareListener skiller seg fra HostListener ved at den sistnevnte aksepterer en liste over argumentnavn (inkludert magiske $event), mens førstnevnte bare aksepterer hendelsesobjekt og er bundet til klasseinstansen. Dette kan endres når det er nødvendig.
Her er en demo.
Det er flere bekymringer som bør tas opp ytterligere her. Hendelseslyttere bør fjernes i ngOnDestroy. Det kan være potensielle problemer med klassearv, dette må testes ytterligere.
