avatarBridget Cougar

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

7759

Abstract

component template how the upload blue button and the invisible file input are linked. When the user clicks on the blue button, a click handler triggers the file input via <code>fileUpload.click()</code>.</p><p id="ac29">The user will then choose a file from the file upload dialog, and the <code>change</code> event will be triggered and handled by <code>onFileSelected()</code>.</p><h1 id="7571">Uploading a file to the backend using the Angular HTTP Client</h1><p id="fff6">Let’s now have a look at our component class and the implementation of <code>onFileSelected()</code>:</p><div id="98fa"><pre><span class="hljs-meta">@Component</span>({ <span class="hljs-attr">selector</span>: <span class="hljs-string">'file-upload'</span>, <span class="hljs-attr">templateUrl</span>: <span class="hljs-string">"file-upload.component.html"</span>, <span class="hljs-attr">styleUrls</span>: [<span class="hljs-string">"file-upload.component.scss"</span>] }) <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">FileUploadComponent</span> {

fileName = <span class="hljs-string">''</span>;

<span class="hljs-title function_">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> http: HttpClient</span>) {}

<span class="hljs-title function_">onFileSelected</span>(<span class="hljs-params">event</span>) {

    <span class="hljs-keyword">const</span> <span class="hljs-attr">file</span>:<span class="hljs-title class_">File</span> = event.<span class="hljs-property">target</span>.<span class="hljs-property">files</span>[<span class="hljs-number">0</span>];

    <span class="hljs-keyword">if</span> (file) {

        <span class="hljs-variable language_">this</span>.<span class="hljs-property">fileName</span> = file.<span class="hljs-property">name</span>;

        <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>();

        formData.<span class="hljs-title function_">append</span>(<span class="hljs-string">"thumbnail"</span>, file);

        <span class="hljs-keyword">const</span> upload$ = <span class="hljs-variable language_">this</span>.<span class="hljs-property">http</span>.<span class="hljs-title function_">post</span>(<span class="hljs-string">"/api/thumbnail-upload"</span>, formData);

        upload$.<span class="hljs-title function_">subscribe</span>();
    }
}

}</pre></div><p id="efe8">Here is what is going in this component:</p><ul><li>we are getting a reference to the files that the user selected by accessing the <code>event.target.files</code> property.</li><li>we then build a form payload by using the <code>FormData</code> API. This is a standard browser API, it's not Angular-specific.</li><li>we then use the Angular HTTP client to create an HTTP request and send the file to the backend</li></ul><p id="8224">At this point, we would already have a working file upload component. But we want to take this component one step further. We want to be able to display a progress indicator to the user, and also support upload cancelation.</p><h1 id="2e9b">How to Display a File Upload Progress Indicator</h1><p id="0abb">We are going to add a few more elements to the UI of our file upload component. Here is the final version of the file upload component template:</p><div id="2cff"><pre><input <span class="hljs-keyword">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"file-input"</span> [accept]=<span class="hljs-string">"requiredFileType"</span> (change)=<span class="hljs-string">"onFileSelected($event)"</span> #fileUpload>

<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"file-upload"</span>></span>

{{fileName || "No file uploaded yet."}}

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">mat-mini-fab</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"primary"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"upload-btn"</span>
  (<span class="hljs-attr">click</span>)=<span class="hljs-string">"fileUpload.click()"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span>&gt;</span>attach_file<span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>

<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress"</span>></span>

<span class="hljs-tag"><<span class="hljs-name">mat-progress-bar</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress-bar"</span> <span class="hljs-attr">mode</span>=<span class="hljs-string">"determinate"</span> [<span class="hljs-attr">value</span>]=<span class="hljs-string">"uploadProgress"</span> *<span class="hljs-attr">ngIf</span>=<span class="hljs-string">"uploadProgress"</span>></span>

<span class="hljs-tag"></<span class="hljs-name">mat-progress-bar</span>></span>

<span class="hljs-tag"><<span class="hljs-name">mat-icon</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cancel-upload"</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"cancelUpload()"</span> *<span class="hljs-attr">ngIf</span>=<span class="hljs-string">"uploadProgress"</span>></span>delete_forever<span class="hljs-tag"></<span class="hljs-name">mat-icon</span>></span>

<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span></pre></div><p id="6548">The two main elements that we have added to the UI are:</p><ul><li>An Angular Material progress bar, which is visible only while the file upload is still in progress.</li><li>A cancel upload button, also only visible when an upload is still ongoing</li></ul><h2 id="8e22">How to know how much of the file has been uploaded?</h2><p id="318b">The way that we implement the progress indicator, is by using the <code>reportProgress</code> feature of the Angular HTTP client.</p><p id="7c34">With this feature, we can get notified of the progress of a file upload via multiple events emitted by the HTTP Observable.</p><p id="8436">To see it in action, let’s have a look at the final version of the file upload component class, with all its features implemented:</p><div id="88a0"><pre><span class="hljs-meta">@Component</span>({ <span class="hljs-attr">selector</span>: <span class="hljs-string">'file-upload'</span>, <span class="hljs-attr">templateUrl</span>: <span class="hljs-string">"file-upload.component.html"</span>, <span class="hljs-attr">styleUrls</span>: [<span class="hljs-string">"file-upload.component.scss"</span>] }) <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">FileUploadComponent</span> {

<span class="hljs-meta">@Input</span>()
<span class="hljs-attr">requiredFileType</span>:<span class="hljs-built_in">string</span>;

fileName = <span class="hljs-string">''</span>;
<span class="hljs-attr">uploadProgress</span>:<span class="hljs-built_in">number</span>;
<span class="hljs-attr">uploadSub</span>: <span class="hljs-title class_">Subscription</span>;

<span class="hljs-title function_">constructor</span>(<span class="hljs-

Options

params"><span class="hljs-keyword">private</span> http: HttpClient</span>) {}

<span class="hljs-title function_">onFileSelected</span>(<span class="hljs-params">event</span>) {
    <span class="hljs-keyword">const</span> <span class="hljs-attr">file</span>:<span class="hljs-title class_">File</span> = event.<span class="hljs-property">target</span>.<span class="hljs-property">files</span>[<span class="hljs-number">0</span>];
  
    <span class="hljs-keyword">if</span> (file) {
        <span class="hljs-variable language_">this</span>.<span class="hljs-property">fileName</span> = file.<span class="hljs-property">name</span>;
        <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>();
        formData.<span class="hljs-title function_">append</span>(<span class="hljs-string">"thumbnail"</span>, file);

        <span class="hljs-keyword">const</span> upload$ = <span class="hljs-variable language_">this</span>.<span class="hljs-property">http</span>.<span class="hljs-title function_">post</span>(<span class="hljs-string">"/api/thumbnail-upload"</span>, formData, {
            <span class="hljs-attr">reportProgress</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">observe</span>: <span class="hljs-string">'events'</span>
        })
        .<span class="hljs-title function_">pipe</span>(
            <span class="hljs-title function_">finalize</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">reset</span>())
        );
      
        <span class="hljs-variable language_">this</span>.<span class="hljs-property">uploadSub</span> = upload$.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
          <span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> == <span class="hljs-title class_">HttpEventType</span>.<span class="hljs-property">UploadProgress</span>) {
            <span class="hljs-variable language_">this</span>.<span class="hljs-property">uploadProgress</span> = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>(<span class="hljs-number">100</span> * (event.<span class="hljs-property">loaded</span> / event.<span class="hljs-property">total</span>));
          }
        })
    }
}

<span class="hljs-title function_">cancelUpload</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">uploadSub</span>.<span class="hljs-title function_">unsubscribe</span>(); <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">reset</span>(); }

<span class="hljs-title function_">reset</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">uploadProgress</span> = <span class="hljs-literal">null</span>; <span class="hljs-variable language_">this</span>.<span class="hljs-property">uploadSub</span> = <span class="hljs-literal">null</span>; } }</pre></div><p id="6e91">As we can see, we have set the <code>reportProgress</code> property to true in our HTTP call, and we have also set the <code>observe</code> property to the value <code>events</code>.</p><p id="cbac">This means that, as the POST call continues, we are going to receive event objects reporting the progress of the HTTP request.</p><p id="294a">These events will be emitted as values of the <code>http</code> Observable, and come in different types:</p><ul><li>events of type <code>UploadProgress</code> report the percentage of the file that has already been uploaded</li><li>events of types <code>Response</code> report that the upload has been completed</li></ul><p id="aa3c">Using the events of type <code>UploadProgress</code>, we are saving the ongoing upload percentage in a member variable <code>uploadProgress</code>, which we then use to update the value of the progress indicator bar.</p><p id="ea5b">When the upload either completes or fails, we need to hide the progress bar from the user.</p><p id="b2ef">We can make sure that we do so by using the RxJs <code>finalize</code> operator, which is going to call the <code>reset()</code> method in both cases: upload success or failure.</p><h1 id="29cb">How to Cancel an Ongoing File Upload</h1><p id="d74e">In order to support file upload cancellation, all we have to is keep a reference to the RxJs Subscription object that we get when the <code>http</code> Observable gets subscribed to.</p><p id="aa0f">In our component, we store this subscription object in the <code>uploadSub</code> member variable.</p><p id="5ae9">While the upload is still in progress, the user might decide to cancel it by clicking on the cancel button. Then the <code>cancelUpload()</code> upload method is going to get triggered and the HTTP request can be canceled by unsubscribing from the <code>uploadSub</code> subscription.</p><p id="5e61">This unsubscription will trigger the immediate cancelation of the ongoing file upload.</p><h1 id="9aad">How to accept only files of a certain type</h1><p id="c8b0">In the final version of our file upload component, we can require the user to upload a file of a certain type, by using the <code>requiredFileType</code> property:</p><div id="8c53"><pre><span class="hljs-tag"><<span class="hljs-name">file-upload</span> <span class="hljs-attr">requiredFileType</span>=<span class="hljs-string">"image/png"</span>></span><span class="hljs-tag"></<span class="hljs-name">file-upload</span>></span></pre></div><p id="fe67">This property is then passed directly to the <code>accept</code> property of the file input in the file upload template, forcing the user to select a png file from the file upload dialog.</p><h1 id="a737">How to Upload Multiple Files</h1><p id="d62f">By default, the browser file selection dialog will allow the user to select only one file for upload.</p><p id="cb3a">But using the <code>multiple</code> property, we can allow the user to select multiple files instead:</p><div id="af5a"><pre><<span class="hljs-built_in">input</span> <span class="hljs-built_in">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"file-upload"</span> multiple></pre></div><p id="a900">Notice that this would need a completely different UI than the one that we have built. A styled upload button with a progress indicator only works well for the upload of a single file.</p><p id="14d6">For a multi-file upload scenario, there are a variety of UIs that could be built: a floating dialog with the upload progress of all files, etc.</p><h1 id="f2f4">Summary</h1><p id="a227">The best way to handle file upload in Angular is to build one or more custom components, depending on the supported upload scenarios.</p><p id="b3e1">A file upload component needs to contain internally an HTML input of type file, that allows the user to select one or more files from the file system.</p><p id="e4ad">This file input should be hidden from the user as it’s not styleable and replaced by a more user-friendly UI.</p><p id="289e">Using the file input in the background, we can get a reference to the file via the <code>change</code> event, which we can then use to build an HTTP request and send the file to the backend.</p><p id="245e">Also, if you have some questions or comments please let me know in the comments below and I will get back to you.</p></article></body>

Why Living a Constrained Life Feels Safer Right Now

I have endless free time, but I’ve set a schedule, and it feels good

Photo by T. Kaiser on Unsplash

This essay was inspired by Kris Gage’s article, “We All Want to See Everyone Else’s Real Quarantine Moments.”

So here’s my quarantine life. But it’s not weird; it’s just kind of obsessive.

Two years ago, I retired and decided to go live as an expat in Thailand. I loved living there for a year and a half, but started getting itchy feet, so in January I gave away all my stuff except for my laptop and camera in my daypack and some clothes and a few art supplies in a rolling suitcase, and hit the road … just in time for the Great Pause.

Mid-March found me in Kuala Lumpur, and when the Malaysian government announced on March 17 that it would close the borders the next day, I fled to Bali on one of the last flights out on March 17, because I already had plans to spend my birthday there, and it felt more like a place I’d be happy staying put for a while. In April, the Indonesian government kindly granted visa extensions to any tourists who couldn’t get home or who wanted to wait out the pandemic here, so I’ve got a semi-safe feeling of some permanence until the world opens up and it’s safe to travel again.

But there’s that underlying anxiety we all have, and it was ruining my digestion when I was surfing the news and worrying about distant family and friends. I lost too much weight last spring and got too skinny. I also was hardly doing anything creative, even with all that free time every day.

Photo by Ivan Diaz on Unsplash

So I set myself a schedule. At first, I was exercising three times a day and conking out at 8:00 p.m., so I cut back to exercising twice a day. Now I have three patterns that I do twice each week: 7:00 a.m. jog & noon aerial yoga; 7:00 a.m. jog & 5:00 p.m. swim; 10:00 a.m. hatha yoga & 5:00 p.m. swim. On days I don’t jog on the beach in the morning, I watch the sunset on the beach. Sundays are without plan, open for whims or art or being a couch potato.

Now I get up every day at dawn.

If it’s a jogging morning, I let myself read news before my run (and that’s the only time I read the news), then I jog for 3 km along the beach, and I feel liberated and sleek when I run. (I’m still thrilled the water is warm, after the cold ocean off California.) On the walk back, I pick up plastic trash as my way of thanking Bali for sheltering me.

If it’s a day for 10:00 hatha yoga, I write poetry before breakfast.

Either way, every day I have breakfast about 8:30, and I read poetry while I eat it. I eat the same thing every day: three pieces of bread with toppings (banana bread with peanut butter, coconut bread with coconut butter, and sesame bread with pineapple jam); a plate of fruit that includes papaya, watermelon and banana; and two cups of black tea with soy milk.

Breakfast at Serenity (photo courtesy of the author, Bridget Cougar)

If it’s a day for 10:00 hatha yoga, after breakfast I answer e-mails from friends for an hour. If it’s a 12:00 aerial yoga day, then after breakfast I write for two hours, either short stories related to my novel or essays for Medium. Yoga makes me feel peaceful, strong and really happy. (I LOVE hanging upside down in aerial yoga — it’s my new favorite relaxation pose.)

Just before lunch, I practice French for a half hour, because my original plan had been to spend this summer in France. Maybe next summer.

At lunch, I read short stories or essays, and almost every day I walk down the street to eat at a little restaurant staffed by two friendly women, partly because I’m not vegan and they make good chicken and fish dishes, and partly because I like to support women. They kindly let me do my first-ever interview with them.

After lunch, I work on my novel for three hours, trying to write 1,500–3,000 words a day. I take a midpoint break to learn and practice a new song every week.

If it wasn’t an aerial yoga day, in the afternoon I swim 1/2 km in a pool with a dolphin yin-yang at the bottom. I feel liberated when I swim; I feel like a dolphin playing in the sea.

Pool with dolphin mosaic at Serenity (photo courtesy of the author, Bridget Cougar)

After swimming or beach sunset, I read novels while eating a giant salad for supper, or brown rice and stir fried vegetables, or vegan nasi goreng, a Balinese specialty. After supper, I either continue reading, or watch a movie.

That’s it. Sounds a little boring, right? But having a repeating schedule seems to calm my inner basket case. The thing is, I’ve given myself permission not to do anything on any day I don’t feel like it, but I don’t let myself skip two times in a row. That way, I do each type of exercise at least 3x/week, and I write something nearly every day (plus do a little music and art on the side).

Balcony at Serenity (photo courtesy of the author, Bridget Cougar)

Having a schedule keeps my heart-rate down, but then blowing it off gives me a lot of fun, random moments. One time, I sat on the balcony and tried to separate the bird calls. I don’t have a Bali bird book, so I sorted them by musical phrases, “oh, twice repeated ascending full-tone; monotone triplets; falling glissando across a fifth; fast repeated descending half-tones, like grace notes.” I don’t know what the birds look like because of all the foliage, but I’m friends with them now.

Another time, I thoroughly enjoyed sorting all the books in the loaner bookshelf by language, and realizing there were several books I couldn’t even figure out what languages they were, but I gained enormous satisfaction by grouping them correctly based on diacritical marks. (Yep, still a nerd.)

When coronavirus first hit, I lost a lot of weight worrying about the world and worrying about family and friends, because I’m half-way around the world from them. Now I stay in touch in a way that’s not frantic, I’m eating and sleeping well (oh! the rain on the bamboo roof!), and I’m making some progress in my writing goals: I’ve published 60 articles on Medium, written several dozen decent poems, and I’m a third of the way through my first novel, which I will finish by year’s end.

Upstairs open-air work space at Serenity (photo courtesy of the author, Bridget Cougar)

I’ve always wanted to be chosen as a guest writer for a month at one of those fancy writers’ retreats. Now, I’ve had my own home-made writer’s retreat for three months and I’m finally starting to think of myself as a real writer.

It’s because I moved to a new place in July, away from the isolated jungle hostel. Now I’m staying at a yoga resort that I never would have been able to afford under normal circumstances — it’s like Club Med for yoga, because it’s got 10 yoga classes per day on offer, as well as on-site massage, a vegan restaurant, the pool, and a small garden, so mostly I just stay here, with outings to the beach, lunch out, or walking my laundry up the street. There are currently only 15 guests, compared to their usual 85, and everything is open air, so it feels pretty safe.

Photo by Ruben Hutabarat on Unsplash

I’m in two of the at-risk groups, so even though Bali opened up for domestic tourism last month, I decided not to go to any tourist places right now, but instead to come back next year to see all the sights. With most tourists coming from Jakarta, where Covid numbers are high, this has proven to be a wise choice, as Covid numbers in Bali have doubled the last two weeks, and there’s talk of going back into lockdown in October.

In the past, I was always keen on hiking or visiting museums or going out for live music, and was always the one organizing adventures for my friends to join in, but staying still feels like an internal adventure. It’s been a long time since I’ve explored my internal landscape thoroughly. I’m not bored and I’m not lonely, because there are lovely people I see every few days for short conversations, and I often have wonderful, long conversations with family and friends.

My life feels sort of monastic, and sort of like adult kindergarten, where I have creative activities but no strict rules. I’m incredibly grateful for the privilege of having Social Security to live on so I can afford to live here without working. That gives me the freedom to explore what it’s like to be a real writer, and be happy and healthy at the same time.

I’m determined to make the best of this weird curveball life has thrown us. After all, there are no guarantees. The women in my family are long-lived, so I might have another 30 years, but I could also be gone in a month, as happened recently to a friend who was diagnosed with cancer near the end of May and passed away four weeks later.

Every day is precious to me. Somehow, by having less and doing less, suddenly everything is opening up. The possibilities seem endless.

Photo by Kevin Noble on Unsplash
Life
Illumination
Essay
Mental Health
Personal Development
Recommended from ReadMedium