Skip to content

Handling Forms in Svelte

Handling Forms in Svelte

If you're building an app or website, it's very likely that at some point you need to add a form to it.

In this post, we'll dive into how to handle forms in Svelte, starting from a simple approach. Then, we'll start adding complexity to it, and finally, we'll have a reusable solution.

Starting with a simple form

Let's build a sign-in form with two fields: email and password.

We'll begin by creating a new Svelte project (I prefer vite).

npm init @vitejs/app

✔ Project name: · forms-in-svelte
✔ Select a framework: · svelte
✔ Select a variant: · svelte-ts

cd forms-in-svelte
pnpm install //use the package manager you prefer
pnpm run dev

NOTE: At the moment of writing there's a bug with Svelte TypeScript projects. If you get an error, add "type":"module" in your package.json file.

Now that we have our application ready, we'll start by removing the content of App.svelte and create a sign-in form.

We should also delete the Counter component created by default.

<!-- App.svelte -->
<main>
  <form>
    <div>
        <label for="name">Email</label>
        <input
          type="text"
          id="email"
          name="email"
          value=""
        />
    </div>
    <div>
      <label for="name">Password</label>
      <input
        type="password"
        id="password"
        name="password"
        value=""
      />
  </div>
    <button type="submit">Submit</button>
  </form>
</main>

<style>
   * {
      box-sizing: border-box;
    }
    form {
      display: flex;
      flex-direction: column;
      width: 300px;
    }

    form > div{
      display: flex;
      justify-content: space-between;
    }

    form > div + * {
      margin-top: 10px;
    }
</style>
svelte-forms-img-1

Handling the submit event

Currently, our form does nothing. We need to handle the submit event on the form. To do that, we must add a handler.

To handle a submit event, we need to add to the form element the on:submit handler.

For now, we will only parse the form, and print it in the console.

<!-- App.svelte -->
<script lang="ts">
  function onSubmit(e) {
    const formData = new FormData(e.target);

    const data = {};
    for (let field of formData) {
      const [key, value] = field;
      data[key] = value;
    }
    console.log(data)
  }
</script>

<main>
  <form on:submit|preventDefault={onSubmit}>
   <!-- ... -->
  </form>
</main>
<!-- ... -->
svelte-forms-img-2

Note that we used the event modifier |preventDefault. This is equivalent to adding the Event preventDefault method in the handler.

Adding validation

Now that our form "works", we can add more features to it. For example, we may want to add some validation when the form is submitted. We will make both inputs required. If the form is invalid, we will print an error in the console. Otherwise, we will print the form data.

<!-- App.svelte -->
<script lang="ts">
  function isFormValid(data: {[fieldName: string]: any}): boolean {
    if(!isRequiredFieldValid(data.email)){
      return false
    }

    if(!isRequiredFieldValid(data.password)){
      return false
    }
    return true
  }

  function isRequiredFieldValid(value){
    return value != null && value !== ""
  }

  function onSubmit(e) {
    const formData = new FormData(e.target);

    const data: any = {};
    for (let field of formData) {
      const [key, value] = field;
      data[key] = value;
    }
    if(isFormValid(data)){
      console.log(data)
    } else {
      console.log("Invalid Form")
    }
  }
</script>
<!-- ... -->
svelte-forms-gif-1

Displaying errors

Now, our form is running a simple validation on submit, but we are missing feedback for the user. It's time to display some errors in case something fails.

<!-- App.svelte -->
<script lang="ts">
  let errors: { [inputName: string]: any } = {};

  function isFormValid(data: { [inputName: string]: any }): boolean {
    return !Object.keys(errors).some((inputName) =>
      Object.keys(errors[inputName]).some(
        (errorName) => errors[inputName][errorName],
      ),
    );
  }

  function validateForm(data: { [inputName: string]: any }):void {
    if (!isRequiredFieldValid(data.email)) {
      errors['email'] = { ...errors['email'], required: true };
    } else {
      errors['email'] = { ...errors['email'], required: false };
    }

    if (!isRequiredFieldValid(data.password)) {
      errors['password'] = { ...errors['password'], required: true };
    } else {
      errors['password'] = { ...errors['password'], required: false };
    }
  }

  function isRequiredFieldValid(value) {
    return value != null && value !== '';
  }

  function onSubmit(e) {
    const formData = new FormData(e.target);

    const data: any = {};
    for (let field of formData) {
      const [key, value] = field;
      data[key] = value;
    }

    validateForm(data);

    if (isFormValid(data)) {
      console.log(data);
    } else {
      console.log('Invalid Form');
    }
  }
</script>

<main>
  <form on:submit|preventDefault={onSubmit}>
    <div>
      <label for="name">Email</label>
      <input type="text" id="email" name="email" value="" />
      {#if errors.email && errors.email.required}
        <p class="error-message">Email is required</p>
      {/if}
    </div>
    <div>
      <label for="name">Password</label>
      <input type="password" id="password" name="password" value="" />
      {#if errors.password && errors.password.required}
        <p class="error-message">Password is required</p>
      {/if}
    </div>
    <button type="submit">Submit</button>
  </form>
</main>

<style>
  /* ... */

  form > div {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
  }

  /* ... */

  .error-message {
    color: tomato;
    flex: 0 0 100%;
    margin: 0 2px;
    font-size: 0.8em;
  }
</style>

First, I created an error object to keep track of each field, and its errors. I created it as an empty object as I expect it to be populated when verification happens.

Three functions will help validation:

  • isRequiredFieldValid checks if a given value is null, undefined, or an empty string.
  • validateForm sets errors to true or false for each form input. In this example, we are checking if both required fields are valid.
  • isFormValidwill check if there's an error in the form.

In the template, error messages are added below each form input, with a message to be displayed if the error is present and true.

Finally, some CSS was added for styling the form.

The result looks something like this:

svelte-forms-gif-2

Preparing for reusability

We have a working form right now, but it would be better if somehow we could make our form reusable.

The pattern will be repeated for each input we add. It would be nice if we could make it configurable, or if we could easily add more errors and validators without repeating ourselves.

Let's move the validators to a new file, and add more information to the response.

// Validators.ts
export interface ValidatorResult {
  [validatorName: string]: {
    error: boolean;
    message?: string;
  };
}

export type ValidatorFn = (value: any) => ValidatorResult;

function required(value: any): ValidatorResult {
  if (value === '' || value == null) {
    return { required: { error: true, message: 'Field is required' } };
  }
  return { required: { error: false } };
}

export const Validators = {
  required,
};

Previously, we had the isRequiredFieldValid method returning just a boolean. Now it returns an object with the error name required, error status, and a message, but can be extended with more information if required.

We now need to make use of this new Validator in our App component.

<!-- App.svelte -->
<script lang="ts">
  import { Validators } from './lib/Validators';
  import type { ValidatorFn, ValidatorResult } from './lib/Validators';

  let errors: { [inputName: string]: ValidatorResult } = {};

  let form: {
    [inputName: string]: {
      validators: ValidatorFn[];
    };
  } = {
    email: {
      validators: [Validators.required],
    },
    password: {
      validators: [Validators.required],
    },
  };

  function isFormValid(): boolean {
    return !Object.values(errors).some((field) =>
      Object.values(field).some((errorObject) => errorObject.error),
    );
  }

  function validateForm(data: { [inputName: string]: any }): void {
    Object.keys(data).forEach((field) => validateField(field, data[field]));
  }

  function validateField(field, value) {
    form[field]?.validators &&
      form[field].validators.forEach((fn) => {
        const error = fn(value);
        errors[field] = { ...errors[field], ...error };
      });
  }

  function onSubmit(e) {
    const formData = new FormData(e.target);

    const data: any = {};
    for (let field of formData) {
      const [key, value] = field;
      data[key] = value;
    }

    validateForm(data);

    if (isFormValid()) {
      console.log(data);
    } else {
      console.log('Invalid Form');
    }
  }
</script>

<main>
  <form on:submit|preventDefault={onSubmit}>
    <div>
      <label for="name">Email</label>
      <input type="text" id="email" name="email" value="" />
      {#if errors?.email?.required?.error}
        <p class="error-message">Email is required</p>
      {/if}
    </div>
    <div>
      <label for="name">Password</label>
      <input type="password" id="password" name="password" value="" />
      {#if errors?.password?.required?.error}
        <p class="error-message">Password is required</p>
      {/if}
    </div>
    <button type="submit">Submit</button>
  </form>
</main>

<!-- ... -->

First, I've created a form object that contains the different input names with a list of the validators that should be checked for each of them.

I added the required validator for both of them.

Then we will run all validators in the form object where the key matches the name of the input element.

The template has been updated too to handle the validator response object.

With this small refactoring, we've preserved our previous behavior, but opened the door to expand our form.

More validators

Let's add another validator to our password input.

We'll check if the password has a minimum length of 6 characters.

We'll start by creating a new validator. The best way is to use a higher-order function to set up the length, and return our validator function from it.

export interface ValidatorResult {
  [validatorName: string]: {[key:string]:any} & {
    error: boolean;
    message?: string;
  };
}

// ...

function minLength(number) {
  return function (value): ValidatorResult {
    if (value == null || value.length < number) {
      return {
        minLength: {
          error: true,
          value: number, 
          message: `Field minimum length is ${number}`,
        },
      };
    }
    return { minLength: { error: false } };
  };
}

export const Validators = {
  required,
  minLength
};

Now, we need to add it to our form configuration object, and handle the error in the template. We are using the error default message.

<!-- App.svelte -->
<script lang="ts">
// ...
  let form: {
    [inputName: string]: {
      validators: ValidatorFn[];
    };
  } = {
    email: {
      validators: [Validators.required],
    },
    password: {
      validators: [Validators.required, Validators.minLength(6)],
    },
  };

 //...
</script>

<main>
  <form on:submit|preventDefault={onSubmit}>
   <!-- ... -->
    <div>
      <label for="name">Password</label>
      <input type="password" id="password" name="password" value="" />
      {#if errors?.password?.required?.error}
        <p class="error-message">Password is required</p>
      {/if}
	  {#if errors?.password?.minLength?.error}
        <p class="error-message">{errors.password.minLength.message}</p>
      {/if}
    </div>
    <button type="submit">Submit</button>
  </form>
</main>

<!-- ... -->
svelte-forms-img-3

Handling other form events

I want to add another feature to our form. I want to validate each field separately on blur.

We can use an event handler with one of each input element to do it.

<!-- App.svelte -->
<script lang="ts">
  //...

  function onBlur(e){
    validateField(e.target.name, e.target.value)
  }

</script>

<main>
  <form on:submit|preventDefault={onSubmit}>
    <!-- ... -->
      <input type="text" id="email" name="email" value="" on:blur={onBlur} />
    <!-- ... -->
      <input type="password" id="password" name="password" value="" on:blur={onBlur} />
       <!-- ... -->
  </form>
</main>

<!-- ... -->

We just added an on:blur event handler, and onBlur method to take care of everything. Now, everytime an input loses focus it will be validated.

svelte-forms-gif-3

Our form is working as expected. However, I'd like to move it to a new component.

Creating a reusable form component

Let's create a new component Form.svelte, and move most of the form logic into it.

The form configuration should remain in the App component, and be passed into the new Form component.

<!-- Form.svelte -->
<script lang="ts">
  import { setContext } from 'svelte';
  import type { ValidatorFn, ValidatorResult } from './Validators';
  import { createEventDispatcher } from 'svelte';
  import { writable } from 'svelte/store';

  export let form: {
    [inputName: string]: {
      validators: ValidatorFn[];
    };
  } = {};

  let formEl;

  const dispatch = createEventDispatcher();
  let errors = writable({});

  function onBlur(e) {
    validateField(e.target.name, e.target.value);
  }

  function isFormValid(): boolean {
    return !Object.values($errors).some((field) =>
      Object.values(field).some(
        (errorObject: ValidatorResult) => errorObject.error,
      ),
    );
  }

  function validateField(field, value) {
    form[field]?.validators &&
      form[field].validators.forEach((fn) => {
        const error = fn(value);
        errors.update((e) => {
          e[field] = { ...e[field], ...error };
          return e;
        });
      });
  }

  function validateForm(data: { [inputName: string]: any }): void {
    Object.keys(data).forEach((field) => validateField(field, data[field]));
  }

  function onSubmit(e) {
    const formData = new FormData(e.target);

    const data: any = {};
    for (let field of formData) {
      const [key, value] = field;
      data[key] = value;
    }
    validateForm(data);

    return dispatch('submit', { valid: isFormValid(), data });
  }

  export function reset() {
    formEl.reset();
  }

  setContext('form', { errors, onBlur });
</script>

<form on:submit|preventDefault={onSubmit} bind:this={formEl}>
  <slot />
</form>

<style>
  form {
    display: flex;
    flex-direction: column;
    width: 300px;
  }

  :global(form > div) {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
  }

  :global(form > div + *) {
    margin-top: 10px;
  }
</style>

The template is very simple. A form element with a slot.

We are also binding the form element, and the on:submit event.

There's only one input: form, which will be in charge of passing data from the parent component.

The element's binding will be used to expose the native reset method from the form element. And the event binding will be used to perform validations, and emit a new submit event to be handled by the parent component.

One important thing to notice is that, to communicate the slotted elements with the Form component, we will use the Context API. There's one caveat though, context is not reactive. But, we can make it reactive by making its content a store.

Currently, we will only need to pass two things to the slotted content, the onBlur method, and the errors object. Because we expect the error object to be changing, I rewrote it as a writable store. Note that everything that previously used the errors object will have to use this new store.

To keep the parent component simple (the one containing the form), the form content will be wrapped in new components that will make use of the context API to get the data from the Form component.

Because I want always to have a label for any given field, I'll make it part of the Input component, and because I want to add validation on blur, I'll get the onBlur method exposed by the Form component through the context API.

<!-- Input.svelte -->
<script lang="ts">
  import { getContext } from 'svelte';
  export let type = 'text';
  export let label;
  export let name;
  const { onBlur } = getContext('form');
</script>

<label for={name}>{label}</label>
<input {name} {type} on:blur={onBlur} />

We are closer to getting the same behavior as before, but we are missing the error components. We can wrap all that functionality in a single component, and get the error information through the context API.

<!-- Error.svelte -->
  import { getContext } from 'svelte';
  const { errors } = getContext('form');
  export let message = null;
  export let fieldName;
  export let errorKey;
</script>

{#if $errors?.[fieldName]?.[errorKey]?.error}
  <p class="error-message">{message || $errors[fieldName][errorKey].message}</p>
{/if}

<style>
  .error-message {
    color: tomato;
    flex: 0 0 100%;
    margin: 0 2px;
    font-size: 0.8em;
  }
</style>

We are now reading the value from the errors store to decide if the message should be shown or not.

We are also using a default message from the validator in case no message is provided.

Now that all the pieces are ready, we can update our App component.

<!-- App.svelte -->
<script lang="ts">
  import { Validators } from './lib/Validators';
  import Form from './lib/Form.svelte';
  import Input from './lib/Input.svelte';
  import Error from './lib/Error.svelte';

  let formEl;
  let form = {
    email: {
      validators: [Validators.required],
    },
    password: {
      validators: [Validators.required, Validators.minLength(6)],
    },
  };

  function onSubmit(e) {
    if (e?.detail?.valid) {
      console.log(e.detail.data);
      setTimeout(() => formEl.reset(), 1000)
    } else {
      console.log('Invalid Form');
    }
  }
</script>

<main>
  <Form {form} on:submit={onSubmit} bind:this={formEl}>
    <div>
      <Input label="Email" name="email" />
      <Error
        fieldName="email"
        errorKey="required"
        message="Email is required"
      />
    </div>
    <div>
      <Input label="Password" name="password" />
      <Error
        fieldName="password"
        errorKey="required"
        message="Password is required"
      />
      <Error fieldName="password" errorKey="minLength" />
    </div>
    <button type="submit">Submit</button>
  </Form>
</main>

<style>
  * {
    box-sizing: border-box;
  }
</style>

Our app component now only needs to set up the validators, handle the submit event, and can reset the form if needed.

Extending the form component

Our form component is now exposing just a few things emitted on the submit event, or through the context API, for the slotted components.

Similar to what we've done with the input field, we must extend the functionality for select, radio buttons, checkboxes, or any other form control that we want.

For example, this is how a select component may look like.

<!-- Select.svelte -->
<script lang="ts">
  import { getContext } from 'svelte';
  export let label;
  export let name;
  const { onBlur } = getContext('form');
</script>

<label for={name}>{label}</label>
<select {name} on:blur={onBlur}>
  <slot />
</select>

And this is how it could be used:

<!-- App.svelte -->
<script lang="ts">
  import { Validators } from './lib/Validators';
  import Form from './lib/Form.svelte';
  import Input from './lib/Input.svelte';
  import Error from './lib/Error.svelte';
  import Select from './lib/Select.svelte';

  let form = {
    name: {
      validators: [Validators.required],
    },
    food: {
      validators: [Validators.required],
    },
  };

  let formEl;

  function onSubmit(e) {
    if (e?.detail?.valid) {
      console.log(e.detail.data);
      setTimeout(() => formEl.reset(), 1000)
    } else {
      console.log('Invalid Form');
    }
  }
</script>

<main>
  <Form {form} on:submit={onSubmit} bind:this={formEl}>
    <div>
      <Input label="Name" name="name" />
      <Error
        fieldName="name"
        errorKey="required"
        message="Name is required"
      />
    </div>
    <div>
      <Select label="Favorite food" name="food">
        <option value="chocolate">Chocolate</option>
        <option value="pizza">Pizza</option>
      </Select>
    </div>
    <button type="submit">Submit</button>
  </Form>
</main>
<!-- ... -->

Available Libraries

There are plenty of available libraries for handling forms in Svelte. You can check some of them in this list.

Final thoughts

I hope this step-by-step guide to handling forms helps you find your own solution to handling them, or gives you a better understanding of how other libraries might be working.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

OAuth2 for JavaScript Developers cover image

OAuth2 for JavaScript Developers

OAuth2 for JavaScript Developers OAuth is a staple of modern web development. It's the magic behind the "Log in with Facebook" or "Connect to Google" buttons you see everywhere. But what exactly is it, and how does it work, especially for a JavaScript developer? Let's dive in, using GitHub as our primary example. What is OAuth? At its core, OAuth (Open Authorization) is a protocol for authorization. It allows third-party applications to access user data without needing the user's password. Instead of users sharing their password with an app, they are granted a token that the app can use to act on their behalf. This offers several benefits; the most significant is that the app doesn't need to store user passwords, which greatly reduces potential attack vectors. Although OAuth is often used interchangeably with OAuth2, they are distinct. OAuth 1.0, introduced in 2007, is the first version of the protocol. While it provided a solid method for consumers to access protected resources without user credentials, it had complexities, such as the need for cryptographic libraries for request signing. Recognizing these challenges, the community introduced OAuth2 in 2012. OAuth2 simplified many aspects, eliminating the need for cryptographic signatures and emphasizing bearer tokens, which are easier to manage. OAuth2 provides flexibility with various methods of obtaining access tokens suitable for different application types. OAuth2 Steps Explained Using GitHub To explain how OAuth2 works, it's best to describe the entire process step-by-step. It's challenging to do this without code, so we'll reference our starter.dev backend showcase, which illustrates integrating a serverless backend with GitHub as an OAuth2 provider. Although the steps below are tailored to GitHub's OAuth2 implementation, it's important to note that the core flow is consistent across most OAuth2 services. For most of these services, you'll only need to adjust elements like URLs, query parameter names, and so on. Also, please note that our starter.dev backend showcase is designed to be deployed on Netlify. As such, all the API endpoints run as Netlify functions. When running the app locally, you must prepend the URL path with /.netlify/functions/server to access these endpoints. This step is unnecessary for the deployed version; API endpoints can be accessed directly (e.g., /api/auth/signin instead of /.netlify/functions/server/api/auth/signin) due to the redirects configured in the netlify.toml file. Step 0: Creating the App on GitHub The first step is to create your app with the provider. For GitHub, this is done on the Settings / Developer Settings / OAuth Apps page. This step is essential so that the provider can identify your app and possibly manage service-level parameters, like rate limits. In the screenshot above, when registering our app on GitHub, the authorization callback URL is a local URL. This is suitable for local development. However, in production, it should be a public API endpoint accessible to GitHub, like an API endpoint on Netlify. After registering the app, you'll receive a public client ID for your app and will need to generate a client secret (which should stay private). The client secret, used alongside the client ID, helps authenticate your application when trading an authorization code for an access token. Essentially, the client secret assures GitHub that the request for an access token genuinely comes from your app and not a malicious actor. Step 1: App Sends User to GitHub for Authorization The first step in the OAuth2 flow typically involves the user clicking a "Log in with GitHub" button on our web app or something similar. This action redirects the user from the web app to the OAuth2 provider's page (in this case, GitHub). For GitHub, the base URL for such a page is https://github.com/login/oauth/authorize, followed by query parameters that provide more details about the app. These parameters usually include the client ID — a mandatory identifier for your app — and several optional parameters, such as the redirect URI (where GitHub will send the user back) and scope (defining the permissions you're requesting). In our starter.dev example, clicking the "Log in with GitHub" button should lead the user to the /api/auth/signin route in our app. This route generates the GitHub authorization URL, populating it with all the necessary query parameters, and then redirects the user to that URL using a standard HTTP 303 redirect. ` Step 2: User Grants Permission In this step, the user arrives at the provider's authorization page and sees a prompt asking if they allow your app to access the specified data. The user can either approve or decline this request. For our scenario, this page would appear as follows: If either the cancel or authorize button is clicked, GitHub will invoke the callback URL specified when registering the app on GitHub. However, if the authorize button is clicked, GitHub will also send a one-time-use code query parameter that can be used to get the access token. Step 3: App Receives an Authorization Code After the user grants permission, GitHub redirects them to the specified callback URL. Attached to this URL is a one-time-use code query parameter. ` Step 3: App Exchanges Authorization Code for Access Token With this code, the app needs to make a POST request to the GitHub API to exchange the code for an access token. As shown below, the app sends the client ID, client secret, and the one-time code to GitHub and receives the access token in return. The access token should then be persisted in a store (such as Redis) or sent to the frontend as a cookie, as shown below: ` Step 4: App Accesses User Data with the Access Token Once you have the access token, you can use it to make authorized API requests on behalf of the user. All the app needs to do is include the access token in the Authorization header. ` And that is all. As long as the access token is valid, you can access any of the API services that were granted by the user in the authorization step. Conclusion OAuth2, though appearing complex initially, provides a strong method for authentication and authorization. For JavaScript developers, knowing its process helps in easy third-party integrations, giving users a safe way to access different services. Whether using GitHub or another platform, the main concepts are the same, allowing for better web applications. Check out our starter.dev backend showcase for the full code! There's also an associated StackBlitz project if you want to play with it....

Angular 17: Continuing the Renaissance cover image

Angular 17: Continuing the Renaissance

Angular 17: A New Era November 8th marked a significant milestone in the world of Angular with the release of Angular 17. This wasn't just any ordinary update; it was a leap forward, signifying a new chapter for the popular framework. But what made this release truly stand out was the unveiling of Angular's revamped website, complete with a fresh brand identity and a new logo. This significant transformation represents the evolving nature of Angular, aligning with the modern demands of web development. To commemorate this launch, we also hosted a release afterparty, where we went deep into its new features with Minko Gechev from the Angular core team, and Google Developer Experts (GDEs) Brandon Roberts, Deborah Kurata, and Enea Jahollari. But what exactly are these notable new features in the latest version? Let's dive in and explore. The Angular Renaissance Angular has been undergoing a significant revival, often referred to as Angular's renaissance, a term coined by Sarah Drasner, the Director of Engineering at Google, earlier this year. This revival has been particularly evident in its recent versions. The Angular team has worked hard to introduce many new improvements, focusing on signal-based reactivity, hydration, server-side rendering, standalone components, and migrating to esbuild and Vite for a better and faster developer experience. This latest release, in particular, marks many of these features as production-ready. Standalone Components About a year ago, Angular began a journey toward modernity with the introduction of standalone components. This move significantly enhanced the developer experience, making Angular more contemporary and user-friendly. In Angular's context, a standalone component is a self-sufficient, reusable code unit that combines logic, data, and user interface elements. What sets these components apart is their independence from Angular's NgModule system, meaning they do not rely on it for configuration or dependencies. By setting a standalone: true flag, you no longer need to embed your component in an NgModule and you can bootstrap directly off that component: ` Compared to the NgModules way of adding components, as shown below, you can immediately see how standalone components make things much simpler. ` In this latest release, the Angular CLI now defaults to generating standalone components, directives, and pipes. This default setting underscores the shift towards a standalone-centric development approach in Angular. New Syntax for Enhanced Control Flow Angular 17 introduces a new syntax for control flow, replacing traditional structural directives like ngIf or ngFor, which have been part of Angular since version 2. This new syntax is designed for fine-grained change detection and eventual zone-less operation when Angular completely migrates to signals. It's more streamlined and performance-efficient, making handling conditional or list content in templates easier. The @if block replaces *ngIf for expressing conditional parts of the UI. ` The @switch block replaces ngSwitch, offering benefits such as not requiring a container element to hold the condition expression or each conditional template. It also supports template type-checking, including type narrowing within each branch. ``` The @for block replaces *ngFor for iteration and presents several differences compared to its structural directive predecessor, ngFor. For example, the tracking expression (calculating keys corresponding to object identities) is mandatory but offers better ergonomics. Additionally, it supports @empty blocks. ` Defer Block for Lazy Loading Angular 17 introduces the @defer block, a dramatically improving lazy loading of content within Angular applications. Within the @defer block framework, several sub-blocks are designed to elegantly manage different phases of the deferred loading process. The main content within the @defer block is the segment designated for lazy loading. Initially, this content is not rendered, becoming visible only when specific triggers are activated or conditions are met, and after the required dependencies have been loaded. By default, the trigger for a @defer block is the browser reaching an idle state. For instance, take the following block: it delays the loading of the calendar-imp component until it comes into the viewport. Until that happens, a placeholder is shown. This placeholder displays a loading message when the calendar-imp component begins to load, and an error message if, for some reason, the component fails to load. ` The on keyword supports a wide a variety of other conditions, such as: - idle (when the browser has reached an idle state) - interaction (when the user interacts with a specified element) - hover (when the mouse has hovered over a trigger area) - timer(x) (triggers after a specified duration) - immediate (triggers the deferred load immediately) The second option of configuring when deferring happens is by using the when keyword. For example: ` Server-Side Rendering (SSR) Angular 17 has made server-side rendering (SSR) much more straightforward. Now, a --ssr option is included in the ng new command, removing the need for additional setup or configurations. When creating a new project with the ng new command, the CLI inquires if SSR should be enabled. As of version 17, the default response is set to 'No'. However, for version 18 and beyond, the plan is to enable SSR by default in newly generated applications. If you prefer to start with SSR right away, you can do so by initializing your project with the --ssr flag: ` For adding SSR to an already existing project, utilize the ng add command of the Angular CLI: ` Hydration In Angular 17, the process of hydration, which is essential for reviving a server-side rendered application on the client-side, has reached a stable, production-ready status. Hydration involves reusing the DOM structures rendered on the server, preserving the application's state, and transferring data retrieved from the server, among other crucial tasks. This functionality is automatically activated when server-side rendering (SSR) is used. It offers a more efficient approach than the previous method, where the server-rendered tree was completely replaced, often causing visible UI flickers. Such re-rendering can adversely affect Core Web Vitals, including Largest Contentful Paint (LCP), leading to layout shifts. By enabling hydration, Angular 17 allows for the reuse of the existing DOM, effectively preventing these flickers. Support for View Transitions The new View Transitions API, supported by some browsers, is now integrated into the Angular router. This feature, which must be activated using the withViewTransitions function, allows for CSS-based animations during route transitions, adding a layer of visual appeal to applications. To use it, first you need to import withViewTransitions: ` Then, you need to add it to the provideRouter configuration: ` Other Notable Changes - Angular 17 has stabilized signals, initially introduced in Angular 16, providing a new method for state management in Angular apps. - Angular 17 no longer supports Node 16. The minimal Node version required is now 18.13. - TypeScript version 5.2 is the least supported version starting from this release of Angular. - The @Component decorator now supports a styleUrl attribute. This allows for specifying a single stylesheet path as a string, simplifying the process of linking a component to a specific style sheet. Previously, even for a single stylesheet, an array was required under styleUrls. Conclusion With the launch of Angular 17, the Angular Renaissance is now in full swing. This release has garnered such positive feedback that developers are showing renewed interest in the framework and are looking forward to leveraging it in upcoming projects. However, it's important to note that it might take some time for IDEs to adapt to the new templating syntax fully. While this transition is underway, rest assured that you can still write perfectly valid code using the old templating syntax, as all the changes in Angular 17 are backward compatible. Looking ahead, the future of Angular appears brighter than ever, and we can't wait to see what the next release has in store!...

Building Mobile Applications with Svelte and NativeScript cover image

Building Mobile Applications with Svelte and NativeScript

Have you ever wanted to build a mobile application using a language you already know? In this tutorial, we'll learn how to start building a mobile application using Svelte and NativeScript. What is NativeScript NativeScript is a framework that will allow you to write Native apps using JavaScript or Typescript and, at the same time, it allows you to access platform-specific Native APIs. Setting up your environment The very first step to developing with NativeScript is installing all the required dependencies. If you're lucky, you'll already have everything installed. But if not, we'll see how to get it to work. The first thing (assuming you already have Node installed) is to install NativeScript globally. ` For this tutorial, I'll be developing an iOS application. The best way to check if your environment is prepared is to use the command provided by NativeScript. ` There's an equivalent command for android if that's your target OS. If you are missing anything, you'll get a bunch of messages with the requirements needed. In my case, I had to install XCode, Ruby, Some Gems, and Python libraries. Please refer to the setup guide to check what you need (macOS + iOS, for me). It's important to have your environment ready. Otherwise, we will not be able to run our project. ` Starting a new project Now that our environment is set up, let's start a new project. We are going to base this example app on this sample from the NativeScript samples page. However, we will add more to it, like including HTTP requests, and a List/Detail navigation. ` The command will create a Svelte + NativeScript project, and install the required dependencies. Our folder structure will look like this: We'll be focusing on the /app folder where our JS/TS will be added. ` ` App.svelte will render the default page Home which contains a message and an icon. You'll notice that these are not the HTML tags you're used to, and that's because these are not HTML elements. These are native elements/views. So if you were thinking of reusing your code in a web app, for example, this is not the part that you'll be able to share. If that's what you need, make sure you extract the pieces that can be reused. Building Our Application Let's delete the content from Home.svelte, and let's talk about what we'll be building. The app we will build will display a list of items, and when clicked, will navigate into a detailed view. The date for this example will be fetched from the PokeAPI. Creating a List view Let´s create our Home page a.k.a the list. But first, let's create some types to be used in our list. ` First, we created a type for the rest API response. It will contain a list of Pokémon with a few properties. We also create a type for the model used in our view. ` Our page consists of a set of layouts and a list view. Each list item will display an image and a label. However, our view is missing data. Integrating with PokeAPI Let's create a service that will fetch a list of Pokémon. ` To make a request I'll use the Http API provided by NativeScript. It contains a set of methods that I recommend you to check out before deciding which one will suit better for your use case. I'm making a little transformation to the received data, to build the URL with the sprite of each item. Once our service is in place, we'll fetch data and display it. By default, we will fetch 100 items starting from the first one. I'll make a request once the item is mounted, which means I'll first render the action bar only, and when the response is received, the list will be rendered. ` Our app should look this by now Much better! Adding the Details view For our sample application to be complete, we want to be able to navigate to a more detailed page of a Pokémon when one list item is selected. First, we need to create our destination component: a detailed view of a Pokémon. We will be adding fetching a description for the Pokémon, but there's a lot of information you can get from the API. Let's add this call to the API service, and create our detailed view. ` ` We will be using a set of stackLayout to build our view, display the image, add the name to the action bar, and finally show a loading message while getting the description, and an error if it fails. Notice there are two properties exported. This means that these properties must be passed as inputs from a parent. Bringing all together It's time to connect the list view, and the details view. Let's see the final Home Page: ` We have now included an event listener (on:itemTap), that will then call the navigate method included in svelte-native. Here, we passed the required props item and index to the Details Component. Now we have connected both views. We don't have to think about a back button in our details view because it's automatically there when you navigate, and add a view to the navigation stack. The final result: Success. Publishing your app If you want to publish the application start by running ns prepare ios --release Open the project in XCode and follow the instructions on how to publish an iOS app. Final words Being able to work on a native application using a language and a framework you're comfortable with can be a benefit. But there's a cost to it. Setting up the environment is not as straightforward as it could be, but thankfully, the CLI makes it a lot easier to diagnose what's required. There's a lot more to explore in NativeScript like using plugins, but it's out of scope for this post, where we explore the usage of Svelte with NativeScript. You can check the code from this tutorial in this repo....

ChatGPT can't solve these problems! with Chris Gardner cover image

ChatGPT can't solve these problems! with Chris Gardner

Chris Gardner talks neural networks, covering fundamental concepts, historical development, and practical applications. It’s important to understand the difference between artificial intelligence (AI) and machine learning, and the role of neural networks in solving intricate problems with vast datasets. This conversation centers around the intricacies of training neural networks, particularly as they become more complex with multiple layers. Chris and Rob touch on the fascinating yet daunting nature of neural networks, discussing their ability to learn beyond human comprehension. Turning to the practical side of using neural networks, Chris shares the existence of libraries that exist to simplify the process of building a network, enabling users to input data, specify parameters, and entrust the system with the training. Both caution about the biases inherent in the data and the responsibility associated with working on machine learning models. They address challenges related to ethics, highlighting the difficulties in identifying biases and emphasizing the delicate balance between excitement and caution in the evolving field of machine learning. Listen to the podcast here: https://modernweb.podbean.com/e/chris-gardner/...