- Angular2 — директива ngFor не отображает удаленный элемент
- 5 ответов
- Обход подводных камней Angular и экономия времени
- №1. Пользовательская директива, которую вы применили, не работает
- №2. ViewChild возвращает undefined
- ▍Вариант решения проблемы №1
- ▍Вариант решения проблемы №2
- №3. Выполнение кода при обновлении списка, сгенерированного с помощью *ngFor (после того, как элементы появились в DOM)
- ▍Шаг №1
- ▍Шаг №2
- ▍Шаг №3
- №4. Проблемы с ActivatedRoute.queryParam, возникающие в том случае, когда запросы можно выполнять без параметров
- ▍Шаг №1
- ▍Шаг №2
- ▍Шаг №3
- №5. Медленная работа страниц
- Итоги
- NgFor doesn’t update data with Pipe in Angular2
- 10 Answers 10
- Pipe Change Detection
- Stateless/pure Pipes
- Stateful Pipes
- Component Change Detection
Angular2 — директива ngFor не отображает удаленный элемент
У меня есть контейнер элемента и шаблон элемента, где дочерние элементы должны быть добавлены и удалены из контейнера. Добавление в порядке, а удаление ничего не делает. Кажется, что директива angular2 * ngFor не работает, когда какой-либо из дочерних элементов удален.
Вот как выглядит DOM после добавления и удаления двух новых элементов:
Проблема в том, что часть li не удаляется. Любая идея?
(Я тестировал его с angular 2.0.0-beta.3 и 2.)
5 ответов
Фактически, Angular2 обнаруживает изменения только при изменении экземпляра. Я имею в виду, что экземпляр массива не изменяется внутри элементов.
Вы можете использовать это (см. Второй вызов slice ):
Эти ответы также могут вам помочь:
Проблема, вероятно, связана с неправильной настройкой прямых ссылок (т.е. вам не следует использовать класс до его объявления). Чтобы решить эту проблему, вы можете использовать общий сервис:
Используйте общий сервис в конструкторе / деструкторе Item :
А также в вашем ItemContainer :
Вам нужно использовать splice() , а не slice() . Здесь нет проблем с обнаружением изменений Angular.
NgFor будет перебирать элементы вашего массива и замечательно определять, когда что-то добавляется или удаляется.
Также вы можете удалить это:
Кроме того, в ваших данных есть циклические ссылки. Измените свой код на следующий
И обратите внимание на ошибку в консоли:
ИСКЛЮЧЕНИЕ: TypeError: преобразование круговой структуры в JSON в [Any Item <
> в ItemContainer
У меня такая же проблема с вами, даже если я использовал splice или detectChange, он все еще не работал @McLac предоставил метод преобразования массива, подобный этому
Спас мою жизнь. Работает отлично. У меня такой код:
Да, это действительно обнаружение изменений. Это то, что сработало для меня:
Источник
Обход подводных камней Angular и экономия времени
С помощью Angular можно сделать всё что угодно. Или почти всё. Но иногда это коварное «почти» приводит к тому, что разработчик губит время, создавая обходные решения, или пытаясь понять, почему что-то происходит, или почему что-то не работает так, как ожидается.
Автор статьи, перевод которой мы сегодня публикуем, говорит, что хочет поделиться советами, которые помогут Angular-разработчикам сэкономить немного времени. Он собирается рассказать о подводных камнях Angular, с которыми ему (и не только ему) довелось встретиться.
№1. Пользовательская директива, которую вы применили, не работает
Итак, вы обнаружили симпатичную директиву Angular сторонней разработки и решили использовать её со стандартными элементами в шаблоне Angular. Замечательно! Попробуем это сделать:
Вы запускаете приложение… И ничего не происходит. Вы, как любой нормальный опытный программист, заглядываете в консоль инструментов разработчика Chrome. И ничего там не видите. Директива не работает и Angular хранит молчание.
Потом какая-нибудь светлая голова из вашей команды решает поместить директиву в квадратные скобки.
После этого, потеряв немного времени, мы видим в консоли следующее.
Вот в чём дело: мы просто забыли импортировать модуль с директивой
Сейчас причина проблемы совершенно очевидна: мы просто забыли импортировать модуль директивы в модуль приложения Angular.
Отсюда выводим важное правило: никогда не используйте директивы без квадратных скобок.
Поэкспериментировать с директивами можно здесь.
№2. ViewChild возвращает undefined
Предположим, вы создали ссылку на элемент для ввода текста, описанный в шаблоне Angular.
Вы собираетесь, с помощью функции RxJS fromEvent, создать поток, в который будет попадать то, что вводится в поле. Для этого вам понадобится ссылка на поле ввода, которую можно получить с помощью декоратора Angular ViewChild :
Здесь мы, с помощью функции RxJS fromEvent , создаём поток, в который будут попадать данные, вводимые в поле.
Испытаем этот код.
Собственно говоря, здесь применимо следующее правило: если ViewChild возвращает undefined — поищите в шаблоне *ngIf .
Вот он — виновник проблемы.
Кроме того, проверьте шаблон на наличие в нём других структурных директив или ng-template выше проблемного элемента.
Рассмотрим возможные варианты решения этой проблемы.
▍Вариант решения проблемы №1
Можно просто скрыть элемент шаблона в том случае, если он вам не нужен. В этом случае элемент всегда будет продолжать существовать и ViewChild сможет вернуть ссылку на него в хуке ngAfterViewInit .
▍Вариант решения проблемы №2
Ещё один способ решения этой проблемы заключается в использовании сеттеров.
Тут, как только Angular назначает свойству inputTag определённое значение, мы создаём поток из данных, введённых в поле ввода.
Вот пара полезных ресурсов, имеющих отношение к этой проблеме:
- Здесь можно почитать о том, что результаты работы ViewChild в Angular 8 могут быть статическими и динамическими.
- Если вы испытываете сложности при работе с RxJs — взгляните на этот видеокурс.
№3. Выполнение кода при обновлении списка, сгенерированного с помощью *ngFor (после того, как элементы появились в DOM)
Предположим, у вас имеется какая-нибудь интересная пользовательская директива для организации прокручиваемых списков. Вы собираетесь применить её к списку, который создан с помощью директивы Angular *ngFor .
Обычно в подобных случаях при обновлении списка нужно вызвать нечто вроде scrollDirective.update для настройки поведения скроллинга с учётом изменений, произошедших в списке.
Может показаться, что это можно сделать с помощью хука ngOnChanges :
Правда, тут мы встречаемся с проблемой. Хук вызывается до вывода обновлённого списка браузером. В результате пересчёт параметров директивы для организации прокрутки списка выполняется неправильно.
Как выполнить вызов сразу после того, как *ngFor завершит работу?
Сделать это можно, выполнив следующие 3 простых шага:
▍Шаг №1
Поместим ссылки на элементы туда, где применяется *ngFor ( #listItems ).
▍Шаг №2
Получим список этих элементов с помощью декоратора Angular ViewChildren . Он возвращает сущность типа QueryList .
▍Шаг №3
Класс QueryList имеет свойство changes, предназначенное только для чтения, которое выдаёт события каждый раз, когда меняется список.
Теперь проблема решена. Здесь можно поэкспериментировать с соответствующим примером.
№4. Проблемы с ActivatedRoute.queryParam, возникающие в том случае, когда запросы можно выполнять без параметров
Понять суть этой проблемы нам поможет следующий код.
К некоторым фрагментам этого кода сделаны комментарии вида Фрагмент #x . Рассмотрим их:
- В главном модуле приложения мы определили маршруты и добавили туда RouterModule . Маршруты настроены так, что если в URL не предоставлен маршрут, мы перенаправляем пользователя на страницу /home .
- В качестве компонента для загрузки мы указываем в главном модуле AppComponent .
- AppComponent использует для вывода соответствующих компонентов маршрута.
- Теперь — самое важное. Нам нужно получить queryParams для маршрута из URL
Предположим, что нам достался такой URL:
В таком случае queryParams будет выглядеть так:
Посмотрим на работу всего этого в браузере.
Тестирование приложения, в котором реализована система маршрутизации
Тут у вас может появиться вопрос о сути проблемы. Параметры мы получили, всё работает как ожидается…
Присмотритесь к приведённой выше копии экрана браузера, и к тому, что выводится в консоль. Тут можно заметить, что объект queryParams выдаётся дважды. Первый объект оказывается пустым, он выдаётся в ходе процесса инициализации маршрутизатора Angular. Только после этого мы получаем объект, в котором содержатся параметры запроса (в нашем случае —
Проблема заключается в том, что если в URL не будет никаких параметров запроса, то маршрутизатор не выдаст ничего. То есть — после выдачи первого пустого объекта второй объект, тоже пустой, который мог бы указывать на отсутствие параметров, выдан не будет.
Второй объект при выполнении запроса без параметров не выдаётся
В результате оказывается, что если код ожидает второго объекта, из которого он может получить данные запроса, то он не будет запущен в том случае, если в URL не было параметров запроса.
Как решить эту проблему? Здесь нам может помочь RxJs. Мы создадим на основе ActivatedRoute.queryParams два наблюдаемых объекта. Как обычно — рассмотрим пошаговое решение проблемы.
▍Шаг №1
Первый наблюдаемый объект, paramsInUrl$ , будет выдавать данные в том случае, если значение queryParams не является пустым:
▍Шаг №2
Второй наблюдаемый объект, noParamsInUrl$ , будет выдавать пустое значение только в том случае, если в URL не было обнаружено параметров запроса:
▍Шаг №3
Теперь скомбинируем наблюдаемые объекты с помощью функции RxJS merge:
Теперь наблюдаемый объект param$ выдаёт значение лишь один раз — независимо от того, содержится ли что-нибудь в queryParams (выдаётся объект с параметрами запроса) или нет (выдаётся пустой объект).
Поэкспериментировать с этим кодом можно здесь.
№5. Медленная работа страниц
Предположим, у вас имеется компонент, который выводит некие отформатированные данные:
Этот компонент решает две задачи:
- Он выводит массив элементов (предполагается, что эта операция выполняется однократно). Кроме того, он форматирует то, что выводится на экран, вызывая метод formatItem .
- Он выводит координаты мыши (это значение, очевидно, будет обновляться очень часто).
Вы не ожидаете того, что у этого компонента будут какие-то проблемы с производительностью. Поэтому запускаете тест производительности только для того, чтобы соблюсти все формальности. Однако в ходе этого теста проявляются некие странности.
Много вызовов formatItem и довольно большая нагрузка на процессор
В чём же дело? А дело в том, что когда Angular перерисовывает шаблон, он вызывает и все функции из шаблона (в нашем случае — функцию formatItem ). В результате, если в функциях шаблона выполняются какие-нибудь тяжёлые вычисления, это создаёт нагрузку на процессор и влияет на то, как пользователи будут воспринимать соответствующую страницу.
Как это исправить? Достаточно выполнить вычисления, выполняемые в formatItem , заранее, и вывести на страницу уже готовые данные.
Теперь тест производительности выглядит гораздо приличнее.
Всего 6 вызовов formatItem и низкая нагрузка на процессор
Теперь приложение работает гораздо лучше. Но у применённого здесь решения есть некоторые особенности, не всегда приятные:
- Так как мы выводим координаты мыши в шаблоне — возникновение события mousemove всё ещё приводит к запуску проверки изменений. Но, так как нам нужны координаты мыши, избавиться от этого мы не можем.
- Если же в обработчике события mousemove должны лишь выполняться некие вычисления (которые не влияют на то, что выводится на странице), тогда, чтобы ускорить приложение, можно поступить следующим образом:
- Можно, внутри функции-обработчика события, использовать NgZone.runOutsideOfAngular . Это позволяет предотвратить запуск проверки изменений при возникновении события mousemove (это повлияет исключительно на данный обработчик).
- Можно предотвратить zone.js-патч для некоторых событий, использовав следующую строку кода в polyfills.ts. Это подействует на всё Angular-приложение.
Если вас интересуют вопросы повышения производительности Angular-приложений — вот, вот, вот и вот — полезные материалы об этом.
Итоги
Сейчас, когда вы прочли эту статью, в вашем арсенале Angular-разработчика должно появиться 5 новых инструментов, с помощью которых вы сможете решать некоторые распространённые проблемы. Надеемся, советы, которые вы здесь нашли, помогут вам сэкономить немного времени.
Уважаемые читатели! Знаете ли вы о чём-то таком, что, при разработке Angular-приложений, помогает экономить время?
Источник
NgFor doesn’t update data with Pipe in Angular2
In this scenario, I’m displaying a list of students (array) to the view with ngFor :
It’s wonderful that it updates whenever I add other student to the list.
However, when I give it a pipe to filter by the student name,
It does not update the list until I type something in the filtering student name field.
Here’s a link to plnkr.
Hello_world.html
sort_by_name_pipe.ts
10 Answers 10
To fully understand the problem and possible solutions, we need to discuss Angular change detection — for pipes and components.
Pipe Change Detection
Stateless/pure Pipes
By default, pipes are stateless/pure. Stateless/pure pipes simply transform input data into output data. They don’t remember anything, so they don’t have any properties – just a transform() method. Angular can therefore optimize treatment of stateless/pure pipes: if their inputs don’t change, the pipes don’t need to be executed during a change detection cycle. For a pipe such as <
For this question, «#student of students | sortByName:queryElem.value» , students and queryElem.value are inputs, and pipe sortByName is stateless/pure. students is an array (reference).
- When a student is added, the array reference doesn’t change – students doesn’t change – hence the stateless/pure pipe is not executed.
- When something is typed into the filter input, queryElem.value does change, hence the stateless/pure pipe is executed.
One way to fix the array issue is to change the array reference each time a student is added – i.e., create a new array each time a student is added. We could do this with concat() :
Although this works, our addNewStudent() method shouldn’t have to be implemented a certain way just because we’re using a pipe. We want to use push() to add to our array.
Stateful Pipes
Stateful pipes have state — they normally have properties, not just a transform() method. They may need to be evaluated even if their inputs haven’t changed. When we specify that a pipe is stateful/non-pure – pure: false – then whenever Angular’s change detection system checks a component for changes and that component uses a stateful pipe, it will check the output of the pipe, whether its input has changed or not.
This sounds like what we want, even though it is less efficient, since we want the pipe to execute even if the students reference hasn’t changed. If we simply make the pipe stateful, we get an error:
According to @drewmoore’s answer, «this error only happens in dev mode (which is enabled by default as of beta-0). If you call enableProdMode() when bootstrapping the app, the error won’t get thrown.» The docs for ApplicationRef.tick() state:
In development mode, tick() also performs a second change detection cycle to ensure that no further changes are detected. If additional changes are picked up during this second cycle, bindings in the app have side-effects that cannot be resolved in a single change detection pass. In this case, Angular throws an error, since an Angular application can only have one change detection pass during which all change detection must complete.
In our scenario I believe the error is bogus/misleading. We have a stateful pipe, and the output can change each time it is called – it can have side-effects and that’s okay. NgFor is evaluated after the pipe, so it should work fine.
However, we can’t really develop with this error being thrown, so one workaround is to add an array property (i.e., state) to the pipe implementation and always return that array. See @pixelbits’s answer for this solution.
However, we can be more efficient, and as we’ll see, we won’t need the array property in the pipe implementation, and we won’t need a workaround for the double change detection.
Component Change Detection
By default, on every browser event, Angular change detection goes through every component to see if it changed – inputs and templates (and maybe other stuff?) are checked.
If we know that a component only depends on its input properties (and template events), and that the input properties are immutable, we can use the much more efficient onPush change detection strategy. With this strategy, instead of checking on every browser event, a component is checked only when the inputs change and when template events trigger. And, apparently, we don’t get that Expression . has changed after it was checked error with this setting. This is because an onPush component is not checked again until it is «marked» ( ChangeDetectorRef.markForCheck() ) again. So Template bindings and stateful pipe outputs are executed/evaluated only once. Stateless/pure pipes are still not executed unless their inputs change. So we still need a stateful pipe here.
This is the solution @EricMartinez suggested: stateful pipe with onPush change detection. See @caffinatedmonkey’s answer for this solution.
Note that with this solution the transform() method doesn’t need to return the same array each time. I find that a bit odd though: a stateful pipe with no state. Thinking about it some more. the stateful pipe probably should always return the same array. Otherwise it could only be used with onPush components in dev mode.
So after all that, I think I like a combination of @Eric’s and @pixelbits’s answers: stateful pipe that returns the same array reference, with onPush change detection if the component allows it. Since the stateful pipe returns the same array reference, the pipe can still be used with components that are not configured with onPush .
This will probably become an Angular 2 idiom: if an array is feeding a pipe, and the array might change (the items in the array that is, not the array reference), we need to use a stateful pipe.
Источник