Het afgelopen jaar heeft Angular veel nieuwe functies gekregen, zoals hydratatie en uitstelbare weergaven, om ontwikkelaars te helpen hun Core Web Vitals te verbeteren en een geweldige ervaring voor hun eindgebruikers te garanderen. Er wordt ook onderzoek gedaan naar aanvullende server-side rendering-gerelateerde functies die voortbouwen op deze functionaliteit, zoals streaming en gedeeltelijke hydratatie.
Helaas is er één patroon dat ervoor kan zorgen dat uw applicatie of bibliotheek niet optimaal kan profiteren van al deze nieuwe en toekomstige functies: handmatige manipulatie van de onderliggende DOM-structuur. Angular vereist dat de structuur van de DOM consistent blijft vanaf het moment dat een component door de server wordt geserialiseerd, totdat deze in de browser wordt gehydrateerd. Het gebruik van ElementRef
, Renderer2
of DOM API's om handmatig knooppunten uit de DOM toe te voegen, te verplaatsen of te verwijderen voordat hydratatie kan inconsistenties introduceren die voorkomen dat deze functies werken.
Niet alle handmatige DOM-manipulatie en -toegang is echter problematisch, en soms zelfs noodzakelijk. De sleutel tot veilig gebruik van de DOM is om uw behoefte eraan zo veel mogelijk te beperken en uw gebruik ervan vervolgens zo lang mogelijk uit te stellen. In de volgende richtlijnen wordt uitgelegd hoe u dit kunt bereiken en werkelijk universele en toekomstbestendige Angular-componenten kunt bouwen die ten volle kunnen profiteren van alle nieuwe en toekomstige functies van Angular.
Vermijd handmatige DOM-manipulatie
De beste manier om de problemen te voorkomen die handmatige DOM-manipulatie veroorzaakt, is, niet verrassend, door deze waar mogelijk helemaal te vermijden. Angular heeft ingebouwde API's en patronen die de meeste aspecten van de DOM kunnen manipuleren: u zou ze liever moeten gebruiken in plaats van rechtstreeks toegang te krijgen tot de DOM.
Muteer het eigen DOM-element van een component
Wanneer u een component of richtlijn schrijft, moet u mogelijk het hostelement (dat wil zeggen het DOM-element dat overeenkomt met de selector van de component of richtlijn) wijzigen om bijvoorbeeld een klasse, stijl of attribuut toe te voegen, in plaats van een wikkelelement. Het is verleidelijk om gewoon naar ElementRef
te grijpen om het onderliggende DOM-element te muteren. In plaats daarvan moet u hostbindingen gebruiken om de waarden declaratief aan een expressie te binden:
@Component({
selector: 'my-component',
template: `...`,
host: {
'[class.foo]': 'true'
},
})
export class MyComponent {
/* ... */
}
Net als bij gegevensbinding in HTML kunt u bijvoorbeeld ook binden aan attributen en stijlen, en 'true'
wijzigen in een andere expressie die Angular zal gebruiken om de waarde indien nodig automatisch toe te voegen of te verwijderen.
In sommige gevallen moet de sleutel dynamisch worden berekend. U kunt ook binden aan een signaal of functie die een set of kaart met waarden retourneert:
@Component({
selector: 'my-component',
template: `...`,
host: {
'[class.foo]': 'true',
'[class]': 'classes()'
},
})
export class MyComponent {
size = signal('large');
classes = computed(() => {
return [`size-${this.size()}`];
});
}
In complexere toepassingen kan het verleidelijk zijn om handmatige DOM-manipulatie uit te voeren om een ExpressionChangedAfterItHasBeenCheckedError
te voorkomen. In plaats daarvan kunt u de waarde aan een signaal binden, zoals in het vorige voorbeeld. Dit kan naar behoefte worden gedaan en vereist geen adoptie van signalen in uw gehele codebase.
Muteer DOM-elementen buiten een sjabloon
Het is verleidelijk om te proberen de DOM te gebruiken om toegang te krijgen tot elementen die normaal gesproken niet toegankelijk zijn, zoals die behoren tot andere bovenliggende of onderliggende componenten. Dit is echter foutgevoelig, is in strijd met de inkapseling en maakt het moeilijk om deze componenten in de toekomst te wijzigen of te upgraden.
In plaats daarvan moet uw component elk ander onderdeel als een zwarte doos beschouwen. Neem de tijd om te overwegen wanneer en waar andere componenten (zelfs binnen dezelfde applicatie of bibliotheek) mogelijk moeten communiceren met uw component of het gedrag of uiterlijk ervan moeten aanpassen, en stel vervolgens een veilige en gedocumenteerde manier voor om dit te doen. Gebruik functies zoals hiërarchische afhankelijkheidsinjectie om een API beschikbaar te maken voor een substructuur wanneer eenvoudige @Input
en @Output
eigenschappen niet voldoende zijn.
Historisch gezien was het gebruikelijk om functies zoals modale dialogen of tooltips te implementeren door een element toe te voegen aan het einde van het <body>
of een ander hostelement en vervolgens de inhoud daarheen te verplaatsen of te projecteren. Tegenwoordig kunt u echter waarschijnlijk een eenvoudig <dialog>
-element in uw sjabloon weergeven:
@Component({
selector: 'my-component',
template: `<dialog #dialog>Hello World</dialog>`,
})
export class MyComponent {
@ViewChild('dialog') dialogRef!: ElementRef;
constructor() {
afterNextRender(() => {
this.dialogRef.nativeElement.showModal();
});
}
}
Stel handmatige DOM-manipulatie uit
Nadat u de voorgaande richtlijnen heeft gevolgd om uw directe DOM-manipulatie en -toegang zoveel mogelijk te minimaliseren, blijft er wellicht nog wat over dat onvermijdelijk is. In dergelijke gevallen is het belangrijk om het zo lang mogelijk uit te stellen. afterRender
en afterNextRender
callbacks zijn een geweldige manier om dit te doen, omdat ze alleen in de browser worden uitgevoerd nadat Angular op eventuele wijzigingen heeft gecontroleerd en deze aan de DOM heeft vastgelegd.
Voer JavaScript alleen in de browser uit
In sommige gevallen beschikt u over een bibliotheek of API die alleen in de browser werkt (bijvoorbeeld een kaartbibliotheek, enig IntersectionObserver
gebruik, enz.). In plaats van voorwaardelijk te controleren of u in de browser draait of gedrag op de server uitschakelt, kunt u gewoon afterNextRender
gebruiken:
@Component({
/* ... */
})
export class MyComponent {
@ViewChild('chart') chartRef: ElementRef;
myChart: MyChart|null = null;
constructor() {
afterNextRender(() => {
this.myChart = new MyChart(this.chartRef.nativeElement);
});
}
}
Voer een aangepaste lay-out uit
Soms moet u mogelijk naar de DOM lezen of schrijven om een bepaalde lay-out uit te voeren die uw doelbrowsers nog niet ondersteunen, zoals het positioneren van knopinfo. afterRender
is hiervoor een goede keuze, omdat u er zeker van kunt zijn dat de DOM zich in een consistente staat bevindt. afterRender
en afterNextRender
accepteren een phase
van EarlyRead
, Read
of Write
. Het lezen van de DOM-lay-out na het schrijven ervan dwingt de browser om de lay-out synchroon opnieuw te berekenen, wat de prestaties ernstig kan beïnvloeden (zie: lay-out thrashing ). Daarom is het belangrijk om uw logica zorgvuldig in de juiste fasen op te delen.
Een tooltipcomponent die tooltip wil weergeven ten opzichte van een ander element op de pagina zou bijvoorbeeld waarschijnlijk twee fasen gebruiken. De EarlyRead
fase zou eerst worden gebruikt om de grootte en positie van de elementen te verkrijgen:
afterRender(() => {
targetRect = targetEl.getBoundingClientRect();
tooltipRect = tooltipEl.getBoundingClientRect();
}, { phase: AfterRenderPhase.EarlyRead },
);
Vervolgens zou de Write
de eerder gelezen waarde gebruiken om de tooltip daadwerkelijk te herpositioneren:
afterRender(() => {
tooltipEl.style.setProperty('left', `${targetRect.left + targetRect.width / 2 - tooltipRect.width / 2}px`);
tooltipEl.style.setProperty('top', `${targetRect.bottom - 4}px`);
}, { phase: AfterRenderPhase.Write },
);
Door onze logica in de juiste fasen op te splitsen, kan Angular DOM-manipulatie effectief in batches over elk ander onderdeel van de applicatie verdelen, waardoor een minimale impact op de prestaties wordt gegarandeerd.
Conclusie
Er zijn veel nieuwe en opwindende verbeteringen aan de Angular-weergave aan de serverzijde in het verschiet, met als doel het voor u gemakkelijker te maken om uw gebruikers een geweldige ervaring te bieden. We hopen dat de voorgaande tips nuttig zullen zijn om u te helpen er optimaal gebruik van te maken in uw toepassingen en bibliotheken!