0

The Problem

So I am trying to detect changes to an array passed in through an input in my component. Regardless of how many times I try to separate the two arrays, they always contain the same values.

What I've Tried

I've tried slice and Array.from and both produce the same result, two arrays with identical information regardless of the fact that one is not updated at all and is a separate array from the original.

The Caveat

The interesting part of this, is if I check that one array is an instance of the other with firstArray == secondArray I get false, whereas this returns true when not using slice or Array.from

The Component

import { Component, Input, DoCheck } from '@angular/core';

class Requirement {
    title: string;
    passes: boolean;
}

@Component({
    selector: 'password-requirement-indicator',
    templateUrl: './password-requirement-indicator.component.html',
    styleUrls: [ './password-requirement-indicator.component.scss' ]
})
export class PasswordRequirementIndicator implements DoCheck {

    // Current requirements
    currentRequirements: Requirement[];

    // Previous requirements
    previousRequirements: Requirement[];

    ngDoCheck(){

        console.log(this.previousRequirements, this.currentRequirements);
        if ( !this.previousRequirements ){
            this.previousRequirements = Array.from(this.currentRequirements);
        }
    }


    @Input() set requirements (requirements: Requirement[] ){
        this.currentRequirements = Array.from(requirements);
    }
}

The Parent (Implementing) Component

import { Component, OnInit, ViewChild } from '@angular/core';

@Component({
    templateUrl: './index.component.html',
    styleUrls: ['./index.component.css']
})
export class IndexComponent {

    passes: boolean = false;

    requirements: any[] = [
        {
            title: 'Has at least one special character', 
            passes: false
        },
        {
            title: 'Has no spaces', 
            passes: false
        },
        {
            title: 'Has at least 1 letter', 
            passes: false
        },
        {
            title: 'Has at least 1 number', 
            passes: false
        },
        {
            title: 'Has 8-10 total characters', 
            passes: false
        },
        {
            title: 'Has no more than 3 of the same character in a row', 
            passes: false
        }
    ]

    // Updates the requirements as needed
    updateRequirements: Function = (password: string) => {

        // Update the special character requirement
        this.requirements[0].passes = this.hasSpecialCharacter(password);

        // Update the no spaces requirement
        this.requirements[1].passes = this.hasNoSpaces(password);

        // Update the one letter requirement
        this.requirements[2].passes = this.hasOneLetter(password);

        // Update the one number requirement
        this.requirements[3].passes = this.hasOneNumber(password);

        // Update the 8-10 character requirement
        this.requirements[4].passes = this.isRightLength(password);

        // Update the onenumber requirement
        this.requirements[5].passes = this.hasNoRepeatCharacters(password);

        // Check if all checks passed
        let passes: boolean = true;
        for ( var i =0, j=this.requirements.length; i<j; i++ ){

            if ( !this.requirements[i].passes ) {
                passes = false;
                i=j;
            }
        }
        this.passes = passes;
    }

    hasSpecialCharacter: Function = (password: string) => {

        // Check for a match and return whether we found one or not
        return password.match(/[\\!\@\#\$\%\^\&\*\(\)\{\}\"\<\>\?\/\:\;\'\-\=\|\[\]\,\.]/g) ? true : false;
    }

    hasNoSpaces: Function = (password: string)  => {

        // Check the password and return if we found spaces or not
        return password.match(/\s/g) ? false : true;
    }

    hasOneLetter: Function = (password: string) => {

        // Check the password and return if we found at least one letter
        return password.match(/[a-zA-Z]/g) ? true : false;
    }

    hasOneNumber: Function = (password: string) => {

        // Check the password and return if we found at least one number
        return password.match(/[0-9]/g) ? true : false;
    }

    isRightLength: Function = (password: string) => {

        // Check the password and return if it is between 8 and 10 characters
        return typeof password === "string" && password.length >= 8 && password.length <= 10;
    }

    hasNoRepeatCharacters: Function = (password: string) => {

        // Check the password and return if any characters are repeated more than three times
        return password.match(/(.).*?\1\1\1/g) ? false : true;
    }
}

Optional Reading

To get a little more in-depth the parent component is creating an initial array and passing this to the child component via an input: <component [requirements]="requirements"></component>.

It then updates this initial array without ever destroying it, meaning that a reference is created when passing this array into the component. Which is fine, this is how I need it to work for my uses.

However when I am trying to create a new array to store the old state for comparison to a new state using ngDoCheck, no matter what I do the previous requirements and current requirements array have the same values, even though they are created at different times and are verifiable as different arrays that are not instances of one another.

This is witchcraft.

The Solution

How can I create an array that maintains the previous values of the array before it was updated so that I can check if the values of any of the objects in the array have changed?

Writing the code to do this is not a problem for me, in fact I had it written out previously, but I need to be able to store the previous state in an array whose values do not update with the original input.

1
  • Would it be possible to create a plunker that demonstrates this issue? That would help us help you. Commented Sep 12, 2017 at 18:15

1 Answer 1

1

Good news: it's not witchcraft! Your problem is that in JavaScript, objects are reference types, not value types. So in this code:

this.previousRequirements = Array.from(this.currentRequirements);

you are making a new array of the existing objects. What you really want to do is clone the existing objects before putting them into the new array. Here's a way to do it:

this.previousRequirements = this.currentRequirements.map(req => Object.assign(new Requirement(), req));

(If you don't have access to Object.assign() this is a little more annoying but you can do it.) Now you have a new array of new objects.

Hope that helps; good luck!

Sign up to request clarification or add additional context in comments.

1 Comment

Referenception!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.