Наличие общей кодовой базы как для серверного, так и для браузерного кода приложения Angular помогает поддерживать проект. Вы можете сделать это с помощью Angular Universal и Node.js, используя концепцию рендеринга на стороне сервера (SSR). Вы даже можете использовать SSR для безопасной передачи данных, включая файлы, между сервером приложений (Node.js) и работающим на нем приложением Angular.

Этот пост покажет вам, как создать приложение для загрузки, хранения, управления и загрузки файлов с сервера Node.js с использованием единой кодовой базы. Когда вы закончите этот проект, вы сможете:

  • Создайте угловое приложение
  • Настройте рендеринг на стороне сервера с помощью Angular Universal и Node.js.
  • Реализовать операции передачи файлов
  • Создайте динамический список файлов в памяти
  • Передавайте данные о содержимом хранилища между серверным приложением и JavaScript, используемым Angular для SSR.

Для выполнения задач из этого поста вам потребуется следующее:

Эти инструменты упоминаются в инструкции, но не являются обязательными:

Для наиболее эффективного обучения из этого поста у вас должно быть следующее:

  • Знание TypeScript и фреймворка Angular
  • Знакомство с наблюдаемыми объектами Angular и внедрением зависимостей
  • Некоторое знакомство с Node.js

Вы можете узнать больше о рендеринге на стороне сервера (SSR) в предыдущем посте.

На GitHub доступен сопутствующий репозиторий для этого поста.

Создайте проект, компоненты и служебные файлы

На этом этапе вы реализуете первый «черновик» приложения. Вы создадите форму, которая будет использоваться для загрузки файлов на сервер, и вы создадите в памяти список загруженных файлов. Как всегда, вам нужно начать с инициализации проекта.

Перейдите в каталог, в котором вы хотите создать проект, и введите следующие инструкции командной строки, чтобы инициализировать проект и добавить Angular Forms:

ng new angular-and-nodejs-data --style css --routing false
cd angular-and-nodejs-data/
npm install @angular/forms

Выполните следующую инструкцию командной строки, чтобы создать класс FileService:

ng g s file --skipTests

Выполните следующие команды, чтобы создать классы FileUploaderComponent и FileListComponent:

ng g c fileUploader --skipTests
ng g c fileList --skipTests

Обязательно обратите внимание на регистр имен компонентов.

Создайте файловую службу

Первоначальная реализация FileService будет временной, что позволит пользователям добавлять и удалять файлы из списка, но на самом деле файлы никуда не перемещаются. Он соединяет компоненты списка файлов и загрузчика файлов и поддерживает список файлов, но, как вы можете видеть из приведенного ниже кода, у него нет функций загрузки или скачивания.

Замените содержимое файла src/app/file.service.ts следующим кодом TypeScript:

Создайте компонент загрузки файлов

Пользовательский интерфейс для компонента загрузки файлов будет основан на форме, поэтому необходимо импортировать ReactiveFormsModule в основной Angular AppModule.

Добавьте следующий оператор импорта в файл src/app/app.module.ts:

Измените раздел imports: файла src/app/app.module.ts, включив в него ReactiveFormsModule:

Реализация FileUploaderComponent начинается с шаблона, который пользователь может использовать для выбора файла для загрузки.

Замените содержимое файла src/app/file-uploader/file-uploader.component.html следующей разметкой HTML:

Реализовать логику загрузки файлов в классе FileUploaderComponent.

Замените содержимое файла src/app/file-uploader/file-uploader.component.ts следующим кодом TypeScript:

Обратите внимание, что метод onFileChange() связан с действием (change) элемента input type="file"формы HTML. Также обратите внимание, что метод patchValue объекта formGroup используется для предоставления Angular содержимого reader, чтобы он мог продолжить проверку формы.

При отправке формы срабатывает событие onSubmit() и загружает указанный файл в fileService, где обновляется список файлов.

Создайте компонент списка файлов

Класс FileListComponent реализует методы для получения списка файлов из каталога FileService. Он также предоставляет операции загрузки и удаления, которые можно выполнять с перечисленными файлами.

Замените содержимое файла src/app/file-list/file-list.component.ts следующим кодом TypeScript:

Данные в наблюдаемом объекте fileList$ будут отображаться в списке, который также включает интерактивные команды для загрузки и удаления каждого файла.

Замените содержимое файла src/app/file-list/file-list.component.html следующей HTML-разметкой:

Цикл *ngFor перебирает список файлов из наблюдаемого объекта fileList$, который генерирует массив строк. Для каждой записи будет создан элемент <li>, содержащий <span> элементов, связанных с download() и remove() операциями.

CSS можно использовать, чтобы указать, что команды, содержащиеся в промежутках, кликабельны.

Вставьте следующий код CSS в файл src/app/file-list/file-list.component.css:

Класс FileListComponent и класс FileUploaderComponent должны быть включены в основной компонент приложения AppComponent для отображения в браузере.

Замените содержимое src/app/app.component.html следующей HTML-разметкой:

Протестируйте базовое приложение

Выполните следующую команду Angular CLI в angular-and-nodejs-data, чтобы собрать и запустить приложение:

ng serve

Откройте вкладку браузера и перейдите по адресу http://localhost:4200. Вы должны увидеть пустой список файлов и форму, готовую для ввода пользователем, как показано ниже:

Выберите подходящий файл и нажмите кнопку загрузить. Имя выбранного файла должно появиться в списке файлов, как показано в примере ниже:

Попробуйте нажать скачать. Вы увидите, что ничего не происходит.

Попробуйте нажать удалить. Имя файла должно быть удалено из списка.

На этом этапе приложение позволяет пользователям выбирать файлы и «загружать» их, но они «загружаются» только в пределах списка файлов в памяти на клиентской машине. Файлы также можно удалять из списка в памяти.

Это не очень полезно, но этого достаточно, чтобы показать вам, как работает пользовательский интерфейс и список файлов.

Если вы хотите выполнить этот шаг, используя код из репозитория GitHub, выполните следующие команды в каталоге, где вы хотите создать каталог проекта:

git clone https://github.com/maciejtreder/angular-and-nodejs-data.git
cd angular-and-nodejs-data
git checkout step1
npm install

Сохраняйте файлы на сервере

Следующим шагом является передача файлов на сервер и сохранение их на диске. Вы сделаете это, добавив дополнительные функции в класс FileService.

Сначала вам нужно добавить сервер Node.js в проект и создать папку, предназначенную для хранения пользовательских загрузок.

В папке angular-and-nodejs-data выполните следующие инструкции в командной строке:

ng add @ng-toolkit/universal
mkdir user_upload

Установка проекта @ng-toolkit/universal добавила в проект поддержку Angular Universal всего одной командой. Он также включает в себя серверную часть Node.js и рендеринг на стороне сервера (SSR). Вы можете узнать больше о SSR в Angular и его значении для поисковой оптимизации (SEO) в этом посте.

Реализовать конечные точки RESTful API в коде сервера.

Конечные точки API будут обеспечивать обработку файлов на сервере, поэтому необходимо внести несколько изменений в файл server.ts. Они включают в себя добавление поддержки модулей fs (для управления файловой системой) и указание каталога для хранения данных.

Откройте файл server.ts и найдите следующее объявление константы:

Добавьте следующие объявления констант сразу после строки выше:

Реализуйте конечную точку /upload, которая будет использоваться интерфейсным приложением.

В файле server.ts найдите следующую строку кода:

Добавьте следующий код TypeScript в файл server.ts сразу после строки выше:

Поскольку мы собираемся загружать данные в кодировке Base64 в теле запроса, нам нужно настроить максимальный размер тела.

В верхней части файла server.ts найдите следующую строку кода:

Замените строку выше следующим кодом TypeScript:

Реализуйте конечную точку /delete.

Добавьте следующий код TypeScript в конец файла server.ts:

Реализуйте конечную точку GET /files.

Добавьте следующую строку кода TypeScript в конец файла server.ts:

Использование метода express.static информирует Node.js о том, что каждый запрос GET, отправленный на конечную точку /files/**, должен рассматриваться как «статический» хостинг, обслуживаемый из каталога userFiles, user_upload.

Эти конечные точки RESTful API на сервере теперь можно использовать во внешнем приложении Angular.

Замените содержимое файла src/app/file.service.ts следующим кодом TypeScript:

Приведенный выше код полностью реализует файловые операции по загрузке, скачиванию и удалению файлов. Он также добавляет метод isLoading(), который возвращает наблюдаемое логическое значение, указывающее, выполняется ли действие загрузки данных или нет. Наблюдаемый объект можно использовать в классе AppComponent для информирования пользователя о состоянии этого действия.

Замените содержимое src/app/app.component.ts следующим кодом TypeScript:

Когда значение из Observable указывает на то, что загрузка выполняется, приложение отобразит следующий GIF-файл загрузчика (который включен в репозиторий GitHub):

Добавьте следующую HTML-разметку в конец файла src/app/app.component.html:

Тестовая загрузка и скачивание файлов

Пересоберите приложение и проверьте, правильно ли работают функции загрузки и выгрузки.

Выполните следующие инструкции командной строки npm в каталоге angular-and-nodejs-data:

npm run build:prod
npm run server

Откройте вкладку браузера и перейдите по адресу http://localhost:8080. Выберите файл и загрузите его.

Имя файла должно отображаться в списке файлов в разделе Ваши файлы, а также присутствовать в каталоге user_upload.

Вы также должны иметь возможность загрузить файл, нажав скачать. Обратите внимание, что ваш браузер может открыть файл в новой вкладке или окне вместо его загрузки, в зависимости от типа файла и настроек вашего браузера. На следующем рисунке показана полная последовательность действий:

Нажмите удалить и убедитесь, что имя файла удалено из списка в разделе Ваши файлы, а сам файл удален из каталога user_upload.

Если вы хотите выполнить этот шаг, используя код из репозитория GitHub, выполните следующие команды в каталоге, где вы хотите создать каталог проекта:

git clone https://github.com/maciejtreder/angular-and-nodejs-data.git
cd angular-and-nodejs-data
git checkout step2
npm install

Получить и отобразить список файлов

Вы почти закончили. Приложение поддерживает загрузку нового файла в хранилище, его извлечение и удаление. Проблема возникает, когда пользователь возвращается к приложению.

Вы можете имитировать это поведение. Если в вашем браузере все еще открыт http://localhost:8080, нажмите кнопку Обновить. Список файлов пропал! Но они все еще находятся на сервере в каталоге user_upload.

Следующим шагом в этом проекте является реализация адаптивного списка файлов для каталога user_upload. Список, отображаемый в окне браузера, будет динамически обновляться, отражая содержимое каталога, и он будет отражать список файлов в каталоге при запуске приложения.

Это можно сделать, добавив на наш сервер еще одну конечную точку REST, которая возвращает список файлов. Это было бы хорошим решением, когда код внутреннего сервера выполняется на другой машине, чем код, выполняющий рендеринг на стороне сервера.

Но пока внутренний код выполняется на том же сервере, что и код, обслуживающий внешний интерфейс, нет смысла выполнять код Angular Universal (рендеринг на стороне сервера) и выполнять вызовы REST на той же машине. . Вместо этого вы можете использовать модуль fs для вывода списка всех файлов по заданному пути.

В предыдущем посте Создавайте более быстрые веб-приложения на JavaScript с помощью Angular Universal, службы TransferState и сторожевого таймера API показано, как реализовать методы isPlatformServer() и isPlatformBrowser(), чтобы определить, на какой платформе выполняется код. Этот проект также использует эти функции.

В предыдущем посте также показано, как обмениваться данными между сервером и клиентом с помощью объекта TransferState путем внедрения его в класс AuthService. Эти методы помогают сделать функциональность модуля fs доступной для клиентского кода, даже если сам модуль не может быть загружен в браузере. Этот проект также использует эту технику.

На следующей диаграмме показана последовательность событий:

  1. Пользователь выполняет GET/запрос к серверу.
  2. Node.js получает запрос.
  3. Node.js запускает Angular и отображает представление на сервере.
  4. Данные хранятся в реестре TransferState.
  5. Представление, отображаемое на стороне сервера, включая JavaScript на стороне браузера и реестр TransferState, передается в браузер, и приложение Angular повторно отображается в браузере.

Здесь есть еще одна вещь, которую следует учитывать. Вы знаете, что браузеры не позволят коду JavaScript манипулировать файловой системой из соображений безопасности. Система связывания модулей JavaScript webpack, используемая Angular CLI, не позволит вам использовать модуль fs для кода, созданного для браузера.

Поскольку этот проект имеет единую кодовую базу для обеих платформ, webpack интерпретирует его как созданный для браузера, что отчасти так и есть. Но ему нужен fs для чтения содержимого каталога и управления файлами, поэтому ему нужно решение, которое позволит обойти запрет на запуск fs в браузере.

На этом этапе вы можете подумать, что вам нужно создать отдельную кодовую базу только для кода на стороне сервера, что даст вам возможность поддерживать два проекта. Но есть метод, который позволяет вам поддерживать единую кодовую базу и по-прежнему управлять файлами из Angular, выполняемого на сервере.

Angular имеет возможность вводить значения и ссылки за пределы «песочницы Angular». Вы можете передать ссылку на функцию Node.js в угловой код и выполнить ее оттуда.

Взгляните на следующую диаграмму:

  1. Браузер отправляет GET/запрос на сервер.
  2. Сервер запускает Angular для визуализации представления и вызывает constructor() из FileService.
  3. Конструктор использует метод isPlatformServer(), чтобы определить, выполняется ли он в Node.js на сервере. Если это так, конструктор вызывает метод listFiles(), внедренный в FileService, в качестве обратного вызова. Метод listFiles() предоставляет текущий список содержимого каталога user_upload, который затем сохраняется в локальной переменной fileList.
  4. Список файлов хранится в объекте TransferState.
  5. Визуализированное представление отправляется обратно в браузер, и браузер отображает представление и загружает Angular на клиенте.
  6. Клиент снова вызывает constructor() и использует isPlatformServer(), чтобы определить, что код выполняется на клиенте.
  7. constructor() извлекает список файлов из объекта TransferState.

Реализовать манипуляции с файлами на стороне сервера

Имея конечные точки API, вы можете завершить реализацию операций манипулирования файлами с клиента.

Откройте файл server.ts и найдите следующую строку кода:

Вставьте следующий код TypeScript под строкой выше:

Найдите следующий код в файле server.ts:

Измените код, показанный выше, чтобы включить дополнительную строку, показанную ниже:

Теперь пришло время использовать эту серверную функцию в приложении Angular.

Откройте файл src/app/file.service.ts и замените существующие директивы import следующим кодом TypeScript:

Чтобы список файлов, отображаемый на странице, мог включать все файлы в каталоге, наблюдаемый тип для fileList$ должен быть изменен на ReplaySubject, наблюдаемый объект, который делает доступным для своих подписчиков список значений, ранее отправленных в Это. Это позволяет наблюдателю получить список файлов, добавленных к наблюдаемому, до того, как наблюдатель подпишется на наблюдаемое. Согласно документации RxJS: ReplaySubject отправляет любому наблюдателю все элементы, которые были отправлены исходным Observable(s), независимо от того, когда наблюдатель подписывается.

Найдите следующую строку кода в файле src/app/file.service.ts:

Замените строку выше следующим кодом TypeScript:

Измените конструктор FileService, чтобы предоставить классу объект PLATFORM_ID (клиент или сервер) и объект TransferState. Если код выполняется на сервере, логика конструктора считывает содержимое каталога user_upload (используя внедренную ссылку на метод listFiles) и добавляет список файлов в объект TransferState. Если код выполняется на клиенте, список файлов в transferState копируется в закрытую переменную-член класса fileList.

Найдите приведенную ниже строку кода в файле src/app/file.service.ts:

Замените строку выше следующим кодом TypeScript:

Протестируйте полное приложение

Пересоберите приложение, выполнив следующую инструкцию в командной строке в каталоге angular-and-nodejs-data:

npm run build:prod
npm run server

Откройте окно браузера и перейдите по адресу http://localhost:8080. Все файлы в каталоге user_upload должны быть перечислены в разделе Ваши файлы, как показано ниже, и вы должны иметь возможность загружать, скачивать и удалять файлы с сервера.

Если вы хотите выполнить этот шаг, используя код из репозитория GitHub, выполните следующие команды в каталоге, где вы хотите создать каталог проекта:

git clone https://github.com/maciejtreder/angular-and-nodejs-data.git
cd angular-and-nodejs-data
git checkout step3
npm install

Как насчет безопасности?

Имеет ли Angular, работающий на клиенте, доступ к внешним данным, таким как файловая система сервера? Да. И у вас одинаковая кодовая база для сервера и браузера? Да, вы делаете.

Вы спросите: А как же «обход пути? Могут ли все в Интернете видеть данные, которые я храню в каталоге user_upload?» Этот вопрос здесь более чем уместен!

В нашем приложении мы передаем ссылку на метод, а не на сам метод. Вот почему предоставление данных из Node.js клиентскому приложению Angular — отличный способ обмена конфиденциальными данными.

Изучите выходные данные сборки и взгляните на конструктор FileService в файле dist/main.hashcode.js:

Как видите, JavaScript ожидает, что Node.js передаст ссылку на функцию, переданную в виде переменной t. Никакая информация о структуре каталогов на нашем сервере не может быть получена из JavaScript в выходном пакете.

Сводка по передаче данных из Node.js в Angular

В этом проекте вы узнали, как передавать файлы между клиентским браузером и сервером Node.js в единой кодовой базе проекта. Пользовательский интерфейс клиента может выбирать файлы для загрузки, загружать их на сервер, где они хранятся, составлять список файлов, хранящихся на сервере, удалять файлы, хранящиеся на сервере, и загружать файлы с сервера. Вы видели, как сделать все это в единой кодовой базе Angular, используя Angular Universal и Node.js. Вы также видели, что это безопасный метод передачи данных между клиентом и сервером, включая файлы, хранящиеся на сервере.

Дополнительные ресурсы

Документация Angular Universal, включая учебные пособия и справку по CLI.

Внедрение зависимостей в действии в Angular

Документация класса TransferState, часть @angular/platform-browser

Объекты ReplaySubject объясняются с другими вариантами объекта Subject

Документация RxJS ReplaySubject, в разработке

Меня зовут Мацей Тредер, свяжитесь со мной через [email protected], https://www.maciejtreder.com или @maciejtreder на GitHub, Twitter и LinkedIn.

Это сообщение изначально опубликовано в Блоге Twilio.