Skip to main content
The ProfileService provides methods for managing user profiles in Supabase, including profile data CRUD operations and avatar image management.

Import

import { ProfileService } from '@jet/services/profile/profile.service';
import { Profile } from '@jet/interfaces/profile.interface';

Usage

Fetch User Profile

import { inject, OnInit } from '@angular/core';
import { ProfileService } from '@jet/services/profile/profile.service';
import { Profile } from '@jet/interfaces/profile.interface';

export class ProfileComponent implements OnInit {
  readonly #profileService = inject(ProfileService);
  
  profile: Profile | null = null;

  async ngOnInit() {
    try {
      const { data } = await this.#profileService.selectProfile();
      this.profile = data;
    } catch (error) {
      console.error('Failed to load profile:', error);
    }
  }
}

Update Profile

async updateProfile(username: string, fullName: string) {
  try {
    const { data } = await this.#profileService.updateAndSelectProfile({
      username,
      full_name: fullName
    });
    console.log('Profile updated:', data);
  } catch (error) {
    console.error('Failed to update profile:', error);
  }
}

Upload Avatar

async onFileSelected(event: Event) {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  
  if (!file) return;

  const { data, error } = await this.#profileService.uploadAvatar(file);
  
  if (error) {
    console.error('Upload failed:', error);
    return;
  }

  // Get public URL for the uploaded avatar
  const publicUrl = this.#profileService.getAvatarPublicUrl(data.path);
  
  // Update profile with new avatar URL
  await this.#profileService.updateAndSelectProfile({
    avatar_url: publicUrl
  });
}

Delete Avatar

async removeAvatar() {
  if (!this.profile?.avatar_url) return;

  const { error } = await this.#profileService.deleteAvatar(
    this.profile.avatar_url
  );
  
  if (error) {
    console.error('Failed to delete avatar:', error);
    return;
  }

  // Clear avatar URL in profile
  await this.#profileService.updateAndSelectProfile({
    avatar_url: null
  });
}

Methods

selectProfile

Fetches the current user’s profile from Supabase.
return
Promise<PostgrestSingleResponse<Profile>>
Promise that resolves with the user’s profile data or throws an error
Source: /home/daytona/workspace/source/src/app/services/profile/profile.service.ts:42
public selectProfile() {
  return this.#supabaseClient
    .from(SupabaseTable.Profiles)
    .select()
    .eq('user_id', this.#user()?.id)
    .single()
    .throwOnError();
}
Example:
const { data, error } = await this.profileService.selectProfile();
if (data) {
  console.log('Username:', data.username);
  console.log('Full name:', data.full_name);
  console.log('Avatar URL:', data.avatar_url);
}

updateAndSelectProfile

Updates the current user’s profile and returns the updated data.
partialProfile
Partial<Profile>
required
Object containing the profile fields to update
partialProfile.username
string
User’s username
partialProfile.full_name
string | null
User’s full name
partialProfile.avatar_url
string | null
URL to user’s avatar image
return
Promise<PostgrestSingleResponse<Profile>>
Promise that resolves with the updated profile data or throws an error
Source: /home/daytona/workspace/source/src/app/services/profile/profile.service.ts:51
public updateAndSelectProfile(partialProfile: Partial<Profile>) {
  return this.#supabaseClient
    .from(SupabaseTable.Profiles)
    .update(partialProfile)
    .eq('user_id', this.#user()?.id)
    .select()
    .single()
    .throwOnError();
}
Example:
// Update username
const { data } = await this.profileService.updateAndSelectProfile({
  username: 'newusername'
});

// Update multiple fields
const { data } = await this.profileService.updateAndSelectProfile({
  username: 'john_doe',
  full_name: 'John Doe',
  avatar_url: 'https://example.com/avatar.jpg'
});

uploadAvatar

Uploads an avatar image file to Supabase Storage.
file
File
required
The image file to upload
return
Promise<FileUploadResponse>
Promise that resolves with upload data including the file path, or an error
data
{ fullPath: string; id: string; path: string } | null
Upload metadata including the storage path
error
StorageError | null
Error object if upload failed
Source: /home/daytona/workspace/source/src/app/services/profile/profile.service.ts:61
public uploadAvatar(file: File): Promise<...> {
  const fileExtension = file.name.split('.').pop();
  const timestamp = Date.now();
  const path = `${this.#userService.user()?.id}/avatar-${timestamp}.${fileExtension}`;

  return this.#supabaseClient.storage
    .from(SupabaseStorage.ProfileAvatars)
    .upload(path, file);
}
Path format: {user_id}/avatar-{timestamp}.{extension} Example:
const file = event.target.files[0];
const { data, error } = await this.profileService.uploadAvatar(file);

if (data) {
  console.log('File uploaded to:', data.path);
  console.log('Full path:', data.fullPath);
  
  // Get public URL
  const url = this.profileService.getAvatarPublicUrl(data.path);
}

getAvatarPublicUrl

Generates a public URL for an avatar stored in Supabase Storage.
path
string
required
The storage path of the avatar file
return
string
The public URL to access the avatar image
Source: /home/daytona/workspace/source/src/app/services/profile/profile.service.ts:34
public getAvatarPublicUrl(path: string): string {
  const { data } = this.#supabaseClient.storage
    .from(SupabaseStorage.ProfileAvatars)
    .getPublicUrl(path);

  return data.publicUrl;
}
Example:
const path = 'user123/avatar-1234567890.jpg';
const publicUrl = this.profileService.getAvatarPublicUrl(path);
// Returns: 'https://...supabase.co/storage/v1/object/public/profile-avatars/user123/avatar-1234567890.jpg'

deleteAvatar

Deletes an avatar file from Supabase Storage.
publicUrl
string
required
The public URL of the avatar to delete
return
Promise<FileDeleteResponse>
Promise that resolves with deletion result or error
data
FileObject[] | null
Array of deleted file objects
error
StorageError | null
Error object if deletion failed
Source: /home/daytona/workspace/source/src/app/services/profile/profile.service.ts:25
public deleteAvatar(publicUrl: string): Promise<...> {
  const fileName = publicUrl.split('/').pop();
  const path = `${this.#userService.user()?.id}/${fileName}`;

  return this.#supabaseClient.storage
    .from(SupabaseStorage.ProfileAvatars)
    .remove([path]);
}
Example:
const avatarUrl = 'https://.../avatar-123.jpg';
const { data, error } = await this.profileService.deleteAvatar(avatarUrl);

if (error) {
  console.error('Failed to delete:', error);
} else {
  console.log('Deleted files:', data);
}

Type Definitions

Profile Interface

export interface Profile {
  avatar_url: null | string;
  created_at: string;
  full_name: null | string;
  updated_at: null | string;
  user_id: User['id'];
  username: string;
}
Source: /home/daytona/workspace/source/src/app/interfaces/profile.interface.ts:3

Configuration

Supabase Setup

The service requires Supabase client injection:
import { SUPABASE_CLIENT } from '@jet/injection-tokens/supabase-client.injection-token';
import { createClient } from '@supabase/supabase-js';

const supabaseClient = createClient(
  environment.supabaseUrl,
  environment.supabaseAnonKey
);

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: SUPABASE_CLIENT, useValue: supabaseClient },
    ProfileService // Must be explicitly provided
  ]
};

Storage Bucket

The service uses the profile-avatars storage bucket:
enum SupabaseStorage {
  ProfileAvatars = 'profile-avatars'
}
Ensure this bucket exists in your Supabase project with appropriate permissions.

Best Practices

  1. Handle errors - Always wrap service calls in try/catch blocks
  2. Validate files - Check file type and size before uploading
  3. Clean up old avatars - Delete previous avatar when uploading a new one
  4. Use loading states - Show spinners during async operations
  5. Update UI reactively - Update profile display after successful operations

Common Patterns

Complete Avatar Update Flow

async updateAvatar(file: File) {
  try {
    // Delete old avatar if exists
    if (this.currentProfile?.avatar_url) {
      await this.profileService.deleteAvatar(this.currentProfile.avatar_url);
    }

    // Upload new avatar
    const { data, error } = await this.profileService.uploadAvatar(file);
    if (error) throw error;

    // Get public URL
    const publicUrl = this.profileService.getAvatarPublicUrl(data.path);

    // Update profile
    const result = await this.profileService.updateAndSelectProfile({
      avatar_url: publicUrl
    });

    this.currentProfile = result.data;
    this.alertService.showAlert('Avatar updated successfully');
  } catch (error) {
    this.alertService.showErrorAlert('Failed to update avatar');
  }
}

Profile Form Component

export class ProfileFormComponent implements OnInit {
  readonly #profileService = inject(ProfileService);
  readonly #alertService = inject(AlertService);
  
  profileForm = new FormGroup({
    username: new FormControl('', Validators.required),
    fullName: new FormControl('')
  });

  async ngOnInit() {
    const { data } = await this.#profileService.selectProfile();
    if (data) {
      this.profileForm.patchValue({
        username: data.username,
        fullName: data.full_name
      });
    }
  }

  async onSubmit() {
    if (!this.profileForm.valid) return;

    try {
      await this.#profileService.updateAndSelectProfile({
        username: this.profileForm.value.username!,
        full_name: this.profileForm.value.fullName
      });
      this.#alertService.showAlert('Profile saved');
    } catch (error) {
      this.#alertService.showErrorAlert('Failed to save profile');
    }
  }
}

Dependencies

The ProfileService depends on:
  • SUPABASE_CLIENT - Supabase client instance
  • LoggerService - For service initialization logging
  • UserService - For current user information
This service is not provided at the root level. You must provide it in your component or module providers.

Build docs developers (and LLMs) love