Skip to content

Instantly share code, notes, and snippets.

@JonCatmull
Last active April 14, 2024 14:27
Show Gist options
  • Star 70 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save JonCatmull/ecdf9441aaa37336d9ae2c7f9cb7289a to your computer and use it in GitHub Desktop.
Save JonCatmull/ecdf9441aaa37336d9ae2c7f9cb7289a to your computer and use it in GitHub Desktop.
Angular2 + TypeScript file size Pipe/Filter. Convert bytes into largest possible unit. e.g. 1024 => 1 KB
/**
* @license
* Copyright (c) 2019 Jonathan Catmull.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { Pipe, PipeTransform } from '@angular/core';
type unit = 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB';
type unitPrecisionMap = {
[u in unit]: number;
};
const defaultPrecisionMap: unitPrecisionMap = {
bytes: 0,
KB: 0,
MB: 1,
GB: 1,
TB: 2,
PB: 2
};
/*
* Convert bytes into largest possible unit.
* Takes an precision argument that can be a number or a map for each unit.
* Usage:
* bytes | fileSize:precision
* @example
* // returns 1 KB
* {{ 1500 | fileSize }}
* @example
* // returns 2.1 GB
* {{ 2100000000 | fileSize }}
* @example
* // returns 1.46 KB
* {{ 1500 | fileSize:2 }}
*/
@Pipe({ name: 'fileSize' })
export class FileSizePipe implements PipeTransform {
private readonly units: unit[] = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
transform(bytes: number = 0, precision: number | unitPrecisionMap = defaultPrecisionMap): string {
if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) return '?';
let unitIndex = 0;
while (bytes >= 1024) {
bytes /= 1024;
unitIndex++;
}
const unit = this.units[unitIndex];
if (typeof precision === 'number') {
return `${bytes.toFixed(+precision)} ${unit}`;
}
return `${bytes.toFixed(precision[unit])} ${unit}`;
}
}
@laverix
Copy link

laverix commented Mar 16, 2017

Thanks

@crazyredd
Copy link

Still works like a charm, thanks for saving me the headache

@AarjavP
Copy link

AarjavP commented Mar 18, 2018

just a minor note, KB might be interpreted as kilobyte which equals 1000 bytes, instead of the kibibyte (KiB) which equals 1024 bytes.

@donmccurdy
Copy link

@JonCatmull this is a helpful filter! Would you mind putting a license header (MIT, Apache, ...) on it for reuse?

@RogierVC
Copy link

Excellent. Thank you!

Copy link

ghost commented Oct 29, 2018

Thank you!

@ianstew12
Copy link

ianstew12 commented Mar 29, 2019

Could someone explain the purpose of a bytes pipe to a junior dev? I'm finding code examples but not a good summary of when it would be needed. I understand what bytes and pipes are but I don't know the purpose of a bytes pipe. Appreciate any help.

@Steph0
Copy link

Steph0 commented Apr 16, 2019

Could someone explain the purpose of a bytes pipe to a junior dev?

  • Bytes is the smallest unit you can have for a file. By using the smallest you are sure you can easily convert to all the upper ones
  • Pipes allow to transform a data value to another "display" state in your templates (example: if your number is will a lot of decimals like 1.66666666666, you would probably prefer to round it for the user on the screen to 1.66; in that case pipes are usefull. There are of course more advantages, like changing the "display" data without changing the original value in your application logic if your pipe is pure and it should be), cf. https://angular.io/guide/pipes
  • So using bytes as base unit you can show to the user a human readable value of a file on the user screen (that is the pipe job)
  • Cherry on the top, if you want to format several bytes values accross your application, you can reuse your pipe everywhere in your templates (DRY, efficient, predictable, and easily testable if your pipe is pure), what could we ask more?

Another example would be dates. If you have different time formats of dates in your app logic it is often a nightmare and it is bug-prone. So in your application logic you stick to one date format (ISO, UTC, whatever) and on your user screen you use pipes like DatePipe to show the final "display" value of a date. Many formats for the user (pipes), but in the background one format to rule them all (ISO, UTC, whatever).

@msteini82
Copy link

Thank you for this great piece of code 😊👍

I just optimized it a little bit for me by adding the standard decimal pipe to convert the number to local format, so it looks better on my German project 😉

https://gist.github.com/msteini82/6b77f92feda1f6f8286bc1bc7e6fd21b

@JonCatmull
Copy link
Author

Thanks, everyone for your comments! Sorry, I haven't been on here and the emails got overlooked.

@coreyog, @AarjavP, @donmccurdy, @msteini82

Thank you for your suggestions, I will definitely look to incorporate these and repost. Then perhaps this is worth putting in an npm module to easily import into your projects if anyone has a suggestion on the name then drop a comment below.

Thanks, again.

@MacGyver98
Copy link

A simple suggestion. if (unit == 0) precision = 0; after the while loop so you don't end up with "38.00 bytes."

I guess this wouldn't help if you were averaging file sizes or something but I bet total file size is it's more popular use.

And what about this one...

let unit = 0, division = bytes;
    
    while ( division >= 1024 ) {
      division /= 1024;
      unit ++;
    }
    if (bytes % 1024 <= 10) precision = 0;

@luckylks
Copy link

I found it cleaner to change:
while ( bytes >= 1024 ) {
to:
while ( bytes >= 1000 ) {
to avoid having something like 1021 kB. This would be rounded down to 1 MB which looks better.

@JonCatmull
Copy link
Author

Added a file size precision map so you can have more finite control over what precision you want per unit.
It still accepts a number for the same precision across all units.

@folencao
Copy link

There is a small issue if the bytes is int value, will raise error bytes.toFixed is not a function, a quick fix is convert to float before run toFixed, bytes = parseFloat(bytes.toString());

@mak009
Copy link

mak009 commented Mar 1, 2021

 import { Pipe, PipeTransform } from '@angular/core';

 const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
 const FILE_SIZE_UNITS_LONG = ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Pettabytes', 'Exabytes', 'Zettabytes', 'Yottabytes'];

 @Pipe({
   name: 'formatFileSize'
 })
 export class FormatFileSizePipe implements PipeTransform {
    transform(sizeInBytes: number, longForm: boolean): string {
     const units = longForm
       ? FILE_SIZE_UNITS_LONG
       : FILE_SIZE_UNITS;

     let power = Math.round(Math.log(sizeInBytes) / Math.log(1024));
     power = Math.min(power, units.length - 1);

const size = sizeInBytes / Math.pow(1024, power); // size in new units
const formattedSize = Math.round(size * 100) / 100; // keep up to 2 decimals
const unit = units[power];

return `${formattedSize} ${unit}`;
   }
 }
   }

@butigy
Copy link

butigy commented Apr 2, 2021

nice, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment