TypeScript: Operator !(wykrzyknik)

Opublikowano: 28.06.2022 - tagi: JavaScript TypeScript Operator

W TypeScript istnieje operator ?, który służy do poinformowania, że oznaczone w taki sposób wyrażenie jest opcjonalne:

const someFunction = (data?: string) => {
}

Jest to przydatne gdy chcemy określić, że na przykład parametr funkcji nie jest wymagany.

Zapis:

data: string | null

możemy zastąpić:

data?: string

Spójrzmy jeszcze na przykład:

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

const displayPersonInfo = (person?: Person) => {
  console.log('Name: ', person.name);
  console.log('Age: ', person.age);
}

Ponieważ w funkcji displayPersonInfo oznaczyliśmy parametr person jako opcjonalny, dostaniemy komunikat o błędzie przy próbie kompilacji. Próbujemy się dostać do właściwości obiektu (parametru), który może w ogóle nie zostać przekazany.

error TS2532: Object is possibly 'undefined'.

A co jeśli mam gwarancję, że do tej funkcji zawsze zostanie przekazany właściwy obiekt?

Operator !

Istnieje operator w TypeScript dzięki, któremu informujemy TS, że wiemy, co robimy. Dajemy gwarancję, że zostanie tam przekazany obiekt. Oto zapis:

const displayPersonInfo = (person?: Person) => {
  console.log('Name: ', person!.name);
  console.log('Age: ', person!.age);
}

Operator ! to tzw.: non-null assertion. Usuwa on z danego typu wartości null lub undefined informując TypeScript "puste" wartości nie zostaną przekazane.

Innym przykład użycia tego operatora to rozwiązanie poniższego problemu. To jest przykład z Angulara:

export class FormComponent implements OnInit {

  @Input()
  data: FormGroup;
	
	...
}	

Przy próbie kompilacji takiego kodu dostaniemy błąd:

error TS2564: Property 'data' has no initializer and is not definitely assigned in the constructor.

Przekazujemy do tego komponentu obiekt FormGroup, który nie jest "pusty". My to wiemy, ale TS nie. Wystarczy napisać:

@Input()
data!: FormGroup;

Problem rozwiązany.


Angular: Jak wypełnić formularz danymi?

Opublikowano: 25.06.2022 - tagi: JavaScript Angular Formularz Dane

W Angularze wypełnienie formularza danymi jest bardzo proste. W tym wpisie pokażę dwa sposoby, jak można to zrobić.

Model danych

Posłużę się tym samym modelem danych opisanym we wpisie: Angular: Jak stworzyć zagnieżdżony formularz?.

Mam taki model:

{
  name: "Foo",
  description: "Lorem ipsum...",
  attributes: {
    width: 100,
    height: 50,
    weight: 25
  }
}

Tworzymy model:

interface ProductAttributes {
  width: number;
  height: number;
  weight: number;
}

interface Product {
  name: string;
  description: string;
  attributes: ProductAttributes
}

Formularz

Tworzymy taki formularz:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'product-form',
  templateUrl: './product-form.component.html',
  styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent {
  form = new FormGroup({
    name: new FormControl(''),
    description: new FormControl(''),
    attributes: new FormGroup({
      width: new FormControl(''),
      height: new FormControl(''),
      weight: new FormControl(''),
    })
  });
}

Mając już model i formularz, możemy wypełnić go danymi.

Sposób 1: Metoda setValue

Metoda setValue pochodzi z klasy FormGroup. Jedynym wymaganym parametrem tej metody jest obiekt, który odzwierciedla strukturę naszego formularza. Czyli obiekt musi składać się z takiej samej struktury jak model danych formularza. Jeśli spróbujemy przekazać coś innego: na przykład będzie brakować jakiegoś pola, to operacja się nie uda.

Poprawne wywołanie:

const data: Product = {
  name: "Foo",
  description: "Lorem ipsum...",
  attributes: {
    width: 100,
    height: 50,
    weight: 25
  }
};

this.form.setValue(data);

Wystąpi błąd:

const data: Product = {
  name: "Bar"
};

this.form.setValue(data); // błąd!

Sposób 2: Metoda patchValue

Ta metoda jest bardzo podobna do poprzedniej także pochodzi z FormGroup, ale jest pewna różnica. Można do niej przekazać obiekt, którego struktura nie jest identyczna jak model danych formularza. Na przykład jeśli przekażemy do wypełnienia tylko jedno pole, to operacja się uda.

Poprawne wywołanie:

const data: Product = {
  name: "Bar"
};

this.form.patchValue(data);

Możemy nawet przekazać jakieś nieistniejące pole:

const data: Product = {
  name: "Bar",
  price: 150
};

this.form.patchValue(data);

Angular: Jak stworzyć zagnieżdżony formularz?

Opublikowano: 23.06.2022 - tagi: JavaScript Angular Formularz Dane

Załóżmy, że mamy taki model danych:

{
  name: "Foo",
  description: "Lorem ipsum...",
  attributes: {
    width: 100,
    height: 50,
    weight: 25
  }
}

I chcemy dla takiej struktury danych stworzyć formularz.

Jak to zrobić w Angular?

Zagnieżdżone formularze

TypeScript:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'product-form',
  templateUrl: './product-form.component.html',
  styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent {
  form = new FormGroup({
    name: new FormControl(''),
    description: new FormControl(''),
    attributes: new FormGroup({
      width: new FormControl(''),
      height: new FormControl(''),
      weight: new FormControl(''),
    })
  });
}

Żeby stworzyć zagnieżdżony formularz, wystarczy posłużyć się FormGroup.

<form [formGroup]="form">
	<div>
		<label for="name">Name: </label>
		<input id="name" formControlName="name">

		<label for="description">Description: </label>
		<textarea id="description" formControlName="description"></textarea>
	</div>
	
	<div formGroupName="attributes">
		<h2>Attributes</h2>

		<label for="width">Width: </label>
		<input id="width" formControlName="width">

		<label for="height">Height: </label>
		<input id="height" formControlName="height">

		<label for="weight">Weight: </label>
		<input id="weight" formControlName="weight">
	</div>
</form>

Za pomocą formGroupName przypisujemy nazwę zagnieżdżonej części formularza w tym przypadku to attributes.


Angular: Jak stworzyć wiązanie dwukierunkowe?

Opublikowano: 21.06.2022 - tagi: Angular JavaScript Dane Komponent

Angular udostępnia sposób przekazywania danych między komponentami: rodzic-dziecko zwany: wiązanie dwukierunkowe (ang.: two-way binding).

Wiązanie jednokierunkowe pozwala na przekazanie danych od rodzica do dziecka. Jeśli u rodzica te dane się zmienią, to zostaną od razu przekazane do dziecka. Z kolei wiązanie dwukierunkowe umożliwia wymianę danych w obu kierunkach: rodzic <--> dziecko. Czyli jeśli zmienimy dane komponecie dziecko, to komponent rodzic także te zmiany odbierze.

Składania

Jednokierunkowe wiązanie:

HTML:

<child [data]="data"></child>

Dwukierunkowe wiązanie:

HTML:

<child [(data)]="data"></child>

Dwukierunkowe wiązanie przykład

Komponent rodzic

parent.component.html:

<div>Counter(parent): <strong>{{ counter }}</strong></div>

<child [(counter)]="counter"></child>

parent.component.ts:

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

@Component({
  selector: 'parent',
  templateUrl: './parent.component.html',
})
export class ParentComponent {

  public counter: number = 10;
}

Komponent dziecko

child.component.html:

<div>Counter(child): <strong>{{ counter }}</strong></div>
<button (click)="increment()">Incremenet</button>
<button (click)="decrement()">Decremenet</button>

child.component.ts:

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'child',
  templateUrl: './child.component.html',
})
export class ChildComponent {

  @Input()
  counter: number = 0;

  @Output()
  counterChange: EventEmitter<number> = new EventEmitter<number>();

  public increment() {
    this.counter += 1;
    this.counterChange.emit(this.counter);
  }

  public decrement() {
    this.counter -= 1;
    this.counterChange.emit(this.counter);
  }
}

Wystarczy poklikać w przyciski. Zmian widać zarówno u rodzica, jak i dziecka.


Comics: Deep Work

Opublikowano: 16.06.2022 - tagi: Komiks Rysowanie Praca Produktywność