Having issues with FileReader and $scope.$watch in AngularJS?

Seeking assistance with a new challenge - working with files in JavaScript is uncharted territory for me. I have a directive with a template that includes an input file and upload button.

<input type="file" ng-file-select ng-model="files">
<button ng-click="onFileSelect()">Upload</button>

In the directive's controller, my code looks like this:

$scope.onFileSelect = function() {
  reader = new FileReader();
  reader.onload = function() {
    $scope.file_contents = this.result;
  };
  reader.readAsArrayBuffer($scope.files[0]);

//debugger;
  $scope.$watch('file_contents',function(file_contents) {
    console.log(file_contents);
    if (angular.isDefined(file_contents)) {
      ... lots of coding to handle file_contents ...
    }
  });
};

After selecting a file and clicking Upload, the console displays undefined for (file_contents) on the first click. It only shows a value after clicking the button a second time.

When I uncomment the debugger, $scope.file_contents has a value when checked.

So, it seems like file_contents takes some time to be set but the $watch doesn't detect it. Could there be a unique reason for this delay? Does $watch not work properly with FileReader objects?

Edit 1:

Following advice provided, here's an updated version of the code:

$scope.onFileSelect = function() {
  reader = new FileReader();
  reader.onload = function() {
    file_contents = this.result;
    upload_file($scope.files[0],file_contents);
  };
   reader.readAsArrayBuffer($scope.files[0]);
};

upload_file = function(file,file_contents) {
  <performing operations related to file_contents>
};

Despite removing all $watches, why do $digest errors still occur? There are no interactions with $scope in upload_file. The error message only states:

Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.3.0-beta.10/$rootScope/inprog?p0=%24digest

What might be causing this issue?

Answer №1

It appears that the watch is not detecting changes because you are updating the scope outside of the angular context. To resolve this, you should manually trigger the digest cycle using scope.$apply(), or another method such as using a $timeout within the onload async function. Additionally, it would be recommended to move the watch outside of the onFileSelect method to prevent adding multiple watches with each upload click.

Consider implementing the following:

$scope.onFileSelect = function() {
  reader = new FileReader();
  reader.onload = function() {
    $scope.file_contents = this.result;
    $scope.$apply(); // <-- Here
  };
  reader.readAsArrayBuffer($scope.files[0]);
};

$scope.$watch('file_contents',function(file_contents) {
    console.log(file_contents);
    if (angular.isDefined(file_contents)) {
      ... code for handling file contents ...
    }
});

In some cases, creating a watch may not be necessary. Instead, encapsulate the file processing logic within a separate method and call this method from the onload event while also triggering a scope.$apply(). The reason why your watch only executes once initially is because it runs upon setup to initiate dirty checking, but at that point in time the asynchronous onload event has not yet assigned any value to the scope. Even if a value is eventually set, the digest cycle remains unaware of it.

Answer №2

There is a more elegant solution for this problem. Here's how you can define a directive:

//directive.js
(function() {
    angular.module('<yourApp>').
    directive('fileread', ['$parse', function($parse){
        // Executes during compile time
        return {
            restrict: 'A',
            link: function($scope, iElm, iAttrs, controller) {

                var model = $parse(iAttrs.fileread);
                var modelSetter = model.assign;

                iElm.bind('change', function(){
                    $scope.$apply(function(){
                        modelSetter($scope, iElm[0].files[0]);

                    });
                });     
            }
        };
    }]);
})();

Next, in your controller (or another directive), create a model like this:

//controller.js
...
$scope.photo.data = null;
...

$scope.photo.upload = function() {
    fd = new FormData();
    fd.append('<name_of_form_field_the_photo_should_be_sent_as>', $scope.photo.data);

$http.post('<url>', fd, {
     transformRequest: angular.identity,
     headers: {'Content-Type': undefined}
})

Finally, add the following code to your HTML file:

<input type='file' fileread="photo.data">
<button ng-click="photo.upload"> </button>

I hope this explanation is helpful.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to click on the icon when modifying Mui Text Field

Utilizing the MUI Text Field component, I have successfully added a select prop to transform it into a dropdown list with values and an icon. However, I encountered an issue while attempting to change the default dropdown icon to a custom one from Figma. D ...

Encountering difficulties in storing array data into MongoDB collection

I am facing an issue with connecting to two different MongoDB instances using different URLs. One URL points to a remote connection string while the other one is for a local MongoDB instance. Initially, I establish a connection to MongoDB using MongoClient ...

Update the browser URL dynamically without redirecting the page, by utilizing JavaScript after an AJAX call receives a

I am currently endeavoring to find a solution to replace the URL on my page without triggering a reload. My approach involves utilizing an infinite scroll JavaScript plugin. Upon receiving a response from an AJAX call, I intend to update the URL with the n ...

Switch up the background picture by chance when hovering over it with the mouse

Would you like to change the background image when hovering over an album, similar to Facebook's functionality? When hovering over an album, display a preview of 3-4 images randomly selected. How can this be achieved using jQuery? I have tried impleme ...

How can we leverage the nullish coalescing operator (`??`) when destructuring object properties?

When working with ReactJS, I often find myself using a common pattern of destructuring props: export default function Example({ ExampleProps }) { const { content, title, date, featuredImage, author, tags, } = ExampleProps || {}; ...

Error message: JavaScript is unable to save data to an array retrieved from Ajax, resulting in

I am facing an issue with retrieving continuous data from the database using AJAX and storing it in a JavaScript variable. Despite my efforts, I am unable to populate an array with the retrieved values as they always end up being undefined. Below are the s ...

Angular 2: Challenges with Utilizing Set - Limited Browser Support and Iteration Issues

I am currently developing an Angular 2 Web Application. My goal is to add users to a game by utilizing checkboxes on a screen. When a checkbox is selected, I plan to add the user to a set. mySet = new Set(); this.mySet.add(e); Once a user clicks the "Ad ...

PhpStorm 2019.2 introduces Material UI components that have optional props instead of being mandatory

My PhpStorm 2019.2 keeps showing me a notification that the Button component from Material UI needs to have an added href prop because it is required. However, when I refer to the Material UI API, I see something different. Take a look at this screenshot: ...

What are the implications of implementing Ajax without relying on jQuery?

At a stage where I require Ajax on my webpage, but only for a specific section - checking if a username is present in the database. As detailed here, Ajax can be implemented using just JavaScript. What are the advantages and disadvantages of opting for t ...

`<picture> contains the main image``

Is it possible to access the currently displayed source of a <picture> element in JavaScript without relying on viewport width or pixel density? Looking for a method similar to <select>:selected If anyone has insights on this, please share! ...

What steps should be taken to safeguard an Angular 1.2 application from XSS vulnerabilities?

Recently, I stumbled upon an interesting article at this link: In my work on an enterprise application, we have been using Angular 1.2 but are now looking to upgrade to newer versions like 1.3 and beyond. I am curious to hear from others who have made sp ...

Working with JavaScript and making AJAX calls to interact with PHP backend

Having trouble with this code, it's not working as expected. I want to pass the value when I select an option from the dropdown menu, process the data using onChange event and display the value in the tag. <label for="headmark" class="lbl-ui selec ...

Is there a way to conditionally redirect to a specific page using NextAuth?

My website has 2 points of user login: one is through my app and the other is via a link on a third-party site. If a user comes from the third-party site, they should be redirected back to it. The only method I can come up with to distinguish if a user is ...

Converting Roman Numerals into Decimal Numbers using JavaScript Algorithm

I am eager to develop a Javascript Algorithm that can convert Roman numerals to Arabic using the provided methods below. Array.prototype.splice() Array.prototype.indexOf() Array.prototype.join() I have managed to find an algorithm that accomplishes this ...

Guide on how to navigate to a different webpage once the ajax request is complete

When making a JQuery ajax call to invoke a void method, I have encountered the need to redirect the user to the home page upon successful login. Below is an example of how this can be achieved: var options = { url: "/Account/Login", data: formvalu ...

Issue [ERR_MODULE_NOT_FOUND]: The module 'buildapp' could not be located within buildserver.js

I am currently working on a node project using typescript. The project's structure is organized in the following way: --src |-- server.ts |-- app.ts --build |-- server.js |-- app.js In server.ts: import { app } from &q ...

Deciphering JavaScript script within a Node.js module

// =============================================================================== // Auth // =============================================================================== const admin = require('firebase-admin'); //what happens if i move th ...

Using ng-init within ng-repeat will only display the information of the final item

I'm looking to iterate through items in the following format: <section class="col-sm-4" data-ng-controller="PredictionsController" data-ng-init="findMyPredictions()"> <div class="games"> <div class="game-row" ng-repeat="pre ...

Alternative method to jQuery's "find" selector

$('.wrapper a').filter('a'); //returns all anchors I am trying to find a way to select all anchor elements using a specific selector. The issue is that the find method only looks at descendants, so I need an alternative solution. Any s ...

Transform array of objects into a two-dimensional array

Is there a way to transform the following group of objects into a 2D array? var tags= [ {id: 0, name: "tag1", project: "p1", bu: "test"}, {id: 1, name: "tag2", project: "p1", bu: "test"}, {i ...